@scriptc/runtime 0.0.0 → 0.0.2

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.
Files changed (416) hide show
  1. package/LICENSE +202 -0
  2. package/package.json +16 -6
  3. package/src/scr_array.c +488 -0
  4. package/src/scr_assert.c +1393 -0
  5. package/src/scr_async.c +2593 -0
  6. package/src/scr_async_dyn.c +607 -0
  7. package/src/scr_bytes.c +1425 -0
  8. package/src/scr_bytes_io.c +161 -0
  9. package/src/scr_child.c +3587 -0
  10. package/src/scr_closure.c +214 -0
  11. package/src/scr_console.c +143 -0
  12. package/src/scr_cycle.c +218 -0
  13. package/src/scr_dc.c +805 -0
  14. package/src/scr_dgram.c +1007 -0
  15. package/src/scr_dyn_handle.c +154 -0
  16. package/src/scr_dyn_invoke.c +644 -0
  17. package/src/scr_error.c +332 -0
  18. package/src/scr_events.c +714 -0
  19. package/src/scr_events_emitter.c +699 -0
  20. package/src/scr_exception.c +242 -0
  21. package/src/scr_fetch.c +1130 -0
  22. package/src/scr_fetch_curl.c +654 -0
  23. package/src/scr_http.c +3482 -0
  24. package/src/scr_http2.c +3030 -0
  25. package/src/scr_inspect.c +803 -0
  26. package/src/scr_inspect_island.c +51 -0
  27. package/src/scr_island.c +9363 -0
  28. package/src/scr_json.c +2151 -0
  29. package/src/scr_lib.c +3612 -0
  30. package/src/scr_loop_epoll.c +241 -0
  31. package/src/scr_loop_kqueue.c +121 -0
  32. package/src/scr_loop_wsapoll.c +208 -0
  33. package/src/scr_map.c +591 -0
  34. package/src/scr_net.c +3017 -0
  35. package/src/scr_net_island.c +458 -0
  36. package/src/scr_number.c +115 -0
  37. package/src/scr_object.c +25 -0
  38. package/src/scr_path.c +1266 -0
  39. package/src/scr_platform.h +119 -0
  40. package/src/scr_readline.c +287 -0
  41. package/src/scr_regex.c +1080 -0
  42. package/src/scr_runtime.h +5046 -0
  43. package/src/scr_stream.c +2877 -0
  44. package/src/scr_string.c +1264 -0
  45. package/src/scr_symbol.c +132 -0
  46. package/src/scr_test.c +662 -0
  47. package/src/scr_tls.c +1520 -0
  48. package/src/scr_union.c +105 -0
  49. package/src/scr_url.c +911 -0
  50. package/src/scr_url_internal.h +29 -0
  51. package/src/scr_url_params.c +561 -0
  52. package/src/scr_watch.c +568 -0
  53. package/src/scr_web.c +1778 -0
  54. package/src/scr_win.c +76 -0
  55. package/src/scr_zlib.c +197 -0
  56. package/src/scr_zlib_island.c +15 -0
  57. package/vendor/README.md +52 -0
  58. package/vendor/curl/COPYRIGHT +534 -0
  59. package/vendor/curl/include/curl/curl.h +3214 -0
  60. package/vendor/curl/include/curl/curlver.h +79 -0
  61. package/vendor/curl/include/curl/easy.h +125 -0
  62. package/vendor/curl/include/curl/header.h +74 -0
  63. package/vendor/curl/include/curl/mprintf.h +52 -0
  64. package/vendor/curl/include/curl/multi.h +460 -0
  65. package/vendor/curl/include/curl/options.h +70 -0
  66. package/vendor/curl/include/curl/stdcheaders.h +35 -0
  67. package/vendor/curl/include/curl/system.h +508 -0
  68. package/vendor/curl/include/curl/typecheck-gcc.h +716 -0
  69. package/vendor/curl/include/curl/urlapi.h +149 -0
  70. package/vendor/curl/include/curl/websockets.h +84 -0
  71. package/vendor/mbedtls/LICENSE +553 -0
  72. package/vendor/mbedtls/include/CMakeLists.txt +22 -0
  73. package/vendor/mbedtls/include/mbedtls/aes.h +631 -0
  74. package/vendor/mbedtls/include/mbedtls/aria.h +343 -0
  75. package/vendor/mbedtls/include/mbedtls/asn1.h +642 -0
  76. package/vendor/mbedtls/include/mbedtls/asn1write.h +390 -0
  77. package/vendor/mbedtls/include/mbedtls/base64.h +82 -0
  78. package/vendor/mbedtls/include/mbedtls/bignum.h +1088 -0
  79. package/vendor/mbedtls/include/mbedtls/block_cipher.h +76 -0
  80. package/vendor/mbedtls/include/mbedtls/build_info.h +194 -0
  81. package/vendor/mbedtls/include/mbedtls/camellia.h +305 -0
  82. package/vendor/mbedtls/include/mbedtls/ccm.h +526 -0
  83. package/vendor/mbedtls/include/mbedtls/chacha20.h +209 -0
  84. package/vendor/mbedtls/include/mbedtls/chachapoly.h +351 -0
  85. package/vendor/mbedtls/include/mbedtls/check_config.h +1149 -0
  86. package/vendor/mbedtls/include/mbedtls/cipher.h +1250 -0
  87. package/vendor/mbedtls/include/mbedtls/cmac.h +246 -0
  88. package/vendor/mbedtls/include/mbedtls/compat-2.x.h +46 -0
  89. package/vendor/mbedtls/include/mbedtls/config_adjust_legacy_crypto.h +578 -0
  90. package/vendor/mbedtls/include/mbedtls/config_adjust_legacy_from_psa.h +873 -0
  91. package/vendor/mbedtls/include/mbedtls/config_adjust_psa_from_legacy.h +359 -0
  92. package/vendor/mbedtls/include/mbedtls/config_adjust_psa_superset_legacy.h +145 -0
  93. package/vendor/mbedtls/include/mbedtls/config_adjust_ssl.h +91 -0
  94. package/vendor/mbedtls/include/mbedtls/config_adjust_x509.h +35 -0
  95. package/vendor/mbedtls/include/mbedtls/config_psa.h +61 -0
  96. package/vendor/mbedtls/include/mbedtls/constant_time.h +36 -0
  97. package/vendor/mbedtls/include/mbedtls/ctr_drbg.h +596 -0
  98. package/vendor/mbedtls/include/mbedtls/debug.h +156 -0
  99. package/vendor/mbedtls/include/mbedtls/des.h +385 -0
  100. package/vendor/mbedtls/include/mbedtls/dhm.h +972 -0
  101. package/vendor/mbedtls/include/mbedtls/ecdh.h +455 -0
  102. package/vendor/mbedtls/include/mbedtls/ecdsa.h +674 -0
  103. package/vendor/mbedtls/include/mbedtls/ecjpake.h +298 -0
  104. package/vendor/mbedtls/include/mbedtls/ecp.h +1517 -0
  105. package/vendor/mbedtls/include/mbedtls/entropy.h +274 -0
  106. package/vendor/mbedtls/include/mbedtls/error.h +201 -0
  107. package/vendor/mbedtls/include/mbedtls/gcm.h +387 -0
  108. package/vendor/mbedtls/include/mbedtls/hkdf.h +124 -0
  109. package/vendor/mbedtls/include/mbedtls/hmac_drbg.h +434 -0
  110. package/vendor/mbedtls/include/mbedtls/lms.h +440 -0
  111. package/vendor/mbedtls/include/mbedtls/mbedtls_config.h +4446 -0
  112. package/vendor/mbedtls/include/mbedtls/md.h +526 -0
  113. package/vendor/mbedtls/include/mbedtls/md5.h +190 -0
  114. package/vendor/mbedtls/include/mbedtls/memory_buffer_alloc.h +142 -0
  115. package/vendor/mbedtls/include/mbedtls/net_sockets.h +299 -0
  116. package/vendor/mbedtls/include/mbedtls/nist_kw.h +166 -0
  117. package/vendor/mbedtls/include/mbedtls/oid.h +727 -0
  118. package/vendor/mbedtls/include/mbedtls/pem.h +160 -0
  119. package/vendor/mbedtls/include/mbedtls/pk.h +1303 -0
  120. package/vendor/mbedtls/include/mbedtls/pkcs12.h +186 -0
  121. package/vendor/mbedtls/include/mbedtls/pkcs5.h +198 -0
  122. package/vendor/mbedtls/include/mbedtls/pkcs7.h +252 -0
  123. package/vendor/mbedtls/include/mbedtls/platform.h +516 -0
  124. package/vendor/mbedtls/include/mbedtls/platform_time.h +79 -0
  125. package/vendor/mbedtls/include/mbedtls/platform_util.h +247 -0
  126. package/vendor/mbedtls/include/mbedtls/poly1305.h +168 -0
  127. package/vendor/mbedtls/include/mbedtls/private_access.h +20 -0
  128. package/vendor/mbedtls/include/mbedtls/psa_util.h +207 -0
  129. package/vendor/mbedtls/include/mbedtls/ripemd160.h +136 -0
  130. package/vendor/mbedtls/include/mbedtls/rsa.h +1170 -0
  131. package/vendor/mbedtls/include/mbedtls/sha1.h +219 -0
  132. package/vendor/mbedtls/include/mbedtls/sha256.h +200 -0
  133. package/vendor/mbedtls/include/mbedtls/sha3.h +172 -0
  134. package/vendor/mbedtls/include/mbedtls/sha512.h +208 -0
  135. package/vendor/mbedtls/include/mbedtls/ssl.h +5887 -0
  136. package/vendor/mbedtls/include/mbedtls/ssl_cache.h +187 -0
  137. package/vendor/mbedtls/include/mbedtls/ssl_ciphersuites.h +482 -0
  138. package/vendor/mbedtls/include/mbedtls/ssl_cookie.h +106 -0
  139. package/vendor/mbedtls/include/mbedtls/ssl_ticket.h +199 -0
  140. package/vendor/mbedtls/include/mbedtls/threading.h +167 -0
  141. package/vendor/mbedtls/include/mbedtls/timing.h +94 -0
  142. package/vendor/mbedtls/include/mbedtls/version.h +78 -0
  143. package/vendor/mbedtls/include/mbedtls/x509.h +500 -0
  144. package/vendor/mbedtls/include/mbedtls/x509_crl.h +184 -0
  145. package/vendor/mbedtls/include/mbedtls/x509_crt.h +1208 -0
  146. package/vendor/mbedtls/include/mbedtls/x509_csr.h +382 -0
  147. package/vendor/mbedtls/include/psa/build_info.h +20 -0
  148. package/vendor/mbedtls/include/psa/crypto.h +4998 -0
  149. package/vendor/mbedtls/include/psa/crypto_adjust_auto_enabled.h +31 -0
  150. package/vendor/mbedtls/include/psa/crypto_adjust_config_dependencies.h +51 -0
  151. package/vendor/mbedtls/include/psa/crypto_adjust_config_key_pair_types.h +101 -0
  152. package/vendor/mbedtls/include/psa/crypto_adjust_config_synonyms.h +49 -0
  153. package/vendor/mbedtls/include/psa/crypto_builtin_composites.h +214 -0
  154. package/vendor/mbedtls/include/psa/crypto_builtin_key_derivation.h +118 -0
  155. package/vendor/mbedtls/include/psa/crypto_builtin_primitives.h +114 -0
  156. package/vendor/mbedtls/include/psa/crypto_compat.h +230 -0
  157. package/vendor/mbedtls/include/psa/crypto_config.h +145 -0
  158. package/vendor/mbedtls/include/psa/crypto_driver_common.h +44 -0
  159. package/vendor/mbedtls/include/psa/crypto_driver_contexts_composites.h +151 -0
  160. package/vendor/mbedtls/include/psa/crypto_driver_contexts_key_derivation.h +52 -0
  161. package/vendor/mbedtls/include/psa/crypto_driver_contexts_primitives.h +105 -0
  162. package/vendor/mbedtls/include/psa/crypto_extra.h +2145 -0
  163. package/vendor/mbedtls/include/psa/crypto_legacy.h +88 -0
  164. package/vendor/mbedtls/include/psa/crypto_platform.h +102 -0
  165. package/vendor/mbedtls/include/psa/crypto_se_driver.h +1383 -0
  166. package/vendor/mbedtls/include/psa/crypto_sizes.h +1319 -0
  167. package/vendor/mbedtls/include/psa/crypto_struct.h +527 -0
  168. package/vendor/mbedtls/include/psa/crypto_types.h +508 -0
  169. package/vendor/mbedtls/include/psa/crypto_values.h +2782 -0
  170. package/vendor/mbedtls/library/aes.c +2294 -0
  171. package/vendor/mbedtls/library/aesce.c +624 -0
  172. package/vendor/mbedtls/library/aesce.h +136 -0
  173. package/vendor/mbedtls/library/aesni.c +846 -0
  174. package/vendor/mbedtls/library/aesni.h +162 -0
  175. package/vendor/mbedtls/library/alignment.h +704 -0
  176. package/vendor/mbedtls/library/aria.c +969 -0
  177. package/vendor/mbedtls/library/asn1parse.c +468 -0
  178. package/vendor/mbedtls/library/asn1write.c +440 -0
  179. package/vendor/mbedtls/library/base64.c +322 -0
  180. package/vendor/mbedtls/library/base64_internal.h +45 -0
  181. package/vendor/mbedtls/library/bignum.c +2583 -0
  182. package/vendor/mbedtls/library/bignum_core.c +1240 -0
  183. package/vendor/mbedtls/library/bignum_core.h +872 -0
  184. package/vendor/mbedtls/library/bignum_core_invasive.h +38 -0
  185. package/vendor/mbedtls/library/bignum_internal.h +122 -0
  186. package/vendor/mbedtls/library/bignum_mod.c +394 -0
  187. package/vendor/mbedtls/library/bignum_mod.h +452 -0
  188. package/vendor/mbedtls/library/bignum_mod_raw.c +276 -0
  189. package/vendor/mbedtls/library/bignum_mod_raw.h +416 -0
  190. package/vendor/mbedtls/library/bignum_mod_raw_invasive.h +34 -0
  191. package/vendor/mbedtls/library/block_cipher.c +207 -0
  192. package/vendor/mbedtls/library/block_cipher_internal.h +99 -0
  193. package/vendor/mbedtls/library/bn_mul.h +1094 -0
  194. package/vendor/mbedtls/library/camellia.c +1058 -0
  195. package/vendor/mbedtls/library/ccm.c +777 -0
  196. package/vendor/mbedtls/library/chacha20.c +563 -0
  197. package/vendor/mbedtls/library/chacha20_internal.h +23 -0
  198. package/vendor/mbedtls/library/chachapoly.c +487 -0
  199. package/vendor/mbedtls/library/check_crypto_config.h +136 -0
  200. package/vendor/mbedtls/library/cipher.c +1712 -0
  201. package/vendor/mbedtls/library/cipher_invasive.h +28 -0
  202. package/vendor/mbedtls/library/cipher_wrap.c +2482 -0
  203. package/vendor/mbedtls/library/cipher_wrap.h +178 -0
  204. package/vendor/mbedtls/library/cmac.c +1067 -0
  205. package/vendor/mbedtls/library/common.h +487 -0
  206. package/vendor/mbedtls/library/constant_time.c +247 -0
  207. package/vendor/mbedtls/library/constant_time_impl.h +541 -0
  208. package/vendor/mbedtls/library/constant_time_internal.h +579 -0
  209. package/vendor/mbedtls/library/ctr.h +35 -0
  210. package/vendor/mbedtls/library/ctr_drbg.c +1016 -0
  211. package/vendor/mbedtls/library/debug.c +475 -0
  212. package/vendor/mbedtls/library/debug_internal.h +185 -0
  213. package/vendor/mbedtls/library/des.c +1042 -0
  214. package/vendor/mbedtls/library/dhm.c +700 -0
  215. package/vendor/mbedtls/library/ecdh.c +696 -0
  216. package/vendor/mbedtls/library/ecdsa.c +858 -0
  217. package/vendor/mbedtls/library/ecjpake.c +1216 -0
  218. package/vendor/mbedtls/library/ecp.c +3674 -0
  219. package/vendor/mbedtls/library/ecp_curves.c +6125 -0
  220. package/vendor/mbedtls/library/ecp_curves_new.c +9 -0
  221. package/vendor/mbedtls/library/ecp_internal_alt.h +287 -0
  222. package/vendor/mbedtls/library/ecp_invasive.h +305 -0
  223. package/vendor/mbedtls/library/entropy.c +680 -0
  224. package/vendor/mbedtls/library/entropy_poll.c +233 -0
  225. package/vendor/mbedtls/library/entropy_poll.h +64 -0
  226. package/vendor/mbedtls/library/error.c +878 -0
  227. package/vendor/mbedtls/library/gcm.c +1330 -0
  228. package/vendor/mbedtls/library/hkdf.c +161 -0
  229. package/vendor/mbedtls/library/hmac_drbg.c +633 -0
  230. package/vendor/mbedtls/library/lmots.c +789 -0
  231. package/vendor/mbedtls/library/lmots.h +288 -0
  232. package/vendor/mbedtls/library/lms.c +778 -0
  233. package/vendor/mbedtls/library/md.c +1108 -0
  234. package/vendor/mbedtls/library/md5.c +426 -0
  235. package/vendor/mbedtls/library/md_psa.h +26 -0
  236. package/vendor/mbedtls/library/md_wrap.h +46 -0
  237. package/vendor/mbedtls/library/memory_buffer_alloc.c +751 -0
  238. package/vendor/mbedtls/library/mps_common.h +181 -0
  239. package/vendor/mbedtls/library/mps_error.h +89 -0
  240. package/vendor/mbedtls/library/mps_reader.c +538 -0
  241. package/vendor/mbedtls/library/mps_reader.h +366 -0
  242. package/vendor/mbedtls/library/mps_trace.c +112 -0
  243. package/vendor/mbedtls/library/mps_trace.h +154 -0
  244. package/vendor/mbedtls/library/net_sockets.c +694 -0
  245. package/vendor/mbedtls/library/nist_kw.c +729 -0
  246. package/vendor/mbedtls/library/oid.c +1166 -0
  247. package/vendor/mbedtls/library/padlock.c +157 -0
  248. package/vendor/mbedtls/library/padlock.h +111 -0
  249. package/vendor/mbedtls/library/pem.c +554 -0
  250. package/vendor/mbedtls/library/pk.c +1602 -0
  251. package/vendor/mbedtls/library/pk_ecc.c +261 -0
  252. package/vendor/mbedtls/library/pk_internal.h +241 -0
  253. package/vendor/mbedtls/library/pk_wrap.c +1618 -0
  254. package/vendor/mbedtls/library/pk_wrap.h +138 -0
  255. package/vendor/mbedtls/library/pkcs12.c +437 -0
  256. package/vendor/mbedtls/library/pkcs5.c +500 -0
  257. package/vendor/mbedtls/library/pkcs7.c +787 -0
  258. package/vendor/mbedtls/library/pkparse.c +1392 -0
  259. package/vendor/mbedtls/library/pkwrite.c +631 -0
  260. package/vendor/mbedtls/library/pkwrite.h +121 -0
  261. package/vendor/mbedtls/library/platform.c +402 -0
  262. package/vendor/mbedtls/library/platform_util.c +258 -0
  263. package/vendor/mbedtls/library/poly1305.c +492 -0
  264. package/vendor/mbedtls/library/psa_crypto.c +9532 -0
  265. package/vendor/mbedtls/library/psa_crypto_aead.c +646 -0
  266. package/vendor/mbedtls/library/psa_crypto_aead.h +499 -0
  267. package/vendor/mbedtls/library/psa_crypto_cipher.c +747 -0
  268. package/vendor/mbedtls/library/psa_crypto_cipher.h +316 -0
  269. package/vendor/mbedtls/library/psa_crypto_client.c +22 -0
  270. package/vendor/mbedtls/library/psa_crypto_core.h +983 -0
  271. package/vendor/mbedtls/library/psa_crypto_core_common.h +52 -0
  272. package/vendor/mbedtls/library/psa_crypto_driver_wrappers.h +2896 -0
  273. package/vendor/mbedtls/library/psa_crypto_driver_wrappers_no_static.c +256 -0
  274. package/vendor/mbedtls/library/psa_crypto_driver_wrappers_no_static.h +31 -0
  275. package/vendor/mbedtls/library/psa_crypto_ecp.c +594 -0
  276. package/vendor/mbedtls/library/psa_crypto_ecp.h +267 -0
  277. package/vendor/mbedtls/library/psa_crypto_ffdh.c +361 -0
  278. package/vendor/mbedtls/library/psa_crypto_ffdh.h +131 -0
  279. package/vendor/mbedtls/library/psa_crypto_hash.c +470 -0
  280. package/vendor/mbedtls/library/psa_crypto_hash.h +211 -0
  281. package/vendor/mbedtls/library/psa_crypto_invasive.h +92 -0
  282. package/vendor/mbedtls/library/psa_crypto_its.h +131 -0
  283. package/vendor/mbedtls/library/psa_crypto_mac.c +505 -0
  284. package/vendor/mbedtls/library/psa_crypto_mac.h +264 -0
  285. package/vendor/mbedtls/library/psa_crypto_pake.c +571 -0
  286. package/vendor/mbedtls/library/psa_crypto_pake.h +159 -0
  287. package/vendor/mbedtls/library/psa_crypto_random.c +181 -0
  288. package/vendor/mbedtls/library/psa_crypto_random.h +72 -0
  289. package/vendor/mbedtls/library/psa_crypto_random_impl.h +200 -0
  290. package/vendor/mbedtls/library/psa_crypto_rsa.c +714 -0
  291. package/vendor/mbedtls/library/psa_crypto_rsa.h +321 -0
  292. package/vendor/mbedtls/library/psa_crypto_se.c +373 -0
  293. package/vendor/mbedtls/library/psa_crypto_se.h +192 -0
  294. package/vendor/mbedtls/library/psa_crypto_slot_management.c +1137 -0
  295. package/vendor/mbedtls/library/psa_crypto_slot_management.h +344 -0
  296. package/vendor/mbedtls/library/psa_crypto_storage.c +481 -0
  297. package/vendor/mbedtls/library/psa_crypto_storage.h +392 -0
  298. package/vendor/mbedtls/library/psa_its_file.c +254 -0
  299. package/vendor/mbedtls/library/psa_util.c +614 -0
  300. package/vendor/mbedtls/library/psa_util_internal.h +100 -0
  301. package/vendor/mbedtls/library/ripemd160.c +490 -0
  302. package/vendor/mbedtls/library/rsa.c +3121 -0
  303. package/vendor/mbedtls/library/rsa_alt_helpers.c +455 -0
  304. package/vendor/mbedtls/library/rsa_alt_helpers.h +209 -0
  305. package/vendor/mbedtls/library/rsa_internal.h +188 -0
  306. package/vendor/mbedtls/library/sha1.c +480 -0
  307. package/vendor/mbedtls/library/sha256.c +983 -0
  308. package/vendor/mbedtls/library/sha3.c +721 -0
  309. package/vendor/mbedtls/library/sha512.c +1115 -0
  310. package/vendor/mbedtls/library/ssl_cache.c +410 -0
  311. package/vendor/mbedtls/library/ssl_ciphersuites.c +2050 -0
  312. package/vendor/mbedtls/library/ssl_ciphersuites_internal.h +154 -0
  313. package/vendor/mbedtls/library/ssl_client.c +1023 -0
  314. package/vendor/mbedtls/library/ssl_client.h +22 -0
  315. package/vendor/mbedtls/library/ssl_cookie.c +384 -0
  316. package/vendor/mbedtls/library/ssl_debug_helpers.h +85 -0
  317. package/vendor/mbedtls/library/ssl_debug_helpers_generated.c +251 -0
  318. package/vendor/mbedtls/library/ssl_misc.h +3198 -0
  319. package/vendor/mbedtls/library/ssl_msg.c +6619 -0
  320. package/vendor/mbedtls/library/ssl_ticket.c +556 -0
  321. package/vendor/mbedtls/library/ssl_tls.c +10234 -0
  322. package/vendor/mbedtls/library/ssl_tls12_client.c +3688 -0
  323. package/vendor/mbedtls/library/ssl_tls12_server.c +4311 -0
  324. package/vendor/mbedtls/library/ssl_tls13_client.c +3208 -0
  325. package/vendor/mbedtls/library/ssl_tls13_generic.c +1793 -0
  326. package/vendor/mbedtls/library/ssl_tls13_invasive.h +23 -0
  327. package/vendor/mbedtls/library/ssl_tls13_keys.c +1918 -0
  328. package/vendor/mbedtls/library/ssl_tls13_keys.h +668 -0
  329. package/vendor/mbedtls/library/ssl_tls13_server.c +3624 -0
  330. package/vendor/mbedtls/library/threading.c +193 -0
  331. package/vendor/mbedtls/library/threading_internal.h +28 -0
  332. package/vendor/mbedtls/library/timing.c +152 -0
  333. package/vendor/mbedtls/library/version.c +32 -0
  334. package/vendor/mbedtls/library/version_features.c +856 -0
  335. package/vendor/mbedtls/library/x509.c +1776 -0
  336. package/vendor/mbedtls/library/x509_create.c +570 -0
  337. package/vendor/mbedtls/library/x509_crl.c +708 -0
  338. package/vendor/mbedtls/library/x509_crt.c +3311 -0
  339. package/vendor/mbedtls/library/x509_csr.c +648 -0
  340. package/vendor/mbedtls/library/x509_internal.h +101 -0
  341. package/vendor/mbedtls/library/x509write.c +174 -0
  342. package/vendor/mbedtls/library/x509write_crt.c +688 -0
  343. package/vendor/mbedtls/library/x509write_csr.c +336 -0
  344. package/vendor/quickjs-ng/CMakeLists.txt +566 -0
  345. package/vendor/quickjs-ng/LICENSE +24 -0
  346. package/vendor/quickjs-ng/README.md +24 -0
  347. package/vendor/quickjs-ng/api-test.c +1195 -0
  348. package/vendor/quickjs-ng/builtin-array-fromasync.h +119 -0
  349. package/vendor/quickjs-ng/builtin-iterator-zip-keyed.h +332 -0
  350. package/vendor/quickjs-ng/builtin-iterator-zip.h +337 -0
  351. package/vendor/quickjs-ng/cutils.h +1998 -0
  352. package/vendor/quickjs-ng/dtoa.c +1619 -0
  353. package/vendor/quickjs-ng/dtoa.h +87 -0
  354. package/vendor/quickjs-ng/gen/function_source.c +81 -0
  355. package/vendor/quickjs-ng/gen/hello.c +53 -0
  356. package/vendor/quickjs-ng/gen/hello_module.c +106 -0
  357. package/vendor/quickjs-ng/gen/repl.c +3036 -0
  358. package/vendor/quickjs-ng/gen/standalone.c +323 -0
  359. package/vendor/quickjs-ng/gen/test_fib.c +81 -0
  360. package/vendor/quickjs-ng/libregexp-opcode.h +73 -0
  361. package/vendor/quickjs-ng/libregexp.c +3474 -0
  362. package/vendor/quickjs-ng/libregexp.h +101 -0
  363. package/vendor/quickjs-ng/libunicode-table.h +5173 -0
  364. package/vendor/quickjs-ng/libunicode.c +2069 -0
  365. package/vendor/quickjs-ng/libunicode.h +172 -0
  366. package/vendor/quickjs-ng/list.h +107 -0
  367. package/vendor/quickjs-ng/lre-test.c +52 -0
  368. package/vendor/quickjs-ng/qjs.c +762 -0
  369. package/vendor/quickjs-ng/qjsc.c +673 -0
  370. package/vendor/quickjs-ng/quickjs-atom.h +280 -0
  371. package/vendor/quickjs-ng/quickjs-c-atomics.h +54 -0
  372. package/vendor/quickjs-ng/quickjs-libc.c +5037 -0
  373. package/vendor/quickjs-ng/quickjs-libc.h +87 -0
  374. package/vendor/quickjs-ng/quickjs-opcode.h +377 -0
  375. package/vendor/quickjs-ng/quickjs.c +64041 -0
  376. package/vendor/quickjs-ng/quickjs.h +1447 -0
  377. package/vendor/quickjs-ng/run-test262.c +2374 -0
  378. package/vendor/quickjs-ng/unicode_gen.c +3121 -0
  379. package/vendor/quickjs-ng/unicode_gen_def.h +310 -0
  380. package/vendor/ryu/LICENSE-Boost +23 -0
  381. package/vendor/ryu/common.h +114 -0
  382. package/vendor/ryu/d2s.c +509 -0
  383. package/vendor/ryu/d2s_full_table.h +367 -0
  384. package/vendor/ryu/d2s_intrinsics.h +357 -0
  385. package/vendor/ryu/d2s_small_table.h +186 -0
  386. package/vendor/ryu/digit_table.h +35 -0
  387. package/vendor/ryu/ryu.h +46 -0
  388. package/vendor/zlib/LICENSE +22 -0
  389. package/vendor/zlib/adler32.c +164 -0
  390. package/vendor/zlib/compress.c +75 -0
  391. package/vendor/zlib/crc32.c +1049 -0
  392. package/vendor/zlib/crc32.h +9446 -0
  393. package/vendor/zlib/deflate.c +2139 -0
  394. package/vendor/zlib/deflate.h +377 -0
  395. package/vendor/zlib/gzclose.c +23 -0
  396. package/vendor/zlib/gzguts.h +214 -0
  397. package/vendor/zlib/gzlib.c +582 -0
  398. package/vendor/zlib/gzread.c +602 -0
  399. package/vendor/zlib/gzwrite.c +631 -0
  400. package/vendor/zlib/infback.c +628 -0
  401. package/vendor/zlib/inffast.c +320 -0
  402. package/vendor/zlib/inffast.h +11 -0
  403. package/vendor/zlib/inffixed.h +94 -0
  404. package/vendor/zlib/inflate.c +1526 -0
  405. package/vendor/zlib/inflate.h +126 -0
  406. package/vendor/zlib/inftrees.c +299 -0
  407. package/vendor/zlib/inftrees.h +62 -0
  408. package/vendor/zlib/trees.c +1117 -0
  409. package/vendor/zlib/trees.h +128 -0
  410. package/vendor/zlib/uncompr.c +85 -0
  411. package/vendor/zlib/zconf.h +543 -0
  412. package/vendor/zlib/zlib.h +1938 -0
  413. package/vendor/zlib/zutil.c +299 -0
  414. package/vendor/zlib/zutil.h +254 -0
  415. package/README.md +0 -3
  416. package/index.js +0 -2
@@ -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
+ }