@scriptc/runtime 0.0.0 → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/package.json +16 -6
- package/src/scr_array.c +488 -0
- package/src/scr_assert.c +1393 -0
- package/src/scr_async.c +2593 -0
- package/src/scr_async_dyn.c +607 -0
- package/src/scr_bytes.c +1425 -0
- package/src/scr_bytes_io.c +161 -0
- package/src/scr_child.c +3587 -0
- package/src/scr_closure.c +214 -0
- package/src/scr_console.c +143 -0
- package/src/scr_cycle.c +218 -0
- package/src/scr_dc.c +805 -0
- package/src/scr_dgram.c +1007 -0
- package/src/scr_dyn_handle.c +154 -0
- package/src/scr_dyn_invoke.c +644 -0
- package/src/scr_error.c +332 -0
- package/src/scr_events.c +714 -0
- package/src/scr_events_emitter.c +699 -0
- package/src/scr_exception.c +242 -0
- package/src/scr_fetch.c +1130 -0
- package/src/scr_fetch_curl.c +654 -0
- package/src/scr_http.c +3482 -0
- package/src/scr_http2.c +3030 -0
- package/src/scr_inspect.c +803 -0
- package/src/scr_inspect_island.c +51 -0
- package/src/scr_island.c +9363 -0
- package/src/scr_json.c +2151 -0
- package/src/scr_lib.c +3612 -0
- package/src/scr_loop_epoll.c +241 -0
- package/src/scr_loop_kqueue.c +121 -0
- package/src/scr_loop_wsapoll.c +208 -0
- package/src/scr_map.c +591 -0
- package/src/scr_net.c +3017 -0
- package/src/scr_net_island.c +458 -0
- package/src/scr_number.c +115 -0
- package/src/scr_object.c +25 -0
- package/src/scr_path.c +1266 -0
- package/src/scr_platform.h +119 -0
- package/src/scr_readline.c +287 -0
- package/src/scr_regex.c +1080 -0
- package/src/scr_runtime.h +5036 -0
- package/src/scr_stream.c +2877 -0
- package/src/scr_string.c +1176 -0
- package/src/scr_symbol.c +132 -0
- package/src/scr_test.c +662 -0
- package/src/scr_tls.c +1520 -0
- package/src/scr_union.c +105 -0
- package/src/scr_url.c +911 -0
- package/src/scr_url_internal.h +29 -0
- package/src/scr_url_params.c +561 -0
- package/src/scr_watch.c +568 -0
- package/src/scr_web.c +1778 -0
- package/src/scr_win.c +76 -0
- package/src/scr_zlib.c +197 -0
- package/src/scr_zlib_island.c +15 -0
- package/vendor/README.md +52 -0
- package/vendor/curl/COPYRIGHT +534 -0
- package/vendor/curl/include/curl/curl.h +3214 -0
- package/vendor/curl/include/curl/curlver.h +79 -0
- package/vendor/curl/include/curl/easy.h +125 -0
- package/vendor/curl/include/curl/header.h +74 -0
- package/vendor/curl/include/curl/mprintf.h +52 -0
- package/vendor/curl/include/curl/multi.h +460 -0
- package/vendor/curl/include/curl/options.h +70 -0
- package/vendor/curl/include/curl/stdcheaders.h +35 -0
- package/vendor/curl/include/curl/system.h +508 -0
- package/vendor/curl/include/curl/typecheck-gcc.h +716 -0
- package/vendor/curl/include/curl/urlapi.h +149 -0
- package/vendor/curl/include/curl/websockets.h +84 -0
- package/vendor/mbedtls/LICENSE +553 -0
- package/vendor/mbedtls/include/CMakeLists.txt +22 -0
- package/vendor/mbedtls/include/mbedtls/aes.h +631 -0
- package/vendor/mbedtls/include/mbedtls/aria.h +343 -0
- package/vendor/mbedtls/include/mbedtls/asn1.h +642 -0
- package/vendor/mbedtls/include/mbedtls/asn1write.h +390 -0
- package/vendor/mbedtls/include/mbedtls/base64.h +82 -0
- package/vendor/mbedtls/include/mbedtls/bignum.h +1088 -0
- package/vendor/mbedtls/include/mbedtls/block_cipher.h +76 -0
- package/vendor/mbedtls/include/mbedtls/build_info.h +194 -0
- package/vendor/mbedtls/include/mbedtls/camellia.h +305 -0
- package/vendor/mbedtls/include/mbedtls/ccm.h +526 -0
- package/vendor/mbedtls/include/mbedtls/chacha20.h +209 -0
- package/vendor/mbedtls/include/mbedtls/chachapoly.h +351 -0
- package/vendor/mbedtls/include/mbedtls/check_config.h +1149 -0
- package/vendor/mbedtls/include/mbedtls/cipher.h +1250 -0
- package/vendor/mbedtls/include/mbedtls/cmac.h +246 -0
- package/vendor/mbedtls/include/mbedtls/compat-2.x.h +46 -0
- package/vendor/mbedtls/include/mbedtls/config_adjust_legacy_crypto.h +578 -0
- package/vendor/mbedtls/include/mbedtls/config_adjust_legacy_from_psa.h +873 -0
- package/vendor/mbedtls/include/mbedtls/config_adjust_psa_from_legacy.h +359 -0
- package/vendor/mbedtls/include/mbedtls/config_adjust_psa_superset_legacy.h +145 -0
- package/vendor/mbedtls/include/mbedtls/config_adjust_ssl.h +91 -0
- package/vendor/mbedtls/include/mbedtls/config_adjust_x509.h +35 -0
- package/vendor/mbedtls/include/mbedtls/config_psa.h +61 -0
- package/vendor/mbedtls/include/mbedtls/constant_time.h +36 -0
- package/vendor/mbedtls/include/mbedtls/ctr_drbg.h +596 -0
- package/vendor/mbedtls/include/mbedtls/debug.h +156 -0
- package/vendor/mbedtls/include/mbedtls/des.h +385 -0
- package/vendor/mbedtls/include/mbedtls/dhm.h +972 -0
- package/vendor/mbedtls/include/mbedtls/ecdh.h +455 -0
- package/vendor/mbedtls/include/mbedtls/ecdsa.h +674 -0
- package/vendor/mbedtls/include/mbedtls/ecjpake.h +298 -0
- package/vendor/mbedtls/include/mbedtls/ecp.h +1517 -0
- package/vendor/mbedtls/include/mbedtls/entropy.h +274 -0
- package/vendor/mbedtls/include/mbedtls/error.h +201 -0
- package/vendor/mbedtls/include/mbedtls/gcm.h +387 -0
- package/vendor/mbedtls/include/mbedtls/hkdf.h +124 -0
- package/vendor/mbedtls/include/mbedtls/hmac_drbg.h +434 -0
- package/vendor/mbedtls/include/mbedtls/lms.h +440 -0
- package/vendor/mbedtls/include/mbedtls/mbedtls_config.h +4446 -0
- package/vendor/mbedtls/include/mbedtls/md.h +526 -0
- package/vendor/mbedtls/include/mbedtls/md5.h +190 -0
- package/vendor/mbedtls/include/mbedtls/memory_buffer_alloc.h +142 -0
- package/vendor/mbedtls/include/mbedtls/net_sockets.h +299 -0
- package/vendor/mbedtls/include/mbedtls/nist_kw.h +166 -0
- package/vendor/mbedtls/include/mbedtls/oid.h +727 -0
- package/vendor/mbedtls/include/mbedtls/pem.h +160 -0
- package/vendor/mbedtls/include/mbedtls/pk.h +1303 -0
- package/vendor/mbedtls/include/mbedtls/pkcs12.h +186 -0
- package/vendor/mbedtls/include/mbedtls/pkcs5.h +198 -0
- package/vendor/mbedtls/include/mbedtls/pkcs7.h +252 -0
- package/vendor/mbedtls/include/mbedtls/platform.h +516 -0
- package/vendor/mbedtls/include/mbedtls/platform_time.h +79 -0
- package/vendor/mbedtls/include/mbedtls/platform_util.h +247 -0
- package/vendor/mbedtls/include/mbedtls/poly1305.h +168 -0
- package/vendor/mbedtls/include/mbedtls/private_access.h +20 -0
- package/vendor/mbedtls/include/mbedtls/psa_util.h +207 -0
- package/vendor/mbedtls/include/mbedtls/ripemd160.h +136 -0
- package/vendor/mbedtls/include/mbedtls/rsa.h +1170 -0
- package/vendor/mbedtls/include/mbedtls/sha1.h +219 -0
- package/vendor/mbedtls/include/mbedtls/sha256.h +200 -0
- package/vendor/mbedtls/include/mbedtls/sha3.h +172 -0
- package/vendor/mbedtls/include/mbedtls/sha512.h +208 -0
- package/vendor/mbedtls/include/mbedtls/ssl.h +5887 -0
- package/vendor/mbedtls/include/mbedtls/ssl_cache.h +187 -0
- package/vendor/mbedtls/include/mbedtls/ssl_ciphersuites.h +482 -0
- package/vendor/mbedtls/include/mbedtls/ssl_cookie.h +106 -0
- package/vendor/mbedtls/include/mbedtls/ssl_ticket.h +199 -0
- package/vendor/mbedtls/include/mbedtls/threading.h +167 -0
- package/vendor/mbedtls/include/mbedtls/timing.h +94 -0
- package/vendor/mbedtls/include/mbedtls/version.h +78 -0
- package/vendor/mbedtls/include/mbedtls/x509.h +500 -0
- package/vendor/mbedtls/include/mbedtls/x509_crl.h +184 -0
- package/vendor/mbedtls/include/mbedtls/x509_crt.h +1208 -0
- package/vendor/mbedtls/include/mbedtls/x509_csr.h +382 -0
- package/vendor/mbedtls/include/psa/build_info.h +20 -0
- package/vendor/mbedtls/include/psa/crypto.h +4998 -0
- package/vendor/mbedtls/include/psa/crypto_adjust_auto_enabled.h +31 -0
- package/vendor/mbedtls/include/psa/crypto_adjust_config_dependencies.h +51 -0
- package/vendor/mbedtls/include/psa/crypto_adjust_config_key_pair_types.h +101 -0
- package/vendor/mbedtls/include/psa/crypto_adjust_config_synonyms.h +49 -0
- package/vendor/mbedtls/include/psa/crypto_builtin_composites.h +214 -0
- package/vendor/mbedtls/include/psa/crypto_builtin_key_derivation.h +118 -0
- package/vendor/mbedtls/include/psa/crypto_builtin_primitives.h +114 -0
- package/vendor/mbedtls/include/psa/crypto_compat.h +230 -0
- package/vendor/mbedtls/include/psa/crypto_config.h +145 -0
- package/vendor/mbedtls/include/psa/crypto_driver_common.h +44 -0
- package/vendor/mbedtls/include/psa/crypto_driver_contexts_composites.h +151 -0
- package/vendor/mbedtls/include/psa/crypto_driver_contexts_key_derivation.h +52 -0
- package/vendor/mbedtls/include/psa/crypto_driver_contexts_primitives.h +105 -0
- package/vendor/mbedtls/include/psa/crypto_extra.h +2145 -0
- package/vendor/mbedtls/include/psa/crypto_legacy.h +88 -0
- package/vendor/mbedtls/include/psa/crypto_platform.h +102 -0
- package/vendor/mbedtls/include/psa/crypto_se_driver.h +1383 -0
- package/vendor/mbedtls/include/psa/crypto_sizes.h +1319 -0
- package/vendor/mbedtls/include/psa/crypto_struct.h +527 -0
- package/vendor/mbedtls/include/psa/crypto_types.h +508 -0
- package/vendor/mbedtls/include/psa/crypto_values.h +2782 -0
- package/vendor/mbedtls/library/aes.c +2294 -0
- package/vendor/mbedtls/library/aesce.c +624 -0
- package/vendor/mbedtls/library/aesce.h +136 -0
- package/vendor/mbedtls/library/aesni.c +846 -0
- package/vendor/mbedtls/library/aesni.h +162 -0
- package/vendor/mbedtls/library/alignment.h +704 -0
- package/vendor/mbedtls/library/aria.c +969 -0
- package/vendor/mbedtls/library/asn1parse.c +468 -0
- package/vendor/mbedtls/library/asn1write.c +440 -0
- package/vendor/mbedtls/library/base64.c +322 -0
- package/vendor/mbedtls/library/base64_internal.h +45 -0
- package/vendor/mbedtls/library/bignum.c +2583 -0
- package/vendor/mbedtls/library/bignum_core.c +1240 -0
- package/vendor/mbedtls/library/bignum_core.h +872 -0
- package/vendor/mbedtls/library/bignum_core_invasive.h +38 -0
- package/vendor/mbedtls/library/bignum_internal.h +122 -0
- package/vendor/mbedtls/library/bignum_mod.c +394 -0
- package/vendor/mbedtls/library/bignum_mod.h +452 -0
- package/vendor/mbedtls/library/bignum_mod_raw.c +276 -0
- package/vendor/mbedtls/library/bignum_mod_raw.h +416 -0
- package/vendor/mbedtls/library/bignum_mod_raw_invasive.h +34 -0
- package/vendor/mbedtls/library/block_cipher.c +207 -0
- package/vendor/mbedtls/library/block_cipher_internal.h +99 -0
- package/vendor/mbedtls/library/bn_mul.h +1094 -0
- package/vendor/mbedtls/library/camellia.c +1058 -0
- package/vendor/mbedtls/library/ccm.c +777 -0
- package/vendor/mbedtls/library/chacha20.c +563 -0
- package/vendor/mbedtls/library/chacha20_internal.h +23 -0
- package/vendor/mbedtls/library/chachapoly.c +487 -0
- package/vendor/mbedtls/library/check_crypto_config.h +136 -0
- package/vendor/mbedtls/library/cipher.c +1712 -0
- package/vendor/mbedtls/library/cipher_invasive.h +28 -0
- package/vendor/mbedtls/library/cipher_wrap.c +2482 -0
- package/vendor/mbedtls/library/cipher_wrap.h +178 -0
- package/vendor/mbedtls/library/cmac.c +1067 -0
- package/vendor/mbedtls/library/common.h +487 -0
- package/vendor/mbedtls/library/constant_time.c +247 -0
- package/vendor/mbedtls/library/constant_time_impl.h +541 -0
- package/vendor/mbedtls/library/constant_time_internal.h +579 -0
- package/vendor/mbedtls/library/ctr.h +35 -0
- package/vendor/mbedtls/library/ctr_drbg.c +1016 -0
- package/vendor/mbedtls/library/debug.c +475 -0
- package/vendor/mbedtls/library/debug_internal.h +185 -0
- package/vendor/mbedtls/library/des.c +1042 -0
- package/vendor/mbedtls/library/dhm.c +700 -0
- package/vendor/mbedtls/library/ecdh.c +696 -0
- package/vendor/mbedtls/library/ecdsa.c +858 -0
- package/vendor/mbedtls/library/ecjpake.c +1216 -0
- package/vendor/mbedtls/library/ecp.c +3674 -0
- package/vendor/mbedtls/library/ecp_curves.c +6125 -0
- package/vendor/mbedtls/library/ecp_curves_new.c +9 -0
- package/vendor/mbedtls/library/ecp_internal_alt.h +287 -0
- package/vendor/mbedtls/library/ecp_invasive.h +305 -0
- package/vendor/mbedtls/library/entropy.c +680 -0
- package/vendor/mbedtls/library/entropy_poll.c +233 -0
- package/vendor/mbedtls/library/entropy_poll.h +64 -0
- package/vendor/mbedtls/library/error.c +878 -0
- package/vendor/mbedtls/library/gcm.c +1330 -0
- package/vendor/mbedtls/library/hkdf.c +161 -0
- package/vendor/mbedtls/library/hmac_drbg.c +633 -0
- package/vendor/mbedtls/library/lmots.c +789 -0
- package/vendor/mbedtls/library/lmots.h +288 -0
- package/vendor/mbedtls/library/lms.c +778 -0
- package/vendor/mbedtls/library/md.c +1108 -0
- package/vendor/mbedtls/library/md5.c +426 -0
- package/vendor/mbedtls/library/md_psa.h +26 -0
- package/vendor/mbedtls/library/md_wrap.h +46 -0
- package/vendor/mbedtls/library/memory_buffer_alloc.c +751 -0
- package/vendor/mbedtls/library/mps_common.h +181 -0
- package/vendor/mbedtls/library/mps_error.h +89 -0
- package/vendor/mbedtls/library/mps_reader.c +538 -0
- package/vendor/mbedtls/library/mps_reader.h +366 -0
- package/vendor/mbedtls/library/mps_trace.c +112 -0
- package/vendor/mbedtls/library/mps_trace.h +154 -0
- package/vendor/mbedtls/library/net_sockets.c +694 -0
- package/vendor/mbedtls/library/nist_kw.c +729 -0
- package/vendor/mbedtls/library/oid.c +1166 -0
- package/vendor/mbedtls/library/padlock.c +157 -0
- package/vendor/mbedtls/library/padlock.h +111 -0
- package/vendor/mbedtls/library/pem.c +554 -0
- package/vendor/mbedtls/library/pk.c +1602 -0
- package/vendor/mbedtls/library/pk_ecc.c +261 -0
- package/vendor/mbedtls/library/pk_internal.h +241 -0
- package/vendor/mbedtls/library/pk_wrap.c +1618 -0
- package/vendor/mbedtls/library/pk_wrap.h +138 -0
- package/vendor/mbedtls/library/pkcs12.c +437 -0
- package/vendor/mbedtls/library/pkcs5.c +500 -0
- package/vendor/mbedtls/library/pkcs7.c +787 -0
- package/vendor/mbedtls/library/pkparse.c +1392 -0
- package/vendor/mbedtls/library/pkwrite.c +631 -0
- package/vendor/mbedtls/library/pkwrite.h +121 -0
- package/vendor/mbedtls/library/platform.c +402 -0
- package/vendor/mbedtls/library/platform_util.c +258 -0
- package/vendor/mbedtls/library/poly1305.c +492 -0
- package/vendor/mbedtls/library/psa_crypto.c +9532 -0
- package/vendor/mbedtls/library/psa_crypto_aead.c +646 -0
- package/vendor/mbedtls/library/psa_crypto_aead.h +499 -0
- package/vendor/mbedtls/library/psa_crypto_cipher.c +747 -0
- package/vendor/mbedtls/library/psa_crypto_cipher.h +316 -0
- package/vendor/mbedtls/library/psa_crypto_client.c +22 -0
- package/vendor/mbedtls/library/psa_crypto_core.h +983 -0
- package/vendor/mbedtls/library/psa_crypto_core_common.h +52 -0
- package/vendor/mbedtls/library/psa_crypto_driver_wrappers.h +2896 -0
- package/vendor/mbedtls/library/psa_crypto_driver_wrappers_no_static.c +256 -0
- package/vendor/mbedtls/library/psa_crypto_driver_wrappers_no_static.h +31 -0
- package/vendor/mbedtls/library/psa_crypto_ecp.c +594 -0
- package/vendor/mbedtls/library/psa_crypto_ecp.h +267 -0
- package/vendor/mbedtls/library/psa_crypto_ffdh.c +361 -0
- package/vendor/mbedtls/library/psa_crypto_ffdh.h +131 -0
- package/vendor/mbedtls/library/psa_crypto_hash.c +470 -0
- package/vendor/mbedtls/library/psa_crypto_hash.h +211 -0
- package/vendor/mbedtls/library/psa_crypto_invasive.h +92 -0
- package/vendor/mbedtls/library/psa_crypto_its.h +131 -0
- package/vendor/mbedtls/library/psa_crypto_mac.c +505 -0
- package/vendor/mbedtls/library/psa_crypto_mac.h +264 -0
- package/vendor/mbedtls/library/psa_crypto_pake.c +571 -0
- package/vendor/mbedtls/library/psa_crypto_pake.h +159 -0
- package/vendor/mbedtls/library/psa_crypto_random.c +181 -0
- package/vendor/mbedtls/library/psa_crypto_random.h +72 -0
- package/vendor/mbedtls/library/psa_crypto_random_impl.h +200 -0
- package/vendor/mbedtls/library/psa_crypto_rsa.c +714 -0
- package/vendor/mbedtls/library/psa_crypto_rsa.h +321 -0
- package/vendor/mbedtls/library/psa_crypto_se.c +373 -0
- package/vendor/mbedtls/library/psa_crypto_se.h +192 -0
- package/vendor/mbedtls/library/psa_crypto_slot_management.c +1137 -0
- package/vendor/mbedtls/library/psa_crypto_slot_management.h +344 -0
- package/vendor/mbedtls/library/psa_crypto_storage.c +481 -0
- package/vendor/mbedtls/library/psa_crypto_storage.h +392 -0
- package/vendor/mbedtls/library/psa_its_file.c +254 -0
- package/vendor/mbedtls/library/psa_util.c +614 -0
- package/vendor/mbedtls/library/psa_util_internal.h +100 -0
- package/vendor/mbedtls/library/ripemd160.c +490 -0
- package/vendor/mbedtls/library/rsa.c +3121 -0
- package/vendor/mbedtls/library/rsa_alt_helpers.c +455 -0
- package/vendor/mbedtls/library/rsa_alt_helpers.h +209 -0
- package/vendor/mbedtls/library/rsa_internal.h +188 -0
- package/vendor/mbedtls/library/sha1.c +480 -0
- package/vendor/mbedtls/library/sha256.c +983 -0
- package/vendor/mbedtls/library/sha3.c +721 -0
- package/vendor/mbedtls/library/sha512.c +1115 -0
- package/vendor/mbedtls/library/ssl_cache.c +410 -0
- package/vendor/mbedtls/library/ssl_ciphersuites.c +2050 -0
- package/vendor/mbedtls/library/ssl_ciphersuites_internal.h +154 -0
- package/vendor/mbedtls/library/ssl_client.c +1023 -0
- package/vendor/mbedtls/library/ssl_client.h +22 -0
- package/vendor/mbedtls/library/ssl_cookie.c +384 -0
- package/vendor/mbedtls/library/ssl_debug_helpers.h +85 -0
- package/vendor/mbedtls/library/ssl_debug_helpers_generated.c +251 -0
- package/vendor/mbedtls/library/ssl_misc.h +3198 -0
- package/vendor/mbedtls/library/ssl_msg.c +6619 -0
- package/vendor/mbedtls/library/ssl_ticket.c +556 -0
- package/vendor/mbedtls/library/ssl_tls.c +10234 -0
- package/vendor/mbedtls/library/ssl_tls12_client.c +3688 -0
- package/vendor/mbedtls/library/ssl_tls12_server.c +4311 -0
- package/vendor/mbedtls/library/ssl_tls13_client.c +3208 -0
- package/vendor/mbedtls/library/ssl_tls13_generic.c +1793 -0
- package/vendor/mbedtls/library/ssl_tls13_invasive.h +23 -0
- package/vendor/mbedtls/library/ssl_tls13_keys.c +1918 -0
- package/vendor/mbedtls/library/ssl_tls13_keys.h +668 -0
- package/vendor/mbedtls/library/ssl_tls13_server.c +3624 -0
- package/vendor/mbedtls/library/threading.c +193 -0
- package/vendor/mbedtls/library/threading_internal.h +28 -0
- package/vendor/mbedtls/library/timing.c +152 -0
- package/vendor/mbedtls/library/version.c +32 -0
- package/vendor/mbedtls/library/version_features.c +856 -0
- package/vendor/mbedtls/library/x509.c +1776 -0
- package/vendor/mbedtls/library/x509_create.c +570 -0
- package/vendor/mbedtls/library/x509_crl.c +708 -0
- package/vendor/mbedtls/library/x509_crt.c +3311 -0
- package/vendor/mbedtls/library/x509_csr.c +648 -0
- package/vendor/mbedtls/library/x509_internal.h +101 -0
- package/vendor/mbedtls/library/x509write.c +174 -0
- package/vendor/mbedtls/library/x509write_crt.c +688 -0
- package/vendor/mbedtls/library/x509write_csr.c +336 -0
- package/vendor/quickjs-ng/CMakeLists.txt +566 -0
- package/vendor/quickjs-ng/LICENSE +24 -0
- package/vendor/quickjs-ng/README.md +24 -0
- package/vendor/quickjs-ng/api-test.c +1195 -0
- package/vendor/quickjs-ng/builtin-array-fromasync.h +119 -0
- package/vendor/quickjs-ng/builtin-iterator-zip-keyed.h +332 -0
- package/vendor/quickjs-ng/builtin-iterator-zip.h +337 -0
- package/vendor/quickjs-ng/cutils.h +1998 -0
- package/vendor/quickjs-ng/dtoa.c +1619 -0
- package/vendor/quickjs-ng/dtoa.h +87 -0
- package/vendor/quickjs-ng/gen/function_source.c +81 -0
- package/vendor/quickjs-ng/gen/hello.c +53 -0
- package/vendor/quickjs-ng/gen/hello_module.c +106 -0
- package/vendor/quickjs-ng/gen/repl.c +3036 -0
- package/vendor/quickjs-ng/gen/standalone.c +323 -0
- package/vendor/quickjs-ng/gen/test_fib.c +81 -0
- package/vendor/quickjs-ng/libregexp-opcode.h +73 -0
- package/vendor/quickjs-ng/libregexp.c +3474 -0
- package/vendor/quickjs-ng/libregexp.h +101 -0
- package/vendor/quickjs-ng/libunicode-table.h +5173 -0
- package/vendor/quickjs-ng/libunicode.c +2069 -0
- package/vendor/quickjs-ng/libunicode.h +172 -0
- package/vendor/quickjs-ng/list.h +107 -0
- package/vendor/quickjs-ng/lre-test.c +52 -0
- package/vendor/quickjs-ng/qjs.c +762 -0
- package/vendor/quickjs-ng/qjsc.c +673 -0
- package/vendor/quickjs-ng/quickjs-atom.h +280 -0
- package/vendor/quickjs-ng/quickjs-c-atomics.h +54 -0
- package/vendor/quickjs-ng/quickjs-libc.c +5037 -0
- package/vendor/quickjs-ng/quickjs-libc.h +87 -0
- package/vendor/quickjs-ng/quickjs-opcode.h +377 -0
- package/vendor/quickjs-ng/quickjs.c +64041 -0
- package/vendor/quickjs-ng/quickjs.h +1447 -0
- package/vendor/quickjs-ng/run-test262.c +2374 -0
- package/vendor/quickjs-ng/unicode_gen.c +3121 -0
- package/vendor/quickjs-ng/unicode_gen_def.h +310 -0
- package/vendor/ryu/LICENSE-Boost +23 -0
- package/vendor/ryu/common.h +114 -0
- package/vendor/ryu/d2s.c +509 -0
- package/vendor/ryu/d2s_full_table.h +367 -0
- package/vendor/ryu/d2s_intrinsics.h +357 -0
- package/vendor/ryu/d2s_small_table.h +186 -0
- package/vendor/ryu/digit_table.h +35 -0
- package/vendor/ryu/ryu.h +46 -0
- package/vendor/zlib/LICENSE +22 -0
- package/vendor/zlib/adler32.c +164 -0
- package/vendor/zlib/compress.c +75 -0
- package/vendor/zlib/crc32.c +1049 -0
- package/vendor/zlib/crc32.h +9446 -0
- package/vendor/zlib/deflate.c +2139 -0
- package/vendor/zlib/deflate.h +377 -0
- package/vendor/zlib/gzclose.c +23 -0
- package/vendor/zlib/gzguts.h +214 -0
- package/vendor/zlib/gzlib.c +582 -0
- package/vendor/zlib/gzread.c +602 -0
- package/vendor/zlib/gzwrite.c +631 -0
- package/vendor/zlib/infback.c +628 -0
- package/vendor/zlib/inffast.c +320 -0
- package/vendor/zlib/inffast.h +11 -0
- package/vendor/zlib/inffixed.h +94 -0
- package/vendor/zlib/inflate.c +1526 -0
- package/vendor/zlib/inflate.h +126 -0
- package/vendor/zlib/inftrees.c +299 -0
- package/vendor/zlib/inftrees.h +62 -0
- package/vendor/zlib/trees.c +1117 -0
- package/vendor/zlib/trees.h +128 -0
- package/vendor/zlib/uncompr.c +85 -0
- package/vendor/zlib/zconf.h +543 -0
- package/vendor/zlib/zlib.h +1938 -0
- package/vendor/zlib/zutil.c +299 -0
- package/vendor/zlib/zutil.h +254 -0
- package/README.md +0 -3
- package/index.js +0 -2
package/src/scr_async.c
ADDED
|
@@ -0,0 +1,2593 @@
|
|
|
1
|
+
/* Async runtime: stackful fibers, promises, and a dependency-free event
|
|
2
|
+
* loop (microtask queue + timer min-heap).
|
|
3
|
+
*
|
|
4
|
+
* Model (see docs/ir.md):
|
|
5
|
+
* - An async function's body is ordinary compiled C, run on its own fiber
|
|
6
|
+
* (heap-allocated ucontext stack). Calling it runs the body EAGERLY until
|
|
7
|
+
* the first suspension (JS's synchronous-prefix rule), then control
|
|
8
|
+
* returns to the spawner with a +1 promise.
|
|
9
|
+
* - `await` on a pending promise parks the fiber on the promise's waiter
|
|
10
|
+
* list and switches back to whoever resumed it. Fulfillment moves waiters
|
|
11
|
+
* to the microtask queue (microtasks run before timers, like Node).
|
|
12
|
+
* - Each fiber carries its own exception cell (scr_exc_swap_cell) so a
|
|
13
|
+
* pending exception can't leak across concurrent execution contexts. A
|
|
14
|
+
* throw escaping an async body rejects its promise; awaiting a rejected
|
|
15
|
+
* promise re-throws into the awaiter.
|
|
16
|
+
* - Loop exhaustion with still-suspended fibers exits normally (Node's
|
|
17
|
+
* behavior); those stacks are deliberately not unwound, and the RC audit
|
|
18
|
+
* downgrades to a note in that case.
|
|
19
|
+
*/
|
|
20
|
+
#define _XOPEN_SOURCE 700
|
|
21
|
+
#include "scr_runtime.h"
|
|
22
|
+
|
|
23
|
+
#include <errno.h>
|
|
24
|
+
#include <fcntl.h>
|
|
25
|
+
#include <stdio.h>
|
|
26
|
+
#include <stdlib.h>
|
|
27
|
+
#include <string.h>
|
|
28
|
+
#include <time.h>
|
|
29
|
+
#ifdef _WIN32
|
|
30
|
+
/* Windows arm: fibers come from the Win32 Fibers API (CreateFiber /
|
|
31
|
+
* SwitchToFiber — the direct ucontext analog: cooperatively scheduled,
|
|
32
|
+
* same thread, own stack); the idle sleep is nanosleep (mingw-w64 ships
|
|
33
|
+
* it, over Sleep). poll(2) has no Windows arm — see the sleep seam in
|
|
34
|
+
* scr_loop_run: the poller-backed units (net/dgram/watch) are not built
|
|
35
|
+
* for win32 targets (cc.ts gates them), so their fds never appear; the
|
|
36
|
+
* events unit DOES cross-compile (scr_events.c's win32 arm) and is
|
|
37
|
+
* served by a capped nanosleep — dispatch at the next turn's top, the
|
|
38
|
+
* cap bounding signal/stdin latency instead of a pollable wake fd. */
|
|
39
|
+
#include <windows.h>
|
|
40
|
+
#else
|
|
41
|
+
#include <poll.h>
|
|
42
|
+
#include <signal.h>
|
|
43
|
+
#include <ucontext.h>
|
|
44
|
+
#include <unistd.h>
|
|
45
|
+
#endif
|
|
46
|
+
|
|
47
|
+
#if defined(__has_feature)
|
|
48
|
+
#if __has_feature(address_sanitizer)
|
|
49
|
+
#define SCR_ASAN_FIBERS 1
|
|
50
|
+
void __sanitizer_start_switch_fiber(void **fake_stack_save, const void *bottom, size_t size);
|
|
51
|
+
void __sanitizer_finish_switch_fiber(void *fake_stack_save, const void **bottom_old, size_t *size_old);
|
|
52
|
+
#endif
|
|
53
|
+
#endif
|
|
54
|
+
|
|
55
|
+
/* ASan inflates stack frames severalfold (unoptimized locals + redzones),
|
|
56
|
+
* so the sanitized lane gets proportionally bigger fiber stacks — same
|
|
57
|
+
* headroom DISCIPLINE, instrumented scale. Keep the island's stack budget
|
|
58
|
+
* (scr_island.c) at half of whichever size is active. 8MB under ASan is
|
|
59
|
+
* the measured need: an engine call costs 64–96KB there, and a real
|
|
60
|
+
* embedded graph entered FROM A FIBER (a commander action awaiting
|
|
61
|
+
* generateText — zod parses inside promise chains) nests dozens of engine
|
|
62
|
+
* frames; the memory is malloc'd and committed lazily, so idle fibers pay
|
|
63
|
+
* address space, not RSS. */
|
|
64
|
+
#ifdef SCR_ASAN_FIBERS
|
|
65
|
+
#define SCR_FIBER_STACK (8 * 1024 * 1024)
|
|
66
|
+
#else
|
|
67
|
+
#define SCR_FIBER_STACK (256 * 1024)
|
|
68
|
+
#endif
|
|
69
|
+
|
|
70
|
+
/* ── promises ─────────────────────────────────────────────────────────── */
|
|
71
|
+
|
|
72
|
+
enum { SCR_PROM_PENDING = 0, SCR_PROM_FULFILLED = 1, SCR_PROM_REJECTED = 2 };
|
|
73
|
+
|
|
74
|
+
typedef struct ScrFiber ScrFiber;
|
|
75
|
+
|
|
76
|
+
struct ScrPromise {
|
|
77
|
+
size_t rc;
|
|
78
|
+
int state;
|
|
79
|
+
/* Payload (fulfillment value or rejection reason), ScrExcCell-style.
|
|
80
|
+
* trace_fn is non-NULL iff the REF payload type carries a cycle header
|
|
81
|
+
* (a promise settled with a cycle-capable value can be a cycle member —
|
|
82
|
+
* e.g. a closure that captures a box holding this very promise). */
|
|
83
|
+
ScrExcKind payload_kind; /* NONE = void fulfillment */
|
|
84
|
+
double f64;
|
|
85
|
+
bool b;
|
|
86
|
+
void *payload;
|
|
87
|
+
void *(*retain_fn)(void *);
|
|
88
|
+
void (*release_fn)(void *);
|
|
89
|
+
ScrTraceFn trace_fn;
|
|
90
|
+
/* Fibers parked on this promise. */
|
|
91
|
+
ScrFiber **waiters;
|
|
92
|
+
size_t nwaiters, waiters_cap;
|
|
93
|
+
/* Combinator callbacks (Promise.race / Promise.all): at settle,
|
|
94
|
+
* fulfilled promises run adapt(dst, this) — an emitted adapter
|
|
95
|
+
* converting the payload into the destination's inner type — and
|
|
96
|
+
* rejections copy raw (reasons are dynamically tagged). Each entry owns
|
|
97
|
+
* a reference on its dst. Promise.all entries carry the shared
|
|
98
|
+
* countdown state (`all` non-NULL, `all_idx` the entry's INPUT index);
|
|
99
|
+
* race entries leave both zeroed and keep the adapt dispatch. */
|
|
100
|
+
struct ScrPromiseCbWaiter {
|
|
101
|
+
void (*adapt)(ScrPromise *dst, ScrPromise *src);
|
|
102
|
+
ScrPromise *dst;
|
|
103
|
+
struct ScrAllState *all;
|
|
104
|
+
size_t all_idx;
|
|
105
|
+
} *cbs;
|
|
106
|
+
size_t ncbs, cbs_cap;
|
|
107
|
+
/* Unhandled-rejection tracking: set when rejected, cleared on await. */
|
|
108
|
+
bool rejection_observed;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
#ifdef SCR_RC_AUDIT
|
|
112
|
+
static long scr_live_promises = 0;
|
|
113
|
+
long scr_promise_live_count(void) { return scr_live_promises; }
|
|
114
|
+
#endif
|
|
115
|
+
|
|
116
|
+
/* ── Promise.all shared state ─────────────────────────────────────────
|
|
117
|
+
* One per Promise.all call: the values array filled per INPUT index as
|
|
118
|
+
* entries fulfill, the countdown of fulfillments still missing, and the
|
|
119
|
+
* per-element-kind store helper. The state holds +1 on `values` (NULL for
|
|
120
|
+
* void-element all) and is itself refcounted by the entries that still
|
|
121
|
+
* point at it (parked cb waiters plus the builder while it subscribes);
|
|
122
|
+
* the RESULT promise is NOT held here — each parked entry's cb.dst is the
|
|
123
|
+
* retained result, so promise teardown paths need no all-specific
|
|
124
|
+
* destination handling. */
|
|
125
|
+
typedef struct ScrAllState {
|
|
126
|
+
size_t rc;
|
|
127
|
+
size_t remaining;
|
|
128
|
+
ScrArr *values;
|
|
129
|
+
void (*store)(ScrArr *a, double i, ScrPromise *src);
|
|
130
|
+
} ScrAllState;
|
|
131
|
+
|
|
132
|
+
static void scr_promise_all_state_release(ScrAllState *st) {
|
|
133
|
+
if (--st->rc == 0) {
|
|
134
|
+
if (st->values) scr_arr_release(st->values);
|
|
135
|
+
free(st);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
static void scr_oom(void) {
|
|
140
|
+
fputs("scriptc: out of memory\n", stderr);
|
|
141
|
+
abort();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
static void scr_promise_trace(void *o, ScrTraceVisit visit, void *ctx) {
|
|
145
|
+
ScrPromise *p = (ScrPromise *)o;
|
|
146
|
+
/* Waiters are fibers (not refcounted objects) — the settled payload and
|
|
147
|
+
* the combinator destinations are the strong references this promise
|
|
148
|
+
* owns (destination promises are cycle-headered). */
|
|
149
|
+
if ((p->payload_kind == SCR_EXC_REF || p->payload_kind == SCR_EXC_OBJ) &&
|
|
150
|
+
p->trace_fn) {
|
|
151
|
+
visit(p->payload, ctx);
|
|
152
|
+
}
|
|
153
|
+
for (size_t i = 0; i < p->ncbs; i++) visit(p->cbs[i].dst, ctx);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/* Teardown for the collector: release the payload only when the trace does
|
|
157
|
+
* NOT visit it (the complement — see the contract in scr_runtime.h). The
|
|
158
|
+
* cb destinations are always visited, so only the array frees here. */
|
|
159
|
+
static void scr_promise_gcfree(void *o) {
|
|
160
|
+
ScrPromise *p = (ScrPromise *)o;
|
|
161
|
+
if (p->payload_kind == SCR_EXC_STR) {
|
|
162
|
+
scr_str_release((ScrStr *)p->payload);
|
|
163
|
+
} else if ((p->payload_kind == SCR_EXC_REF || p->payload_kind == SCR_EXC_OBJ) &&
|
|
164
|
+
p->payload && !p->trace_fn) {
|
|
165
|
+
p->release_fn(p->payload);
|
|
166
|
+
}
|
|
167
|
+
/* Parked Promise.all states are NOT traced children (their values array
|
|
168
|
+
* is reachable only through them) — release like the untraced payload
|
|
169
|
+
* above; the cb destinations stay untouched (trace visits them). */
|
|
170
|
+
for (size_t i = 0; i < p->ncbs; i++) {
|
|
171
|
+
if (p->cbs[i].all) scr_promise_all_state_release(p->cbs[i].all);
|
|
172
|
+
}
|
|
173
|
+
free(p->waiters);
|
|
174
|
+
free(p->cbs);
|
|
175
|
+
#ifdef SCR_RC_AUDIT
|
|
176
|
+
scr_live_promises--;
|
|
177
|
+
#endif
|
|
178
|
+
scr_cyc_free(p);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
ScrPromise *scr_promise_new(void) {
|
|
182
|
+
ScrPromise *p = scr_cyc_alloc(sizeof *p, &scr_promise_trace, &scr_promise_gcfree);
|
|
183
|
+
p->rc = 1;
|
|
184
|
+
#ifdef SCR_RC_AUDIT
|
|
185
|
+
scr_live_promises++;
|
|
186
|
+
#endif
|
|
187
|
+
return p;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
ScrPromise *scr_promise_retain(ScrPromise *p) {
|
|
191
|
+
if (p && p->rc != SIZE_MAX) {
|
|
192
|
+
p->rc++;
|
|
193
|
+
scr_cyc_mark_live(p);
|
|
194
|
+
}
|
|
195
|
+
return p;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
static void scr_promise_release_payload(ScrPromise *p) {
|
|
199
|
+
if (p->payload_kind == SCR_EXC_STR) scr_str_release((ScrStr *)p->payload);
|
|
200
|
+
else if ((p->payload_kind == SCR_EXC_REF || p->payload_kind == SCR_EXC_OBJ) &&
|
|
201
|
+
p->payload) p->release_fn(p->payload);
|
|
202
|
+
p->payload_kind = SCR_EXC_NONE;
|
|
203
|
+
p->payload = NULL;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
void scr_promise_release(ScrPromise *p) {
|
|
207
|
+
if (!p || p->rc == SIZE_MAX) return;
|
|
208
|
+
if (--p->rc == 0) {
|
|
209
|
+
scr_cyc_on_dead(p);
|
|
210
|
+
scr_promise_release_payload(p);
|
|
211
|
+
free(p->waiters);
|
|
212
|
+
for (size_t i = 0; i < p->ncbs; i++) {
|
|
213
|
+
scr_promise_release(p->cbs[i].dst);
|
|
214
|
+
if (p->cbs[i].all) scr_promise_all_state_release(p->cbs[i].all);
|
|
215
|
+
}
|
|
216
|
+
free(p->cbs);
|
|
217
|
+
#ifdef SCR_RC_AUDIT
|
|
218
|
+
scr_live_promises--;
|
|
219
|
+
#endif
|
|
220
|
+
scr_cyc_free(p);
|
|
221
|
+
} else {
|
|
222
|
+
scr_cyc_on_release(p); /* possible cycle root; may collect — p is done */
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
void *scr_promise_retain_v(void *p) { return scr_promise_retain((ScrPromise *)p); }
|
|
227
|
+
void scr_promise_release_v(void *p) { scr_promise_release((ScrPromise *)p); }
|
|
228
|
+
void scr_promise_trace_v(void *p, ScrTraceVisit visit, void *ctx) {
|
|
229
|
+
scr_promise_trace(p, visit, ctx);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/* ── fibers and the scheduler ─────────────────────────────────────────── */
|
|
233
|
+
|
|
234
|
+
/* One saved execution context. POSIX: a ucontext_t (swapcontext saves the
|
|
235
|
+
* outgoing context INTO the `from` slot). Windows: the fiber HANDLE —
|
|
236
|
+
* SwitchToFiber saves the outgoing state inside the fiber object itself,
|
|
237
|
+
* so the slot only needs to name the destination; `from` is unused. */
|
|
238
|
+
#ifdef _WIN32
|
|
239
|
+
typedef void *ScrCtx;
|
|
240
|
+
#else
|
|
241
|
+
typedef ucontext_t ScrCtx;
|
|
242
|
+
#endif
|
|
243
|
+
|
|
244
|
+
struct ScrFiber {
|
|
245
|
+
ScrCtx ctx;
|
|
246
|
+
ScrCtx *return_to; /* whoever resumed us last (spawner or the loop) */
|
|
247
|
+
char *stack; /* POSIX only; Windows fibers own their stack (NULL) */
|
|
248
|
+
ScrPromise *promise; /* the promise this fiber settles (owned +1); NULL
|
|
249
|
+
* for generator fibers (gen non-NULL instead) */
|
|
250
|
+
ScrGen *gen; /* the generator this fiber runs (borrowed — the
|
|
251
|
+
* ScrGen owns the fiber, never the reverse) */
|
|
252
|
+
ScrExcCell exc;
|
|
253
|
+
/* AsyncLocalStorage context (owned; NULL = empty). Inherited from the
|
|
254
|
+
* SPAWNER at spawn (Node's init-time capture), swapped active with the
|
|
255
|
+
* fiber (the exc-cell pattern) so run()'s window rides awaits. */
|
|
256
|
+
ScrAlsCtx *als;
|
|
257
|
+
bool done;
|
|
258
|
+
/* Trampoline args: the spawn wrapper stores a pointer to a stack-local
|
|
259
|
+
* argpack; the trampoline copies it out before the spawner resumes. */
|
|
260
|
+
void *argpack;
|
|
261
|
+
void (*entry)(ScrFiber *self, void *argpack);
|
|
262
|
+
/* queueMicrotask entries: a stackless pseudo-fiber — the READY queue's
|
|
263
|
+
* FIFO IS the microtask queue, so a closure rides it as a fiber-shaped
|
|
264
|
+
* envelope and scr_resume_fiber runs it on the main stack (a throw is
|
|
265
|
+
* an UNCAUGHT exception, like Node's queueMicrotask, never a
|
|
266
|
+
* rejection). Owned; NULL on real fibers. */
|
|
267
|
+
ScrClosure *micro_cb;
|
|
268
|
+
#ifdef SCR_ASAN_FIBERS
|
|
269
|
+
void *fake_stack;
|
|
270
|
+
#endif
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
/* ── AsyncLocalStorage (node:async_hooks) ─────────────────────────────
|
|
274
|
+
* Stores are process-lived ids; the CONTEXT is an immutable refcounted
|
|
275
|
+
* snapshot of (store id → DOM value) entries. One ACTIVE SLOT pointer
|
|
276
|
+
* (the exc-cell pattern): main owns a static slot, every fiber owns its
|
|
277
|
+
* own field, scr_switch repoints the active slot — so run()'s window
|
|
278
|
+
* rides the fiber across awaits, and a spawned fiber INHERITS the
|
|
279
|
+
* spawner's snapshot (Node's init-time capture through AsyncResource).
|
|
280
|
+
* Snapshots are immutable: enter builds a fresh one (entries retained),
|
|
281
|
+
* restore swaps the previous back — timer capture retains pointers. */
|
|
282
|
+
|
|
283
|
+
/* The ScrAlsCtx layout and the API over it live in scr_runtime.h /
|
|
284
|
+
* scr_async_dyn.c (gated — the size-class stance); this always-linked
|
|
285
|
+
* core keeps only what the fiber machinery itself touches: the active
|
|
286
|
+
* slot, the snapshot RC pair, and the switch/spawn/destroy wiring. */
|
|
287
|
+
static ScrAlsCtx *scr_als_main_slot = NULL;
|
|
288
|
+
ScrAlsCtx **scr_als_active = &scr_als_main_slot;
|
|
289
|
+
|
|
290
|
+
ScrAlsCtx *scr_als_ctx_retain(ScrAlsCtx *c) {
|
|
291
|
+
if (c) c->rc++;
|
|
292
|
+
return c;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
void scr_als_ctx_release(ScrAlsCtx *c) {
|
|
296
|
+
if (!c || --c->rc != 0) return;
|
|
297
|
+
for (size_t i = 0; i < c->len; i++) scr_dyn_release(c->entries[i].value);
|
|
298
|
+
free(c);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
static ScrCtx scr_loop_ctx; /* the main stack (scheduler home) */
|
|
304
|
+
static ScrFiber *scr_current = NULL;
|
|
305
|
+
static long scr_fibers_live = 0;
|
|
306
|
+
static long scr_fibers_abandoned = 0;
|
|
307
|
+
|
|
308
|
+
long scr_abandoned_fiber_count(void) { return scr_fibers_abandoned; }
|
|
309
|
+
|
|
310
|
+
/* True while executing on an async fiber (vs the main stack). The island
|
|
311
|
+
* sizes its engine stack budget per stack: fibers are small and fixed,
|
|
312
|
+
* the main stack is the process's megabytes (scr_island.c isl_entry). */
|
|
313
|
+
bool scr_on_fiber(void) { return scr_current != NULL; }
|
|
314
|
+
void *scr_fiber_self(void) { return scr_current; }
|
|
315
|
+
|
|
316
|
+
/* Microtask queue: ready fibers, FIFO. */
|
|
317
|
+
static ScrFiber **scr_ready = NULL;
|
|
318
|
+
static size_t scr_ready_head = 0, scr_ready_len = 0, scr_ready_cap = 0;
|
|
319
|
+
|
|
320
|
+
static void scr_ready_push(ScrFiber *f) {
|
|
321
|
+
if (scr_ready_head + scr_ready_len == scr_ready_cap) {
|
|
322
|
+
memmove(scr_ready, scr_ready + scr_ready_head, scr_ready_len * sizeof *scr_ready);
|
|
323
|
+
scr_ready_head = 0;
|
|
324
|
+
if (scr_ready_len == scr_ready_cap) {
|
|
325
|
+
scr_ready_cap = scr_ready_cap ? scr_ready_cap * 2 : 16;
|
|
326
|
+
scr_ready = realloc(scr_ready, scr_ready_cap * sizeof *scr_ready);
|
|
327
|
+
if (!scr_ready) scr_oom();
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
scr_ready[scr_ready_head + scr_ready_len++] = f;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/* Timer min-heap, FIFO tiebreak via sequence numbers. `id` is nonzero only
|
|
334
|
+
* for setInterval entries (the clearInterval handle; setTimeout has no
|
|
335
|
+
* clear surface, so plain timeouts never need one) and `repeat_ms` is the
|
|
336
|
+
* interval's period — a fired interval re-enters the heap with a fresh
|
|
337
|
+
* deadline and seq, so ties against later timers stay FIFO like Node's. */
|
|
338
|
+
typedef struct {
|
|
339
|
+
double deadline_ms;
|
|
340
|
+
unsigned long seq;
|
|
341
|
+
ScrClosure *cb; /* owned */
|
|
342
|
+
double repeat_ms;
|
|
343
|
+
unsigned long id;
|
|
344
|
+
bool reffed; /* keeps the loop alive; unref() clears it (Node semantics) */
|
|
345
|
+
double delay_ms; /* the original delay — refresh() re-arms to now + this */
|
|
346
|
+
} ScrTimer;
|
|
347
|
+
|
|
348
|
+
static ScrTimer *scr_timers = NULL;
|
|
349
|
+
static size_t scr_ntimers = 0, scr_timers_cap = 0;
|
|
350
|
+
static unsigned long scr_timer_seq = 0;
|
|
351
|
+
/* REF'd armed timers — the loop's timer-liveness count (an unref'd timer
|
|
352
|
+
* sits in the heap and still FIRES if the loop runs for other reasons, but
|
|
353
|
+
* does not by itself keep the loop alive). Kept in sync by push/pop/remove
|
|
354
|
+
* and by ref/unref. */
|
|
355
|
+
static size_t scr_reffed_timers = 0;
|
|
356
|
+
|
|
357
|
+
/* Exported: the island's timer machinery (scr_web.c) shares this clock so
|
|
358
|
+
* its deadlines are comparable with the loop's. */
|
|
359
|
+
double scr_now_ms(void) {
|
|
360
|
+
struct timespec ts;
|
|
361
|
+
clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
362
|
+
return (double)ts.tv_sec * 1000.0 + (double)ts.tv_nsec / 1e6;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
static bool scr_timer_before(const ScrTimer *a, const ScrTimer *b) {
|
|
366
|
+
if (a->deadline_ms != b->deadline_ms) return a->deadline_ms < b->deadline_ms;
|
|
367
|
+
return a->seq < b->seq;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
static void scr_timer_push(ScrTimer t) {
|
|
371
|
+
if (scr_ntimers == scr_timers_cap) {
|
|
372
|
+
scr_timers_cap = scr_timers_cap ? scr_timers_cap * 2 : 16;
|
|
373
|
+
scr_timers = realloc(scr_timers, scr_timers_cap * sizeof *scr_timers);
|
|
374
|
+
if (!scr_timers) scr_oom();
|
|
375
|
+
}
|
|
376
|
+
if (t.reffed) scr_reffed_timers++;
|
|
377
|
+
size_t i = scr_ntimers++;
|
|
378
|
+
scr_timers[i] = t;
|
|
379
|
+
while (i > 0) {
|
|
380
|
+
size_t parent = (i - 1) / 2;
|
|
381
|
+
if (!scr_timer_before(&scr_timers[i], &scr_timers[parent])) break;
|
|
382
|
+
ScrTimer tmp = scr_timers[i];
|
|
383
|
+
scr_timers[i] = scr_timers[parent];
|
|
384
|
+
scr_timers[parent] = tmp;
|
|
385
|
+
i = parent;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
static ScrTimer scr_timer_pop(void) {
|
|
390
|
+
ScrTimer top = scr_timers[0];
|
|
391
|
+
if (top.reffed && scr_reffed_timers > 0) scr_reffed_timers--;
|
|
392
|
+
scr_timers[0] = scr_timers[--scr_ntimers];
|
|
393
|
+
size_t i = 0;
|
|
394
|
+
for (;;) {
|
|
395
|
+
size_t l = 2 * i + 1, r = 2 * i + 2, min = i;
|
|
396
|
+
if (l < scr_ntimers && scr_timer_before(&scr_timers[l], &scr_timers[min])) min = l;
|
|
397
|
+
if (r < scr_ntimers && scr_timer_before(&scr_timers[r], &scr_timers[min])) min = r;
|
|
398
|
+
if (min == i) break;
|
|
399
|
+
ScrTimer tmp = scr_timers[i];
|
|
400
|
+
scr_timers[i] = scr_timers[min];
|
|
401
|
+
scr_timers[min] = tmp;
|
|
402
|
+
i = min;
|
|
403
|
+
}
|
|
404
|
+
return top;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/* Node's delay coercion (lib/internal/timers.js): NaN/negative/sub-ms
|
|
408
|
+
* clamp to 1, the delay TRUNCATES to integer milliseconds (setTimeout(cb,
|
|
409
|
+
* 1.8) and setTimeout(cb, 1.1) both land in the 1ms bucket, so a batch of
|
|
410
|
+
* fractional delays fires in REGISTRATION order — test-timers-non-integer-
|
|
411
|
+
* delay's contract), and anything past TIMEOUT_MAX (2^31-1) becomes 1. */
|
|
412
|
+
static double scr_timer_coerce_ms(double ms) {
|
|
413
|
+
if (!(ms >= 1)) return 1;
|
|
414
|
+
if (ms > 2147483647.0) return 1; /* past TIMEOUT_MAX (also +Infinity) */
|
|
415
|
+
return (double)(unsigned long long)ms; /* trunc: positive, finite, in range */
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
void scr_set_timeout(ScrClosure *cb, double ms) {
|
|
419
|
+
ms = scr_timer_coerce_ms(ms);
|
|
420
|
+
ScrTimer t = {scr_now_ms() + ms, scr_timer_seq++, cb /* ownership moves in */, 0, 0, true, ms};
|
|
421
|
+
scr_timer_push(t);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/* ── setInterval / clearInterval ─────────────────────────────────────
|
|
425
|
+
* Intervals ride the same heap: the entry carries its period and a
|
|
426
|
+
* nonzero id (the compiled handle — Node's Timeout object reduced to the
|
|
427
|
+
* number the fallback declarations promise). A live interval keeps the
|
|
428
|
+
* loop alive exactly because it sits in the heap; clearInterval removes
|
|
429
|
+
* the entry EAGERLY (a lazily-cancelled entry would hold the loop open
|
|
430
|
+
* until its deadline — an interval cleared at t=0 with a one-hour period
|
|
431
|
+
* must let the program exit NOW, like Node). Clearing the interval whose
|
|
432
|
+
* callback is currently running (the self-clearing spinner pattern) is
|
|
433
|
+
* handled by the firing pair below: the entry is out of the heap while
|
|
434
|
+
* its callback runs, so the run loop re-pushes it only if the callback
|
|
435
|
+
* did not clear it. */
|
|
436
|
+
|
|
437
|
+
static unsigned long scr_interval_next_id = 1;
|
|
438
|
+
static unsigned long scr_firing_id = 0; /* interval currently running */
|
|
439
|
+
static bool scr_firing_cleared = false;
|
|
440
|
+
static bool scr_firing_reffed = true; /* the running interval's ref state */
|
|
441
|
+
static bool scr_firing_refresh = false; /* refresh() called mid-callback */
|
|
442
|
+
|
|
443
|
+
/* Removes heap entry i (swap-with-last, then sift whichever way the moved
|
|
444
|
+
* entry needs). */
|
|
445
|
+
static void scr_timer_remove_at(size_t i) {
|
|
446
|
+
if (scr_timers[i].reffed && scr_reffed_timers > 0) scr_reffed_timers--;
|
|
447
|
+
scr_timers[i] = scr_timers[--scr_ntimers];
|
|
448
|
+
if (i >= scr_ntimers) return;
|
|
449
|
+
/* Sift up if the moved entry beats its parent, else sift down. */
|
|
450
|
+
while (i > 0) {
|
|
451
|
+
size_t parent = (i - 1) / 2;
|
|
452
|
+
if (!scr_timer_before(&scr_timers[i], &scr_timers[parent])) break;
|
|
453
|
+
ScrTimer tmp = scr_timers[i];
|
|
454
|
+
scr_timers[i] = scr_timers[parent];
|
|
455
|
+
scr_timers[parent] = tmp;
|
|
456
|
+
i = parent;
|
|
457
|
+
}
|
|
458
|
+
for (;;) {
|
|
459
|
+
size_t l = 2 * i + 1, r = 2 * i + 2, min = i;
|
|
460
|
+
if (l < scr_ntimers && scr_timer_before(&scr_timers[l], &scr_timers[min])) min = l;
|
|
461
|
+
if (r < scr_ntimers && scr_timer_before(&scr_timers[r], &scr_timers[min])) min = r;
|
|
462
|
+
if (min == i) break;
|
|
463
|
+
ScrTimer tmp = scr_timers[i];
|
|
464
|
+
scr_timers[i] = scr_timers[min];
|
|
465
|
+
scr_timers[min] = tmp;
|
|
466
|
+
i = min;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
double scr_set_interval(ScrClosure *cb, double ms) {
|
|
471
|
+
ms = scr_timer_coerce_ms(ms); /* Node's clamp-and-trunc, like setTimeout */
|
|
472
|
+
unsigned long id = scr_interval_next_id++;
|
|
473
|
+
ScrTimer t = {scr_now_ms() + ms, scr_timer_seq++, cb /* ownership moves in */, ms, id, true, ms};
|
|
474
|
+
scr_timer_push(t);
|
|
475
|
+
return (double)id;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/* A one-shot timer WITH a clear handle — the island's setTimeout (its
|
|
479
|
+
* clearTimeout must cancel, unlike the static surface's clear-less
|
|
480
|
+
* setTimeout). Rides the interval id space so scr_clear_interval serves
|
|
481
|
+
* both; repeat_ms 0 keeps it one-shot at the firing site. */
|
|
482
|
+
double scr_set_timeout_handle(ScrClosure *cb, double ms) {
|
|
483
|
+
ms = scr_timer_coerce_ms(ms);
|
|
484
|
+
unsigned long id = scr_interval_next_id++;
|
|
485
|
+
ScrTimer t = {scr_now_ms() + ms, scr_timer_seq++, cb /* ownership moves in */, 0, id, true, ms};
|
|
486
|
+
scr_timer_push(t);
|
|
487
|
+
return (double)id;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/* process.env-style timer ref bookkeeping: unref() drops a timer from the
|
|
491
|
+
* loop's liveness count (it still fires if the loop runs on), ref() puts
|
|
492
|
+
* it back. Node-exact for the static timer surface; NaN/0/absent handles
|
|
493
|
+
* are tolerated (never a handle). The one-shot setTimeout with NO clear
|
|
494
|
+
* handle (id 0) cannot be unref'd by id — the compiler routes .unref()
|
|
495
|
+
* only over handle-returning timers, so id 0 never reaches here. */
|
|
496
|
+
static ScrTimer *scr_timer_find(unsigned long id) {
|
|
497
|
+
for (size_t i = 0; i < scr_ntimers; i++) {
|
|
498
|
+
if (scr_timers[i].id == id) return &scr_timers[i];
|
|
499
|
+
}
|
|
500
|
+
return NULL;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
void scr_timer_unref(double handle) {
|
|
504
|
+
if (!(handle >= 1)) return;
|
|
505
|
+
unsigned long id = (unsigned long)handle;
|
|
506
|
+
if (id == scr_firing_id) {
|
|
507
|
+
/* Firing interval unref'ing itself: the re-arm carries the flag. */
|
|
508
|
+
scr_firing_reffed = false;
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
ScrTimer *t = scr_timer_find(id);
|
|
512
|
+
if (t && t->reffed) {
|
|
513
|
+
t->reffed = false;
|
|
514
|
+
if (scr_reffed_timers > 0) scr_reffed_timers--;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
void scr_timer_ref(double handle) {
|
|
519
|
+
if (!(handle >= 1)) return;
|
|
520
|
+
unsigned long id = (unsigned long)handle;
|
|
521
|
+
if (id == scr_firing_id) {
|
|
522
|
+
scr_firing_reffed = true;
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
ScrTimer *t = scr_timer_find(id);
|
|
526
|
+
if (t && !t->reffed) {
|
|
527
|
+
t->reffed = true;
|
|
528
|
+
scr_reffed_timers++;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/* refresh(): re-arm to now + the original delay — Node's Timeout.refresh.
|
|
533
|
+
* A HEAP entry (armed timeout or interval between ticks) re-enters the
|
|
534
|
+
* heap with a fresh deadline and seq (FIFO against later same-deadline
|
|
535
|
+
* timers, like a brand-new timer); the timer whose callback is RUNNING
|
|
536
|
+
* sets the firing flag and the loop re-arms the one-shot after the
|
|
537
|
+
* callback returns (the interval re-arm already does). A one-shot that
|
|
538
|
+
* fired on an earlier turn is gone — its closure released — so refresh
|
|
539
|
+
* of a dead handle is a tolerated no-op (SEMANTICS: Node re-activates
|
|
540
|
+
* even a fired Timeout; the compiled handle cannot). */
|
|
541
|
+
void scr_timer_refresh(double handle) {
|
|
542
|
+
if (!(handle >= 1)) return;
|
|
543
|
+
unsigned long id = (unsigned long)handle;
|
|
544
|
+
if (id == scr_firing_id) {
|
|
545
|
+
scr_firing_refresh = true;
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
for (size_t i = 0; i < scr_ntimers; i++) {
|
|
549
|
+
if (scr_timers[i].id == id) {
|
|
550
|
+
ScrTimer t = scr_timers[i];
|
|
551
|
+
scr_timer_remove_at(i);
|
|
552
|
+
t.deadline_ms = scr_now_ms() + t.delay_ms;
|
|
553
|
+
t.seq = scr_timer_seq++;
|
|
554
|
+
scr_timer_push(t);
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/* hasRef(): true iff the handle names a live, still-reffed timer. */
|
|
561
|
+
bool scr_timer_has_ref(double handle) {
|
|
562
|
+
if (!(handle >= 1)) return false;
|
|
563
|
+
unsigned long id = (unsigned long)handle;
|
|
564
|
+
if (id == scr_firing_id) return scr_firing_reffed;
|
|
565
|
+
ScrTimer *t = scr_timer_find(id);
|
|
566
|
+
return t != NULL && t->reffed;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/* ── process.getActiveResourcesInfo (the loop's own bookkeeping) ──────
|
|
570
|
+
* 'Timeout' per armed heap timer — plus the firing, uncleared one (Node
|
|
571
|
+
* counts a Timeout as active while its callback runs, until clearTimeout
|
|
572
|
+
* drops it) — and 'Immediate' per queued, unfired immediate (a FIRED
|
|
573
|
+
* immediate no longer counts, Node's current answer inside the
|
|
574
|
+
* callback). Resource kinds this runtime does not model as loop handles
|
|
575
|
+
* (TCP wraps, FS requests, ...) are absent — SEMANTICS.md names the
|
|
576
|
+
* divergence. Result +1. */
|
|
577
|
+
static size_t scr_pending_immediates; /* defined below with the queue */
|
|
578
|
+
ScrArr *scr_active_resources(void) {
|
|
579
|
+
ScrArr *arr = scr_arr_new(SCR_ELEM_STR, 8);
|
|
580
|
+
size_t timeouts = scr_ntimers + ((scr_firing_id != 0 && !scr_firing_cleared) ? 1 : 0);
|
|
581
|
+
for (size_t i = 0; i < timeouts; i++) scr_arr_push_ref(arr, scr_str_new("Timeout", 7));
|
|
582
|
+
for (size_t i = 0; i < scr_pending_immediates; i++) scr_arr_push_ref(arr, scr_str_new("Immediate", 9));
|
|
583
|
+
return arr;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/* ── process.nextTick (the user tick queue) ───────────────────────────
|
|
587
|
+
* A FIFO of user callbacks drained BEFORE promise jobs at every loop
|
|
588
|
+
* checkpoint, to joint exhaustion with them — Node's tick-then-microtask
|
|
589
|
+
* order. Pending ticks are always-ready work: the loop neither sleeps
|
|
590
|
+
* nor exits while any exist. Ticks scheduled from 'exit' listeners (or
|
|
591
|
+
* left queued on the uncaught paths) never run — the teardown releases
|
|
592
|
+
* them, like Node dropping the queue at exit. */
|
|
593
|
+
typedef struct ScrNtick {
|
|
594
|
+
ScrClosure *cb; /* owned */
|
|
595
|
+
struct ScrNtick *next;
|
|
596
|
+
} ScrNtick;
|
|
597
|
+
|
|
598
|
+
static ScrNtick *scr_nt_head = NULL;
|
|
599
|
+
static ScrNtick *scr_nt_tail = NULL;
|
|
600
|
+
|
|
601
|
+
/* Public: releases every queued tick without running it — the loop-exit
|
|
602
|
+
* teardown below AND the exit-listener runner (scr_events.c) both call
|
|
603
|
+
* it, because 'exit' listeners run AFTER the loop's teardown and may
|
|
604
|
+
* enqueue ticks that must never run (Node) yet must not leak. */
|
|
605
|
+
|
|
606
|
+
void scr_next_tick(ScrClosure *cb /*moves*/) {
|
|
607
|
+
ScrNtick *t = calloc(1, sizeof *t);
|
|
608
|
+
if (!t) scr_oom();
|
|
609
|
+
t->cb = cb;
|
|
610
|
+
if (scr_nt_tail) scr_nt_tail->next = t;
|
|
611
|
+
else scr_nt_head = t;
|
|
612
|
+
scr_nt_tail = t;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
void scr_nticks_teardown(void) {
|
|
616
|
+
while (scr_nt_head != NULL) {
|
|
617
|
+
ScrNtick *t = scr_nt_head;
|
|
618
|
+
scr_nt_head = t->next;
|
|
619
|
+
scr_closure_release(t->cb);
|
|
620
|
+
free(t);
|
|
621
|
+
}
|
|
622
|
+
scr_nt_tail = NULL;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/* Releases every armed timer — the island teardown calls this before the
|
|
626
|
+
* engine dies so closures holding engine callbacks free first and the
|
|
627
|
+
* counting allocator's zero-live audit holds (the loop only exits with
|
|
628
|
+
* entries still armed on the uncaught/unhandled paths). */
|
|
629
|
+
static void scr_immediates_teardown(void);
|
|
630
|
+
void scr_timers_teardown(void) {
|
|
631
|
+
for (size_t i = 0; i < scr_ntimers; i++) scr_closure_release(scr_timers[i].cb);
|
|
632
|
+
scr_ntimers = 0;
|
|
633
|
+
scr_reffed_timers = 0;
|
|
634
|
+
scr_immediates_teardown();
|
|
635
|
+
scr_nticks_teardown();
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
void scr_clear_interval(double handle) {
|
|
639
|
+
if (!(handle >= 1)) return; /* NaN/0/negative: never a handle; Node tolerates */
|
|
640
|
+
unsigned long id = (unsigned long)handle;
|
|
641
|
+
if (id == scr_firing_id) {
|
|
642
|
+
scr_firing_cleared = true; /* the run loop drops the callback */
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
for (size_t i = 0; i < scr_ntimers; i++) {
|
|
646
|
+
if (scr_timers[i].id == id) {
|
|
647
|
+
scr_closure_release(scr_timers[i].cb);
|
|
648
|
+
scr_timer_remove_at(i);
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/* ── immediates (Node's check phase) ──────────────────────────────────
|
|
655
|
+
* setImmediate callbacks run once per loop turn, AFTER due timers, in
|
|
656
|
+
* FIFO order — and immediates queued while the phase runs wait for the
|
|
657
|
+
* NEXT turn (the phase snapshots its end index). The queue is an
|
|
658
|
+
* append-only array with a head cursor; clearImmediate NULLs the slot in
|
|
659
|
+
* place (order and indices stay stable mid-phase). Ids ride their own
|
|
660
|
+
* space — clearTimeout of an Immediate is a no-op, like Node. */
|
|
661
|
+
typedef struct {
|
|
662
|
+
unsigned long id;
|
|
663
|
+
ScrClosure *cb; /* owned; NULL once fired or cleared */
|
|
664
|
+
bool reffed; /* keeps the loop alive; unref() clears it (Node semantics) */
|
|
665
|
+
} ScrImmediate;
|
|
666
|
+
|
|
667
|
+
static ScrImmediate *scr_immediates = NULL;
|
|
668
|
+
static size_t scr_nimmediates = 0, scr_immediates_head = 0, scr_immediates_cap = 0;
|
|
669
|
+
static unsigned long scr_immediate_seq = 0;
|
|
670
|
+
/* Pending (queued, uncleared) immediates and the reffed subset — the
|
|
671
|
+
* loop's no-sleep signal and liveness count respectively. */
|
|
672
|
+
static size_t scr_pending_immediates = 0;
|
|
673
|
+
static size_t scr_reffed_immediates = 0;
|
|
674
|
+
|
|
675
|
+
double scr_set_immediate(ScrClosure *cb) {
|
|
676
|
+
if (scr_nimmediates == scr_immediates_cap) {
|
|
677
|
+
scr_immediates_cap = scr_immediates_cap ? scr_immediates_cap * 2 : 16;
|
|
678
|
+
scr_immediates = realloc(scr_immediates, scr_immediates_cap * sizeof *scr_immediates);
|
|
679
|
+
if (!scr_immediates) scr_oom();
|
|
680
|
+
}
|
|
681
|
+
unsigned long id = ++scr_immediate_seq;
|
|
682
|
+
scr_immediates[scr_nimmediates].id = id;
|
|
683
|
+
scr_immediates[scr_nimmediates].cb = cb; /* ownership moves in */
|
|
684
|
+
scr_immediates[scr_nimmediates].reffed = true;
|
|
685
|
+
scr_nimmediates++;
|
|
686
|
+
scr_pending_immediates++;
|
|
687
|
+
scr_reffed_immediates++;
|
|
688
|
+
return (double)id;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
static ScrImmediate *scr_immediate_find(unsigned long id) {
|
|
692
|
+
for (size_t i = scr_immediates_head; i < scr_nimmediates; i++) {
|
|
693
|
+
if (scr_immediates[i].id == id && scr_immediates[i].cb != NULL) return &scr_immediates[i];
|
|
694
|
+
}
|
|
695
|
+
return NULL;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
void scr_clear_immediate(double handle) {
|
|
699
|
+
if (!(handle >= 1)) return; /* NaN/0/negative: never a handle; Node tolerates */
|
|
700
|
+
ScrImmediate *im = scr_immediate_find((unsigned long)handle);
|
|
701
|
+
if (im == NULL) return; /* already fired/cleared: no-op, like Node */
|
|
702
|
+
scr_closure_release(im->cb);
|
|
703
|
+
im->cb = NULL;
|
|
704
|
+
if (scr_pending_immediates > 0) scr_pending_immediates--;
|
|
705
|
+
if (im->reffed) {
|
|
706
|
+
im->reffed = false;
|
|
707
|
+
if (scr_reffed_immediates > 0) scr_reffed_immediates--;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/* Immediate.unref()/ref()/hasRef(): the Timeout trio's exact story over
|
|
712
|
+
* the immediate queue. A fired or cleared handle is a tolerated no-op
|
|
713
|
+
* (Node's Immediate methods on a dead handle do nothing). */
|
|
714
|
+
void scr_immediate_unref(double handle) {
|
|
715
|
+
if (!(handle >= 1)) return;
|
|
716
|
+
ScrImmediate *im = scr_immediate_find((unsigned long)handle);
|
|
717
|
+
if (im && im->reffed) {
|
|
718
|
+
im->reffed = false;
|
|
719
|
+
if (scr_reffed_immediates > 0) scr_reffed_immediates--;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
void scr_immediate_ref(double handle) {
|
|
724
|
+
if (!(handle >= 1)) return;
|
|
725
|
+
ScrImmediate *im = scr_immediate_find((unsigned long)handle);
|
|
726
|
+
if (im && !im->reffed) {
|
|
727
|
+
im->reffed = true;
|
|
728
|
+
scr_reffed_immediates++;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
bool scr_immediate_has_ref(double handle) {
|
|
733
|
+
if (!(handle >= 1)) return false;
|
|
734
|
+
ScrImmediate *im = scr_immediate_find((unsigned long)handle);
|
|
735
|
+
return im != NULL && im->reffed;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/* The teardown sweep's immediate arm (called from scr_timers_teardown):
|
|
739
|
+
* unref'd immediates left queued at normal loop exit never fire (Node),
|
|
740
|
+
* so their closures release here or the RC audit counts them as leaks. */
|
|
741
|
+
static void scr_immediates_teardown(void) {
|
|
742
|
+
for (size_t i = scr_immediates_head; i < scr_nimmediates; i++) {
|
|
743
|
+
if (scr_immediates[i].cb != NULL) scr_closure_release(scr_immediates[i].cb);
|
|
744
|
+
}
|
|
745
|
+
scr_nimmediates = 0;
|
|
746
|
+
scr_immediates_head = 0;
|
|
747
|
+
scr_pending_immediates = 0;
|
|
748
|
+
scr_reffed_immediates = 0;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/* Unhandled rejections: rejected promises retained here until observed. */
|
|
752
|
+
static ScrPromise **scr_maybe_unhandled = NULL;
|
|
753
|
+
static size_t scr_nunhandled = 0, scr_unhandled_cap = 0;
|
|
754
|
+
|
|
755
|
+
static void scr_track_rejection(ScrPromise *p) {
|
|
756
|
+
if (scr_nunhandled == scr_unhandled_cap) {
|
|
757
|
+
scr_unhandled_cap = scr_unhandled_cap ? scr_unhandled_cap * 2 : 8;
|
|
758
|
+
scr_maybe_unhandled = realloc(scr_maybe_unhandled, scr_unhandled_cap * sizeof *scr_maybe_unhandled);
|
|
759
|
+
if (!scr_maybe_unhandled) scr_oom();
|
|
760
|
+
}
|
|
761
|
+
scr_maybe_unhandled[scr_nunhandled++] = scr_promise_retain(p);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
/* ── context switching (ASan-annotated) ───────────────────────────────── */
|
|
765
|
+
|
|
766
|
+
#ifdef SCR_ASAN_FIBERS
|
|
767
|
+
static void *scr_main_fake_stack; /* fake-stack slot for the main context */
|
|
768
|
+
#endif
|
|
769
|
+
|
|
770
|
+
#ifdef _WIN32
|
|
771
|
+
/* The current context's handle, converting the main thread to a fiber on
|
|
772
|
+
* first need (SwitchToFiber requires the caller BE a fiber; the runtime is
|
|
773
|
+
* single-threaded, so one lazy conversion covers the process). Also the
|
|
774
|
+
* loop-home keeper: whenever main is the caller this IS scr_loop_ctx's
|
|
775
|
+
* value — assigned at the conversion so parked fibers can always switch
|
|
776
|
+
* back to it. */
|
|
777
|
+
static void *scr_win_self(void) {
|
|
778
|
+
static bool converted = false;
|
|
779
|
+
if (!converted) {
|
|
780
|
+
scr_loop_ctx = ConvertThreadToFiber(NULL);
|
|
781
|
+
if (scr_loop_ctx == NULL) {
|
|
782
|
+
fputs("scriptc: ConvertThreadToFiber failed\n", stderr);
|
|
783
|
+
abort();
|
|
784
|
+
}
|
|
785
|
+
converted = true;
|
|
786
|
+
}
|
|
787
|
+
return GetCurrentFiber();
|
|
788
|
+
}
|
|
789
|
+
#endif
|
|
790
|
+
|
|
791
|
+
static void scr_switch(ScrCtx *from, ScrCtx *to, ScrFiber *to_fiber) {
|
|
792
|
+
#ifdef SCR_ASAN_FIBERS
|
|
793
|
+
ScrFiber *from_fiber = scr_current;
|
|
794
|
+
#endif
|
|
795
|
+
scr_current = to_fiber;
|
|
796
|
+
scr_exc_swap_cell(to_fiber ? &to_fiber->exc : NULL);
|
|
797
|
+
scr_als_active = to_fiber ? &to_fiber->als : &scr_als_main_slot;
|
|
798
|
+
#ifdef _WIN32
|
|
799
|
+
/* SwitchToFiber snapshots the outgoing fiber's state inside its own
|
|
800
|
+
* fiber object — `from` has nothing to record. */
|
|
801
|
+
(void)from;
|
|
802
|
+
SwitchToFiber(*to);
|
|
803
|
+
#elif defined(SCR_ASAN_FIBERS)
|
|
804
|
+
void **save = from_fiber ? &from_fiber->fake_stack : &scr_main_fake_stack;
|
|
805
|
+
const void *bottom = to_fiber ? to_fiber->stack : NULL;
|
|
806
|
+
size_t size = to_fiber ? SCR_FIBER_STACK : 0;
|
|
807
|
+
__sanitizer_start_switch_fiber(save, bottom, size);
|
|
808
|
+
swapcontext(from, to);
|
|
809
|
+
const void *old_bottom;
|
|
810
|
+
size_t old_size;
|
|
811
|
+
__sanitizer_finish_switch_fiber(
|
|
812
|
+
scr_current ? scr_current->fake_stack : scr_main_fake_stack, &old_bottom, &old_size);
|
|
813
|
+
#else
|
|
814
|
+
swapcontext(from, to);
|
|
815
|
+
#endif
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
static void scr_promise_settle_wake(ScrPromise *p);
|
|
819
|
+
|
|
820
|
+
/* Copies src's settlement into a still-pending dst (payload RETAINED —
|
|
821
|
+
* src can settle other destinations and still be awaited) and wakes dst's
|
|
822
|
+
* waiters. A rejection consumed this way counts as HANDLED on src (a
|
|
823
|
+
* combinator attached a handler, like Node's race). The Promise.race
|
|
824
|
+
* same-inner-type adapter and the rejection path both land here. */
|
|
825
|
+
static void scr_promise_settle_from(ScrPromise *dst, ScrPromise *src) {
|
|
826
|
+
if (dst->state == SCR_PROM_PENDING) {
|
|
827
|
+
dst->state = src->state;
|
|
828
|
+
dst->payload_kind = src->payload_kind;
|
|
829
|
+
dst->f64 = src->f64;
|
|
830
|
+
dst->b = src->b;
|
|
831
|
+
dst->retain_fn = src->retain_fn;
|
|
832
|
+
dst->release_fn = src->release_fn;
|
|
833
|
+
dst->trace_fn = src->trace_fn;
|
|
834
|
+
dst->payload = NULL;
|
|
835
|
+
if (src->payload_kind == SCR_EXC_STR && src->payload) {
|
|
836
|
+
dst->payload = scr_str_retain((ScrStr *)src->payload);
|
|
837
|
+
} else if ((src->payload_kind == SCR_EXC_REF || src->payload_kind == SCR_EXC_OBJ) &&
|
|
838
|
+
src->payload) {
|
|
839
|
+
dst->payload = src->retain_fn(src->payload);
|
|
840
|
+
}
|
|
841
|
+
scr_promise_settle_wake(dst);
|
|
842
|
+
}
|
|
843
|
+
if (src->state == SCR_PROM_REJECTED) src->rejection_observed = true;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
/* The emitted same-type Promise.race adapter (see raceAdapterFor). */
|
|
847
|
+
void scr_promise_adapt_copy(ScrPromise *dst, ScrPromise *src) {
|
|
848
|
+
scr_promise_settle_from(dst, src);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
/* One Promise.all entry settling: a fulfillment stores its payload into
|
|
852
|
+
* the values array at the entry's INPUT index (input order regardless of
|
|
853
|
+
* settlement order) and the LAST missing fulfillment fulfills the result
|
|
854
|
+
* with the array (void-element all fulfills void); a rejection copies raw
|
|
855
|
+
* into the result — the first one in SETTLEMENT order wins through the
|
|
856
|
+
* destination's own pending check, and later ones still count as HANDLED
|
|
857
|
+
* on their entries (settle_from marks them observed), exactly Node's
|
|
858
|
+
* subscribe-to-everything behavior. */
|
|
859
|
+
static void scr_promise_all_settle(ScrAllState *st, ScrPromise *result, size_t idx,
|
|
860
|
+
ScrPromise *src) {
|
|
861
|
+
if (src->state == SCR_PROM_FULFILLED) {
|
|
862
|
+
if (st->store) st->store(st->values, (double)idx, src);
|
|
863
|
+
if (--st->remaining == 0 && result->state == SCR_PROM_PENDING) {
|
|
864
|
+
if (st->values) {
|
|
865
|
+
scr_promise_fulfill_ref(result, scr_arr_retain(st->values), scr_arr_retain_v,
|
|
866
|
+
scr_arr_release_v,
|
|
867
|
+
st->values->elem_trace ? scr_arr_trace_v : NULL);
|
|
868
|
+
} else {
|
|
869
|
+
scr_promise_fulfill_void(result);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
} else {
|
|
873
|
+
scr_promise_settle_from(result, src);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
/* Settle helpers wake waiters into the microtask queue and run the
|
|
878
|
+
* combinator callbacks: adapters for fulfillments (they convert the
|
|
879
|
+
* payload to the destination's inner type), the raw copy for rejections
|
|
880
|
+
* (reasons are dynamically tagged). first-settle-wins rides the
|
|
881
|
+
* destination's own pending check. Promise.all entries dispatch through
|
|
882
|
+
* their shared countdown state instead of the adapt pair. */
|
|
883
|
+
static void scr_promise_settle_wake(ScrPromise *p) {
|
|
884
|
+
for (size_t i = 0; i < p->nwaiters; i++) scr_ready_push(p->waiters[i]);
|
|
885
|
+
p->nwaiters = 0;
|
|
886
|
+
for (size_t i = 0; i < p->ncbs; i++) {
|
|
887
|
+
if (p->cbs[i].all) {
|
|
888
|
+
scr_promise_all_settle(p->cbs[i].all, p->cbs[i].dst, p->cbs[i].all_idx, p);
|
|
889
|
+
scr_promise_all_state_release(p->cbs[i].all);
|
|
890
|
+
} else if (p->state == SCR_PROM_FULFILLED) {
|
|
891
|
+
p->cbs[i].adapt(p->cbs[i].dst, p);
|
|
892
|
+
} else {
|
|
893
|
+
scr_promise_settle_from(p->cbs[i].dst, p);
|
|
894
|
+
}
|
|
895
|
+
scr_promise_release(p->cbs[i].dst);
|
|
896
|
+
}
|
|
897
|
+
p->ncbs = 0;
|
|
898
|
+
if (p->state == SCR_PROM_REJECTED) scr_track_rejection(p);
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
/* ── Promise.race ─────────────────────────────────────────────────────
|
|
902
|
+
* The compiler emits: a fresh result promise, then one race_add per
|
|
903
|
+
* entry. A settled entry settles the result immediately (first add
|
|
904
|
+
* wins); pending entries park a callback waiter that fires inside the
|
|
905
|
+
* entry's settle. Fibers awaiting the result still wake through the
|
|
906
|
+
* microtask queue. Losing entries keep their own settlements (payloads
|
|
907
|
+
* are retained, not moved). */
|
|
908
|
+
|
|
909
|
+
void scr_promise_race_add(ScrPromise *race, ScrPromise *in,
|
|
910
|
+
void (*adapt)(ScrPromise *dst, ScrPromise *src)) {
|
|
911
|
+
if (in->state != SCR_PROM_PENDING) {
|
|
912
|
+
if (in->state == SCR_PROM_FULFILLED) adapt(race, in);
|
|
913
|
+
else scr_promise_settle_from(race, in);
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
if (in->ncbs == in->cbs_cap) {
|
|
917
|
+
in->cbs_cap = in->cbs_cap ? in->cbs_cap * 2 : 4;
|
|
918
|
+
in->cbs = realloc(in->cbs, in->cbs_cap * sizeof *in->cbs);
|
|
919
|
+
if (!in->cbs) scr_oom();
|
|
920
|
+
}
|
|
921
|
+
in->cbs[in->ncbs].adapt = adapt;
|
|
922
|
+
in->cbs[in->ncbs].dst = scr_promise_retain(race);
|
|
923
|
+
in->cbs[in->ncbs].all = NULL;
|
|
924
|
+
in->cbs[in->ncbs].all_idx = 0;
|
|
925
|
+
in->ncbs++;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
/* ── Promise.all ──────────────────────────────────────────────────────
|
|
929
|
+
* BORROWS the entries array `ps` and the pre-capacity values array
|
|
930
|
+
* `values` (NULL for void elements), retains what it keeps, and returns
|
|
931
|
+
* the result promise +1. `values` arrives EMPTY with cap >= ps->len (the
|
|
932
|
+
* emitted construction passes the length) and is pre-sized here: every
|
|
933
|
+
* slot's zero bits are the per-kind placeholder (0.0 / false / NULL),
|
|
934
|
+
* legal to overwrite through the store helpers and to release. Entries
|
|
935
|
+
* already settled settle inline (a rejection settles the result NOW —
|
|
936
|
+
* first rejection in settlement order — and later rejected entries are
|
|
937
|
+
* still marked handled); pending entries park a countdown waiter. The
|
|
938
|
+
* empty array fulfills immediately (awaiters take the settled-await
|
|
939
|
+
* microtask hop, like Node's one-tick resolve). */
|
|
940
|
+
ScrPromise *scr_promise_all(ScrArr *ps, ScrArr *values,
|
|
941
|
+
void (*store)(ScrArr *a, double i, ScrPromise *src)) {
|
|
942
|
+
size_t n = ps->len;
|
|
943
|
+
ScrPromise *result = scr_promise_new();
|
|
944
|
+
if (values && n > 0) {
|
|
945
|
+
memset(values->data, 0, n * sizeof *values->data);
|
|
946
|
+
values->len = n;
|
|
947
|
+
}
|
|
948
|
+
ScrAllState *st = malloc(sizeof *st);
|
|
949
|
+
if (!st) scr_oom();
|
|
950
|
+
st->rc = 1; /* the builder's reference, dropped at the end */
|
|
951
|
+
st->remaining = n;
|
|
952
|
+
st->values = values ? scr_arr_retain(values) : NULL;
|
|
953
|
+
st->store = store;
|
|
954
|
+
for (size_t i = 0; i < n; i++) {
|
|
955
|
+
ScrPromise *in = (ScrPromise *)scr_arr_get_ref(ps, (double)i); /* +1 */
|
|
956
|
+
if (in->state != SCR_PROM_PENDING) {
|
|
957
|
+
scr_promise_all_settle(st, result, i, in);
|
|
958
|
+
} else {
|
|
959
|
+
if (in->ncbs == in->cbs_cap) {
|
|
960
|
+
in->cbs_cap = in->cbs_cap ? in->cbs_cap * 2 : 4;
|
|
961
|
+
in->cbs = realloc(in->cbs, in->cbs_cap * sizeof *in->cbs);
|
|
962
|
+
if (!in->cbs) scr_oom();
|
|
963
|
+
}
|
|
964
|
+
in->cbs[in->ncbs].adapt = NULL;
|
|
965
|
+
in->cbs[in->ncbs].dst = scr_promise_retain(result);
|
|
966
|
+
in->cbs[in->ncbs].all = st;
|
|
967
|
+
in->cbs[in->ncbs].all_idx = i;
|
|
968
|
+
in->ncbs++;
|
|
969
|
+
st->rc++;
|
|
970
|
+
}
|
|
971
|
+
scr_promise_release(in);
|
|
972
|
+
}
|
|
973
|
+
/* n == 0, or every entry was already fulfilled inline. */
|
|
974
|
+
if (st->remaining == 0 && result->state == SCR_PROM_PENDING) {
|
|
975
|
+
if (st->values) {
|
|
976
|
+
scr_promise_fulfill_ref(result, scr_arr_retain(st->values), scr_arr_retain_v,
|
|
977
|
+
scr_arr_release_v,
|
|
978
|
+
st->values->elem_trace ? scr_arr_trace_v : NULL);
|
|
979
|
+
} else {
|
|
980
|
+
scr_promise_fulfill_void(result);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
scr_promise_all_state_release(st);
|
|
984
|
+
return result;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
/* Per-element-kind store helpers for the emitted Promise.all: write the
|
|
988
|
+
* entry's fulfillment payload into the values array at its input index.
|
|
989
|
+
* The payload accessors return retained/by-value (losing a reference is
|
|
990
|
+
* impossible: set_* takes ownership and releases the zero placeholder). */
|
|
991
|
+
void scr_promise_all_store_f64(ScrArr *a, double i, ScrPromise *src) {
|
|
992
|
+
scr_arr_set_f64(a, i, scr_promise_payload_f64(src));
|
|
993
|
+
}
|
|
994
|
+
void scr_promise_all_store_bool(ScrArr *a, double i, ScrPromise *src) {
|
|
995
|
+
scr_arr_set_bool(a, i, scr_promise_payload_bool(src));
|
|
996
|
+
}
|
|
997
|
+
void scr_promise_all_store_str(ScrArr *a, double i, ScrPromise *src) {
|
|
998
|
+
scr_arr_set_ref(a, i, scr_promise_payload_str(src));
|
|
999
|
+
}
|
|
1000
|
+
void scr_promise_all_store_ref(ScrArr *a, double i, ScrPromise *src) {
|
|
1001
|
+
scr_arr_set_ref(a, i, scr_promise_payload_ref(src));
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
/* Fulfillment payload accessors for the emitted race adapters — all
|
|
1005
|
+
* retained/by-value (the source keeps its settlement). */
|
|
1006
|
+
double scr_promise_payload_f64(ScrPromise *p) { return p->f64; }
|
|
1007
|
+
bool scr_promise_payload_bool(ScrPromise *p) { return p->b; }
|
|
1008
|
+
ScrStr *scr_promise_payload_str(ScrPromise *p) {
|
|
1009
|
+
return scr_str_retain((ScrStr *)p->payload);
|
|
1010
|
+
}
|
|
1011
|
+
/* Thin views for the gated dyn-async TU (scr_async_dyn.c): the payload
|
|
1012
|
+
* KIND, whether a REF payload is a DOM value (the dyn adapters), and the
|
|
1013
|
+
* settled-await primitive (park/hop + rejection re-throw; true =
|
|
1014
|
+
* fulfilled). */
|
|
1015
|
+
static bool scr_await_settled(ScrPromise *p); /* defined with the await family */
|
|
1016
|
+
int scr_promise_payload_kind(const ScrPromise *p) { return (int)p->payload_kind; }
|
|
1017
|
+
bool scr_promise_payload_is_dyn(const ScrPromise *p) { return p->retain_fn == scr_dyn_retain_v; }
|
|
1018
|
+
double scr_promise_payload_num(const ScrPromise *p) { return p->f64; }
|
|
1019
|
+
bool scr_promise_payload_flag(const ScrPromise *p) { return p->b; }
|
|
1020
|
+
bool scr_promise_await_settled(ScrPromise *p) { return scr_await_settled(p); }
|
|
1021
|
+
void scr_promise_payload_release(const ScrPromise *p, void *v) { p->release_fn(v); }
|
|
1022
|
+
|
|
1023
|
+
void *scr_promise_payload_ref(ScrPromise *p) {
|
|
1024
|
+
return p->payload ? p->retain_fn(p->payload) : NULL;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
/* MOVES a pending exception out of `cell` into `p` as its rejection payload
|
|
1028
|
+
* (the cell resets to NONE). Shared by fiber completion and the new-Promise
|
|
1029
|
+
* executor runners; callers wake waiters themselves. */
|
|
1030
|
+
static void scr_promise_reject_from_cell(ScrPromise *p, ScrExcCell *cell) {
|
|
1031
|
+
p->state = SCR_PROM_REJECTED;
|
|
1032
|
+
p->payload_kind = cell->kind;
|
|
1033
|
+
p->f64 = cell->f64;
|
|
1034
|
+
p->b = cell->b;
|
|
1035
|
+
p->payload = cell->payload;
|
|
1036
|
+
p->retain_fn = cell->retain_fn;
|
|
1037
|
+
p->release_fn = cell->release_fn;
|
|
1038
|
+
p->trace_fn = cell->trace_fn;
|
|
1039
|
+
cell->kind = SCR_EXC_NONE;
|
|
1040
|
+
cell->payload = NULL;
|
|
1041
|
+
cell->trace_fn = NULL;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
static void scr_fiber_finish(ScrFiber *self) {
|
|
1045
|
+
if (self->gen != NULL) {
|
|
1046
|
+
/* Generator completion: the emitted trampoline already stored the
|
|
1047
|
+
* completion value (or consumed the GENRET sentinel); a real body
|
|
1048
|
+
* exception stays pending in this fiber's cell — the consumer-side
|
|
1049
|
+
* resume moves it into the resumer's cell. No promise to settle. */
|
|
1050
|
+
self->done = true;
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
ScrPromise *p = self->promise;
|
|
1054
|
+
if (scr_exc_pending()) {
|
|
1055
|
+
/* The body's exception escaped: the promise rejects with the payload
|
|
1056
|
+
* (moved from the fiber's cell into the promise). */
|
|
1057
|
+
scr_promise_reject_from_cell(p, &self->exc);
|
|
1058
|
+
}
|
|
1059
|
+
/* Fulfillment payload was stored by the trampoline before finishing. */
|
|
1060
|
+
scr_promise_settle_wake(p);
|
|
1061
|
+
self->done = true;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
#ifdef _WIN32
|
|
1065
|
+
static void CALLBACK scr_trampoline(void *param) {
|
|
1066
|
+
(void)param;
|
|
1067
|
+
#else
|
|
1068
|
+
static void scr_trampoline(void) {
|
|
1069
|
+
#endif
|
|
1070
|
+
#ifdef SCR_ASAN_FIBERS
|
|
1071
|
+
const void *ob;
|
|
1072
|
+
size_t os;
|
|
1073
|
+
__sanitizer_finish_switch_fiber(scr_current->fake_stack, &ob, &os);
|
|
1074
|
+
#endif
|
|
1075
|
+
ScrFiber *self = scr_current;
|
|
1076
|
+
self->entry(self, self->argpack);
|
|
1077
|
+
scr_fiber_finish(self);
|
|
1078
|
+
/* Dead fiber: hop back to whoever resumed us. The loop frees us. */
|
|
1079
|
+
scr_switch(&self->ctx, self->return_to, NULL);
|
|
1080
|
+
/* unreachable */
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
/* Frees a finished fiber's execution resources (the promise release and
|
|
1084
|
+
* bookkeeping stay at the call sites). Windows: DeleteFiber tears down the
|
|
1085
|
+
* fiber object and its stack — legal here because a finished fiber has
|
|
1086
|
+
* switched away and can never be current again. */
|
|
1087
|
+
static void scr_fiber_destroy(ScrFiber *f) {
|
|
1088
|
+
#ifdef _WIN32
|
|
1089
|
+
DeleteFiber(f->ctx);
|
|
1090
|
+
#endif
|
|
1091
|
+
scr_als_ctx_release(f->als);
|
|
1092
|
+
free(f->stack);
|
|
1093
|
+
free(f);
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
/* Spawns and EAGERLY runs an async body until its first suspension (JS's
|
|
1097
|
+
* synchronous-prefix rule). Returns the fiber's promise, +1. */
|
|
1098
|
+
ScrPromise *scr_async_spawn(void (*entry)(ScrFiber *, void *), void *argpack) {
|
|
1099
|
+
ScrFiber *f = calloc(1, sizeof *f);
|
|
1100
|
+
if (!f) scr_oom();
|
|
1101
|
+
f->promise = scr_promise_new();
|
|
1102
|
+
f->entry = entry;
|
|
1103
|
+
f->argpack = argpack;
|
|
1104
|
+
/* AsyncLocalStorage: the child runs in the SPAWNER's context (Node's
|
|
1105
|
+
* init-time capture); snapshots are immutable, so a retain suffices. */
|
|
1106
|
+
f->als = scr_als_ctx_retain(*scr_als_active);
|
|
1107
|
+
scr_fibers_live++;
|
|
1108
|
+
|
|
1109
|
+
#ifdef _WIN32
|
|
1110
|
+
/* The commit size is the ucontext stack's size; the reserve stays the
|
|
1111
|
+
* default 1MB. Committed lazily by the OS, like the malloc'd stacks. */
|
|
1112
|
+
ScrCtx here = scr_win_self();
|
|
1113
|
+
f->ctx = CreateFiber(SCR_FIBER_STACK, scr_trampoline, NULL);
|
|
1114
|
+
if (f->ctx == NULL) scr_oom();
|
|
1115
|
+
#else
|
|
1116
|
+
f->stack = malloc(SCR_FIBER_STACK);
|
|
1117
|
+
if (!f->stack) scr_oom();
|
|
1118
|
+
getcontext(&f->ctx);
|
|
1119
|
+
f->ctx.uc_stack.ss_sp = f->stack;
|
|
1120
|
+
f->ctx.uc_stack.ss_size = SCR_FIBER_STACK;
|
|
1121
|
+
f->ctx.uc_link = NULL;
|
|
1122
|
+
makecontext(&f->ctx, scr_trampoline, 0);
|
|
1123
|
+
|
|
1124
|
+
ucontext_t here;
|
|
1125
|
+
#endif
|
|
1126
|
+
f->return_to = &here;
|
|
1127
|
+
ScrFiber *spawner = scr_current;
|
|
1128
|
+
scr_switch(&here, &f->ctx, f);
|
|
1129
|
+
/* back: the fiber suspended or finished. The switch back targeted NULL
|
|
1130
|
+
* (the child doesn't know who spawned it), which pointed both the current
|
|
1131
|
+
* fiber AND the exception machinery at main — restore the spawner's cell
|
|
1132
|
+
* too, or a fiber that eagerly spawned another keeps throwing/catching
|
|
1133
|
+
* against main's cell and completes with its own cell empty (a phantom
|
|
1134
|
+
* `undefined` rejection while the real payload leaks). */
|
|
1135
|
+
scr_current = spawner;
|
|
1136
|
+
scr_exc_swap_cell(spawner ? &spawner->exc : NULL);
|
|
1137
|
+
scr_als_active = spawner ? &spawner->als : &scr_als_main_slot;
|
|
1138
|
+
ScrPromise *result = scr_promise_retain(f->promise);
|
|
1139
|
+
if (f->done) {
|
|
1140
|
+
scr_promise_release(f->promise);
|
|
1141
|
+
scr_fiber_destroy(f);
|
|
1142
|
+
scr_fibers_live--;
|
|
1143
|
+
}
|
|
1144
|
+
return result;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
/* Parks the current fiber on `p` until it settles. Only fibers await. */
|
|
1148
|
+
static void scr_await_park(ScrPromise *p) {
|
|
1149
|
+
ScrFiber *self = scr_current;
|
|
1150
|
+
if (!self) {
|
|
1151
|
+
fputs("scriptc: internal error: await outside an async function\n", stderr);
|
|
1152
|
+
abort();
|
|
1153
|
+
}
|
|
1154
|
+
if (p->nwaiters == p->waiters_cap) {
|
|
1155
|
+
p->waiters_cap = p->waiters_cap ? p->waiters_cap * 2 : 4;
|
|
1156
|
+
p->waiters = realloc(p->waiters, p->waiters_cap * sizeof *p->waiters);
|
|
1157
|
+
if (!p->waiters) scr_oom();
|
|
1158
|
+
}
|
|
1159
|
+
p->waiters[p->nwaiters++] = self;
|
|
1160
|
+
scr_switch(&self->ctx, self->return_to, NULL);
|
|
1161
|
+
/* Resumed by the loop: return_to must now point at the loop's context. */
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
/* One microtask hop: park the current fiber on the READY queue itself and
|
|
1165
|
+
* yield. JS's `await` ALWAYS suspends — even on an already-settled promise
|
|
1166
|
+
* the continuation runs as a microtask — so without this hop an awaiter of
|
|
1167
|
+
* a settled promise would continue synchronously inside the spawner,
|
|
1168
|
+
* observably earlier than Node (corpus 1428 pins the ordering). */
|
|
1169
|
+
static void scr_await_yield(void) {
|
|
1170
|
+
ScrFiber *self = scr_current;
|
|
1171
|
+
if (!self) {
|
|
1172
|
+
fputs("scriptc: internal error: await outside an async function\n", stderr);
|
|
1173
|
+
abort();
|
|
1174
|
+
}
|
|
1175
|
+
scr_ready_push(self);
|
|
1176
|
+
scr_switch(&self->ctx, self->return_to, NULL);
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
/* The emitted promise-or-absent await's unit arm (`await u` where u holds
|
|
1180
|
+
* undefined/null): JS awaits non-thenables through exactly one microtask
|
|
1181
|
+
* turn — the same hop a settled promise takes. */
|
|
1182
|
+
void scr_await_hop(void) { scr_await_yield(); }
|
|
1183
|
+
|
|
1184
|
+
/* Await result extraction. Rejection re-throws into the awaiter (payload
|
|
1185
|
+
* RETAINED, not moved — a promise can be awaited more than once). */
|
|
1186
|
+
static bool scr_await_settled(ScrPromise *p) {
|
|
1187
|
+
if (p->state != SCR_PROM_PENDING) scr_await_yield();
|
|
1188
|
+
while (p->state == SCR_PROM_PENDING) scr_await_park(p);
|
|
1189
|
+
p->rejection_observed = true;
|
|
1190
|
+
if (p->state == SCR_PROM_REJECTED) {
|
|
1191
|
+
switch (p->payload_kind) {
|
|
1192
|
+
case SCR_EXC_F64: scr_throw_f64(p->f64); break;
|
|
1193
|
+
case SCR_EXC_BOOL: scr_throw_bool(p->b); break;
|
|
1194
|
+
case SCR_EXC_STR: scr_throw_str(scr_str_retain((ScrStr *)p->payload)); break;
|
|
1195
|
+
case SCR_EXC_REF: scr_throw_ref(p->retain_fn(p->payload), p->retain_fn, p->release_fn, p->trace_fn); break;
|
|
1196
|
+
case SCR_EXC_OBJ: scr_throw_obj(p->retain_fn(p->payload), p->retain_fn, p->release_fn, p->trace_fn); break;
|
|
1197
|
+
case SCR_EXC_NONE:
|
|
1198
|
+
case SCR_EXC_GENRET: /* unreachable: the sentinel never settles a promise */
|
|
1199
|
+
scr_throw_str(scr_str_new("undefined", 9));
|
|
1200
|
+
break;
|
|
1201
|
+
}
|
|
1202
|
+
return false;
|
|
1203
|
+
}
|
|
1204
|
+
return true;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
double scr_await_f64(ScrPromise *p) {
|
|
1208
|
+
return scr_await_settled(p) ? p->f64 : 0;
|
|
1209
|
+
}
|
|
1210
|
+
bool scr_await_bool(ScrPromise *p) {
|
|
1211
|
+
return scr_await_settled(p) ? p->b : false;
|
|
1212
|
+
}
|
|
1213
|
+
void *scr_await_ref(ScrPromise *p) {
|
|
1214
|
+
return scr_await_settled(p) && p->payload ? p->retain_fn(p->payload) : NULL;
|
|
1215
|
+
}
|
|
1216
|
+
void scr_await_void(ScrPromise *p) { scr_await_settled(p); }
|
|
1217
|
+
|
|
1218
|
+
|
|
1219
|
+
|
|
1220
|
+
|
|
1221
|
+
|
|
1222
|
+
|
|
1223
|
+
/* Fulfillment storers, called by the compiled trampolines / resolve thunks. */
|
|
1224
|
+
void scr_promise_fulfill_f64(ScrPromise *p, double v) {
|
|
1225
|
+
if (p->state != SCR_PROM_PENDING) return; /* first settle wins */
|
|
1226
|
+
p->state = SCR_PROM_FULFILLED;
|
|
1227
|
+
p->payload_kind = SCR_EXC_F64;
|
|
1228
|
+
p->f64 = v;
|
|
1229
|
+
scr_promise_settle_wake(p);
|
|
1230
|
+
}
|
|
1231
|
+
void scr_promise_fulfill_bool(ScrPromise *p, bool v) {
|
|
1232
|
+
if (p->state != SCR_PROM_PENDING) return;
|
|
1233
|
+
p->state = SCR_PROM_FULFILLED;
|
|
1234
|
+
p->payload_kind = SCR_EXC_BOOL;
|
|
1235
|
+
p->b = v;
|
|
1236
|
+
scr_promise_settle_wake(p);
|
|
1237
|
+
}
|
|
1238
|
+
void scr_promise_fulfill_ref(ScrPromise *p, void *v, void *(*retain)(void *), void (*release)(void *), ScrTraceFn trace) {
|
|
1239
|
+
if (p->state != SCR_PROM_PENDING) {
|
|
1240
|
+
if (release) release(v); /* value was +1 for us; drop it */
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
p->state = SCR_PROM_FULFILLED;
|
|
1244
|
+
p->payload_kind = v ? SCR_EXC_REF : SCR_EXC_NONE;
|
|
1245
|
+
p->payload = v;
|
|
1246
|
+
p->retain_fn = retain;
|
|
1247
|
+
p->release_fn = release;
|
|
1248
|
+
p->trace_fn = trace;
|
|
1249
|
+
scr_promise_settle_wake(p);
|
|
1250
|
+
}
|
|
1251
|
+
void scr_promise_fulfill_void(ScrPromise *p) {
|
|
1252
|
+
if (p->state != SCR_PROM_PENDING) return;
|
|
1253
|
+
p->state = SCR_PROM_FULFILLED;
|
|
1254
|
+
p->payload_kind = SCR_EXC_NONE;
|
|
1255
|
+
scr_promise_settle_wake(p);
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
/* String payloads ride the STR kind so awaits type them precisely. */
|
|
1259
|
+
void scr_promise_fulfill_str(ScrPromise *p, ScrStr *v) {
|
|
1260
|
+
if (p->state != SCR_PROM_PENDING) {
|
|
1261
|
+
scr_str_release(v);
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
p->state = SCR_PROM_FULFILLED;
|
|
1265
|
+
p->payload_kind = SCR_EXC_STR;
|
|
1266
|
+
p->payload = v; /* ownership moves in */
|
|
1267
|
+
p->retain_fn = (void *(*)(void *))scr_str_retain;
|
|
1268
|
+
p->release_fn = (void (*)(void *))scr_str_release;
|
|
1269
|
+
scr_promise_settle_wake(p);
|
|
1270
|
+
}
|
|
1271
|
+
ScrStr *scr_await_str(ScrPromise *p) {
|
|
1272
|
+
return scr_await_settled(p) && p->payload ? scr_str_retain((ScrStr *)p->payload) : NULL;
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
/* REJECTS `p` with the exception pending in the ACTIVE cell (moved out —
|
|
1276
|
+
* the cell resets, so the caller returns to the engine/loop with a clean
|
|
1277
|
+
* cell) and wakes waiters; the rejection enters the unhandled ledger like
|
|
1278
|
+
* any other. The island → static promise bridge's rejection half: its
|
|
1279
|
+
* settle callback converts the engine reason into a pending exception
|
|
1280
|
+
* (the exception bridge's one conversion), then lands it here. A no-op
|
|
1281
|
+
* with nothing pending; an already-settled `p` just clears the cell
|
|
1282
|
+
* (first settle wins, like resolve-then-throw). */
|
|
1283
|
+
void scr_promise_reject_pending(ScrPromise *p) {
|
|
1284
|
+
if (!scr_exc_pending()) return;
|
|
1285
|
+
if (p->state == SCR_PROM_PENDING) {
|
|
1286
|
+
scr_promise_reject_from_cell(p, scr_exc_current_cell());
|
|
1287
|
+
scr_promise_settle_wake(p);
|
|
1288
|
+
} else {
|
|
1289
|
+
scr_exc_clear();
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
/* ── settled-promise minting (the fs/promises bridge) ─────────────────
|
|
1294
|
+
* The promise-returning stdlib functions run their syscall SYNCHRONOUSLY
|
|
1295
|
+
* (the documented non-interleaving divergence, SEMANTICS.md) and wrap the
|
|
1296
|
+
* outcome here: a pending exception in the active cell becomes the
|
|
1297
|
+
* promise's REJECTION (moved out of the cell — the caller's pending check
|
|
1298
|
+
* then sees a clean cell, exactly like Node where fs/promises failures
|
|
1299
|
+
* reject instead of throwing), anything else fulfills with the payload.
|
|
1300
|
+
* The rejected case still enters the unhandled-rejection ledger, so a
|
|
1301
|
+
* never-awaited failure reports at exit like any other rejection. */
|
|
1302
|
+
|
|
1303
|
+
static ScrPromise *scr_promise_settled_common(void) {
|
|
1304
|
+
if (!scr_exc_pending()) return NULL;
|
|
1305
|
+
ScrPromise *p = scr_promise_new();
|
|
1306
|
+
scr_promise_reject_from_cell(p, scr_exc_current_cell());
|
|
1307
|
+
scr_promise_settle_wake(p); /* no waiters yet; enters the ledger */
|
|
1308
|
+
return p;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
ScrPromise *scr_promise_settled_str(ScrStr *v) {
|
|
1312
|
+
ScrPromise *rejected = scr_promise_settled_common();
|
|
1313
|
+
if (rejected) {
|
|
1314
|
+
scr_str_release(v); /* NULL-tolerant; the op returned a dummy */
|
|
1315
|
+
return rejected;
|
|
1316
|
+
}
|
|
1317
|
+
ScrPromise *p = scr_promise_new();
|
|
1318
|
+
scr_promise_fulfill_str(p, v); /* moves in */
|
|
1319
|
+
return p;
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
ScrPromise *scr_promise_settled_void(void) {
|
|
1323
|
+
ScrPromise *rejected = scr_promise_settled_common();
|
|
1324
|
+
if (rejected) return rejected;
|
|
1325
|
+
ScrPromise *p = scr_promise_new();
|
|
1326
|
+
scr_promise_fulfill_void(p);
|
|
1327
|
+
return p;
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
ScrPromise *scr_promise_settled_ref(void *v, void *(*retain)(void *), void (*release)(void *),
|
|
1331
|
+
ScrTraceFn trace) {
|
|
1332
|
+
ScrPromise *rejected = scr_promise_settled_common();
|
|
1333
|
+
if (rejected) {
|
|
1334
|
+
if (v && release) release(v);
|
|
1335
|
+
return rejected;
|
|
1336
|
+
}
|
|
1337
|
+
ScrPromise *p = scr_promise_new();
|
|
1338
|
+
scr_promise_fulfill_ref(p, v, retain, release, trace); /* moves in */
|
|
1339
|
+
return p;
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
/* ── fs/promises ─────────────────────────────────────────────────────
|
|
1343
|
+
* The promise forms run the SAME sync operations and mint an already-
|
|
1344
|
+
* settled promise (scr_promise_settled_*): success fulfills, a pending
|
|
1345
|
+
* exception moves in as the rejection — catchable at the await, like
|
|
1346
|
+
* Node. DOCUMENTED DIVERGENCE (SEMANTICS.md): the syscall blocks the
|
|
1347
|
+
* event loop, so I/O never interleaves with timers or other fibers —
|
|
1348
|
+
* observable only in concurrent code, not in the sequential await
|
|
1349
|
+
* chains CLIs are made of. */
|
|
1350
|
+
|
|
1351
|
+
ScrPromise *scr_fsp_read_file(ScrStr *path) {
|
|
1352
|
+
return scr_promise_settled_str(scr_fs_read_file(path));
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
ScrPromise *scr_fsp_write_file(ScrStr *path, ScrStr *data) {
|
|
1356
|
+
scr_fs_write_file(path, data);
|
|
1357
|
+
return scr_promise_settled_void();
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
ScrPromise *scr_fsp_mkdir(ScrStr *path) {
|
|
1361
|
+
scr_fs_mkdir(path);
|
|
1362
|
+
return scr_promise_settled_void();
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
ScrPromise *scr_fsp_mkdir_mode(ScrStr *path, double mode) {
|
|
1366
|
+
scr_fs_mkdir_mode(path, mode);
|
|
1367
|
+
return scr_promise_settled_void();
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
ScrPromise *scr_fsp_mkdir_recursive(ScrStr *path) {
|
|
1371
|
+
scr_fs_mkdir_recursive(path);
|
|
1372
|
+
return scr_promise_settled_void();
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
ScrPromise *scr_fsp_mkdir_recursive_mode(ScrStr *path, double mode) {
|
|
1376
|
+
scr_fs_mkdir_recursive_mode(path, mode);
|
|
1377
|
+
return scr_promise_settled_void();
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
ScrPromise *scr_fsp_unlink(ScrStr *path) {
|
|
1381
|
+
scr_fs_unlink(path);
|
|
1382
|
+
return scr_promise_settled_void();
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
ScrPromise *scr_fsp_chmod(ScrStr *path, double mode) {
|
|
1386
|
+
scr_fs_chmod(path, mode);
|
|
1387
|
+
return scr_promise_settled_void();
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
ScrPromise *scr_fsp_readdir(ScrStr *path) {
|
|
1391
|
+
ScrArr *names = scr_fs_readdir(path);
|
|
1392
|
+
return scr_promise_settled_ref(names, &scr_arr_retain_v, &scr_arr_release_v, NULL);
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
ScrPromise *scr_fsp_rm(ScrStr *path) {
|
|
1396
|
+
scr_fs_rm(path);
|
|
1397
|
+
return scr_promise_settled_void();
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
ScrPromise *scr_fsp_stat(ScrStr *path) {
|
|
1401
|
+
ScrStats *st = scr_fs_stat(path);
|
|
1402
|
+
return scr_promise_settled_ref(st, &scr_stats_retain_v, &scr_stats_release_v, NULL);
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
/* ── node:timers/promises ────────────────────────────────────────────
|
|
1406
|
+
* The promisified pair: a PENDING void promise a one-shot heap timer
|
|
1407
|
+
* (setTimeout) or the immediate queue (setImmediate) fulfills — the
|
|
1408
|
+
* resolve closure is scr_make_resolve's void thunk, so settlement wakes
|
|
1409
|
+
* awaiting fibers exactly like every other promise, and the armed
|
|
1410
|
+
* timer/immediate keeps the loop alive until it fires (Node: an awaited
|
|
1411
|
+
* timers/promises sleep holds the process open). Neither throws; the
|
|
1412
|
+
* delay rides scr_set_timeout's own Node-exact coercion (clamp to 1,
|
|
1413
|
+
* truncate). */
|
|
1414
|
+
|
|
1415
|
+
ScrPromise *scr_tp_set_timeout(double ms) {
|
|
1416
|
+
ScrPromise *p = scr_promise_new();
|
|
1417
|
+
scr_set_timeout(scr_make_resolve(p, 3 /* void */), ms);
|
|
1418
|
+
return p;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
ScrPromise *scr_tp_set_immediate(void) {
|
|
1422
|
+
ScrPromise *p = scr_promise_new();
|
|
1423
|
+
scr_set_immediate(scr_make_resolve(p, 3 /* void */));
|
|
1424
|
+
return p;
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
/* ── setImmediate as a first-class DOM value ─────────────────────────
|
|
1428
|
+
* The global passed AS A FUNCTION VALUE into untyped code (the Node-suite
|
|
1429
|
+
* traceCallback shape: `channel.traceCallback(setImmediate, 0, ctx, null,
|
|
1430
|
+
* cb)`). The minted dyn callable schedules its own immediate that calls
|
|
1431
|
+
* args[0] with the remaining arguments (Node's setImmediate(cb, ...args)
|
|
1432
|
+
* contract) and answers undefined (the Immediate handle object has no DOM
|
|
1433
|
+
* story — clearImmediate over this value is not modeled). A non-function
|
|
1434
|
+
* first argument throws Node's ERR_INVALID_ARG_TYPE synchronously. */
|
|
1435
|
+
|
|
1436
|
+
static void scr_imm_dyn_fire(ScrClosure *c) {
|
|
1437
|
+
ScrDyn *fn = (ScrDyn *)scr_box_get_ref(c->caps[0]);
|
|
1438
|
+
ScrDyn *args = (ScrDyn *)scr_box_get_ref(c->caps[1]);
|
|
1439
|
+
ScrDyn *r = scr_dyn_call(fn, args->v.arr.items, args->v.arr.len, "immediate callback");
|
|
1440
|
+
if (r != NULL) scr_dyn_release(r);
|
|
1441
|
+
scr_dyn_release(fn);
|
|
1442
|
+
scr_dyn_release(args);
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
static ScrDyn *scr_imm_dyn_thunk(ScrClosure *clo, ScrDyn *const *args, size_t argc) {
|
|
1446
|
+
(void)clo;
|
|
1447
|
+
if (argc == 0 || args[0]->kind != SCR_DYN_FUNC) {
|
|
1448
|
+
static const char msg[] = "The \"callback\" argument must be of type function.";
|
|
1449
|
+
scr_throw_error_msg_code(SCR_ERR_TYPE, msg, sizeof msg - 1, "ERR_INVALID_ARG_TYPE");
|
|
1450
|
+
return NULL;
|
|
1451
|
+
}
|
|
1452
|
+
ScrDyn *rest = scr_dyn_new_arr();
|
|
1453
|
+
for (size_t i = 1; i < argc; i++) scr_dyn_arr_push(rest, scr_dyn_retain(args[i]));
|
|
1454
|
+
ScrClosure *c = scr_closure_new((void *)scr_imm_dyn_fire, 2);
|
|
1455
|
+
c->caps[0] = scr_box_new_obj(scr_dyn_retain_v, scr_dyn_release_v, NULL);
|
|
1456
|
+
scr_box_set_ref(c->caps[0], scr_dyn_retain(args[0]));
|
|
1457
|
+
c->caps[1] = scr_box_new_obj(scr_dyn_retain_v, scr_dyn_release_v, NULL);
|
|
1458
|
+
scr_box_set_ref(c->caps[1], rest); /* moves */
|
|
1459
|
+
scr_set_immediate(c);
|
|
1460
|
+
return scr_dyn_retain(scr_dyn_undefined());
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
ScrDyn *scr_set_immediate_dyn_value(void) {
|
|
1464
|
+
return scr_dyn_new_func(scr_closure_new((void *)scr_imm_dyn_thunk, 0), scr_imm_dyn_thunk,
|
|
1465
|
+
1, "(cb,...)", "setImmediate");
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
/* ── queueMicrotask ──────────────────────────────────────────────────
|
|
1469
|
+
* The READY queue's FIFO is the microtask queue (promise continuations
|
|
1470
|
+
* ride it as parked fibers), so a queueMicrotask callback enters the SAME
|
|
1471
|
+
* queue as a stackless envelope — one order across both producers, like
|
|
1472
|
+
* V8's single microtask queue. scr_resume_fiber runs the closure on the
|
|
1473
|
+
* main stack; a throw is an uncaught exception (Node's queueMicrotask),
|
|
1474
|
+
* never a rejection. */
|
|
1475
|
+
void scr_queue_microtask(ScrClosure *cb) {
|
|
1476
|
+
ScrFiber *f = calloc(1, sizeof *f);
|
|
1477
|
+
if (!f) scr_oom();
|
|
1478
|
+
f->micro_cb = cb; /* ownership moves in */
|
|
1479
|
+
scr_ready_push(f);
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
/* The checked-dynamic argument form (JS files — common.mustCall wrappers
|
|
1483
|
+
* and the suite's invalid-input probes): a non-function throws Node's
|
|
1484
|
+
* ERR_INVALID_ARG_TYPE synchronously; a function value is called with
|
|
1485
|
+
* zero arguments (Node passes none — extra queueMicrotask arguments are
|
|
1486
|
+
* ignored). */
|
|
1487
|
+
static void scr_micro_dyn_fire(ScrClosure *c) {
|
|
1488
|
+
ScrDyn *fn = (ScrDyn *)scr_box_get_ref(c->caps[0]);
|
|
1489
|
+
ScrDyn *r = scr_dyn_call(fn, NULL, 0, "queueMicrotask callback");
|
|
1490
|
+
if (r != NULL) scr_dyn_release(r);
|
|
1491
|
+
scr_dyn_release(fn);
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
void scr_queue_microtask_dyn(const ScrDyn *cb) {
|
|
1495
|
+
if (cb->kind != SCR_DYN_FUNC) {
|
|
1496
|
+
static const char msg[] = "The \"callback\" argument must be of type function.";
|
|
1497
|
+
scr_throw_error_msg_code(SCR_ERR_TYPE, msg, sizeof msg - 1, "ERR_INVALID_ARG_TYPE");
|
|
1498
|
+
return;
|
|
1499
|
+
}
|
|
1500
|
+
ScrClosure *c = scr_closure_new((void *)scr_micro_dyn_fire, 1);
|
|
1501
|
+
c->caps[0] = scr_box_new_obj(scr_dyn_retain_v, scr_dyn_release_v, NULL);
|
|
1502
|
+
scr_box_set_ref(c->caps[0], scr_dyn_retain((ScrDyn *)cb));
|
|
1503
|
+
scr_queue_microtask(c);
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
/* ── the event hooks (scr_events.c) ──────────────────────────────────
|
|
1507
|
+
* Process signal/exit events and the piped-stdin surface live in
|
|
1508
|
+
* scr_events.c, which links ONLY into binaries whose IR uses those
|
|
1509
|
+
* surfaces (the io-hook precedent, gated like scr_regex/scr_fetch):
|
|
1510
|
+
* scr_events_install() fills these slots before %main. Event-free
|
|
1511
|
+
* builds keep every slot NULL and the loop is byte-identical in
|
|
1512
|
+
* behavior. The scr_lib.c hooks live in scr_lib.c and the abnormal-exit
|
|
1513
|
+
* code hint in scr_exception.c, so each unit stays self-contained (the
|
|
1514
|
+
* runtime C tests link them separately). */
|
|
1515
|
+
|
|
1516
|
+
static bool (*scr_events_pending_fn)(void) = NULL; /* keeps the loop alive */
|
|
1517
|
+
static bool (*scr_events_watching_fn)(void) = NULL; /* wants the poll sleep */
|
|
1518
|
+
static void (*scr_events_dispatch_fn)(void) = NULL; /* fire due listeners */
|
|
1519
|
+
static int (*scr_events_pollfds_fn)(int out[2]) = NULL;
|
|
1520
|
+
|
|
1521
|
+
void scr_loop_set_events(bool (*pending)(void), bool (*watching)(void),
|
|
1522
|
+
void (*dispatch)(void), int (*pollfds)(int out[2])) {
|
|
1523
|
+
scr_events_pending_fn = pending;
|
|
1524
|
+
scr_events_watching_fn = watching;
|
|
1525
|
+
scr_events_dispatch_fn = dispatch;
|
|
1526
|
+
scr_events_pollfds_fn = pollfds;
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
/* ── the event loop ───────────────────────────────────────────────────── */
|
|
1530
|
+
|
|
1531
|
+
static void scr_resume_fiber(ScrFiber *f) {
|
|
1532
|
+
/* A queueMicrotask envelope: run the closure on the main stack, no
|
|
1533
|
+
* context switch. A throw leaves the exception cell pending — every
|
|
1534
|
+
* drain site returns to main's uncaught report, Node's queueMicrotask
|
|
1535
|
+
* semantics (uncaughtException, never an unhandled rejection). */
|
|
1536
|
+
if (f->micro_cb != NULL) {
|
|
1537
|
+
ScrClosure *cb = f->micro_cb;
|
|
1538
|
+
free(f);
|
|
1539
|
+
((void (*)(ScrClosure *))cb->fn)(cb);
|
|
1540
|
+
scr_closure_release(cb);
|
|
1541
|
+
return;
|
|
1542
|
+
}
|
|
1543
|
+
#ifdef _WIN32
|
|
1544
|
+
/* The loop runs on the main stack; make sure scr_loop_ctx names its
|
|
1545
|
+
* fiber handle (a no-op after the first conversion). */
|
|
1546
|
+
(void)scr_win_self();
|
|
1547
|
+
#endif
|
|
1548
|
+
f->return_to = &scr_loop_ctx;
|
|
1549
|
+
scr_switch(&scr_loop_ctx, &f->ctx, f);
|
|
1550
|
+
if (f->done) {
|
|
1551
|
+
scr_promise_release(f->promise);
|
|
1552
|
+
scr_fiber_destroy(f);
|
|
1553
|
+
scr_fibers_live--;
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
/* Reap granularity while children are pending on the POLLING fallback:
|
|
1558
|
+
* the loop polls waitpid(WNOHANG) at least this often instead of sleeping
|
|
1559
|
+
* to the next timer deadline. The primary path has no cap — the sleep
|
|
1560
|
+
* waits on the child watch (scr_children_wait — kqueue NOTE_EXIT on BSD,
|
|
1561
|
+
* pidfd epoll on Linux, the timer deadline riding the wait timeout) and a
|
|
1562
|
+
* child's exit wakes it immediately. The fallback covers platforms with
|
|
1563
|
+
* neither backend, children whose exit watch could not be armed, and
|
|
1564
|
+
* turns where the io poll takes the sleep (curl's fds can't join it). */
|
|
1565
|
+
#define SCR_CHILD_POLL_MS 1.0
|
|
1566
|
+
|
|
1567
|
+
/* Sleep cap while only external io is pending (no timer models its
|
|
1568
|
+
* wakeup): the io poll sleeps on REAL fds inside this window (a fetch
|
|
1569
|
+
* transfer's sockets wake it early), so a generous cap costs nothing. */
|
|
1570
|
+
#define SCR_IO_POLL_MS 1000.0
|
|
1571
|
+
|
|
1572
|
+
/* Sleep cap while signal handlers or stdin consumers are pending AND the
|
|
1573
|
+
* io poll owns the sleep (curl's fd wait retries on EINTR and can't watch
|
|
1574
|
+
* the wake pipe): bounded signal/stdin latency during a fetch at 20
|
|
1575
|
+
* wakeups/second. The dedicated poll(2) sleep needs no cap — it waits on
|
|
1576
|
+
* the wake pipe and fd 0 directly. */
|
|
1577
|
+
#define SCR_SIGNAL_POLL_MS 50.0
|
|
1578
|
+
|
|
1579
|
+
/* The external io hook (scr_runtime.h): the dynamic island registers
|
|
1580
|
+
* engine-job draining + fetch transfer polling here. Static builds never
|
|
1581
|
+
* set it — both slots stay NULL and the loop is byte-identical in
|
|
1582
|
+
* behavior. */
|
|
1583
|
+
static bool (*scr_io_pending_fn)(void) = NULL;
|
|
1584
|
+
static void (*scr_io_poll_fn)(double max_wait_ms) = NULL;
|
|
1585
|
+
|
|
1586
|
+
void scr_loop_set_io(bool (*pending)(void), void (*poll)(double)) {
|
|
1587
|
+
scr_io_pending_fn = pending;
|
|
1588
|
+
scr_io_poll_fn = poll;
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
/* The island's earliest armed timer deadline (HUGE_VAL when none): caps
|
|
1592
|
+
* the sleep below so an armed AbortSignal.timeout fires on time while
|
|
1593
|
+
* the loop waits on socket readiness — without keeping the loop alive by
|
|
1594
|
+
* itself (the liveness test never consults it, Node's unref'd timer). */
|
|
1595
|
+
static double (*scr_island_deadline_fn)(void) = NULL;
|
|
1596
|
+
|
|
1597
|
+
void scr_loop_set_island_deadline(double (*fn)(void)) { scr_island_deadline_fn = fn; }
|
|
1598
|
+
|
|
1599
|
+
/* The net hook (scr_net.c, when linked — the events-hook shape):
|
|
1600
|
+
* `pending` keeps the loop alive while servers/sockets are live,
|
|
1601
|
+
* `dispatch` fires at every turn top, and `pollfd` is the unit's poller
|
|
1602
|
+
* fd for the idle poll(2) sleep (readable while events are pending —
|
|
1603
|
+
* kqueue and epoll fds both behave this way).
|
|
1604
|
+
* Net-free builds keep every slot NULL and the loop is byte-identical. */
|
|
1605
|
+
static bool (*scr_net_pending_fn)(void) = NULL;
|
|
1606
|
+
static void (*scr_net_dispatch_fn)(void) = NULL;
|
|
1607
|
+
static int (*scr_net_pollfd_fn)(void) = NULL;
|
|
1608
|
+
|
|
1609
|
+
void scr_loop_set_net(bool (*pending)(void), void (*dispatch)(void), int (*pollfd)(void)) {
|
|
1610
|
+
scr_net_pending_fn = pending;
|
|
1611
|
+
scr_net_dispatch_fn = dispatch;
|
|
1612
|
+
scr_net_pollfd_fn = pollfd;
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
/* The dgram hook (scr_dgram.c, when linked) — the net hook's exact shape:
|
|
1616
|
+
* one more set of nullable slots, byte-identical loop behavior when
|
|
1617
|
+
* unset. */
|
|
1618
|
+
static bool (*scr_dgram_pending_fn)(void) = NULL;
|
|
1619
|
+
static void (*scr_dgram_dispatch_fn)(void) = NULL;
|
|
1620
|
+
static int (*scr_dgram_pollfd_fn)(void) = NULL;
|
|
1621
|
+
|
|
1622
|
+
void scr_loop_set_dgram(bool (*pending)(void), void (*dispatch)(void), int (*pollfd)(void)) {
|
|
1623
|
+
scr_dgram_pending_fn = pending;
|
|
1624
|
+
scr_dgram_dispatch_fn = dispatch;
|
|
1625
|
+
scr_dgram_pollfd_fn = pollfd;
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
/* The fs.watch hook (scr_watch.c, when linked) — the net hook's exact
|
|
1629
|
+
* shape: one more set of nullable slots, byte-identical loop behavior
|
|
1630
|
+
* when unset. */
|
|
1631
|
+
static bool (*scr_watch_pending_fn)(void) = NULL;
|
|
1632
|
+
static void (*scr_watch_dispatch_fn)(void) = NULL;
|
|
1633
|
+
static int (*scr_watch_pollfd_fn)(void) = NULL;
|
|
1634
|
+
|
|
1635
|
+
void scr_loop_set_watch(bool (*pending)(void), void (*dispatch)(void), int (*pollfd)(void)) {
|
|
1636
|
+
scr_watch_pending_fn = pending;
|
|
1637
|
+
scr_watch_dispatch_fn = dispatch;
|
|
1638
|
+
scr_watch_pollfd_fn = pollfd;
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
/* The stream hook (scr_stream.c, when linked): `pending` keeps the loop
|
|
1642
|
+
* alive while deferred stream ticks exist, `dispatch` drains them at the
|
|
1643
|
+
* TOP of every turn — before the events/net stations, the closest
|
|
1644
|
+
* placement to Node's process.nextTick (whose stream emissions these
|
|
1645
|
+
* are). No poller fd: ticks are pure CPU work, always ready. */
|
|
1646
|
+
static bool (*scr_stream_pending_fn)(void) = NULL;
|
|
1647
|
+
static void (*scr_stream_dispatch_fn)(void) = NULL;
|
|
1648
|
+
|
|
1649
|
+
void scr_loop_set_stream(bool (*pending)(void), void (*dispatch)(void)) {
|
|
1650
|
+
scr_stream_pending_fn = pending;
|
|
1651
|
+
scr_stream_dispatch_fn = dispatch;
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
/* Dispatch hooks yield between event batches when fibers are already
|
|
1655
|
+
* queued, so promise jobs interleave with event delivery (Node's
|
|
1656
|
+
* microtask checkpoints between macrotasks). */
|
|
1657
|
+
bool scr_loop_has_ready(void) { return scr_ready_len > 0; }
|
|
1658
|
+
|
|
1659
|
+
void scr_loop_run(void) {
|
|
1660
|
+
/* The FIRST checkpoint after the synchronous main body runs promise
|
|
1661
|
+
* jobs BEFORE the first tick drain: Node's main-module evaluation is
|
|
1662
|
+
* itself awaited (the runMain continuation is a microtask queued after
|
|
1663
|
+
* the body's own), so microtasks scheduled during the body beat ticks
|
|
1664
|
+
* scheduled during the body exactly once, at startup — differentially
|
|
1665
|
+
* pinned. Every later checkpoint drains ticks first. */
|
|
1666
|
+
bool first_checkpoint = true;
|
|
1667
|
+
for (;;) {
|
|
1668
|
+
/* process.nextTick callbacks BEFORE promise jobs (Node's checkpoint
|
|
1669
|
+
* order): the tick queue to exhaustion, then the microtask queue to
|
|
1670
|
+
* exhaustion, and back while either has work — a microtask's
|
|
1671
|
+
* nextTick runs before the turn proceeds, but never preempts the
|
|
1672
|
+
* microtask queue mid-drain (V8 drains it fully). A tick's uncaught
|
|
1673
|
+
* throw ends the loop like any listener's (main reports it). */
|
|
1674
|
+
if (!first_checkpoint) {
|
|
1675
|
+
while (scr_nt_head != NULL) {
|
|
1676
|
+
ScrNtick *t = scr_nt_head;
|
|
1677
|
+
scr_nt_head = t->next;
|
|
1678
|
+
if (scr_nt_head == NULL) scr_nt_tail = NULL;
|
|
1679
|
+
ScrClosure *cb = t->cb;
|
|
1680
|
+
free(t);
|
|
1681
|
+
((void (*)(ScrClosure *))cb->fn)(cb);
|
|
1682
|
+
scr_closure_release(cb);
|
|
1683
|
+
if (scr_exc_pending()) return;
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
/* Microtasks to exhaustion (Node: promise jobs before timers). */
|
|
1687
|
+
while (scr_ready_len > 0) {
|
|
1688
|
+
ScrFiber *f = scr_ready[scr_ready_head++];
|
|
1689
|
+
scr_ready_len--;
|
|
1690
|
+
scr_resume_fiber(f);
|
|
1691
|
+
}
|
|
1692
|
+
first_checkpoint = false;
|
|
1693
|
+
if (scr_nt_head != NULL) continue;
|
|
1694
|
+
/* Quiescent between turns (microtasks drained, nothing running):
|
|
1695
|
+
* collect any cycles the turn left behind. No-op on an empty buffer. */
|
|
1696
|
+
scr_collect_cycles();
|
|
1697
|
+
/* Stream tick dispatch (scr_stream.c, when linked): the deferred
|
|
1698
|
+
* next-tick emissions ('data' flow kicks, 'readable'/'end'/'finish'/
|
|
1699
|
+
* 'drain'/'error'/'close') fire now, FIRST — the nextTick station.
|
|
1700
|
+
* Listeners may enqueue microtasks or more ticks — restart the turn
|
|
1701
|
+
* so those drain first. */
|
|
1702
|
+
if (scr_stream_dispatch_fn != NULL) {
|
|
1703
|
+
scr_stream_dispatch_fn();
|
|
1704
|
+
if (scr_exc_pending()) return; /* uncaught throw in a listener */
|
|
1705
|
+
if (scr_ready_len > 0) continue;
|
|
1706
|
+
if (scr_stream_pending_fn != NULL && scr_stream_pending_fn()) continue;
|
|
1707
|
+
}
|
|
1708
|
+
/* Event dispatch (scr_events.c, when linked): watched signals
|
|
1709
|
+
* delivered since the last turn fire their listeners now (macrotasks,
|
|
1710
|
+
* like timers), then stdin — while a consumer exists, probe fd 0 and
|
|
1711
|
+
* deliver one arrived chunk / the EOF events. Both may enqueue
|
|
1712
|
+
* microtasks — restart the turn so those drain first. */
|
|
1713
|
+
if (scr_events_dispatch_fn != NULL) {
|
|
1714
|
+
scr_events_dispatch_fn();
|
|
1715
|
+
if (scr_exc_pending()) return; /* uncaught throw in a listener */
|
|
1716
|
+
if (scr_ready_len > 0) continue;
|
|
1717
|
+
}
|
|
1718
|
+
/* Net dispatch (scr_net.c, when linked): accepts, arrived data,
|
|
1719
|
+
* connect completions, and the deferred listening/error/close emits
|
|
1720
|
+
* fire now (macrotasks, like the events hook). Callbacks may enqueue
|
|
1721
|
+
* microtasks — restart the turn so those drain first. */
|
|
1722
|
+
if (scr_net_dispatch_fn != NULL) {
|
|
1723
|
+
scr_net_dispatch_fn();
|
|
1724
|
+
if (scr_exc_pending()) return; /* uncaught throw in a listener */
|
|
1725
|
+
if (scr_ready_len > 0) continue;
|
|
1726
|
+
}
|
|
1727
|
+
/* Dgram dispatch (scr_dgram.c, when linked): arrived datagrams and
|
|
1728
|
+
* the deferred listening/connect/error/close emits (plus pending
|
|
1729
|
+
* dns.lookup callbacks) fire now — the net hook's exact station. */
|
|
1730
|
+
if (scr_dgram_dispatch_fn != NULL) {
|
|
1731
|
+
scr_dgram_dispatch_fn();
|
|
1732
|
+
if (scr_exc_pending()) return; /* uncaught throw in a listener */
|
|
1733
|
+
if (scr_ready_len > 0) continue;
|
|
1734
|
+
}
|
|
1735
|
+
/* Watch dispatch (scr_watch.c, when linked): file events queued on
|
|
1736
|
+
* the unit's event backend fire their FSWatcher listeners now — the
|
|
1737
|
+
* net hook's exact station. */
|
|
1738
|
+
if (scr_watch_dispatch_fn != NULL) {
|
|
1739
|
+
scr_watch_dispatch_fn();
|
|
1740
|
+
if (scr_exc_pending()) return; /* uncaught throw in a listener */
|
|
1741
|
+
if (scr_ready_len > 0) continue;
|
|
1742
|
+
}
|
|
1743
|
+
/* Reap spawned children and fire their listeners (before timers,
|
|
1744
|
+
* like Node's nextTick-ish spawn-failure "error"). A callback may
|
|
1745
|
+
* enqueue microtasks or spawn again — restart the turn if so.
|
|
1746
|
+
* EXCEPT when ONLY unref'd children remain: Node's liveness check
|
|
1747
|
+
* precedes any further poll/reap, so once nothing reffed holds the
|
|
1748
|
+
* loop their pending exits are DROPPED at loop exit — never
|
|
1749
|
+
* delivered. Without this gate the kill-then-exit shape (a timer's
|
|
1750
|
+
* last act kills an unref'd child) would race machine timing: a
|
|
1751
|
+
* death landing before this sweep fired its 'exit' where Node
|
|
1752
|
+
* deterministically never does. The teardown below releases the
|
|
1753
|
+
* undelivered listeners, so the RC audit stays clean. */
|
|
1754
|
+
if (scr_children_pending()) {
|
|
1755
|
+
bool held =
|
|
1756
|
+
scr_reffed_timers > 0 || scr_reffed_immediates > 0 || scr_children_reffed_pending() ||
|
|
1757
|
+
/* spawn failures settle regardless: their 'error' is a
|
|
1758
|
+
* next-tick event, delivered even on an unref'd child */
|
|
1759
|
+
scr_children_failed_pending() ||
|
|
1760
|
+
(scr_io_pending_fn != NULL && scr_io_pending_fn()) ||
|
|
1761
|
+
(scr_events_pending_fn != NULL && scr_events_pending_fn()) ||
|
|
1762
|
+
(scr_net_pending_fn != NULL && scr_net_pending_fn()) ||
|
|
1763
|
+
(scr_dgram_pending_fn != NULL && scr_dgram_pending_fn()) ||
|
|
1764
|
+
(scr_watch_pending_fn != NULL && scr_watch_pending_fn());
|
|
1765
|
+
if (held) {
|
|
1766
|
+
scr_children_poll();
|
|
1767
|
+
if (scr_exc_pending()) return; /* uncaught throw in a listener */
|
|
1768
|
+
if (scr_ready_len > 0) continue;
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
/* Deferred stream ticks are always-ready work: never sleep or exit
|
|
1772
|
+
* while any exist (the dispatch above drains them, so reaching here
|
|
1773
|
+
* with ticks pending means a dispatch station enqueued more). User
|
|
1774
|
+
* nextTicks enqueued by a station listener are the same story. */
|
|
1775
|
+
if (scr_stream_pending_fn != NULL && scr_stream_pending_fn()) continue;
|
|
1776
|
+
if (scr_nt_head != NULL) continue;
|
|
1777
|
+
bool kids = scr_children_pending();
|
|
1778
|
+
bool io = scr_io_pending_fn != NULL && scr_io_pending_fn();
|
|
1779
|
+
bool events = scr_events_pending_fn != NULL && scr_events_pending_fn();
|
|
1780
|
+
bool net = scr_net_pending_fn != NULL && scr_net_pending_fn();
|
|
1781
|
+
bool dgram = scr_dgram_pending_fn != NULL && scr_dgram_pending_fn();
|
|
1782
|
+
bool watch = scr_watch_pending_fn != NULL && scr_watch_pending_fn();
|
|
1783
|
+
/* Timer liveness counts only REF'd timers: an unref'd timer stays in
|
|
1784
|
+
* the heap (and fires if the loop runs on for other reasons) but does
|
|
1785
|
+
* not by itself keep the process alive — Node's unref semantics.
|
|
1786
|
+
* Children follow the same rule: an unref'd child is still REAPED
|
|
1787
|
+
* while the loop runs (kids drives the sweeps and sleeps above) but
|
|
1788
|
+
* only reffed ones keep the process alive. */
|
|
1789
|
+
if (scr_reffed_timers == 0 && scr_reffed_immediates == 0 && !scr_children_reffed_pending() && !io && !events && !net && !dgram && !watch) break;
|
|
1790
|
+
/* Sleep to the earliest deadline, then run every due timer (each may
|
|
1791
|
+
* enqueue microtasks, which the next iteration drains first). Who
|
|
1792
|
+
* sleeps depends on what is pending:
|
|
1793
|
+
* - external io: the io POLL takes the sleep — it waits on real fds
|
|
1794
|
+
* up to the deadline (socket readiness wakes it early) and makes
|
|
1795
|
+
* progress (engine jobs, arrived response data) before timers run.
|
|
1796
|
+
* A child's exit can't wake curl's fds, so children re-impose the
|
|
1797
|
+
* reap-granularity cap on this path.
|
|
1798
|
+
* - signals watched or stdin consumed: poll(2) takes the sleep — the
|
|
1799
|
+
* wake pipe, fd 0, and the child watch's fd together, so any of
|
|
1800
|
+
* them ends it immediately (details at the branch).
|
|
1801
|
+
* - children only: the sleep waits on the child watch — the exit
|
|
1802
|
+
* wakeup and the timer deadline together via the wait timeout — so a
|
|
1803
|
+
* child's exit ends the sleep immediately and the next turn's reap
|
|
1804
|
+
* pass fires its listeners. scr_children_wait declines when it
|
|
1805
|
+
* can't wake for every pending child; the ~1ms polling cap is the
|
|
1806
|
+
* fallback.
|
|
1807
|
+
* - timers only: plain nanosleep to the deadline. */
|
|
1808
|
+
double now = scr_now_ms();
|
|
1809
|
+
double due = scr_ntimers > 0 ? scr_timers[0].deadline_ms : now + SCR_IO_POLL_MS;
|
|
1810
|
+
/* An armed island timer (AbortSignal.timeout) caps the sleep: it must
|
|
1811
|
+
* fire on time even while the poller waits on socket readiness. */
|
|
1812
|
+
if (scr_island_deadline_fn != NULL) {
|
|
1813
|
+
double isl_due = scr_island_deadline_fn();
|
|
1814
|
+
if (isl_due < due) due = isl_due;
|
|
1815
|
+
}
|
|
1816
|
+
/* Pending immediates are always-ready work: no sleep — run due timers
|
|
1817
|
+
* (Node's timers phase precedes check), then the check phase below. */
|
|
1818
|
+
if (scr_pending_immediates > 0) due = now;
|
|
1819
|
+
bool evw = scr_events_watching_fn != NULL && scr_events_watching_fn();
|
|
1820
|
+
if (io) {
|
|
1821
|
+
if (kids && due > now + SCR_CHILD_POLL_MS) due = now + SCR_CHILD_POLL_MS;
|
|
1822
|
+
/* Signals/stdin/net can't wake curl's fd wait (and its poll retries
|
|
1823
|
+
* on EINTR), so they re-impose a coarser cap — bounded Ctrl-C and
|
|
1824
|
+
* socket latency during a fetch, without the reap-granularity
|
|
1825
|
+
* cost. */
|
|
1826
|
+
else if ((evw || net || dgram || watch) && due > now + SCR_SIGNAL_POLL_MS) due = now + SCR_SIGNAL_POLL_MS;
|
|
1827
|
+
scr_io_poll_fn(due > now ? due - now : 0);
|
|
1828
|
+
now = scr_now_ms();
|
|
1829
|
+
if (scr_ready_len > 0) continue; /* io callbacks woke fibers */
|
|
1830
|
+
} else if (evw || net || dgram || watch) {
|
|
1831
|
+
#ifdef _WIN32
|
|
1832
|
+
/* The win32 arm: no poll(2), so the sleep is a capped nanosleep and
|
|
1833
|
+
* the next turn's dispatch does the work — signal flags (set by the
|
|
1834
|
+
* CRT handler on msvcrt's console-ctrl thread) and the stdin probe
|
|
1835
|
+
* at SCR_SIGNAL_POLL_MS granularity (scr_events.c), socket/dgram
|
|
1836
|
+
* readiness at the child-reap cap (scr_loop_wsapoll.c's zero-timeout
|
|
1837
|
+
* WSAPoll drains at dispatch; ~1ms keeps loopback exchanges inside
|
|
1838
|
+
* the granularity Node's IOCP-woken loop delivers, where 50ms could
|
|
1839
|
+
* reorder a socket emit past a short timer). When the caps ever
|
|
1840
|
+
* show up in a profile, the upgrade is a real waitable arm —
|
|
1841
|
+
* WaitForMultipleObjects over WSAEVENTs, or IOCP. */
|
|
1842
|
+
if (evw && due > now + SCR_SIGNAL_POLL_MS) due = now + SCR_SIGNAL_POLL_MS;
|
|
1843
|
+
if ((net || dgram || watch) && due > now + SCR_CHILD_POLL_MS) due = now + SCR_CHILD_POLL_MS;
|
|
1844
|
+
if (kids && due > now + SCR_CHILD_POLL_MS) due = now + SCR_CHILD_POLL_MS;
|
|
1845
|
+
if (due > now) {
|
|
1846
|
+
double wait = due - now;
|
|
1847
|
+
struct timespec ts = {(time_t)(wait / 1000.0), (long)((wait - (double)((time_t)(wait / 1000.0)) * 1000.0) * 1e6)};
|
|
1848
|
+
nanosleep(&ts, NULL);
|
|
1849
|
+
}
|
|
1850
|
+
now = scr_now_ms();
|
|
1851
|
+
#else
|
|
1852
|
+
/* poll(2) takes the sleep: the events unit's fds — the signal wake
|
|
1853
|
+
* pipe (a signal delivered BEFORE the poll entered still has its
|
|
1854
|
+
* byte in the pipe — no lost-wakeup race) and fd 0 while stdin has
|
|
1855
|
+
* a consumer — plus the child watch's fd when it can represent
|
|
1856
|
+
* every pending child (kqueue and epoll fds poll readable when
|
|
1857
|
+
* events are pending); unrepresentable children keep the ~1ms reap cap
|
|
1858
|
+
* instead. Dispatch happens at the next turn's top — the poll only
|
|
1859
|
+
* decides how long to sleep. */
|
|
1860
|
+
struct pollfd fds[6];
|
|
1861
|
+
int nfds = 0;
|
|
1862
|
+
int evfds[2];
|
|
1863
|
+
int nev = evw && scr_events_pollfds_fn != NULL ? scr_events_pollfds_fn(evfds) : 0;
|
|
1864
|
+
for (int i = 0; i < nev; i++) {
|
|
1865
|
+
fds[nfds].fd = evfds[i];
|
|
1866
|
+
fds[nfds].events = POLLIN;
|
|
1867
|
+
fds[nfds++].revents = 0;
|
|
1868
|
+
}
|
|
1869
|
+
if (net) {
|
|
1870
|
+
/* The net unit's poller fd: readable while socket events are
|
|
1871
|
+
* pending, so arrived data/accepts end the sleep immediately.
|
|
1872
|
+
* Dispatch (which also DRAINS the poller) happens at the next
|
|
1873
|
+
* turn's top — the poll only decides how long to sleep. */
|
|
1874
|
+
int nfd = scr_net_pollfd_fn != NULL ? scr_net_pollfd_fn() : -1;
|
|
1875
|
+
if (nfd >= 0) {
|
|
1876
|
+
fds[nfds].fd = nfd;
|
|
1877
|
+
fds[nfds].events = POLLIN;
|
|
1878
|
+
fds[nfds++].revents = 0;
|
|
1879
|
+
} else if (due > now + SCR_SIGNAL_POLL_MS) {
|
|
1880
|
+
due = now + SCR_SIGNAL_POLL_MS;
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
if (dgram) {
|
|
1884
|
+
/* The dgram unit's poller fd — the net slot's exact story. */
|
|
1885
|
+
int dfd = scr_dgram_pollfd_fn != NULL ? scr_dgram_pollfd_fn() : -1;
|
|
1886
|
+
if (dfd >= 0) {
|
|
1887
|
+
fds[nfds].fd = dfd;
|
|
1888
|
+
fds[nfds].events = POLLIN;
|
|
1889
|
+
fds[nfds++].revents = 0;
|
|
1890
|
+
} else if (due > now + SCR_SIGNAL_POLL_MS) {
|
|
1891
|
+
due = now + SCR_SIGNAL_POLL_MS;
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
if (watch) {
|
|
1895
|
+
/* The watch unit's event fd — the net slot's exact story. */
|
|
1896
|
+
int wfd = scr_watch_pollfd_fn != NULL ? scr_watch_pollfd_fn() : -1;
|
|
1897
|
+
if (wfd >= 0) {
|
|
1898
|
+
fds[nfds].fd = wfd;
|
|
1899
|
+
fds[nfds].events = POLLIN;
|
|
1900
|
+
fds[nfds++].revents = 0;
|
|
1901
|
+
} else if (due > now + SCR_SIGNAL_POLL_MS) {
|
|
1902
|
+
due = now + SCR_SIGNAL_POLL_MS;
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
if (kids) {
|
|
1906
|
+
int cfd = scr_children_wake_fd();
|
|
1907
|
+
if (cfd >= 0) {
|
|
1908
|
+
fds[nfds].fd = cfd;
|
|
1909
|
+
fds[nfds].events = POLLIN;
|
|
1910
|
+
fds[nfds++].revents = 0;
|
|
1911
|
+
} else if (due > now + SCR_CHILD_POLL_MS) {
|
|
1912
|
+
due = now + SCR_CHILD_POLL_MS;
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
double wait = due > now ? due - now : 0;
|
|
1916
|
+
if (wait > 1e9) wait = 1e9; /* poll takes int ms */
|
|
1917
|
+
(void)poll(fds, (nfds_t)nfds, (int)(wait + 0.999));
|
|
1918
|
+
/* Consume queued child-exit events NOW (zero-timeout drain) — an
|
|
1919
|
+
* unconsumed event keeps the kqueue fd readable and would turn the
|
|
1920
|
+
* next poll into a busy spin while other children still run. The
|
|
1921
|
+
* WNOHANG sweep at the next turn's top stays the reaper. */
|
|
1922
|
+
if (kids) (void)scr_children_wait(0);
|
|
1923
|
+
now = scr_now_ms();
|
|
1924
|
+
#endif /* !_WIN32 */
|
|
1925
|
+
} else if (kids && scr_children_wait(due > now ? due - now : 0)) {
|
|
1926
|
+
now = scr_now_ms(); /* woke early on an exit, or hit the deadline */
|
|
1927
|
+
} else {
|
|
1928
|
+
if (kids && due > now + SCR_CHILD_POLL_MS) due = now + SCR_CHILD_POLL_MS;
|
|
1929
|
+
if (due > now) {
|
|
1930
|
+
double wait = due - now;
|
|
1931
|
+
struct timespec ts = {(time_t)(wait / 1000.0), (long)((wait - (double)((time_t)(wait / 1000.0)) * 1000.0) * 1e6)};
|
|
1932
|
+
nanosleep(&ts, NULL);
|
|
1933
|
+
now = due;
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
while (scr_ntimers > 0 && scr_timers[0].deadline_ms <= now) {
|
|
1937
|
+
ScrTimer t = scr_timer_pop();
|
|
1938
|
+
/* Timer callbacks are plain sync closures, run on the main stack.
|
|
1939
|
+
* An interval entry is OUT of the heap while its callback runs;
|
|
1940
|
+
* clearInterval(its own id) from inside sets the firing flag and
|
|
1941
|
+
* the re-arm below drops it instead. A throw kills the interval
|
|
1942
|
+
* with the program (Node: the uncaught exception ends the process
|
|
1943
|
+
* before any rescheduled tick could run). */
|
|
1944
|
+
if (t.id != 0) {
|
|
1945
|
+
scr_firing_id = t.id;
|
|
1946
|
+
scr_firing_cleared = false;
|
|
1947
|
+
scr_firing_reffed = t.reffed; /* unref/ref during the callback lands here */
|
|
1948
|
+
scr_firing_refresh = false;
|
|
1949
|
+
}
|
|
1950
|
+
((void (*)(ScrClosure *))t.cb->fn)(t.cb);
|
|
1951
|
+
if (t.id != 0 && t.repeat_ms > 0 && !scr_firing_cleared && !scr_exc_pending()) {
|
|
1952
|
+
/* Re-arm relative to the post-callback clock (libuv's uv_timer
|
|
1953
|
+
* repeat behavior: no catch-up bursts after a slow callback). The
|
|
1954
|
+
* ref state carries across ticks (a self-unref'd interval stays
|
|
1955
|
+
* unref'd). */
|
|
1956
|
+
ScrTimer again = {scr_now_ms() + t.repeat_ms, scr_timer_seq++, t.cb, t.repeat_ms, t.id, scr_firing_reffed, t.delay_ms};
|
|
1957
|
+
scr_timer_push(again);
|
|
1958
|
+
} else if (t.id != 0 && scr_firing_refresh && !scr_firing_cleared && !scr_exc_pending()) {
|
|
1959
|
+
/* refresh() from inside the one-shot's own callback: re-arm to
|
|
1960
|
+
* now + the original delay, ref state carried (Node's
|
|
1961
|
+
* Timeout.refresh — the timer fires again). */
|
|
1962
|
+
ScrTimer again = {scr_now_ms() + t.delay_ms, scr_timer_seq++, t.cb, 0, t.id, scr_firing_reffed, t.delay_ms};
|
|
1963
|
+
scr_timer_push(again);
|
|
1964
|
+
} else {
|
|
1965
|
+
scr_closure_release(t.cb);
|
|
1966
|
+
}
|
|
1967
|
+
scr_firing_id = 0;
|
|
1968
|
+
if (scr_exc_pending()) return; /* uncaught throw in a callback: main handles it */
|
|
1969
|
+
if (scr_ready_len > 0) break; /* drain microtasks before more timers */
|
|
1970
|
+
}
|
|
1971
|
+
/* The check phase: immediates queued BEFORE this phase started run
|
|
1972
|
+
* now, FIFO; ones a callback queues wait for the next turn (the end
|
|
1973
|
+
* snapshot — Node's once-per-turn rule, so a setImmediate chain can't
|
|
1974
|
+
* starve the loop). Cleared slots (cb NULL) skip. Microtasks drain
|
|
1975
|
+
* between callbacks, like Node's inter-macrotask checkpoints; a woken
|
|
1976
|
+
* fiber restarts the turn first (the timer loop's break above), so the
|
|
1977
|
+
* phase only runs on a drained queue. */
|
|
1978
|
+
if (scr_ready_len == 0 && scr_pending_immediates > 0) {
|
|
1979
|
+
size_t end = scr_nimmediates;
|
|
1980
|
+
while (scr_immediates_head < end) {
|
|
1981
|
+
size_t i = scr_immediates_head++;
|
|
1982
|
+
ScrClosure *cb = scr_immediates[i].cb;
|
|
1983
|
+
if (cb == NULL) continue; /* cleared */
|
|
1984
|
+
scr_immediates[i].cb = NULL; /* fired: clear/ref/unref on this id no-op now */
|
|
1985
|
+
if (scr_pending_immediates > 0) scr_pending_immediates--;
|
|
1986
|
+
if (scr_immediates[i].reffed) {
|
|
1987
|
+
scr_immediates[i].reffed = false;
|
|
1988
|
+
if (scr_reffed_immediates > 0) scr_reffed_immediates--;
|
|
1989
|
+
}
|
|
1990
|
+
((void (*)(ScrClosure *))cb->fn)(cb);
|
|
1991
|
+
scr_closure_release(cb);
|
|
1992
|
+
if (scr_exc_pending()) return; /* uncaught throw: main handles it */
|
|
1993
|
+
while (scr_ready_len > 0) {
|
|
1994
|
+
ScrFiber *f = scr_ready[scr_ready_head++];
|
|
1995
|
+
scr_ready_len--;
|
|
1996
|
+
scr_resume_fiber(f);
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
/* Fully drained: reset the cursor so the array reuses its slots. */
|
|
2000
|
+
if (scr_immediates_head == scr_nimmediates) {
|
|
2001
|
+
scr_immediates_head = 0;
|
|
2002
|
+
scr_nimmediates = 0;
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
/* Normal exit can now leave UNREF'd timers armed in the heap (the loop
|
|
2007
|
+
* stopped keeping itself alive for them, Node's unref semantics) — they
|
|
2008
|
+
* never fire, so release their closures here or the RC audit counts them
|
|
2009
|
+
* as leaks. Timers left on the uncaught/unhandled paths (the loop
|
|
2010
|
+
* `return`s above, skipping this) are covered by the abandoned-fiber
|
|
2011
|
+
* audit skip. */
|
|
2012
|
+
scr_timers_teardown();
|
|
2013
|
+
/* Same story for unref'd children the loop never reaped: release the
|
|
2014
|
+
* registry's references (their listeners never fire — the process is
|
|
2015
|
+
* exiting, Node's behavior; the OS reparents the children). */
|
|
2016
|
+
scr_children_teardown();
|
|
2017
|
+
scr_fibers_abandoned = scr_fibers_live;
|
|
2018
|
+
scr_note_abandoned_fibers(scr_fibers_abandoned);
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
/* The island's half of the unhandled-rejection report (scr_island.c
|
|
2022
|
+
* registers it at engine boot): called with print=true when the static
|
|
2023
|
+
* ledger below reported nothing — one report, one voice, like Node's
|
|
2024
|
+
* first-unhandled-rejection death. Returns whether the island had any.
|
|
2025
|
+
* Static builds never set it. */
|
|
2026
|
+
static bool (*scr_island_rejections_fn)(bool print) = NULL;
|
|
2027
|
+
|
|
2028
|
+
void scr_loop_set_island_rejections(bool (*fn)(bool print)) {
|
|
2029
|
+
scr_island_rejections_fn = fn;
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
/* ── process.on('unhandledRejection') ─────────────────────────────────
|
|
2033
|
+
* DOM listeners called per never-observed rejection instead of the
|
|
2034
|
+
* default report below. Node fires the event at end-of-turn; the
|
|
2035
|
+
* compiled runtime fires at loop exhaustion, where the ledger is
|
|
2036
|
+
* decided — the same values, later (SEMANTICS.md; mustCall-style
|
|
2037
|
+
* assertions and reason identity observe no difference, cross-turn
|
|
2038
|
+
* interleaving would). A registered listener suppresses the report and
|
|
2039
|
+
* the exit-1, exactly Node's handled-event contract. */
|
|
2040
|
+
|
|
2041
|
+
|
|
2042
|
+
|
|
2043
|
+
/* The unhandled-rejection LISTENER hook (scr_async_dyn.c installs it at
|
|
2044
|
+
* registration — the loop-hook pattern, so listener-free binaries keep
|
|
2045
|
+
* their size class): called per never-observed rejection; false = a
|
|
2046
|
+
* listener threw (the uncaught crash path). */
|
|
2047
|
+
bool (*scr_urj_deliver_fn)(ScrPromise *p) = NULL;
|
|
2048
|
+
|
|
2049
|
+
/* Unhandled rejections at loop end: Node prints an error and exits 1. */
|
|
2050
|
+
bool scr_report_unhandled_rejections(void) {
|
|
2051
|
+
bool any = false;
|
|
2052
|
+
bool crashed = false;
|
|
2053
|
+
for (size_t i = 0; i < scr_nunhandled; i++) {
|
|
2054
|
+
ScrPromise *p = scr_maybe_unhandled[i];
|
|
2055
|
+
if (p->state == SCR_PROM_REJECTED && !p->rejection_observed && !crashed) {
|
|
2056
|
+
if (scr_urj_deliver_fn != NULL) {
|
|
2057
|
+
/* Listener dispatch (scr_async_dyn.c installed the hook at
|
|
2058
|
+
* registration): the event handles it, like Node — per entry,
|
|
2059
|
+
* no report, exit 0. A listener throw is the uncaught crash. */
|
|
2060
|
+
p->rejection_observed = true;
|
|
2061
|
+
if (!scr_urj_deliver_fn(p)) crashed = true;
|
|
2062
|
+
} else if (!any) {
|
|
2063
|
+
any = true;
|
|
2064
|
+
fflush(stdout);
|
|
2065
|
+
fputs("Unhandled promise rejection: ", stderr);
|
|
2066
|
+
switch (p->payload_kind) {
|
|
2067
|
+
case SCR_EXC_F64: {
|
|
2068
|
+
char buf[32];
|
|
2069
|
+
scr_f64_to_str(p->f64, buf);
|
|
2070
|
+
fputs(buf, stderr);
|
|
2071
|
+
break;
|
|
2072
|
+
}
|
|
2073
|
+
case SCR_EXC_BOOL: fputs(p->b ? "true" : "false", stderr); break;
|
|
2074
|
+
case SCR_EXC_STR: fputs(((ScrStr *)p->payload)->data, stderr); break;
|
|
2075
|
+
case SCR_EXC_OBJ:
|
|
2076
|
+
/* Error rejections render "name: message", like the uncaught path. */
|
|
2077
|
+
if (scr_error_is(p->payload)) {
|
|
2078
|
+
ScrStr *s = scr_error_to_string((ScrError *)p->payload);
|
|
2079
|
+
fwrite(s->data, 1, s->len, stderr);
|
|
2080
|
+
scr_str_release(s);
|
|
2081
|
+
break;
|
|
2082
|
+
}
|
|
2083
|
+
/* fall through */
|
|
2084
|
+
default: fputs("[object]", stderr); break;
|
|
2085
|
+
}
|
|
2086
|
+
fputc('\n', stderr);
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
scr_promise_release(p);
|
|
2090
|
+
}
|
|
2091
|
+
scr_nunhandled = 0;
|
|
2092
|
+
if (crashed) {
|
|
2093
|
+
scr_exc_print_uncaught();
|
|
2094
|
+
scr_exit_code_note(1);
|
|
2095
|
+
return true;
|
|
2096
|
+
}
|
|
2097
|
+
if (scr_island_rejections_fn != NULL) {
|
|
2098
|
+
bool island = scr_island_rejections_fn(!any);
|
|
2099
|
+
any = any || island;
|
|
2100
|
+
}
|
|
2101
|
+
/* main returns 1 on a reported rejection — the 'exit' listeners (atexit)
|
|
2102
|
+
* must see that code, like Node's. */
|
|
2103
|
+
if (any) scr_exit_code_note(1);
|
|
2104
|
+
return any;
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
/* ── new Promise(executor) ────────────────────────────────────────────── */
|
|
2108
|
+
|
|
2109
|
+
/* resolve closures: caps[0] is a box whose slot holds the promise (+1). */
|
|
2110
|
+
static ScrPromise *scr_resolve_target(ScrClosure *self) {
|
|
2111
|
+
return (ScrPromise *)scr_box_get_ref(self->caps[0]);
|
|
2112
|
+
}
|
|
2113
|
+
static void scr_resolve_thunk_f64(ScrClosure *self, double v) {
|
|
2114
|
+
ScrPromise *p = scr_resolve_target(self);
|
|
2115
|
+
scr_promise_fulfill_f64(p, v);
|
|
2116
|
+
scr_promise_release(p);
|
|
2117
|
+
}
|
|
2118
|
+
static void scr_resolve_thunk_bool(ScrClosure *self, bool v) {
|
|
2119
|
+
ScrPromise *p = scr_resolve_target(self);
|
|
2120
|
+
scr_promise_fulfill_bool(p, v);
|
|
2121
|
+
scr_promise_release(p);
|
|
2122
|
+
}
|
|
2123
|
+
static void scr_resolve_thunk_str(ScrClosure *self, ScrStr *v) {
|
|
2124
|
+
ScrPromise *p = scr_resolve_target(self);
|
|
2125
|
+
scr_promise_fulfill_str(p, v); /* callee owns its params: moves in */
|
|
2126
|
+
scr_promise_release(p);
|
|
2127
|
+
}
|
|
2128
|
+
static void scr_resolve_thunk_void(ScrClosure *self) {
|
|
2129
|
+
ScrPromise *p = scr_resolve_target(self);
|
|
2130
|
+
scr_promise_fulfill_void(p);
|
|
2131
|
+
scr_promise_release(p);
|
|
2132
|
+
}
|
|
2133
|
+
/* Ref payloads: emitted code passes the concrete retain/release via a
|
|
2134
|
+
* second box slot? Keep simple: ref resolve thunks are monomorphized by the
|
|
2135
|
+
* EMITTER (it knows the arm type) — it emits a tiny static thunk calling
|
|
2136
|
+
* scr_promise_fulfill_ref with the concrete helpers, wrapped over caps[0].
|
|
2137
|
+
* The runtime provides only the scalar/str/void thunks. */
|
|
2138
|
+
|
|
2139
|
+
ScrClosure *scr_make_resolve(ScrPromise *p, int kind /*0 f64,1 bool,2 str,3 void*/) {
|
|
2140
|
+
void *fns[4] = {(void *)&scr_resolve_thunk_f64, (void *)&scr_resolve_thunk_bool,
|
|
2141
|
+
(void *)&scr_resolve_thunk_str, (void *)&scr_resolve_thunk_void};
|
|
2142
|
+
ScrClosure *c = scr_closure_new(fns[kind], 1);
|
|
2143
|
+
ScrBox *b = scr_box_new_obj(scr_promise_retain_v, scr_promise_release_v,
|
|
2144
|
+
scr_promise_trace_v);
|
|
2145
|
+
scr_box_set_ref(b, scr_promise_retain(p));
|
|
2146
|
+
c->caps[0] = b;
|
|
2147
|
+
return c;
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
/* Emitted ref-kind resolve thunks call this. */
|
|
2151
|
+
void scr_resolve_ref_impl(ScrClosure *self, void *v, void *(*retain)(void *), void (*release)(void *), ScrTraceFn trace) {
|
|
2152
|
+
ScrPromise *p = scr_resolve_target(self);
|
|
2153
|
+
scr_promise_fulfill_ref(p, v, retain, release, trace);
|
|
2154
|
+
scr_promise_release(p);
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
/* ── emitter support ──────────────────────────────────────────────────── */
|
|
2158
|
+
|
|
2159
|
+
ScrPromise *scr_fiber_promise(ScrFiber *f) { return f->promise; }
|
|
2160
|
+
|
|
2161
|
+
/* Generalized resolve construction: `fn` is an emitted thunk with closure
|
|
2162
|
+
* calling convention (self, value) that forwards to scr_resolve_ref_impl
|
|
2163
|
+
* with the concrete RC helpers. */
|
|
2164
|
+
ScrClosure *scr_make_resolve_fn(ScrPromise *p, void *fn) {
|
|
2165
|
+
ScrClosure *c = scr_closure_new(fn, 1);
|
|
2166
|
+
ScrBox *b = scr_box_new_obj(scr_promise_retain_v, scr_promise_release_v,
|
|
2167
|
+
scr_promise_trace_v);
|
|
2168
|
+
scr_box_set_ref(b, scr_promise_retain(p));
|
|
2169
|
+
c->caps[0] = b;
|
|
2170
|
+
return c;
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
/* ── reject closures (new Promise((resolve, reject) => ...)) ──────────
|
|
2174
|
+
* Mirror of the resolve closures: caps[0] is a box whose slot holds the
|
|
2175
|
+
* promise (+1). The reason is always an Error-hierarchy instance (the
|
|
2176
|
+
* frontend pins the reject parameter to `(reason: Error) => void`), and it
|
|
2177
|
+
* stores as SCR_EXC_OBJ — exactly the thrown-Error representation — so an
|
|
2178
|
+
* await rethrows it, catch-side instanceof answers, and the uncaught
|
|
2179
|
+
* printer prints "name: message". First settle wins, exactly JS: reject
|
|
2180
|
+
* after any settle (resolve-then-reject, double reject) releases the
|
|
2181
|
+
* reason and does nothing. The closure calling convention makes the thunk
|
|
2182
|
+
* own its param, like every compiled callee. */
|
|
2183
|
+
static void scr_reject_thunk(ScrClosure *self, ScrError *reason) {
|
|
2184
|
+
ScrPromise *p = scr_resolve_target(self);
|
|
2185
|
+
if (p->state != SCR_PROM_PENDING) {
|
|
2186
|
+
scr_error_release(reason);
|
|
2187
|
+
} else {
|
|
2188
|
+
p->state = SCR_PROM_REJECTED;
|
|
2189
|
+
p->payload_kind = SCR_EXC_OBJ;
|
|
2190
|
+
p->payload = reason; /* ownership moves in */
|
|
2191
|
+
p->retain_fn = scr_error_retain_v;
|
|
2192
|
+
p->release_fn = scr_error_release_v;
|
|
2193
|
+
p->trace_fn = scr_error_trace_arg();
|
|
2194
|
+
scr_promise_settle_wake(p);
|
|
2195
|
+
}
|
|
2196
|
+
scr_promise_release(p);
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
ScrClosure *scr_make_reject(ScrPromise *p) {
|
|
2200
|
+
ScrClosure *c = scr_closure_new((void *)&scr_reject_thunk, 1);
|
|
2201
|
+
ScrBox *b = scr_box_new_obj(scr_promise_retain_v, scr_promise_release_v,
|
|
2202
|
+
scr_promise_trace_v);
|
|
2203
|
+
scr_box_set_ref(b, scr_promise_retain(p));
|
|
2204
|
+
c->caps[0] = b;
|
|
2205
|
+
return c;
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
/* Shared executor epilogue: an escaping throw rejects a still-pending
|
|
2209
|
+
* promise (payload moved out of the active exception cell); a throw after
|
|
2210
|
+
* the promise settled is swallowed, like resolve-then-throw in JS. */
|
|
2211
|
+
static void scr_executor_check_throw(ScrPromise *p) {
|
|
2212
|
+
if (!scr_exc_pending()) return;
|
|
2213
|
+
if (p->state == SCR_PROM_PENDING) {
|
|
2214
|
+
scr_promise_reject_from_cell(p, scr_exc_current_cell());
|
|
2215
|
+
scr_promise_settle_wake(p);
|
|
2216
|
+
} else {
|
|
2217
|
+
scr_exc_clear();
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
|
|
2221
|
+
/* Runs a `new Promise(executor)` executor synchronously; an escaping throw
|
|
2222
|
+
* rejects the promise (JS-exact). Borrows `p` and `exec`; `resolve`
|
|
2223
|
+
* ownership moves INTO the executor call (compiled callees own and finally
|
|
2224
|
+
* release their params). */
|
|
2225
|
+
void scr_promise_run_executor(ScrPromise *p, ScrClosure *exec, ScrClosure *resolve) {
|
|
2226
|
+
((void (*)(ScrClosure *, ScrClosure *))exec->fn)(exec, resolve);
|
|
2227
|
+
scr_executor_check_throw(p);
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
/* Zero-param executor (`new Promise(() => {})`): nothing can ever resolve —
|
|
2231
|
+
* a deliberately-forever-pending promise. Borrows both. */
|
|
2232
|
+
void scr_promise_run_executor0(ScrPromise *p, ScrClosure *exec) {
|
|
2233
|
+
((void (*)(ScrClosure *))exec->fn)(exec);
|
|
2234
|
+
scr_executor_check_throw(p);
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
/* Two-param executor (`new Promise((resolve, reject) => ...)`): same as
|
|
2238
|
+
* scr_promise_run_executor with the reject closure's +1 also moving into
|
|
2239
|
+
* the call. */
|
|
2240
|
+
void scr_promise_run_executor2(ScrPromise *p, ScrClosure *exec, ScrClosure *resolve,
|
|
2241
|
+
ScrClosure *reject) {
|
|
2242
|
+
((void (*)(ScrClosure *, ScrClosure *, ScrClosure *))exec->fn)(exec, resolve, reject);
|
|
2243
|
+
scr_executor_check_throw(p);
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
/* ── sync generators (function*) ──────────────────────────────────────
|
|
2247
|
+
* Contract in scr_runtime.h. The fiber machinery above is the engine:
|
|
2248
|
+
* scr_switch is a synchronous coroutine hop, so generators need no event
|
|
2249
|
+
* loop — .next() from the main stack (or any fiber) blocks until the
|
|
2250
|
+
* yield. */
|
|
2251
|
+
|
|
2252
|
+
enum { SCR_GEN_UNSTARTED = 0, SCR_GEN_SUSPENDED, SCR_GEN_RUNNING, SCR_GEN_DONE };
|
|
2253
|
+
|
|
2254
|
+
/* One type-erased value slot: the exception cell's payload technique,
|
|
2255
|
+
* with only the release entry point (takes MOVE — nothing here retains). */
|
|
2256
|
+
typedef struct {
|
|
2257
|
+
ScrExcKind kind; /* NONE / F64 / BOOL / REF (strings ride REF) */
|
|
2258
|
+
double f64;
|
|
2259
|
+
bool b;
|
|
2260
|
+
void *payload;
|
|
2261
|
+
void (*release_fn)(void *);
|
|
2262
|
+
} ScrGenSlot;
|
|
2263
|
+
|
|
2264
|
+
static void scr_gen_slot_reset(ScrGenSlot *s) {
|
|
2265
|
+
if (s->kind == SCR_EXC_REF && s->payload != NULL) s->release_fn(s->payload);
|
|
2266
|
+
s->kind = SCR_EXC_NONE;
|
|
2267
|
+
s->payload = NULL;
|
|
2268
|
+
s->release_fn = NULL;
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
struct ScrGen {
|
|
2272
|
+
size_t rc;
|
|
2273
|
+
ScrFiber *fiber; /* NULL once torn down (done, or unstarted release) */
|
|
2274
|
+
int state;
|
|
2275
|
+
ScrGenSlot out; /* yielded value / completion value */
|
|
2276
|
+
ScrGenSlot in; /* the .next(v) argument */
|
|
2277
|
+
ScrGenSlot ret; /* a parked .return(v) value */
|
|
2278
|
+
/* The never-started teardown: drops the packed (already-retained)
|
|
2279
|
+
* arguments the spawn wrapper built. Emitted per generator function. */
|
|
2280
|
+
void (*drop_args)(void *);
|
|
2281
|
+
};
|
|
2282
|
+
|
|
2283
|
+
ScrGen *scr_gen_new(void (*entry)(ScrFiber *, void *), void *argpack,
|
|
2284
|
+
void (*drop_args)(void *)) {
|
|
2285
|
+
ScrGen *g = calloc(1, sizeof *g);
|
|
2286
|
+
if (!g) scr_oom();
|
|
2287
|
+
g->rc = 1;
|
|
2288
|
+
g->state = SCR_GEN_UNSTARTED;
|
|
2289
|
+
g->drop_args = drop_args;
|
|
2290
|
+
scr_obj_alloc_note();
|
|
2291
|
+
|
|
2292
|
+
ScrFiber *f = calloc(1, sizeof *f);
|
|
2293
|
+
if (!f) scr_oom();
|
|
2294
|
+
f->gen = g;
|
|
2295
|
+
f->entry = entry;
|
|
2296
|
+
f->argpack = argpack; /* owned by the fiber start; drop_args if never started */
|
|
2297
|
+
/* AsyncLocalStorage: generator bodies run in their CREATOR's context
|
|
2298
|
+
* (the spawn-inheritance stance; resumes swap it active like any
|
|
2299
|
+
* fiber switch). */
|
|
2300
|
+
f->als = scr_als_ctx_retain(*scr_als_active);
|
|
2301
|
+
scr_fibers_live++;
|
|
2302
|
+
#ifdef _WIN32
|
|
2303
|
+
f->ctx = CreateFiber(SCR_FIBER_STACK, scr_trampoline, NULL);
|
|
2304
|
+
if (f->ctx == NULL) scr_oom();
|
|
2305
|
+
#else
|
|
2306
|
+
f->stack = malloc(SCR_FIBER_STACK);
|
|
2307
|
+
if (!f->stack) scr_oom();
|
|
2308
|
+
getcontext(&f->ctx);
|
|
2309
|
+
f->ctx.uc_stack.ss_sp = f->stack;
|
|
2310
|
+
f->ctx.uc_stack.ss_size = SCR_FIBER_STACK;
|
|
2311
|
+
f->ctx.uc_link = NULL;
|
|
2312
|
+
makecontext(&f->ctx, scr_trampoline, 0);
|
|
2313
|
+
#endif
|
|
2314
|
+
g->fiber = f;
|
|
2315
|
+
return g;
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
ScrGen *scr_gen_retain(ScrGen *g) {
|
|
2319
|
+
if (g) g->rc++;
|
|
2320
|
+
return g;
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
void scr_gen_release(ScrGen *g) {
|
|
2324
|
+
if (!g || --g->rc != 0) return;
|
|
2325
|
+
scr_gen_slot_reset(&g->out);
|
|
2326
|
+
scr_gen_slot_reset(&g->in);
|
|
2327
|
+
scr_gen_slot_reset(&g->ret);
|
|
2328
|
+
if (g->fiber != NULL) {
|
|
2329
|
+
if (g->state == SCR_GEN_UNSTARTED) {
|
|
2330
|
+
/* Never ran: nothing on the stack owns anything — clean teardown.
|
|
2331
|
+
* The packed arguments (+1 each) drop through the emitted helper. */
|
|
2332
|
+
if (g->drop_args != NULL) g->drop_args(g->fiber->argpack);
|
|
2333
|
+
else free(g->fiber->argpack);
|
|
2334
|
+
scr_fiber_destroy(g->fiber);
|
|
2335
|
+
scr_fibers_live--;
|
|
2336
|
+
} else {
|
|
2337
|
+
/* Suspended: the stack holds live locals — unwinding would run user
|
|
2338
|
+
* finally blocks Node's GC never runs, so the fiber is deliberately
|
|
2339
|
+
* ABANDONED (it stays in the live count; a program that drops a
|
|
2340
|
+
* suspended generator gets the abandoned-fiber audit note, the
|
|
2341
|
+
* loop-exhaustion story). The fiber's back-pointer clears so a
|
|
2342
|
+
* dangling resume can never reach the freed ScrGen. */
|
|
2343
|
+
g->fiber->gen = NULL;
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
scr_obj_free_note();
|
|
2347
|
+
free(g);
|
|
2348
|
+
}
|
|
2349
|
+
|
|
2350
|
+
void *scr_gen_retain_v(void *g) { return scr_gen_retain((ScrGen *)g); }
|
|
2351
|
+
void scr_gen_release_v(void *g) { scr_gen_release((ScrGen *)g); }
|
|
2352
|
+
|
|
2353
|
+
bool scr_gen_done(ScrGen *g) { return g->state == SCR_GEN_DONE; }
|
|
2354
|
+
ScrGen *scr_gen_of_fiber(ScrFiber *f) { return f->gen; }
|
|
2355
|
+
|
|
2356
|
+
/* Slot setters (release the previous occupant; payloads MOVE in). */
|
|
2357
|
+
static void scr_gen_slot_f64(ScrGenSlot *s, double v) {
|
|
2358
|
+
scr_gen_slot_reset(s);
|
|
2359
|
+
s->kind = SCR_EXC_F64;
|
|
2360
|
+
s->f64 = v;
|
|
2361
|
+
}
|
|
2362
|
+
static void scr_gen_slot_bool(ScrGenSlot *s, bool v) {
|
|
2363
|
+
scr_gen_slot_reset(s);
|
|
2364
|
+
s->kind = SCR_EXC_BOOL;
|
|
2365
|
+
s->b = v;
|
|
2366
|
+
}
|
|
2367
|
+
static void scr_gen_slot_ref(ScrGenSlot *s, void *v, void (*release)(void *)) {
|
|
2368
|
+
scr_gen_slot_reset(s);
|
|
2369
|
+
s->kind = SCR_EXC_REF;
|
|
2370
|
+
s->payload = v;
|
|
2371
|
+
s->release_fn = release;
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2374
|
+
void scr_gen_in_f64(ScrGen *g, double v) { scr_gen_slot_f64(&g->in, v); }
|
|
2375
|
+
void scr_gen_in_bool(ScrGen *g, bool v) { scr_gen_slot_bool(&g->in, v); }
|
|
2376
|
+
void scr_gen_in_ref(ScrGen *g, void *v, void (*release)(void *)) {
|
|
2377
|
+
scr_gen_slot_ref(&g->in, v, release);
|
|
2378
|
+
}
|
|
2379
|
+
void scr_gen_in_none(ScrGen *g) { scr_gen_slot_reset(&g->in); }
|
|
2380
|
+
|
|
2381
|
+
void scr_gen_ret_f64(ScrGen *g, double v) { scr_gen_slot_f64(&g->ret, v); }
|
|
2382
|
+
void scr_gen_ret_bool(ScrGen *g, bool v) { scr_gen_slot_bool(&g->ret, v); }
|
|
2383
|
+
void scr_gen_ret_ref(ScrGen *g, void *v, void (*release)(void *)) {
|
|
2384
|
+
scr_gen_slot_ref(&g->ret, v, release);
|
|
2385
|
+
}
|
|
2386
|
+
void scr_gen_ret_none(ScrGen *g) { scr_gen_slot_reset(&g->ret); }
|
|
2387
|
+
|
|
2388
|
+
void scr_gen_out_f64(ScrGen *g, double v) { scr_gen_slot_f64(&g->out, v); }
|
|
2389
|
+
void scr_gen_out_bool(ScrGen *g, bool v) { scr_gen_slot_bool(&g->out, v); }
|
|
2390
|
+
void scr_gen_out_ref(ScrGen *g, void *v, void (*release)(void *)) {
|
|
2391
|
+
scr_gen_slot_ref(&g->out, v, release);
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2394
|
+
/* Takes MOVE the slot's value out (the slot resets — a done generator's
|
|
2395
|
+
* later resumes answer NONE, JS's undefined). */
|
|
2396
|
+
static double scr_gen_slot_take_f64(ScrGenSlot *s) {
|
|
2397
|
+
double v = s->kind == SCR_EXC_F64 ? s->f64 : 0;
|
|
2398
|
+
s->kind = SCR_EXC_NONE;
|
|
2399
|
+
return v;
|
|
2400
|
+
}
|
|
2401
|
+
static bool scr_gen_slot_take_bool(ScrGenSlot *s) {
|
|
2402
|
+
bool v = s->kind == SCR_EXC_BOOL ? s->b : false;
|
|
2403
|
+
s->kind = SCR_EXC_NONE;
|
|
2404
|
+
return v;
|
|
2405
|
+
}
|
|
2406
|
+
static void *scr_gen_slot_take_ref(ScrGenSlot *s) {
|
|
2407
|
+
void *v = s->kind == SCR_EXC_REF ? s->payload : NULL;
|
|
2408
|
+
s->kind = SCR_EXC_NONE;
|
|
2409
|
+
s->payload = NULL;
|
|
2410
|
+
s->release_fn = NULL;
|
|
2411
|
+
return v;
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
bool scr_gen_out_has(ScrGen *g) { return g->out.kind != SCR_EXC_NONE; }
|
|
2415
|
+
double scr_gen_take_out_f64(ScrGen *g) { return scr_gen_slot_take_f64(&g->out); }
|
|
2416
|
+
bool scr_gen_take_out_bool(ScrGen *g) { return scr_gen_slot_take_bool(&g->out); }
|
|
2417
|
+
void *scr_gen_take_out_ref(ScrGen *g) { return scr_gen_slot_take_ref(&g->out); }
|
|
2418
|
+
|
|
2419
|
+
/* The running fiber's generator — the body-side helpers' anchor. */
|
|
2420
|
+
static ScrGen *scr_gen_self(void) {
|
|
2421
|
+
ScrGen *g = scr_current != NULL ? scr_current->gen : NULL;
|
|
2422
|
+
if (g == NULL) {
|
|
2423
|
+
fputs("scriptc: internal error: yield outside a generator\n", stderr);
|
|
2424
|
+
abort();
|
|
2425
|
+
}
|
|
2426
|
+
return g;
|
|
2427
|
+
}
|
|
2428
|
+
|
|
2429
|
+
double scr_gen_take_in_f64(void) { return scr_gen_slot_take_f64(&scr_gen_self()->in); }
|
|
2430
|
+
bool scr_gen_take_in_bool(void) { return scr_gen_slot_take_bool(&scr_gen_self()->in); }
|
|
2431
|
+
void *scr_gen_take_in_ref(void) { return scr_gen_slot_take_ref(&scr_gen_self()->in); }
|
|
2432
|
+
|
|
2433
|
+
/* yield: park the value in OUT and hop back to the resumer. Control
|
|
2434
|
+
* returns here at the next resume — possibly with an injected .throw
|
|
2435
|
+
* payload or the GENRET sentinel pending (the emitted check handles it). */
|
|
2436
|
+
static void scr_gen_yield_switch(void) {
|
|
2437
|
+
ScrFiber *self = scr_current;
|
|
2438
|
+
scr_switch(&self->ctx, self->return_to, NULL);
|
|
2439
|
+
}
|
|
2440
|
+
void scr_gen_yield_f64(double v) {
|
|
2441
|
+
scr_gen_slot_f64(&scr_gen_self()->out, v);
|
|
2442
|
+
scr_gen_yield_switch();
|
|
2443
|
+
}
|
|
2444
|
+
void scr_gen_yield_bool(bool v) {
|
|
2445
|
+
scr_gen_slot_bool(&scr_gen_self()->out, v);
|
|
2446
|
+
scr_gen_yield_switch();
|
|
2447
|
+
}
|
|
2448
|
+
void scr_gen_yield_ref(void *v, void (*release)(void *)) {
|
|
2449
|
+
scr_gen_slot_ref(&scr_gen_self()->out, v, release);
|
|
2450
|
+
scr_gen_yield_switch();
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2453
|
+
bool scr_exc_genret_pending(void) {
|
|
2454
|
+
return scr_exc_current_cell()->kind == SCR_EXC_GENRET;
|
|
2455
|
+
}
|
|
2456
|
+
|
|
2457
|
+
void scr_gen_ret_to_out(ScrGen *g) {
|
|
2458
|
+
scr_gen_slot_reset(&g->out);
|
|
2459
|
+
g->out = g->ret;
|
|
2460
|
+
g->ret.kind = SCR_EXC_NONE;
|
|
2461
|
+
g->ret.payload = NULL;
|
|
2462
|
+
g->ret.release_fn = NULL;
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2465
|
+
/* The consumer→fiber hop shared by every resume mode: switches in, and on
|
|
2466
|
+
* the way back moves a pending body exception into the RESUMER's cell
|
|
2467
|
+
* (synchronous propagation — the await-rethrow analog) and tears down a
|
|
2468
|
+
* finished fiber. The gen is retained across the hop so a body that
|
|
2469
|
+
* releases the consumer's last reference cannot free it mid-run. */
|
|
2470
|
+
static void scr_gen_switch_in(ScrGen *g) {
|
|
2471
|
+
ScrFiber *f = g->fiber;
|
|
2472
|
+
scr_gen_retain(g);
|
|
2473
|
+
g->state = SCR_GEN_RUNNING;
|
|
2474
|
+
#ifdef _WIN32
|
|
2475
|
+
ScrCtx here = scr_win_self();
|
|
2476
|
+
#else
|
|
2477
|
+
ucontext_t here;
|
|
2478
|
+
#endif
|
|
2479
|
+
f->return_to = &here;
|
|
2480
|
+
ScrFiber *me = scr_current;
|
|
2481
|
+
scr_switch(&here, &f->ctx, f);
|
|
2482
|
+
/* Back on the consumer: restore identity + the consumer's cell (the
|
|
2483
|
+
* yield/finish switch targeted NULL — main's cell). */
|
|
2484
|
+
scr_current = me;
|
|
2485
|
+
scr_exc_swap_cell(me != NULL ? &me->exc : NULL);
|
|
2486
|
+
if (f->done) {
|
|
2487
|
+
g->state = SCR_GEN_DONE;
|
|
2488
|
+
if (f->exc.kind != SCR_EXC_NONE) {
|
|
2489
|
+
/* The body's exception escaped: move it into the resumer's cell
|
|
2490
|
+
* (replace semantics match a throw at the resume site; the resumer's
|
|
2491
|
+
* cell is clean here — a pending exception cannot reach a resume). */
|
|
2492
|
+
ScrExcCell *mine = scr_exc_current_cell();
|
|
2493
|
+
*mine = f->exc;
|
|
2494
|
+
f->exc.kind = SCR_EXC_NONE;
|
|
2495
|
+
f->exc.payload = NULL;
|
|
2496
|
+
f->exc.trace_fn = NULL;
|
|
2497
|
+
}
|
|
2498
|
+
scr_fiber_destroy(f);
|
|
2499
|
+
g->fiber = NULL;
|
|
2500
|
+
scr_fibers_live--;
|
|
2501
|
+
} else {
|
|
2502
|
+
g->state = SCR_GEN_SUSPENDED;
|
|
2503
|
+
}
|
|
2504
|
+
scr_gen_release(g);
|
|
2505
|
+
}
|
|
2506
|
+
|
|
2507
|
+
void scr_gen_resume(ScrGen *g) {
|
|
2508
|
+
switch (g->state) {
|
|
2509
|
+
case SCR_GEN_RUNNING: {
|
|
2510
|
+
static const char sc_m[] = "Generator is already running";
|
|
2511
|
+
scr_throw_error_msg(SCR_ERR_TYPE, sc_m, sizeof sc_m - 1);
|
|
2512
|
+
return;
|
|
2513
|
+
}
|
|
2514
|
+
case SCR_GEN_DONE:
|
|
2515
|
+
/* next() on a done generator: { value: undefined, done: true } —
|
|
2516
|
+
* OUT is NONE (the completing resume took it). */
|
|
2517
|
+
return;
|
|
2518
|
+
default:
|
|
2519
|
+
scr_gen_switch_in(g);
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2523
|
+
void scr_gen_resume_return(ScrGen *g) {
|
|
2524
|
+
switch (g->state) {
|
|
2525
|
+
case SCR_GEN_RUNNING: {
|
|
2526
|
+
static const char sc_m[] = "Generator is already running";
|
|
2527
|
+
scr_throw_error_msg(SCR_ERR_TYPE, sc_m, sizeof sc_m - 1);
|
|
2528
|
+
return;
|
|
2529
|
+
}
|
|
2530
|
+
case SCR_GEN_DONE:
|
|
2531
|
+
scr_gen_ret_to_out(g);
|
|
2532
|
+
return;
|
|
2533
|
+
case SCR_GEN_UNSTARTED:
|
|
2534
|
+
/* The body never runs: tear the fiber down cleanly (drop the packed
|
|
2535
|
+
* arguments) and complete with the parked value. */
|
|
2536
|
+
if (g->drop_args != NULL) g->drop_args(g->fiber->argpack);
|
|
2537
|
+
else free(g->fiber->argpack);
|
|
2538
|
+
scr_fiber_destroy(g->fiber);
|
|
2539
|
+
g->fiber = NULL;
|
|
2540
|
+
scr_fibers_live--;
|
|
2541
|
+
g->state = SCR_GEN_DONE;
|
|
2542
|
+
scr_gen_ret_to_out(g);
|
|
2543
|
+
return;
|
|
2544
|
+
default:
|
|
2545
|
+
/* Suspended at a yield: inject the sentinel (an earlier .return whose
|
|
2546
|
+
* unwind a finally-yield parked leaves it already set) and resume. */
|
|
2547
|
+
if (g->fiber->exc.kind == SCR_EXC_NONE) g->fiber->exc.kind = SCR_EXC_GENRET;
|
|
2548
|
+
scr_gen_switch_in(g);
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
|
|
2552
|
+
void scr_gen_resume_throw(ScrGen *g) {
|
|
2553
|
+
switch (g->state) {
|
|
2554
|
+
case SCR_GEN_RUNNING: {
|
|
2555
|
+
/* Replace the parked payload with Node's reentrancy TypeError. */
|
|
2556
|
+
static const char sc_m[] = "Generator is already running";
|
|
2557
|
+
scr_throw_error_msg(SCR_ERR_TYPE, sc_m, sizeof sc_m - 1);
|
|
2558
|
+
return;
|
|
2559
|
+
}
|
|
2560
|
+
case SCR_GEN_DONE:
|
|
2561
|
+
/* .throw on a done generator: it stays done; the payload stays
|
|
2562
|
+
* pending in the caller — the call site's check rethrows it. */
|
|
2563
|
+
return;
|
|
2564
|
+
case SCR_GEN_UNSTARTED:
|
|
2565
|
+
/* The body never runs; the generator becomes done and the payload
|
|
2566
|
+
* stays pending in the caller (probed Node behavior). */
|
|
2567
|
+
if (g->drop_args != NULL) g->drop_args(g->fiber->argpack);
|
|
2568
|
+
else free(g->fiber->argpack);
|
|
2569
|
+
scr_fiber_destroy(g->fiber);
|
|
2570
|
+
g->fiber = NULL;
|
|
2571
|
+
scr_fibers_live--;
|
|
2572
|
+
g->state = SCR_GEN_DONE;
|
|
2573
|
+
return;
|
|
2574
|
+
default: {
|
|
2575
|
+
/* Move the caller's pending payload into the fiber's cell (an
|
|
2576
|
+
* earlier sentinel parked by a finally-yield is replaced — the
|
|
2577
|
+
* injected throw wins, like a throw inside that finally). */
|
|
2578
|
+
ScrExcCell *mine = scr_exc_current_cell();
|
|
2579
|
+
ScrExcCell *dst = &g->fiber->exc;
|
|
2580
|
+
dst->kind = mine->kind;
|
|
2581
|
+
dst->f64 = mine->f64;
|
|
2582
|
+
dst->b = mine->b;
|
|
2583
|
+
dst->payload = mine->payload;
|
|
2584
|
+
dst->retain_fn = mine->retain_fn;
|
|
2585
|
+
dst->release_fn = mine->release_fn;
|
|
2586
|
+
dst->trace_fn = mine->trace_fn;
|
|
2587
|
+
mine->kind = SCR_EXC_NONE;
|
|
2588
|
+
mine->payload = NULL;
|
|
2589
|
+
mine->trace_fn = NULL;
|
|
2590
|
+
scr_gen_switch_in(g);
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
}
|