@scriptc/compiler 0.2.5 → 0.2.7

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 (520) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +62 -48
  2. package/dist/backend/cycle-analysis.d.ts +21 -9
  3. package/dist/backend/cycle-analysis.js +139 -67
  4. package/dist/backend/cycle-analysis.js.map +1 -1
  5. package/dist/backend/cycle-analysis.test.js +283 -3
  6. package/dist/backend/cycle-analysis.test.js.map +1 -1
  7. package/dist/backend/cycle-mutability.d.ts +39 -0
  8. package/dist/backend/cycle-mutability.js +205 -0
  9. package/dist/backend/cycle-mutability.js.map +1 -0
  10. package/dist/backend/executable-features.js +1 -0
  11. package/dist/backend/executable-features.js.map +1 -1
  12. package/dist/backend/link-plan-core.d.ts +4 -1
  13. package/dist/backend/link-plan-core.js +16 -3
  14. package/dist/backend/link-plan-core.js.map +1 -1
  15. package/dist/backend/link-plan.d.ts +3 -1
  16. package/dist/backend/link-plan.js +3 -0
  17. package/dist/backend/link-plan.js.map +1 -1
  18. package/dist/backend/llvm/alloc.d.ts +40 -0
  19. package/dist/backend/llvm/alloc.js +205 -0
  20. package/dist/backend/llvm/alloc.js.map +1 -0
  21. package/dist/backend/llvm/alloc.test.d.ts +1 -0
  22. package/dist/backend/llvm/alloc.test.js +102 -0
  23. package/dist/backend/llvm/alloc.test.js.map +1 -0
  24. package/dist/backend/llvm/byte-numbers.js +32 -4
  25. package/dist/backend/llvm/byte-numbers.js.map +1 -1
  26. package/dist/backend/llvm/byte-windows.d.ts +31 -0
  27. package/dist/backend/llvm/byte-windows.js +334 -0
  28. package/dist/backend/llvm/byte-windows.js.map +1 -0
  29. package/dist/backend/llvm/byte-windows.test.d.ts +1 -0
  30. package/dist/backend/llvm/byte-windows.test.js +244 -0
  31. package/dist/backend/llvm/byte-windows.test.js.map +1 -0
  32. package/dist/backend/llvm/call-lifetimes.d.ts +17 -5
  33. package/dist/backend/llvm/call-lifetimes.js +58 -24
  34. package/dist/backend/llvm/call-lifetimes.js.map +1 -1
  35. package/dist/backend/llvm/call-lifetimes.test.js +170 -0
  36. package/dist/backend/llvm/call-lifetimes.test.js.map +1 -1
  37. package/dist/backend/llvm/classes.d.ts +18 -2
  38. package/dist/backend/llvm/classes.js +115 -41
  39. package/dist/backend/llvm/classes.js.map +1 -1
  40. package/dist/backend/llvm/classes.test.d.ts +1 -0
  41. package/dist/backend/llvm/classes.test.js +132 -0
  42. package/dist/backend/llvm/classes.test.js.map +1 -0
  43. package/dist/backend/llvm/common.d.ts +13 -0
  44. package/dist/backend/llvm/common.js +30 -0
  45. package/dist/backend/llvm/common.js.map +1 -1
  46. package/dist/backend/llvm/constant-callbacks.d.ts +27 -0
  47. package/dist/backend/llvm/constant-callbacks.js +450 -0
  48. package/dist/backend/llvm/constant-callbacks.js.map +1 -0
  49. package/dist/backend/llvm/constant-callbacks.test.d.ts +1 -0
  50. package/dist/backend/llvm/constant-callbacks.test.js +261 -0
  51. package/dist/backend/llvm/constant-callbacks.test.js.map +1 -0
  52. package/dist/backend/llvm/dense-array-access.d.ts +29 -0
  53. package/dist/backend/llvm/dense-array-access.js +335 -0
  54. package/dist/backend/llvm/dense-array-access.js.map +1 -0
  55. package/dist/backend/llvm/dense-array-access.test.d.ts +1 -0
  56. package/dist/backend/llvm/dense-array-access.test.js +149 -0
  57. package/dist/backend/llvm/dense-array-access.test.js.map +1 -0
  58. package/dist/backend/llvm/dyn.d.ts +35 -1
  59. package/dist/backend/llvm/dyn.js +196 -35
  60. package/dist/backend/llvm/dyn.js.map +1 -1
  61. package/dist/backend/llvm/emitter.d.ts +143 -4
  62. package/dist/backend/llvm/emitter.js +882 -136
  63. package/dist/backend/llvm/emitter.js.map +1 -1
  64. package/dist/backend/llvm/emitter.test.js +82 -14
  65. package/dist/backend/llvm/emitter.test.js.map +1 -1
  66. package/dist/backend/llvm/expr-async.js +58 -4
  67. package/dist/backend/llvm/expr-async.js.map +1 -1
  68. package/dist/backend/llvm/expr-async.test.d.ts +1 -0
  69. package/dist/backend/llvm/expr-async.test.js +62 -0
  70. package/dist/backend/llvm/expr-async.test.js.map +1 -0
  71. package/dist/backend/llvm/expr-bytes.js +11 -0
  72. package/dist/backend/llvm/expr-bytes.js.map +1 -1
  73. package/dist/backend/llvm/expr-calls.js +151 -25
  74. package/dist/backend/llvm/expr-calls.js.map +1 -1
  75. package/dist/backend/llvm/expr-containers.js +37 -14
  76. package/dist/backend/llvm/expr-containers.js.map +1 -1
  77. package/dist/backend/llvm/expr-control.js +92 -2
  78. package/dist/backend/llvm/expr-control.js.map +1 -1
  79. package/dist/backend/llvm/expr-dispatch.js +2 -0
  80. package/dist/backend/llvm/expr-dispatch.js.map +1 -1
  81. package/dist/backend/llvm/expr-dynamic.js +210 -28
  82. package/dist/backend/llvm/expr-dynamic.js.map +1 -1
  83. package/dist/backend/llvm/expr-primitives.d.ts +1 -1
  84. package/dist/backend/llvm/expr-primitives.js +58 -23
  85. package/dist/backend/llvm/expr-primitives.js.map +1 -1
  86. package/dist/backend/llvm/expr-primitives.test.js +1 -1
  87. package/dist/backend/llvm/expr-records.js +3 -3
  88. package/dist/backend/llvm/expr-records.js.map +1 -1
  89. package/dist/backend/llvm/expr-stream-bridges.js +59 -2
  90. package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
  91. package/dist/backend/llvm/expr-stream-bridges.test.js +1 -1
  92. package/dist/backend/llvm/integer-remainder.d.ts +18 -0
  93. package/dist/backend/llvm/integer-remainder.js +117 -0
  94. package/dist/backend/llvm/integer-remainder.js.map +1 -0
  95. package/dist/backend/llvm/layouts.d.ts +2 -1
  96. package/dist/backend/llvm/layouts.js +4 -3
  97. package/dist/backend/llvm/layouts.js.map +1 -1
  98. package/dist/backend/llvm/lazy-captures.d.ts +16 -0
  99. package/dist/backend/llvm/lazy-captures.js +96 -0
  100. package/dist/backend/llvm/lazy-captures.js.map +1 -0
  101. package/dist/backend/llvm/lazy-captures.test.d.ts +1 -0
  102. package/dist/backend/llvm/lazy-captures.test.js +78 -0
  103. package/dist/backend/llvm/lazy-captures.test.js.map +1 -0
  104. package/dist/backend/llvm/lib-abi.js +7 -0
  105. package/dist/backend/llvm/lib-abi.js.map +1 -1
  106. package/dist/backend/llvm/lib-dispatch.js +7 -22
  107. package/dist/backend/llvm/lib-dispatch.js.map +1 -1
  108. package/dist/backend/llvm/lib-filesystem.js +3 -4
  109. package/dist/backend/llvm/lib-filesystem.js.map +1 -1
  110. package/dist/backend/llvm/lib-shared.js +15 -0
  111. package/dist/backend/llvm/lib-shared.js.map +1 -1
  112. package/dist/backend/llvm/local-array-reads.d.ts +13 -3
  113. package/dist/backend/llvm/local-array-reads.js +159 -77
  114. package/dist/backend/llvm/local-array-reads.js.map +1 -1
  115. package/dist/backend/llvm/local-array-reads.test.js +105 -4
  116. package/dist/backend/llvm/local-array-reads.test.js.map +1 -1
  117. package/dist/backend/llvm/local-union-storage.d.ts +5 -1
  118. package/dist/backend/llvm/local-union-storage.js +48 -6
  119. package/dist/backend/llvm/local-union-storage.js.map +1 -1
  120. package/dist/backend/llvm/local-union-storage.test.js +91 -4
  121. package/dist/backend/llvm/local-union-storage.test.js.map +1 -1
  122. package/dist/backend/llvm/loop-array-borrows.d.ts +11 -0
  123. package/dist/backend/llvm/loop-array-borrows.js +70 -0
  124. package/dist/backend/llvm/loop-array-borrows.js.map +1 -0
  125. package/dist/backend/llvm/loop-array-borrows.test.d.ts +1 -0
  126. package/dist/backend/llvm/loop-array-borrows.test.js +145 -0
  127. package/dist/backend/llvm/loop-array-borrows.test.js.map +1 -0
  128. package/dist/backend/llvm/map-read-lifetimes.d.ts +5 -0
  129. package/dist/backend/llvm/map-read-lifetimes.js +20 -5
  130. package/dist/backend/llvm/map-read-lifetimes.js.map +1 -1
  131. package/dist/backend/llvm/map-read-lifetimes.test.js +1 -1
  132. package/dist/backend/llvm/nullable-fields.d.ts +28 -0
  133. package/dist/backend/llvm/nullable-fields.js +45 -0
  134. package/dist/backend/llvm/nullable-fields.js.map +1 -0
  135. package/dist/backend/llvm/nullable-fields.test.d.ts +1 -0
  136. package/dist/backend/llvm/nullable-fields.test.js +48 -0
  137. package/dist/backend/llvm/nullable-fields.test.js.map +1 -0
  138. package/dist/backend/llvm/reference-effects.d.ts +2 -1
  139. package/dist/backend/llvm/reference-effects.js +7 -0
  140. package/dist/backend/llvm/reference-effects.js.map +1 -1
  141. package/dist/backend/llvm/serialized-ir-emission.test.d.ts +1 -0
  142. package/dist/backend/llvm/serialized-ir-emission.test.js +52 -0
  143. package/dist/backend/llvm/serialized-ir-emission.test.js.map +1 -0
  144. package/dist/backend/llvm/shapes.d.ts +73 -3
  145. package/dist/backend/llvm/shapes.js +281 -34
  146. package/dist/backend/llvm/shapes.js.map +1 -1
  147. package/dist/backend/llvm/shapes.test.js +114 -5
  148. package/dist/backend/llvm/shapes.test.js.map +1 -1
  149. package/dist/backend/llvm/split-loops.d.ts +48 -0
  150. package/dist/backend/llvm/split-loops.js +183 -0
  151. package/dist/backend/llvm/split-loops.js.map +1 -0
  152. package/dist/backend/llvm/split.js +0 -7
  153. package/dist/backend/llvm/split.js.map +1 -1
  154. package/dist/backend/llvm/split.test.js +66 -2
  155. package/dist/backend/llvm/split.test.js.map +1 -1
  156. package/dist/backend/llvm/stack-callbacks.d.ts +14 -0
  157. package/dist/backend/llvm/stack-callbacks.js +72 -0
  158. package/dist/backend/llvm/stack-callbacks.js.map +1 -0
  159. package/dist/backend/llvm/stack-captures.d.ts +27 -0
  160. package/dist/backend/llvm/stack-captures.js +152 -0
  161. package/dist/backend/llvm/stack-captures.js.map +1 -0
  162. package/dist/backend/llvm/stack-unions.test.js +38 -1
  163. package/dist/backend/llvm/stack-unions.test.js.map +1 -1
  164. package/dist/backend/llvm/string-construction.test.js +1 -1
  165. package/dist/backend/llvm/string-key-hash.d.ts +8 -0
  166. package/dist/backend/llvm/string-key-hash.js +59 -0
  167. package/dist/backend/llvm/string-key-hash.js.map +1 -0
  168. package/dist/backend/llvm/string-key-hash.test.d.ts +1 -0
  169. package/dist/backend/llvm/string-key-hash.test.js +34 -0
  170. package/dist/backend/llvm/string-key-hash.test.js.map +1 -0
  171. package/dist/backend/llvm/string-lifetimes.test.js +48 -7
  172. package/dist/backend/llvm/string-lifetimes.test.js.map +1 -1
  173. package/dist/backend/llvm/string-slices.d.ts +20 -0
  174. package/dist/backend/llvm/string-slices.js +87 -0
  175. package/dist/backend/llvm/string-slices.js.map +1 -0
  176. package/dist/backend/llvm/string-slices.test.d.ts +1 -0
  177. package/dist/backend/llvm/string-slices.test.js +98 -0
  178. package/dist/backend/llvm/string-slices.test.js.map +1 -0
  179. package/dist/backend/llvm/switch-dispatch.js +1 -1
  180. package/dist/backend/llvm/switch-dispatch.js.map +1 -1
  181. package/dist/backend/llvm/switch-dispatch.test.js +2 -2
  182. package/dist/backend/llvm/switch-dispatch.test.js.map +1 -1
  183. package/dist/backend/llvm/walkers.d.ts +4 -14
  184. package/dist/backend/llvm/walkers.js +53 -311
  185. package/dist/backend/llvm/walkers.js.map +1 -1
  186. package/dist/backend/may-throw.js +4 -1
  187. package/dist/backend/may-throw.js.map +1 -1
  188. package/dist/backend/may-throw.test.js +1 -1
  189. package/dist/backend/native/cache-warm.d.ts +2 -1
  190. package/dist/backend/native/cache-warm.js.map +1 -1
  191. package/dist/backend/native/contracts.d.ts +5 -2
  192. package/dist/backend/native/contracts.js.map +1 -1
  193. package/dist/backend/native/executable.js +12 -4
  194. package/dist/backend/native/executable.js.map +1 -1
  195. package/dist/backend/native/runtime-inputs.d.ts +1 -1
  196. package/dist/backend/native/runtime-inputs.js +3 -0
  197. package/dist/backend/native/runtime-inputs.js.map +1 -1
  198. package/dist/backend/native-codegen-core.d.ts +7 -0
  199. package/dist/backend/native-codegen-core.js +21 -0
  200. package/dist/backend/native-codegen-core.js.map +1 -1
  201. package/dist/backend/native-codegen.d.ts +9 -0
  202. package/dist/backend/native-codegen.js +38 -18
  203. package/dist/backend/native-codegen.js.map +1 -1
  204. package/dist/backend/native-codegen.test.js +70 -2
  205. package/dist/backend/native-codegen.test.js.map +1 -1
  206. package/dist/backend/native-link-info-core.d.ts +4 -2
  207. package/dist/backend/native-link-info-core.js.map +1 -1
  208. package/dist/backend/native-link-info.d.ts +2 -1
  209. package/dist/backend/native-link-info.js.map +1 -1
  210. package/dist/backend/native-link-info.test.js +2 -2
  211. package/dist/backend/native-link-info.test.js.map +1 -1
  212. package/dist/backend/native-tools.d.ts +6 -2
  213. package/dist/backend/native-tools.js +6 -5
  214. package/dist/backend/native-tools.js.map +1 -1
  215. package/dist/backend/optimization.d.ts +25 -0
  216. package/dist/backend/optimization.js +21 -0
  217. package/dist/backend/optimization.js.map +1 -0
  218. package/dist/backend/optimization.test.d.ts +1 -0
  219. package/dist/backend/optimization.test.js +24 -0
  220. package/dist/backend/optimization.test.js.map +1 -0
  221. package/dist/backend/runtime-abi.d.ts +2 -2
  222. package/dist/backend/runtime-abi.js +2 -2
  223. package/dist/backend/runtime-pack-core.d.ts +16 -4
  224. package/dist/backend/runtime-pack-core.js +31 -13
  225. package/dist/backend/runtime-pack-core.js.map +1 -1
  226. package/dist/backend/runtime-pack-native.d.ts +7 -3
  227. package/dist/backend/runtime-pack-native.js +0 -3
  228. package/dist/backend/runtime-pack-native.js.map +1 -1
  229. package/dist/backend/runtime-pack-targets.test.js +41 -4
  230. package/dist/backend/runtime-pack-targets.test.js.map +1 -1
  231. package/dist/backend/runtime-pack.d.ts +20 -2
  232. package/dist/backend/runtime-pack.js +23 -0
  233. package/dist/backend/runtime-pack.js.map +1 -1
  234. package/dist/backend/runtime-pack.test.js +125 -2
  235. package/dist/backend/runtime-pack.test.js.map +1 -1
  236. package/dist/backend/target-diagnostics.test.js +1 -1
  237. package/dist/backend/targets.d.ts +2 -1
  238. package/dist/backend/targets.js +1 -1
  239. package/dist/backend/targets.js.map +1 -1
  240. package/dist/cli/command.js +5 -4
  241. package/dist/cli/command.js.map +1 -1
  242. package/dist/cli/host.d.ts +2 -1
  243. package/dist/cli/usage.d.ts +1 -1
  244. package/dist/cli/usage.js +5 -2
  245. package/dist/cli/usage.js.map +1 -1
  246. package/dist/compile-types.d.ts +5 -2
  247. package/dist/diagnostics/diagnostic.js +10 -8
  248. package/dist/diagnostics/diagnostic.js.map +1 -1
  249. package/dist/executable/executable-cache.d.ts +3 -2
  250. package/dist/executable/executable-cache.js +8 -4
  251. package/dist/executable/executable-cache.js.map +1 -1
  252. package/dist/executable/executable-cache.test.js +35 -0
  253. package/dist/executable/executable-cache.test.js.map +1 -1
  254. package/dist/frontend/fork-target.d.ts +5 -0
  255. package/dist/frontend/fork-target.js +40 -8
  256. package/dist/frontend/fork-target.js.map +1 -1
  257. package/dist/frontend/fork-target.test.d.ts +1 -0
  258. package/dist/frontend/fork-target.test.js +59 -0
  259. package/dist/frontend/fork-target.test.js.map +1 -0
  260. package/dist/frontend/lowering/abstract-equality.js +5 -49
  261. package/dist/frontend/lowering/abstract-equality.js.map +1 -1
  262. package/dist/frontend/lowering/array-values.d.ts +23 -0
  263. package/dist/frontend/lowering/array-values.js +129 -0
  264. package/dist/frontend/lowering/array-values.js.map +1 -1
  265. package/dist/frontend/lowering/binding-analysis.js +2 -2
  266. package/dist/frontend/lowering/binding-analysis.js.map +1 -1
  267. package/dist/frontend/lowering/builtins/atomics.d.ts +2 -11
  268. package/dist/frontend/lowering/builtins/atomics.js +64 -28
  269. package/dist/frontend/lowering/builtins/atomics.js.map +1 -1
  270. package/dist/frontend/lowering/builtins/errors.js +17 -2
  271. package/dist/frontend/lowering/builtins/errors.js.map +1 -1
  272. package/dist/frontend/lowering/builtins/filesystem-calls.d.ts +3 -0
  273. package/dist/frontend/lowering/builtins/filesystem-calls.js +51 -4
  274. package/dist/frontend/lowering/builtins/filesystem-calls.js.map +1 -1
  275. package/dist/frontend/lowering/builtins/module-calls.js +6 -1
  276. package/dist/frontend/lowering/builtins/module-calls.js.map +1 -1
  277. package/dist/frontend/lowering/builtins/process.d.ts +6 -17
  278. package/dist/frontend/lowering/builtins/process.js +56 -29
  279. package/dist/frontend/lowering/builtins/process.js.map +1 -1
  280. package/dist/frontend/lowering/builtins/workers.d.ts +5 -0
  281. package/dist/frontend/lowering/builtins/workers.js +65 -0
  282. package/dist/frontend/lowering/builtins/workers.js.map +1 -0
  283. package/dist/frontend/lowering/caught-value-dispatch.d.ts +11 -0
  284. package/dist/frontend/lowering/caught-value-dispatch.js +87 -0
  285. package/dist/frontend/lowering/caught-value-dispatch.js.map +1 -0
  286. package/dist/frontend/lowering/class-assertions.d.ts +11 -0
  287. package/dist/frontend/lowering/class-assertions.js +104 -0
  288. package/dist/frontend/lowering/class-assertions.js.map +1 -0
  289. package/dist/frontend/lowering/class-callbacks.d.ts +3 -3
  290. package/dist/frontend/lowering/class-callbacks.js +18 -5
  291. package/dist/frontend/lowering/class-callbacks.js.map +1 -1
  292. package/dist/frontend/lowering/class-dynamic-dispatch.d.ts +1 -0
  293. package/dist/frontend/lowering/class-dynamic-dispatch.js +4 -1
  294. package/dist/frontend/lowering/class-dynamic-dispatch.js.map +1 -1
  295. package/dist/frontend/lowering/class-method-mutations.d.ts +5 -0
  296. package/dist/frontend/lowering/class-method-mutations.js +472 -0
  297. package/dist/frontend/lowering/class-method-mutations.js.map +1 -0
  298. package/dist/frontend/lowering/class-method-values.js +25 -5
  299. package/dist/frontend/lowering/class-method-values.js.map +1 -1
  300. package/dist/frontend/lowering/class-protocols.d.ts +17 -0
  301. package/dist/frontend/lowering/class-protocols.js +126 -0
  302. package/dist/frontend/lowering/class-protocols.js.map +1 -0
  303. package/dist/frontend/lowering/class-prototypes.js +8 -2
  304. package/dist/frontend/lowering/class-prototypes.js.map +1 -1
  305. package/dist/frontend/lowering/class-unions.d.ts +16 -0
  306. package/dist/frontend/lowering/class-unions.js +127 -0
  307. package/dist/frontend/lowering/class-unions.js.map +1 -0
  308. package/dist/frontend/lowering/coercions/builders.js +8 -1
  309. package/dist/frontend/lowering/coercions/builders.js.map +1 -1
  310. package/dist/frontend/lowering/coercions/function-adapters.js +2 -0
  311. package/dist/frontend/lowering/coercions/function-adapters.js.map +1 -1
  312. package/dist/frontend/lowering/coercions/identity.d.ts +7 -0
  313. package/dist/frontend/lowering/coercions/identity.js +26 -0
  314. package/dist/frontend/lowering/coercions/identity.js.map +1 -0
  315. package/dist/frontend/lowering/coercions/structural.js +9 -6
  316. package/dist/frontend/lowering/coercions/structural.js.map +1 -1
  317. package/dist/frontend/lowering/collection-narrowing.d.ts +6 -0
  318. package/dist/frontend/lowering/collection-narrowing.js +31 -0
  319. package/dist/frontend/lowering/collection-narrowing.js.map +1 -0
  320. package/dist/frontend/lowering/containers/array-callback-call.d.ts +7 -0
  321. package/dist/frontend/lowering/containers/array-callback-call.js +52 -0
  322. package/dist/frontend/lowering/containers/array-callback-call.js.map +1 -0
  323. package/dist/frontend/lowering/containers/array-callbacks.d.ts +8 -12
  324. package/dist/frontend/lowering/containers/array-callbacks.js +415 -136
  325. package/dist/frontend/lowering/containers/array-callbacks.js.map +1 -1
  326. package/dist/frontend/lowering/containers/array-concat.d.ts +7 -0
  327. package/dist/frontend/lowering/containers/array-concat.js +130 -0
  328. package/dist/frontend/lowering/containers/array-concat.js.map +1 -0
  329. package/dist/frontend/lowering/containers/array-construction.d.ts +2 -0
  330. package/dist/frontend/lowering/containers/array-construction.js +95 -3
  331. package/dist/frontend/lowering/containers/array-construction.js.map +1 -1
  332. package/dist/frontend/lowering/containers/array-methods.d.ts +1 -11
  333. package/dist/frontend/lowering/containers/array-methods.js +43 -132
  334. package/dist/frontend/lowering/containers/array-methods.js.map +1 -1
  335. package/dist/frontend/lowering/containers/bytes.js +12 -34
  336. package/dist/frontend/lowering/containers/bytes.js.map +1 -1
  337. package/dist/frontend/lowering/containers/collection-methods.js +10 -1
  338. package/dist/frontend/lowering/containers/collection-methods.js.map +1 -1
  339. package/dist/frontend/lowering/containers/indexed-objects.js +56 -70
  340. package/dist/frontend/lowering/containers/indexed-objects.js.map +1 -1
  341. package/dist/frontend/lowering/containers/string-and-regexp.js +22 -3
  342. package/dist/frontend/lowering/containers/string-and-regexp.js.map +1 -1
  343. package/dist/frontend/lowering/cycle-initialization.d.ts +41 -0
  344. package/dist/frontend/lowering/cycle-initialization.js +277 -0
  345. package/dist/frontend/lowering/cycle-initialization.js.map +1 -0
  346. package/dist/frontend/lowering/expressions/evaluation-safety.js +4 -0
  347. package/dist/frontend/lowering/expressions/evaluation-safety.js.map +1 -1
  348. package/dist/frontend/lowering/expressions/object-literals.js +140 -38
  349. package/dist/frontend/lowering/expressions/object-literals.js.map +1 -1
  350. package/dist/frontend/lowering/expressions/union-field-write.d.ts +4 -4
  351. package/dist/frontend/lowering/expressions/union-field-write.js +54 -44
  352. package/dist/frontend/lowering/expressions/union-field-write.js.map +1 -1
  353. package/dist/frontend/lowering/generic-functions.d.ts +2 -5
  354. package/dist/frontend/lowering/generic-functions.js +13 -10
  355. package/dist/frontend/lowering/generic-functions.js.map +1 -1
  356. package/dist/frontend/lowering/instantiation-path.d.ts +8 -0
  357. package/dist/frontend/lowering/instantiation-path.js +10 -0
  358. package/dist/frontend/lowering/instantiation-path.js.map +1 -0
  359. package/dist/frontend/lowering/instantiation-path.test.d.ts +1 -0
  360. package/dist/frontend/lowering/instantiation-path.test.js +23 -0
  361. package/dist/frontend/lowering/instantiation-path.test.js.map +1 -0
  362. package/dist/frontend/lowering/iterator-adapters.d.ts +20 -0
  363. package/dist/frontend/lowering/iterator-adapters.js +605 -0
  364. package/dist/frontend/lowering/iterator-adapters.js.map +1 -0
  365. package/dist/frontend/lowering/lower-array-sort.d.ts +1 -1
  366. package/dist/frontend/lowering/lower-array-sort.js +44 -5
  367. package/dist/frontend/lowering/lower-array-sort.js.map +1 -1
  368. package/dist/frontend/lowering/lower-array-sort.test.d.ts +1 -0
  369. package/dist/frontend/lowering/lower-array-sort.test.js +31 -0
  370. package/dist/frontend/lowering/lower-array-sort.test.js.map +1 -0
  371. package/dist/frontend/lowering/lower-assert.js +7 -0
  372. package/dist/frontend/lowering/lower-assert.js.map +1 -1
  373. package/dist/frontend/lowering/lower-builtin-values.js +12 -7
  374. package/dist/frontend/lowering/lower-builtin-values.js.map +1 -1
  375. package/dist/frontend/lowering/lower-calls.d.ts +3 -3
  376. package/dist/frontend/lowering/lower-calls.js +198 -124
  377. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  378. package/dist/frontend/lowering/lower-classes.d.ts +7 -0
  379. package/dist/frontend/lowering/lower-classes.js +166 -96
  380. package/dist/frontend/lowering/lower-classes.js.map +1 -1
  381. package/dist/frontend/lowering/lower-event-emitter.js +1 -1
  382. package/dist/frontend/lowering/lower-event-emitter.js.map +1 -1
  383. package/dist/frontend/lowering/lower-exprs.d.ts +9 -2
  384. package/dist/frontend/lowering/lower-exprs.js +421 -167
  385. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  386. package/dist/frontend/lowering/lower-functions.d.ts +1 -1
  387. package/dist/frontend/lowering/lower-functions.js +33 -20
  388. package/dist/frontend/lowering/lower-functions.js.map +1 -1
  389. package/dist/frontend/lowering/lower-inspect.js +26 -2
  390. package/dist/frontend/lowering/lower-inspect.js.map +1 -1
  391. package/dist/frontend/lowering/lower-island.js +1 -1
  392. package/dist/frontend/lowering/lower-island.js.map +1 -1
  393. package/dist/frontend/lowering/lower-modules.d.ts +1 -0
  394. package/dist/frontend/lowering/lower-modules.js +54 -11
  395. package/dist/frontend/lowering/lower-modules.js.map +1 -1
  396. package/dist/frontend/lowering/lower-stmts.js +59 -69
  397. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  398. package/dist/frontend/lowering/lower-stream.js +1 -1
  399. package/dist/frontend/lowering/lower-stream.js.map +1 -1
  400. package/dist/frontend/lowering/lowerer.d.ts +50 -1
  401. package/dist/frontend/lowering/lowerer.js +333 -26
  402. package/dist/frontend/lowering/lowerer.js.map +1 -1
  403. package/dist/frontend/lowering/native-ffi.js +3 -1
  404. package/dist/frontend/lowering/native-ffi.js.map +1 -1
  405. package/dist/frontend/lowering/runtime-optional-provenance.d.ts +15 -0
  406. package/dist/frontend/lowering/runtime-optional-provenance.js +40 -0
  407. package/dist/frontend/lowering/runtime-optional-provenance.js.map +1 -0
  408. package/dist/frontend/lowering/sanitize-class-types.test.js +2 -2
  409. package/dist/frontend/lowering/split-consumers.d.ts +7 -0
  410. package/dist/frontend/lowering/split-consumers.js +64 -0
  411. package/dist/frontend/lowering/split-consumers.js.map +1 -0
  412. package/dist/frontend/lowering/strict-equality.d.ts +9 -0
  413. package/dist/frontend/lowering/strict-equality.js +116 -0
  414. package/dist/frontend/lowering/strict-equality.js.map +1 -0
  415. package/dist/frontend/lowering/surfaces.d.ts +3 -4
  416. package/dist/frontend/lowering/surfaces.js +38 -7
  417. package/dist/frontend/lowering/surfaces.js.map +1 -1
  418. package/dist/frontend/lowering/union-comparison.d.ts +5 -0
  419. package/dist/frontend/lowering/union-comparison.js +47 -0
  420. package/dist/frontend/lowering/union-comparison.js.map +1 -0
  421. package/dist/frontend/module-source-candidates.d.ts +10 -0
  422. package/dist/frontend/module-source-candidates.js +40 -0
  423. package/dist/frontend/module-source-candidates.js.map +1 -0
  424. package/dist/frontend/module-source-candidates.test.d.ts +1 -0
  425. package/dist/frontend/module-source-candidates.test.js +53 -0
  426. package/dist/frontend/module-source-candidates.test.js.map +1 -0
  427. package/dist/frontend/pipeline.js +2 -2
  428. package/dist/frontend/pipeline.js.map +1 -1
  429. package/dist/frontend/program-analysis.d.ts +2 -0
  430. package/dist/frontend/program-analysis.js +2 -0
  431. package/dist/frontend/program-analysis.js.map +1 -1
  432. package/dist/frontend/program.d.ts +38 -2
  433. package/dist/frontend/program.js +457 -88
  434. package/dist/frontend/program.js.map +1 -1
  435. package/dist/frontend/program.test.js +59 -6
  436. package/dist/frontend/program.test.js.map +1 -1
  437. package/dist/frontend/ts7/ast-node.js +6 -8
  438. package/dist/frontend/ts7/ast-node.js.map +1 -1
  439. package/dist/frontend/ts7/ast-node.test.js +42 -1
  440. package/dist/frontend/ts7/ast-node.test.js.map +1 -1
  441. package/dist/frontend/ts7/ast-wire.d.ts +4 -0
  442. package/dist/frontend/ts7/ast-wire.js +28 -0
  443. package/dist/frontend/ts7/ast-wire.js.map +1 -1
  444. package/dist/frontend/ts7/ast.d.ts +2 -1
  445. package/dist/frontend/ts7/ast.js +8 -4
  446. package/dist/frontend/ts7/ast.js.map +1 -1
  447. package/dist/frontend/ts7/checker.d.ts +8 -2
  448. package/dist/frontend/ts7/checker.js +41 -5
  449. package/dist/frontend/ts7/checker.js.map +1 -1
  450. package/dist/frontend/ts7/checker.test.js +42 -0
  451. package/dist/frontend/ts7/checker.test.js.map +1 -1
  452. package/dist/frontend/ts7/program-host.d.ts +7 -0
  453. package/dist/frontend/ts7/program-host.js +26 -2
  454. package/dist/frontend/ts7/program-host.js.map +1 -1
  455. package/dist/frontend/ts7/program-host.test.js +29 -0
  456. package/dist/frontend/ts7/program-host.test.js.map +1 -1
  457. package/dist/frontend/type-mapper.d.ts +4 -1
  458. package/dist/frontend/type-mapper.js +133 -16
  459. package/dist/frontend/type-mapper.js.map +1 -1
  460. package/dist/frontend/type-mapper.test.js +15 -3
  461. package/dist/frontend/type-mapper.test.js.map +1 -1
  462. package/dist/frontend/worker-target.d.ts +6 -0
  463. package/dist/frontend/worker-target.js +108 -0
  464. package/dist/frontend/worker-target.js.map +1 -0
  465. package/dist/frontend/worker-target.test.d.ts +1 -0
  466. package/dist/frontend/worker-target.test.js +71 -0
  467. package/dist/frontend/worker-target.test.js.map +1 -0
  468. package/dist/index.js +47 -14
  469. package/dist/index.js.map +1 -1
  470. package/dist/ir/analysis.d.ts +3 -1
  471. package/dist/ir/analysis.js +4 -0
  472. package/dist/ir/analysis.js.map +1 -1
  473. package/dist/ir/builtin-effects.js +12 -0
  474. package/dist/ir/builtin-effects.js.map +1 -1
  475. package/dist/ir/builtin-signatures.d.ts +1 -1
  476. package/dist/ir/builtin-signatures.js +15 -15
  477. package/dist/ir/builtin-signatures.js.map +1 -1
  478. package/dist/ir/constant-tables.test.js +1 -1
  479. package/dist/ir/initializer-bindings.test.js +1 -1
  480. package/dist/ir/ir.d.ts +85 -23
  481. package/dist/ir/ir.js +20 -6
  482. package/dist/ir/ir.js.map +1 -1
  483. package/dist/ir/numeric-call-specialization.d.ts +8 -0
  484. package/dist/ir/numeric-call-specialization.js +198 -0
  485. package/dist/ir/numeric-call-specialization.js.map +1 -0
  486. package/dist/ir/runtime-features.js +4 -3
  487. package/dist/ir/runtime-features.js.map +1 -1
  488. package/dist/ir/scalar-records.test.js +1 -1
  489. package/dist/ir/serialize.d.ts +1 -1
  490. package/dist/ir/serialize.js +3 -3
  491. package/dist/ir/serialize.js.map +1 -1
  492. package/dist/ir/traverse.js +8 -0
  493. package/dist/ir/traverse.js.map +1 -1
  494. package/dist/ir/validate.js +207 -43
  495. package/dist/ir/validate.js.map +1 -1
  496. package/dist/ir/validate.test.js +356 -8
  497. package/dist/ir/validate.test.js.map +1 -1
  498. package/dist/library/fence-eval.test.js +1 -1
  499. package/dist/library/int-infer.test.js +4 -4
  500. package/dist/library/ir-policy.js +2 -0
  501. package/dist/library/ir-policy.js.map +1 -1
  502. package/dist/library/library-cache.test.js +5 -5
  503. package/dist/library/library-profile.d.ts +2 -1
  504. package/dist/library/library-profile.js +4 -3
  505. package/dist/library/library-profile.js.map +1 -1
  506. package/dist/native/compiler.js +24 -11
  507. package/dist/native/compiler.js.map +1 -1
  508. package/dist/native/compiler.test.js +7 -3
  509. package/dist/native/compiler.test.js.map +1 -1
  510. package/dist/native/library.d.ts +2 -1
  511. package/dist/native/library.js.map +1 -1
  512. package/dist/native/prepare.js +29 -3
  513. package/dist/native/prepare.js.map +1 -1
  514. package/dist/native/sanitizer.d.ts +3 -2
  515. package/dist/native/sanitizer.js +3 -13
  516. package/dist/native/sanitizer.js.map +1 -1
  517. package/dist/native/sanitizer.test.js +1 -0
  518. package/dist/native/sanitizer.test.js.map +1 -1
  519. package/package.json +20 -20
  520. package/surface-manifest.json +37 -3
@@ -1,4 +1,5 @@
1
1
  import { emitLlvmLayouts } from "./layouts.js";
2
+ import { inlineAllocSupported } from "./alloc.js";
2
3
  import { llvmBytes as llBytes } from "../literals.js";
3
4
  import { InternalCompilerError } from "../../errors.js";
4
5
  /** Lower typed IR to LLVM, using the runtime's C ABI.
@@ -19,15 +20,18 @@ import { InternalCompilerError } from "../../errors.js";
19
20
  * compressed source tables that the runtime inflates on demand.
20
21
  */
21
22
  import { deflateRawSync } from "node:zlib";
22
- import { endsWithJump, matchStringSelfConcat, streamTypedRefEligible } from "../../ir/analysis.js";
23
+ import { endsWithJump, matchStringSelfConcat, streamTypedRefEligible, undefinedArmTag, } from "../../ir/analysis.js";
23
24
  import { emitLibraryIdentityLines } from "../library-identity-markers.js";
24
- import { CAUGHT, ffiCallbackType, isDynTypedRefType, isFfiContextParam, isRefCounted, isUnitType, POINTER_KINDS, RUNTIME_EMITTER_CLASS, RUNTIME_ERROR_CLASSES, RUNTIME_STREAM_CLASSES, typeKey, VOID, } from "../../ir/ir.js";
25
+ import { CAUGHT, ffiCallbackType, isDynTypedRefType, isFfiContextParam, isRefCounted, isUnitType, POINTER_KINDS, RUNTIME_EMITTER_CLASS, RUNTIME_ERROR_CLASSES, RUNTIME_STREAM_CLASSES, typeEquals, typeKey, STRING, VOID, } from "../../ir/ir.js";
25
26
  import { moduleRuntimeFeatures, moduleEmbedsBuiltin, moduleEmbedsCompressedNpm, NPM_COMPRESS_MIN, } from "../../ir/runtime-features.js";
26
27
  import { matchIntegerArrayForLoop, matchIntegerBytesForLoop, matchIntegerCountedForLoop, } from "../../ir/integer-loops.js";
27
28
  import { emitCountedLoopLimit } from "./counted-loops.js";
28
29
  import { emitLiteralSwitch } from "./switch-dispatch.js";
29
30
  import { findBytesBounds } from "./bytes-bounds.js";
31
+ import { emitByteWindowGuard, findInitializedByteLoopBindings, matchByteWindow, } from "./byte-windows.js";
32
+ import { canDeferSplitPiece, emitSplitSpanNext, materializeSplitPiece, emitSplitCursor, emitSplitNext, emitSplitScratch, emitSplitSnapshot, findPrivateSplitLocals, loadSplitSnapshot, storeSplitSnapshot, stringSplit, } from "./split-loops.js";
30
33
  import { scalarizeNumericRecords } from "../../ir/scalar-records.js";
34
+ import { specializeNumericCalls } from "../../ir/numeric-call-specialization.js";
31
35
  import { everyStmtList } from "../../ir/traverse.js";
32
36
  import { analyzeIntegerRanges } from "../../ir/integer-ranges.js";
33
37
  import { findIntegerViews } from "./integer-views.js";
@@ -41,13 +45,21 @@ import { analyzeCallLifetimes } from "./call-lifetimes.js";
41
45
  import { canStackUnion, emitStackUnion, findLocalStackUnions } from "./stack-unions.js";
42
46
  import { allocateLocalUnion, findLocalUnionStorage, storeLocalUnion, } from "./local-union-storage.js";
43
47
  import { BlockBuilder } from "./blocks.js";
44
- import { emitLocalArrayRead, findLocalArrayReads, findCallArrayReads, OptionalArrayReads, } from "./local-array-reads.js";
48
+ import { NullableRefFields } from "./nullable-fields.js";
49
+ import { emitLocalArrayRead, emitProjectedArrayRead, emitBorrowedArrayRead, findLocalArrayReads, findCallArrayReads, OptionalArrayReads, } from "./local-array-reads.js";
50
+ import { emitDenseArrayGet, emitDenseArraySet } from "./dense-array-access.js";
45
51
  import { emitStackMapRead, findMapReadLifetimes, matchMapRead, } from "./map-read-lifetimes.js";
46
52
  import { emitBorrowedFieldSequence } from "./borrowed-receivers.js";
47
53
  import { emitBorrowedInput } from "./borrowed-inputs.js";
48
54
  import { ReferenceEffects } from "./reference-effects.js";
49
55
  import { LlvmDebugInfo } from "./debug-info.js";
50
- import { f64Lit, ffiNativeTypeLl, ffiNativeParamLl, ffiNativeReturnLl, llvmCommentText, } from "./common.js";
56
+ import { StackCallbacks } from "./stack-callbacks.js";
57
+ import { findLoopArrayBorrows } from "./loop-array-borrows.js";
58
+ import { AmbientReceiverReaders, findConstantCallbacks } from "./constant-callbacks.js";
59
+ import { findScalarStringSlices, emitStringSliceSnapshot, } from "./string-slices.js";
60
+ import { LazyCaptures } from "./lazy-captures.js";
61
+ import { StackCaptures } from "./stack-captures.js";
62
+ import { ABSENT_FIELD_PAYLOAD, emitFieldAbsentTest, f64Lit, ffiNativeTypeLl, ffiNativeParamLl, ffiNativeReturnLl, llvmCommentText, } from "./common.js";
51
63
  import { ffiExtendsNarrowIntegers } from "../targets.js";
52
64
  import { emitLiteralExpr, emitOperatorExpr, emitStringExpr, emitContainerExpr, emitRecordExpr, } from "./expr-primitives.js";
53
65
  import { emitControlExpr } from "./expr-control.js";
@@ -67,11 +79,12 @@ import { emitChildProcessLibCall, emitAsyncContextLibCall, emitProcessLibCall, e
67
79
  import { emitStreamLibCall } from "./lib-stream.js";
68
80
  import { emitNetworkHttpLibCall } from "./lib-network.js";
69
81
  import { emitAssertInspectLibCall, emitIoLibCall, emitGenericLibCall, emitLibCall, } from "./lib-dispatch.js";
70
- import { buildClassGraph, classEnvironmentIndex, classFieldIndex, classStructSym, } from "./classes.js";
82
+ import { buildClassGraph, classMembershipIntervals, emitClassMembershipHelper, classEnvironmentIndex, classFieldIndex, classStructSym, } from "./classes.js";
71
83
  import { LlDyn } from "./dyn.js";
72
84
  import { LlvmUnsupportedError } from "./unsupported.js";
73
85
  import { LlWalkers } from "./walkers.js";
74
- import { arrNewCall, boxAccess, boxNewCall, computeTraced, elemAccess, FN_ATTRS, llFieldType, releaseSym, retainSym, traceArg, typedRefConstructor, vAdapters, } from "./shapes.js";
86
+ import { literalCapWord } from "./string-key-hash.js";
87
+ import { arrNewCall, boxAccess, boxNewCall, boxReleaseSym, computeTraced, elemAccess, emitInlineRcHelpers, FN_ATTRS, inlineRcDecls, llFieldType, releaseSym, retainSym, traceArg, typedRefConstructor, vAdapters, } from "./shapes.js";
75
88
  export { LlvmUnsupportedError } from "./unsupported.js";
76
89
  function ffiCallbackDummyLl(callback) {
77
90
  switch (callback.returns) {
@@ -100,11 +113,15 @@ function ffiCallbackDummyLl(callback) {
100
113
  }
101
114
  export function emitLlvmModule(mod, options = {}) {
102
115
  // Keep source storage intact for debugger inspection in dev builds.
103
- return new LlEmitter(options.debugSources === undefined ? scalarizeNumericRecords(mod) : mod, options).emit();
116
+ return new LlEmitter(options.debugSources === undefined
117
+ ? scalarizeNumericRecords(specializeNumericCalls(mod, mangleFunction))
118
+ : mod, options).emit();
104
119
  }
105
120
  /** Keep large translation units below the host's single-string limit. */
106
121
  export function emitLlvmModuleSource(mod, options = {}) {
107
- const parts = new LlEmitter(options.debugSources === undefined ? scalarizeNumericRecords(mod) : mod, options).emitParts();
122
+ const parts = new LlEmitter(options.debugSources === undefined
123
+ ? scalarizeNumericRecords(specializeNumericCalls(mod, mangleFunction))
124
+ : mod, options).emitParts();
108
125
  const length = parts.reduce((total, part) => total + part.length, Math.max(0, parts.length - 1));
109
126
  if (length <= 256 * 1024 * 1024)
110
127
  return parts.join("\n");
@@ -116,6 +133,9 @@ function llStrBytes(text) {
116
133
  export class LlEmitter {
117
134
  mod;
118
135
  localArrayReads = new Map();
136
+ loopArrayBorrows = new Set();
137
+ scalarStringSlices = new Map();
138
+ stringSlices = new Map();
119
139
  localStackUnions = new Map();
120
140
  localUnionStorageProofs = new Map();
121
141
  localUnionStorage = new Map();
@@ -167,6 +187,8 @@ export class LlEmitter {
167
187
  * immortal (rc == SIZE_MAX) static per (union, unit tag).
168
188
  * RC entry points and the collector skip immortals. */
169
189
  unitInstances = new Map();
190
+ /** unionId → symbol of the union's immortal ABSENT field-slot instance. */
191
+ absentInstances = new Map();
170
192
  immortalValues = new Set();
171
193
  /** Regex literal templates: "<flags>/<pattern>" → { symbol, interned
172
194
  * source/flags literal refs } — one immortal ScrRegex template per distinct
@@ -200,13 +222,31 @@ export class LlEmitter {
200
222
  needsBadTag = false;
201
223
  needsBadKey = false;
202
224
  needsRetainBox = false;
225
+ /** Inline RC fast-path helpers requested by call sites (shapes.ts); null
226
+ * when inline RC is off. */
227
+ rcHelpers;
203
228
  fnByName = new Map();
229
+ /** Emitted LLVM text of every IR function body, by function name: the
230
+ * fiberless-async analysis scans it for suspension entry points. */
231
+ fnDefText = new Map();
204
232
  referenceEffects;
205
233
  optionalArrayReads;
206
234
  callArrayReads = new Map();
207
235
  mapReadLifetimes = { locals: new Map(), arguments: new Map() };
208
236
  callLifetimes;
237
+ constantCallbacks;
238
+ currentConstantCallbacks = new Map();
239
+ stackCallbacks;
240
+ receiverReaders;
241
+ stackCaptures;
242
+ lazyCaptures;
243
+ /** Unchanged captured parameters of the current function: each maps to
244
+ * the slot caching its box, NULL until the first closure needs it. */
245
+ lazyCaptureBoxes = new Map();
246
+ lazyStackBoxes = new Map();
209
247
  borrowedParameters = new Set();
248
+ /** Parameters proven projection-only: callers may pass stack boxes. */
249
+ projectedParameters = new Set();
210
250
  stableCallBindings = new Set();
211
251
  /** Manifest-bound native imports, used by ffiCall emission. */
212
252
  ffiByName = new Map();
@@ -246,6 +286,7 @@ export class LlEmitter {
246
286
  liveDynUnionRefAdapters = new Map();
247
287
  dynPromiseAdapters = new Map();
248
288
  unionsById = new Map();
289
+ nullableFields;
249
290
  recordsById = new Map();
250
291
  recordCloneShapes = new Set();
251
292
  tracedShapes;
@@ -288,6 +329,10 @@ export class LlEmitter {
288
329
  * for blocks and switches. */
289
330
  jumpTargets = [];
290
331
  currentLocals = new Map();
332
+ privateSplitLocals = new Map();
333
+ splitSpans = new Map();
334
+ storedSplits = new Map();
335
+ streamingSplitsEnabled = false;
291
336
  initializerBindings;
292
337
  numericLocals = new Map();
293
338
  captureIds = new Set();
@@ -295,6 +340,8 @@ export class LlEmitter {
295
340
  integerLoopBindings = new Map();
296
341
  countedLoopsEnabled = false;
297
342
  integerRanges = new Map();
343
+ byteWindowsEnabled = true;
344
+ byteWindowEntries = new Map();
298
345
  bytesBounds = new Set();
299
346
  integerViews = new Map();
300
347
  /** Enclosing try-with-FINALLY regions, innermost last: a `return`
@@ -345,6 +392,7 @@ export class LlEmitter {
345
392
  this.wasi = options.wasi === true;
346
393
  this.emitLibraryIdentity = options.emitLibraryIdentity !== false;
347
394
  this.runtimeAbiMarker = options.runtimeAbiMarker === true;
395
+ this.rcHelpers = options.inlineRc === true ? new Set() : null;
348
396
  // ScrCycHdr.color stays 12 bytes behind a wasm32 object and 16 bytes
349
397
  // behind a 64-bit object. The wasm32 header pads before color so its
350
398
  // payload remains double-aligned without changing this ABI offset.
@@ -359,7 +407,10 @@ export class LlEmitter {
359
407
  }
360
408
  const mt = computeMayThrow(mod);
361
409
  this.mayThrow = mt.fns;
362
- this.indirectMayThrow = mt.indirect;
410
+ if (mod.workers)
411
+ for (const fn of mod.functions)
412
+ this.mayThrow.add(fn.name);
413
+ this.indirectMayThrow = mt.indirect || mod.workers === true;
363
414
  for (const cls of mod.classes ?? []) {
364
415
  for (const m of cls.methods ?? []) {
365
416
  if (this.mayThrow.has(`%${cls.name}.${m}`))
@@ -368,9 +419,15 @@ export class LlEmitter {
368
419
  }
369
420
  for (const u of mod.unions ?? [])
370
421
  this.unionsById.set(u.id, u);
422
+ this.nullableFields = new NullableRefFields(mod.classes ?? [], this.unionsById);
371
423
  this.optionalArrayReads = new OptionalArrayReads(this.fnByName, this.unionsById);
372
424
  this.referenceEffects = new ReferenceEffects(this.fnByName, (call) => this.optionalArrayReads.get(call) !== null);
373
- this.callLifetimes = analyzeCallLifetimes(this.fnByName);
425
+ this.callLifetimes = analyzeCallLifetimes(this.fnByName, (className, field) => this.nullableFields.get(className, field) !== null);
426
+ this.constantCallbacks = findConstantCallbacks(mod, this.callLifetimes);
427
+ this.stackCallbacks = new StackCallbacks(this.fnByName);
428
+ this.receiverReaders = new AmbientReceiverReaders(this.fnByName);
429
+ this.stackCaptures = new StackCaptures(this.fnByName, this.callLifetimes, this.stackCallbacks);
430
+ this.lazyCaptures = new LazyCaptures(this.fnByName);
374
431
  for (const r of mod.records ?? [])
375
432
  this.recordsById.set(r.id, r);
376
433
  const traced = computeTraced(mod);
@@ -383,6 +440,17 @@ export class LlEmitter {
383
440
  tracedUnions: this.tracedUnions,
384
441
  recordsById: this.recordsById,
385
442
  recordCloneShapes: this.recordCloneShapes,
443
+ objectAudit: options.objectAudit !== false,
444
+ inlineAlloc: inlineAllocSupported({
445
+ pointerBits: options.pointerBits,
446
+ wasi: options.wasi,
447
+ targetTriple: options.targetTriple,
448
+ // Worker programs (SCR_WORKERS) make scr_cyc_live and
449
+ // scr_weak_dispose_hook thread-local and compile the allocator out,
450
+ // exactly like thread-instanced libraries.
451
+ threadInstances: mod.lib?.threadInstances === true || mod.workers === true,
452
+ }),
453
+ rcHelpers: this.rcHelpers,
386
454
  unionsById: this.unionsById,
387
455
  declare: (decl) => this.declare(decl),
388
456
  needOom: () => this.needOom(),
@@ -390,14 +458,16 @@ export class LlEmitter {
390
458
  internLiteral: (text) => this.internLiteral(text),
391
459
  cstr: (text) => this.cstr(text),
392
460
  unitInstanceRef: (unionId, tag) => this.unitInstanceRef(unionId, tag),
461
+ absentInstanceRef: (unionId) => this.absentInstanceRef(unionId),
393
462
  liveDynRefAdapter: (type) => this.liveDynRefAdapter(type),
394
463
  liveDynUnionRefAdapter: (type) => this.liveDynUnionRefAdapter(type),
395
464
  dynPromiseAdapter: (type) => this.dynPromiseAdapter(type),
396
465
  isErrorClass: (name) => this.classMeta.get(name)?.root.def.name === "%Error",
397
466
  classSubtypes: (name) => {
398
467
  const target = this.classMetaOf(name);
468
+ const intervals = classMembershipIntervals(this.classMeta, target.def.name);
399
469
  return [...this.classMeta.values()]
400
- .filter((meta) => target.pre <= meta.pre && meta.pre <= target.post)
470
+ .filter((meta) => intervals.some((range) => range.pre <= meta.pre && meta.pre <= range.post))
401
471
  .map((meta) => meta.def.name);
402
472
  },
403
473
  };
@@ -507,7 +577,7 @@ export class LlEmitter {
507
577
  if (adapter.global !== null)
508
578
  globals.push(`@${adapter.global} = internal thread_local global ptr null`);
509
579
  if (adapter.table !== null) {
510
- globals.push(`@${adapter.table} = internal global %ScrFfiTable zeroinitializer`);
580
+ globals.push(`@${adapter.table} = internal ${this.mod.workers ? "thread_local " : ""}global %ScrFfiTable zeroinitializer`);
511
581
  }
512
582
  const params = cb.params.flatMap((param, i) => {
513
583
  if (isFfiContextParam(param))
@@ -765,8 +835,11 @@ export class LlEmitter {
765
835
  // Function bodies first (the literal/unit/fn-value tables fill as they
766
836
  // emit), then the file assembles around them — the runtime ABI’s order.
767
837
  const fnDefs = [];
768
- for (const fn of this.mod.functions)
769
- fnDefs.push(this.emitFunction(fn));
838
+ for (const fn of this.mod.functions) {
839
+ const def = this.emitFunction(fn);
840
+ fnDefs.push(def);
841
+ this.fnDefText.set(fn.name, def);
842
+ }
770
843
  const errorViews = [];
771
844
  for (const property of ["name", "message"])
772
845
  if (this.mod.functions.some((fn) => fn.name === `%error.${property}.read`)) {
@@ -785,7 +858,7 @@ export class LlEmitter {
785
858
  this.declare(`declare void @scr_error_register_dyn(ptr, ptr)`);
786
859
  errorViews.push(` call void @scr_error_register_dyn(ptr @${mangleVtInstance(meta.def.name)}, ptr @${name})`);
787
860
  }
788
- const layouts = emitLlvmLayouts(this.shapeHost, this.mod, this.classMeta, this.classObjs, this.fnByName, (t) => this.llType(t));
861
+ const layouts = emitLlvmLayouts(this.shapeHost, this.mod, this.classMeta, this.classObjs, this.fnByName, (t) => this.llType(t), this.nullableFields);
789
862
  const shapes = layouts.records;
790
863
  const classShapes = layouts.classes;
791
864
  const classObjDefs = layouts.classObjects;
@@ -953,7 +1026,7 @@ export class LlEmitter {
953
1026
  // with neither keep the atexit path, so their listener timing is
954
1027
  // unchanged.
955
1028
  const hasRefGlobals = globals.some((g) => isRefCounted(g.type)) || fnValueProps.length > 0;
956
- const inlineExitListeners = usesEvents && (hasRefGlobals || this.ffiHasRetainedCallback);
1029
+ const inlineExitListeners = usesEvents && (this.mod.workers || hasRefGlobals || this.ffiHasRetainedCallback);
957
1030
  this.declare(`declare i32 @scr_exit_code_hint_get()`);
958
1031
  if (inlineExitListeners) {
959
1032
  this.declare(`declare void @scr_run_exit_listeners(double)`);
@@ -1081,7 +1154,10 @@ export class LlEmitter {
1081
1154
  }
1082
1155
  // Helpers assemble BEFORE the declaration table flushes (they add
1083
1156
  // write/abort declarations).
1084
- const helpers = this.helperDefs();
1157
+ const helpers = [
1158
+ ...this.helperDefs(),
1159
+ ...emitClassMembershipHelper(this.classMeta, this.sizeType),
1160
+ ];
1085
1161
  if (this.constantNumericTables.size > 0)
1086
1162
  this.declare(`declare double @scr_arr_get_number(ptr, double)`);
1087
1163
  const out = [
@@ -1097,13 +1173,19 @@ export class LlEmitter {
1097
1173
  `%ScrLogArg = type { i32, i64 }`,
1098
1174
  `%ScrVt = type { ${this.sizeType}, ${this.sizeType}, ptr }`,
1099
1175
  `%ScrUnion = type { ${this.sizeType}, i32, ptr, ptr, ptr, i64 }`,
1100
- `%ScrClosure = type { ${this.sizeType}, ptr, ${this.sizeType}, ptr, i32 }`,
1176
+ `%ScrClosure = type { ${this.sizeType}, ptr, ${this.sizeType}, ptr, i32, i32, ptr }`,
1101
1177
  `%ScrFfiTable = type { ptr, ${this.sizeType}, ${this.sizeType}, ptr, i8, ptr, ptr, ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, ptr, ptr }`,
1102
- `%ScrRegex = type { ${this.sizeType}, ptr, ptr, ptr, double, ptr }`,
1178
+ `%ScrRegex = type { ${this.sizeType}, ptr, ptr, ptr, double, ptr, ptr }`,
1103
1179
  // ScrArr mirrors scr_runtime.h field-for-field. Live dynamic stream
1104
1180
  // commits swap its mutable dense, sparse, presence, and property
1105
1181
  // storage while preserving the target object's identity.
1106
1182
  `%ScrArr = type { ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, i32, ptr, ptr, ptr, ptr, ptr, ptr, ${this.sizeType}, ${this.sizeType}, ptr, ${this.sizeType}, ${this.sizeType}, ptr }`,
1183
+ // The ScrMap prefix { rc, key_kind, val_kind, val_retain, val_release,
1184
+ // val_trace, key_retain, key_release, key_trace } — the inline RC
1185
+ // fast paths test the two trace slots (shapes.ts).
1186
+ ...(this.rcHelpers === null
1187
+ ? []
1188
+ : [`%ScrMapRc = type { ${this.sizeType}, i32, i32, ptr, ptr, ptr, ptr, ptr, ptr }`]),
1107
1189
  // The runtime error prefix { rc, vt, name, message, code, cause } and the
1108
1190
  // class-object shape { rc, pre, post, ctor, name } — field reads on
1109
1191
  // builtin errors and classval loads GEP through these.
@@ -1121,11 +1203,11 @@ export class LlEmitter {
1121
1203
  // scr_runtime.h's natural alignment: kind at 8, f64 at 16, b at 24,
1122
1204
  // payload at 32).
1123
1205
  `%ScrCaught = type { ${this.sizeType}, i32, double, i8, ptr, ptr, ptr, ptr }`,
1124
- // ScrBytes { rc, len, elem(i32+pad), data, backing }. Indexed
1206
+ // ScrBytes { rc, len, elem(i32+pad), data, backing, brands, shared }. Indexed
1125
1207
  // typed-array access GEPs through this directly: the IR type already
1126
1208
  // fixes elem, so the hot path needs neither a runtime kind load nor
1127
1209
  // the generic scr_bytes_get/set call.
1128
- `%ScrBytes = type { ${this.sizeType}, ${this.sizeType}, i32, ptr, ptr, i8 }`,
1210
+ `%ScrBytes = type { ${this.sizeType}, ${this.sizeType}, i32, ptr, ptr, i8, i8, i8, ptr }`,
1129
1211
  // The capture box { rc, kind, obj_retain, obj_release, obj_trace,
1130
1212
  // slot } — TDZ reads peek the payload slot (offset 40) directly.
1131
1213
  `%ScrBox = type { ${this.sizeType}, i32, ptr, ptr, ptr, i64 }`,
@@ -1148,13 +1230,14 @@ export class LlEmitter {
1148
1230
  // objects compiled with -DSCR_THREAD_INSTANCES. Immutable interned
1149
1231
  // data (string literals, unit arms, template arrays, vtables) stays
1150
1232
  // shared.
1151
- const tl = this.mod.lib?.threadInstances === true ? "thread_local " : "";
1152
- out.push(``, `@scr_error_vts = external ${tl}global [${RUNTIME_ERROR_CLASSES.size} x %ScrVt]`, `declare void @scr_init()`, `declare void @scr_lib_init(i32, ptr)`, ...(this.runtimeAbiMarker && this.mod.lib === undefined
1233
+ const tl = this.mod.workers === true || this.mod.lib?.threadInstances === true ? "thread_local " : "";
1234
+ out.push(``, `@scr_error_vts = external ${tl}global [${RUNTIME_ERROR_CLASSES.size} x %ScrVt]`, `declare void @scr_init()`, `declare void @scr_lib_init(i32, ptr)`, ...(this.mod.workers ? [`declare void @scr_runtime_workers_v8()`] : []), ...(this.runtimeAbiMarker && this.mod.lib === undefined
1153
1235
  ? [`declare void @${RUNTIME_ABI_MARKER}()`]
1154
1236
  : []));
1155
1237
  for (const d of this.decls)
1156
1238
  out.push(d);
1157
1239
  out.push(``);
1240
+ const flushedDecls = new Set(this.decls);
1158
1241
  for (const table of this.constantNumericTables.values()) {
1159
1242
  const n = table.values.length;
1160
1243
  out.push(`@${table.symbol} = private constant [${n} x double] [${table.values.map((v) => `double ${f64Lit(v)}`).join(", ")}]`, `define internal double @${table.symbol}_get(ptr %a, double %i) alwaysinline #0 {`, `entry:`, ` %initialized = icmp ne ptr %a, null`, ` %nonnegative = fcmp oge double %i, ${f64Lit(0)}`, ` %below = fcmp olt double %i, ${f64Lit(n)}`, ` %range = and i1 %nonnegative, %below`, ` %safe = and i1 %initialized, %range`, ` br i1 %safe, label %convert, label %fallback`, `convert:`,
@@ -1165,8 +1248,11 @@ export class LlEmitter {
1165
1248
  for (const [text, lit] of this.literals) {
1166
1249
  // Immortal interned ScrStr: { rc = SIZE_MAX, len, cap = len, bytes\0 } —
1167
1250
  // the runtime ABI’s static table, retain/release skip rc == SIZE_MAX.
1251
+ // 64-bit (little-endian) targets split the capacity word: the high
1252
+ // half carries the precomputed Map key hash (SCR_STR_HASH_CACHE).
1253
+ const capWord = this.sizeType === "i64" ? literalCapWord(Buffer.from(text, "utf8")) : String(lit.len);
1168
1254
  out.push(`@${lit.sym} = internal global { ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, [${lit.len + 1} x i8] } ` +
1169
- `{ ${this.sizeType} -1, ${this.sizeType} ${lit.len}, ${this.sizeType} ${lit.len}, [${lit.len + 1} x i8] c"${llStrBytes(text)}" }`);
1255
+ `{ ${this.sizeType} -1, ${this.sizeType} ${lit.len}, ${this.sizeType} ${capWord}, [${lit.len + 1} x i8] c"${llStrBytes(text)}" }`);
1170
1256
  }
1171
1257
  if (this.literals.size > 0)
1172
1258
  out.push(``);
@@ -1179,11 +1265,23 @@ export class LlEmitter {
1179
1265
  }
1180
1266
  if (this.unitInstances.size > 0)
1181
1267
  out.push(``);
1268
+ for (const [unionId, sym] of this.absentInstances) {
1269
+ // One immortal ABSENT field-slot instance per undefined-armed union:
1270
+ // the undefined arm's tag with payload 1 (ordinary unit instances
1271
+ // carry 0). Every tag reader sees undefined; recordHas and the
1272
+ // field reads compare tag and payload, never the address, so the
1273
+ // per-object copies of separately compiled units agree.
1274
+ const tag = undefinedArmTag({ kind: "union", unionId }, this.unionsById);
1275
+ out.push(`@${sym} = internal global %ScrUnion { ${this.sizeType} -1, i32 ${tag}, ptr null, ptr null, ptr null, i64 ${ABSENT_FIELD_PAYLOAD} } ; ${unionId} absent field`);
1276
+ }
1277
+ if (this.absentInstances.size > 0)
1278
+ out.push(``);
1182
1279
  for (const [key, re] of this.regexInstances) {
1183
1280
  // One immortal ScrRegex per (pattern, flags) literal, pointing at
1184
- // the interned source/flags strings. The bc slot starts null (lazy
1185
- // compile, cached by the runtime) — a mutable global, not constant.
1186
- out.push(`@${re.sym} = internal ${tl}global %ScrRegex { ${this.sizeType} -1, ptr ${re.src}, ptr ${re.fl}, ptr null, double 0.0, ptr null } ; ${key.replace(/\n/g, "\\n")}`);
1281
+ // the interned source/flags strings. The bc and native-matcher slots
1282
+ // start null (lazy compile, cached by the runtime) — a mutable
1283
+ // global, not constant.
1284
+ out.push(`@${re.sym} = internal ${tl}global %ScrRegex { ${this.sizeType} -1, ptr ${re.src}, ptr ${re.fl}, ptr null, double 0.0, ptr null, ptr null } ; ${key.replace(/\n/g, "\\n")}`);
1187
1285
  }
1188
1286
  if (this.regexInstances.size > 0)
1189
1287
  out.push(``);
@@ -1306,7 +1404,7 @@ export class LlEmitter {
1306
1404
  // The runtime stream vtables' intervals (streamVtStampLines) — the
1307
1405
  // emitter story: instanceof and dynamic teardown dispatch through
1308
1406
  // them.
1309
- out.push(`@${iv.vt} = external global %ScrVt`);
1407
+ out.push(`@${iv.vt} = external ${tl}global %ScrVt`);
1310
1408
  stamps.push(` store ${this.sizeType} ${iv.pre}, ptr getelementptr inbounds (%ScrVt, ptr @${iv.vt}, i64 0, i32 0)`, ` store ${this.sizeType} ${iv.post}, ptr getelementptr inbounds (%ScrVt, ptr @${iv.vt}, i64 0, i32 1) ; ${iv.lib}`);
1311
1409
  }
1312
1410
  if (this.streamIntervals.length > 0)
@@ -1332,6 +1430,7 @@ export class LlEmitter {
1332
1430
  // symbols specified by the library IR instead.
1333
1431
  for (const line of this.emitLibDefs(globals, globalReleaseLines, stamps))
1334
1432
  out.push(line);
1433
+ out.push(...this.inlineRcTail(flushedDecls));
1335
1434
  out.push(`attributes #0 = { sanitize_address }`);
1336
1435
  if (this.wasi)
1337
1436
  out.push(`attributes #1 = { sanitize_address presplitcoroutine }`);
@@ -1343,7 +1442,7 @@ export class LlEmitter {
1343
1442
  out.push(this.debug.render());
1344
1443
  return out;
1345
1444
  }
1346
- out.push(`define i32 @${this.wasi ? "__main_argc_argv" : "main"}(i32 %argc, ptr %argv) ${FN_ATTRS} {`, `entry:`, ...(this.runtimeAbiMarker ? [` call void @${RUNTIME_ABI_MARKER}()`] : []), ` call void @scr_init()`, ...stamps,
1445
+ out.push(`define i32 @${this.mod.workers ? "sc_context_entry" : this.wasi ? "__main_argc_argv" : "main"}(i32 %argc, ptr %argv) ${FN_ATTRS} {`, `entry:`, ...(this.runtimeAbiMarker ? [` call void @${RUNTIME_ABI_MARKER}()`] : []), ...(this.mod.workers ? [` call void @scr_runtime_workers_v8()`] : []), ` call void @scr_init()`, ...stamps,
1347
1446
  // Event-surface programs (signal/exit listeners) fill the loop's
1348
1447
  // nullable event hooks before %main — scr_events.c links only when
1349
1448
  // this line is emitted (native/executable.ts gates on the same predicate).
@@ -1451,11 +1550,14 @@ export class LlEmitter {
1451
1550
  ? [` %test_exit = call i32 @scr_test_exit_code()`, ` ret i32 %test_exit`]
1452
1551
  : usesIsland
1453
1552
  ? [` %island_exit = call i32 @scr_island_exit_code()`, ` ret i32 %island_exit`]
1454
- : [` %native_exit = call i32 @scr_exit_code_hint_get()`, ` ret i32 %native_exit`]), `}`, ``,
1553
+ : [` %native_exit = call i32 @scr_exit_code_hint_get()`, ` ret i32 %native_exit`]), `}`, ``, ...this.inlineRcTail(flushedDecls),
1455
1554
  // sanitize_address is inert under the plain pipeline; the sanitized
1456
1555
  // lane's -fsanitize=address link activates instrumentation over the
1457
1556
  // emitted functions too (the runtime TUs get theirs from clang).
1458
1557
  `attributes #0 = { sanitize_address }`, ...(this.wasi ? [`attributes #1 = { sanitize_address presplitcoroutine }`] : []), ...(hasNoInlineRecordClone ? [`attributes #2 = { noinline sanitize_address }`] : []), ``);
1558
+ if (this.mod.workers) {
1559
+ out.push(`declare void @scr_context_cleanup()`, `declare i32 @scr_worker_argc()`, `declare ptr @scr_worker_argv()`, `define i32 @sc_worker_entry(i32 %root) ${FN_ATTRS} {`, `entry:`, ` %argc = call i32 @scr_worker_argc()`, ` %argv = call ptr @scr_worker_argv()`, ` %result = call i32 @sc_context_entry(i32 %argc, ptr %argv)`, ` ret i32 %result`, `}`, `define i32 @main(i32 %argc, ptr %argv) ${FN_ATTRS} {`, `entry:`, ` %result = call i32 @sc_context_entry(i32 %argc, ptr %argv)`, ` call void @scr_context_cleanup()`, ` ret i32 %result`, `}`);
1560
+ }
1459
1561
  out.push(...this.fieldAliasMetadata());
1460
1562
  if (this.debug !== null)
1461
1563
  out.push(this.debug.render());
@@ -1705,10 +1807,62 @@ export class LlEmitter {
1705
1807
  // emitted once with internal linkage, like the record retains.
1706
1808
  defs.push(`define internal ptr @sc_retain_box(ptr %b) ${FN_ATTRS} {`, `entry:`, ` %rc = load ${this.sizeType}, ptr %b`, ` %imm = icmp eq ${this.sizeType} %rc, -1`, ` br i1 %imm, label %done, label %inc`, `inc:`, ` %n = add ${this.sizeType} %rc, 1`, ` store ${this.sizeType} %n, ptr %b`, ` %colorp = getelementptr i8, ptr %b, ${this.sizeType} -${this.cycleColorOffset}`, ` store i32 0, ptr %colorp ; mark live`, ` br label %done`, `done:`, ` ret ptr %b`, `}`, ``);
1707
1809
  }
1810
+ // String comparisons enter through small internal wrappers that LLVM
1811
+ // inlines at each site: interned literals and shared values compare by
1812
+ // identity, and equality rejects different byte lengths, before any
1813
+ // runtime call (scr_str_eq/scr_str_cmp keep the complete semantics).
1814
+ if (this.decls.has(`declare zeroext i1 @scr_str_eq(ptr, ptr)`)) {
1815
+ defs.push(`define internal zeroext i1 @sc_str_eq(ptr %a, ptr %b) ${FN_ATTRS} {`, `entry:`, ` %same = icmp eq ptr %a, %b`, ` br i1 %same, label %yes, label %lengths`, `lengths:`, ` %alen.p = getelementptr inbounds %ScrStr, ptr %a, i32 0, i32 1`, ` %blen.p = getelementptr inbounds %ScrStr, ptr %b, i32 0, i32 1`, ` %alen = load ${this.sizeType}, ptr %alen.p`, ` %blen = load ${this.sizeType}, ptr %blen.p`, ` %samelen = icmp eq ${this.sizeType} %alen, %blen`, ` br i1 %samelen, label %bytes, label %no`, `bytes:`, ` %eq = call zeroext i1 @scr_str_eq(ptr %a, ptr %b)`, ` ret i1 %eq`, `yes:`, ` ret i1 true`, `no:`, ` ret i1 false`, `}`, ``);
1816
+ }
1817
+ // Ordering also decides inline when the first bytes differ: byte order
1818
+ // is the plain comparison's order, and the UTF-16 order whenever both
1819
+ // bytes are ASCII. Callers only test the sign of the result.
1820
+ for (const fn of ["scr_str_cmp", "scr_str_cmp_u16"]) {
1821
+ if (!this.decls.has(`declare i32 @${fn}(ptr, ptr)`))
1822
+ continue;
1823
+ this.declare("declare i64 @llvm.bswap.i64(i64)");
1824
+ const utf16 = fn === "scr_str_cmp_u16";
1825
+ defs.push(`define internal i32 @${fn.replace(/^scr_/, "sc_")}(ptr %a, ptr %b) ${FN_ATTRS} {`, `entry:`, ` %same = icmp eq ptr %a, %b`, ` br i1 %same, label %equal, label %lengths`, `lengths:`, ` %alen.p = getelementptr inbounds %ScrStr, ptr %a, i32 0, i32 1`, ` %blen.p = getelementptr inbounds %ScrStr, ptr %b, i32 0, i32 1`, ` %alen = load ${this.sizeType}, ptr %alen.p`, ` %blen = load ${this.sizeType}, ptr %blen.p`, ` %aempty = icmp eq ${this.sizeType} %alen, 0`, ` %bempty = icmp eq ${this.sizeType} %blen, 0`, ` %empty = or i1 %aempty, %bempty`, ` br i1 %empty, label %order, label %words`,
1826
+ // Both at least 8 bytes: the first differing byte of one word pair.
1827
+ // Byte-swapped little-endian words compare in byte order.
1828
+ `words:`, ` %along = icmp uge ${this.sizeType} %alen, 8`, ` %blong = icmp uge ${this.sizeType} %blen, 8`, ` %long = and i1 %along, %blong`, ` br i1 %long, label %word, label %first`, `word:`, ` %awp = getelementptr inbounds %ScrStr, ptr %a, i32 1`, ` %bwp = getelementptr inbounds %ScrStr, ptr %b, i32 1`, ` %aw = load i64, ptr %awp, align 1`, ` %bw = load i64, ptr %bwp, align 1`, ` %wdiffer = icmp ne i64 %aw, %bw`, ` br i1 %wdiffer, label %wdiff, label %wsame`, `wdiff:`, ...(utf16
1829
+ ? [
1830
+ ` %wbits = or i64 %aw, %bw`,
1831
+ ` %whigh = and i64 %wbits, -9187201950435737472`,
1832
+ ` %wascii = icmp eq i64 %whigh, 0`,
1833
+ ` br i1 %wascii, label %wordorder, label %order`,
1834
+ ]
1835
+ : [` br label %wordorder`]), `wordorder:`, ` %asw = call i64 @llvm.bswap.i64(i64 %aw)`, ` %bsw = call i64 @llvm.bswap.i64(i64 %bw)`, ` %wbelow = icmp ult i64 %asw, %bsw`, ` %wsign = select i1 %wbelow, i32 -1, i32 1`, ` ret i32 %wsign`,
1836
+ // Equal first words and one length of at most 16 bytes: the word
1837
+ // ending at that length covers the rest, and any difference lies
1838
+ // past the equal first word.
1839
+ `wsame:`, ` %samelen = icmp eq ${this.sizeType} %alen, %blen`, ` %short = icmp ule ${this.sizeType} %alen, 16`, ` %tailable = and i1 %samelen, %short`, ` br i1 %tailable, label %tail, label %order`, `tail:`, ` %toff = sub ${this.sizeType} %alen, 8`, ` %atp = getelementptr inbounds i8, ptr %awp, ${this.sizeType} %toff`, ` %btp = getelementptr inbounds i8, ptr %bwp, ${this.sizeType} %toff`, ` %at = load i64, ptr %atp, align 1`, ` %bt = load i64, ptr %btp, align 1`, ` %tdiffer = icmp ne i64 %at, %bt`, ` br i1 %tdiffer, label %tdiff, label %equal`, `tdiff:`, ...(utf16
1840
+ ? [
1841
+ ` %tbits = or i64 %at, %bt`,
1842
+ ` %thigh = and i64 %tbits, -9187201950435737472`,
1843
+ ` %tascii = icmp eq i64 %thigh, 0`,
1844
+ ` br i1 %tascii, label %tailorder, label %order`,
1845
+ ]
1846
+ : [` br label %tailorder`]), `tailorder:`, ` %ats = call i64 @llvm.bswap.i64(i64 %at)`, ` %bts = call i64 @llvm.bswap.i64(i64 %bt)`, ` %tbelow = icmp ult i64 %ats, %bts`, ` %tsign = select i1 %tbelow, i32 -1, i32 1`, ` ret i32 %tsign`, `first:`, ` %adata = getelementptr inbounds %ScrStr, ptr %a, i32 1`, ` %bdata = getelementptr inbounds %ScrStr, ptr %b, i32 1`, ` %x = load i8, ptr %adata`, ` %y = load i8, ptr %bdata`, ` %differ = icmp ne i8 %x, %y`, ...(utf16
1847
+ ? [
1848
+ ` %bits = or i8 %x, %y`,
1849
+ ` %ascii = icmp sge i8 %bits, 0`,
1850
+ ` %decide = and i1 %differ, %ascii`,
1851
+ ]
1852
+ : [` %decide = and i1 %differ, true`]), ` br i1 %decide, label %byte, label %order`, `byte:`, ` %below = icmp ult i8 %x, %y`, ` %sign = select i1 %below, i32 -1, i32 1`, ` ret i32 %sign`, `order:`, ` %c = call i32 @${fn}(ptr %a, ptr %b)`, ` ret i32 %c`, `equal:`, ` ret i32 0`, `}`, ``);
1853
+ }
1708
1854
  // The declarations these helpers added must land in the extern block,
1709
1855
  // which already flushed — append here instead (LLVM is order-free).
1710
1856
  return defs.length > 0 ? [...defs] : defs;
1711
1857
  }
1858
+ /** The inline RC helpers every call site requested, emitted last so
1859
+ * requests from main/library epilogues are included. Slow-path
1860
+ * declarations a late request added after the extern block flushed land
1861
+ * here too. */
1862
+ inlineRcTail(flushedDecls) {
1863
+ const late = inlineRcDecls(this.shapeHost).filter((d) => !flushedDecls.has(d));
1864
+ return [...late, ...emitInlineRcHelpers(this.shapeHost)];
1865
+ }
1712
1866
  /** Env-signature wrappers + interned immortal closures for declared
1713
1867
  * functions used as values (the runtime ABI’s sc_w_/sc_fc_ pair): every
1714
1868
  * mention of `f` yields the same pointer, so `f === f` holds. */
@@ -1723,7 +1877,7 @@ export class LlEmitter {
1723
1877
  // never the inner return type.
1724
1878
  const ret = fn.async === true || fn.generator !== undefined ? "ptr" : this.llType(fn.returnType);
1725
1879
  const call = `call ${ret} @${this.callTarget(name)}(${args})`;
1726
- out.push(`define internal ${ret} @${mangleWrapper(name)}(ptr %env${params.length ? ", " + params.join(", ") : ""}) ${FN_ATTRS} { ; ${name} as a value`, `entry:`, ret === "void" ? ` ${call}` : ` %r = ${call}`, ret === "void" ? ` ret void` : ` ret ${ret} %r`, `}`, `@${mangleFnClosure(name)} = internal global %ScrClosure { ${this.sizeType} -1, ptr @${mangleWrapper(name)}, ${this.sizeType} 0, ptr null, i32 ${(fn.generator ? 1 : 0) + (fn.async ? 2 : 0) + (fn.ownsPrototype ? 4 : 0)} }`, ``);
1880
+ out.push(`define internal ${ret} @${mangleWrapper(name)}(ptr %env${params.length ? ", " + params.join(", ") : ""}) ${FN_ATTRS} { ; ${name} as a value`, `entry:`, ret === "void" ? ` ${call}` : ` %r = ${call}`, ret === "void" ? ` ret void` : ` ret ${ret} %r`, `}`, `@${mangleFnClosure(name)} = internal ${this.mod.workers === true || this.mod.lib?.threadInstances === true ? "thread_local " : ""}global %ScrClosure { ${this.sizeType} -1, ptr @${mangleWrapper(name)}, ${this.sizeType} 0, ptr null, i32 ${(fn.generator ? 1 : 0) + (fn.async ? 2 : 0) + (fn.ownsPrototype ? 4 : 0)}, i32 0, ptr null }`, ``);
1727
1881
  }
1728
1882
  return out;
1729
1883
  }
@@ -1777,6 +1931,154 @@ export class LlEmitter {
1777
1931
  });
1778
1932
  return { definitions, pack, lifted, fieldTys, ret, tr, spawnParams, argPackLines };
1779
1933
  }
1934
+ /** Settles `promise` from a clean async body result `%r` (moved in). */
1935
+ asyncFulfillLines(ret, promise) {
1936
+ switch (ret.kind) {
1937
+ case "void":
1938
+ this.declare(`declare void @scr_promise_fulfill_void(ptr)`);
1939
+ return [` call void @scr_promise_fulfill_void(ptr ${promise})`];
1940
+ case "f64":
1941
+ case "date":
1942
+ this.declare(`declare void @scr_promise_fulfill_f64(ptr, double)`);
1943
+ return [` call void @scr_promise_fulfill_f64(ptr ${promise}, double %r)`];
1944
+ case "bool":
1945
+ this.declare(`declare void @scr_promise_fulfill_bool(ptr, i1 zeroext)`);
1946
+ return [` call void @scr_promise_fulfill_bool(ptr ${promise}, i1 %r)`];
1947
+ case "string":
1948
+ this.declare(`declare void @scr_promise_fulfill_str(ptr, ptr)`);
1949
+ return [` call void @scr_promise_fulfill_str(ptr ${promise}, ptr %r) ; moves in`];
1950
+ case "dyn":
1951
+ this.declare(`declare void @scr_promise_resolve_dyn(ptr, ptr)`);
1952
+ return [` call void @scr_promise_resolve_dyn(ptr ${promise}, ptr %r) ; moves in`];
1953
+ default: {
1954
+ const v = vAdapters(this.shapeHost, ret);
1955
+ this.declare(`declare void @scr_promise_fulfill_ref(ptr, ptr, ptr, ptr, ptr)`);
1956
+ return [
1957
+ ` call void @scr_promise_fulfill_ref(ptr ${promise}, ptr %r, ptr ${v.retain}, ptr ${v.release}, ptr ${traceArg(this.shapeHost, ret)})`,
1958
+ ];
1959
+ }
1960
+ }
1961
+ }
1962
+ /** Async functions whose calls can skip the fiber (scr_async_inline_*).
1963
+ * A body qualifies only when nothing it runs can park its execution
1964
+ * context: its emitted text, and the text of every module function it
1965
+ * mentions (transitively), contains no suspension entry point. Such a
1966
+ * body always completes before the call returns, which is exactly the
1967
+ * behavior of a fiber that never suspends. Excluded conservatively:
1968
+ * wasm32 (its coroutine lowering owns async bodies), programs embedding
1969
+ * the dynamic engine, module-evaluation bodies (their promise caches),
1970
+ * `dyn` results (resolution may adopt a thenable), and every program
1971
+ * that uses node:test subtests — `t.test()` runs a subtest inline and
1972
+ * awaits it on the CURRENT fiber even from synchronous code. Worker
1973
+ * programs keep fibers too: their fiber epilogue owns context
1974
+ * termination (a stopping worker rejects and observes the promise), which
1975
+ * the fiberless wrapper does not reproduce. A missed suspension still
1976
+ * cannot reorder silently: the runtime traps when a context parks while a
1977
+ * fiberless body is on its stack. */
1978
+ fiberlessAsyncFunctions() {
1979
+ const eligible = new Set();
1980
+ if (this.wasi || this.mod.workers === true)
1981
+ return eligible;
1982
+ const embedded = this.mod.embedded;
1983
+ if (embedded !== undefined && embedded.modules.length > 0)
1984
+ return eligible;
1985
+ const suspends = /@scr_(?:await_|module_await\b|promise_await_settled\b|test_sub\b|gen_yield_|async_gen_|fiber_)/;
1986
+ for (const decl of this.decls)
1987
+ if (/@scr_test_sub\b/.test(decl))
1988
+ return eligible;
1989
+ for (const text of this.fnDefText.values())
1990
+ if (/@scr_test_sub\b/.test(text))
1991
+ return eligible;
1992
+ const byMangled = new Map();
1993
+ for (const fn of this.mod.functions) {
1994
+ byMangled.set(mangleFunction(fn.name), fn.name);
1995
+ byMangled.set(mangleBorrowedFunction(fn.name), fn.name);
1996
+ }
1997
+ // Reverse mention edges, then propagate "may park" to every mentioner.
1998
+ const mentionedBy = new Map();
1999
+ const parks = new Set();
2000
+ const work = [];
2001
+ for (const [name, text] of this.fnDefText) {
2002
+ if (suspends.test(text)) {
2003
+ parks.add(name);
2004
+ work.push(name);
2005
+ }
2006
+ for (const match of text.matchAll(/@(sc_b?f_[A-Za-z0-9_]+)/g)) {
2007
+ const callee = byMangled.get(match[1]);
2008
+ if (callee === undefined || callee === name)
2009
+ continue;
2010
+ let callers = mentionedBy.get(callee);
2011
+ if (callers === undefined)
2012
+ mentionedBy.set(callee, (callers = new Set()));
2013
+ callers.add(name);
2014
+ }
2015
+ }
2016
+ while (work.length > 0) {
2017
+ const callers = mentionedBy.get(work.pop());
2018
+ if (callers === undefined)
2019
+ continue;
2020
+ for (const caller of callers) {
2021
+ if (parks.has(caller))
2022
+ continue;
2023
+ parks.add(caller);
2024
+ work.push(caller);
2025
+ }
2026
+ }
2027
+ for (const fn of this.mod.functions) {
2028
+ if (fn.async !== true || fn.generator !== undefined)
2029
+ continue;
2030
+ if (fn.asyncCacheGlobal !== undefined || fn.asyncCycleCacheGlobal !== undefined)
2031
+ continue;
2032
+ if (fn.returnType.kind === "dyn" || parks.has(fn.name))
2033
+ continue;
2034
+ if (!this.fnDefText.has(fn.name))
2035
+ continue;
2036
+ eligible.add(fn.name);
2037
+ }
2038
+ return eligible;
2039
+ }
2040
+ /** The spawn wrapper of a fiberless async function: runs the body on the
2041
+ * caller's stack between scr_async_inline_enter/leave and settles the
2042
+ * fresh promise from its outcome — the fiber trampoline's epilogue,
2043
+ * without the fiber. */
2044
+ emitFiberlessAsyncSpawn(fn, spawnParams) {
2045
+ const ret = fn.returnType;
2046
+ const retTy = this.llType(ret);
2047
+ const lifted = fn.captures !== undefined;
2048
+ this.declare(`declare void @scr_async_inline_enter(ptr)`);
2049
+ this.declare(`declare ptr @scr_async_inline_leave(ptr)`);
2050
+ this.declare(`declare void @scr_promise_reject_pending(ptr)`);
2051
+ this.declare(`declare zeroext i1 @scr_exc_pending()`);
2052
+ if (lifted) {
2053
+ this.declare(`declare ptr @scr_closure_retain_v(ptr)`);
2054
+ this.declare(`declare void @scr_closure_release(ptr)`);
2055
+ }
2056
+ const bodyCall = `call ${retTy} @${mangleFunction(fn.name)}(${spawnParams.join(", ")})`;
2057
+ const out = [
2058
+ `define internal ptr @${mangleAsyncSpawn(fn.name)}(${spawnParams.join(", ")}) ${FN_ATTRS} { ; fiberless ${fn.name}`,
2059
+ `entry:`,
2060
+ ` %frame = alloca { ptr, ptr }`,
2061
+ // The fiber path holds +1 on a lifted environment for the body's
2062
+ // duration (packed, released by the trampoline); keep that lifetime.
2063
+ ...(lifted ? [` %env = call ptr @scr_closure_retain_v(ptr %a0)`] : []),
2064
+ ` call void @scr_async_inline_enter(ptr %frame)`,
2065
+ retTy === "void" ? ` ${bodyCall}` : ` %r = ${bodyCall}`,
2066
+ ...(lifted ? [` call void @scr_closure_release(ptr %a0)`] : []),
2067
+ ` %pend = call zeroext i1 @scr_exc_pending()`,
2068
+ ` %p = call ptr @scr_async_inline_leave(ptr %frame)`,
2069
+ ` br i1 %pend, label %thrown, label %clean`,
2070
+ `clean:`,
2071
+ ...this.asyncFulfillLines(ret, "%p"),
2072
+ ` ret ptr %p`,
2073
+ `thrown:`,
2074
+ ];
2075
+ if (ret.kind !== "void" && isRefCounted(ret)) {
2076
+ // An escaping throw means %r is the never-read dummy (NULL).
2077
+ out.push(` call void ${releaseSym(this.shapeHost, ret)}(ptr %r)`);
2078
+ }
2079
+ out.push(` call void @scr_promise_reject_pending(ptr %p)`, ` ret ptr %p`, `}`, ``);
2080
+ return out;
2081
+ }
1780
2082
  /** Per-async-function machinery — async.ts's scaffolding, .ll
1781
2083
  * flavored: an argument-pack struct type, a fiber trampoline (unpacks,
1782
2084
  * frees the pack, runs the ordinary compiled body, settles the
@@ -1791,9 +2093,20 @@ export class LlEmitter {
1791
2093
  this.declare(`declare void @llvm.coro.destroy(ptr)`);
1792
2094
  out.push(`define void @scr_wasi_coro_resume(ptr %handle) ${FN_ATTRS} {`, `entry:`, ` call void @llvm.coro.resume(ptr %handle)`, ` ret void`, `}`, `define void @scr_wasi_coro_destroy(ptr %handle) ${FN_ATTRS} {`, `entry:`, ` call void @llvm.coro.destroy(ptr %handle)`, ` ret void`, `}`, ``);
1793
2095
  }
2096
+ const fiberless = this.fiberlessAsyncFunctions();
1794
2097
  for (const fn of this.mod.functions) {
1795
2098
  if (fn.async !== true || fn.generator !== undefined)
1796
2099
  continue;
2100
+ if (fiberless.has(fn.name)) {
2101
+ const fieldTys = [
2102
+ ...(fn.captures !== undefined ? ["ptr"] : []),
2103
+ ...fn.params.map((p) => this.llType(p.type)),
2104
+ ];
2105
+ const params = fieldTys.map((ty, i) => `${ty} %a${i}`);
2106
+ for (const line of this.emitFiberlessAsyncSpawn(fn, params))
2107
+ out.push(line);
2108
+ continue;
2109
+ }
1797
2110
  const { definitions, ret, tr, spawnParams, argPackLines } = this.emitArgPackAndTrampolinePrologue(fn);
1798
2111
  for (const line of definitions)
1799
2112
  out.push(line);
@@ -1811,34 +2124,8 @@ export class LlEmitter {
1811
2124
  this.declare(`declare void @scr_closure_release(ptr)`);
1812
2125
  }
1813
2126
  tr.push(` %pend = call zeroext i1 @scr_exc_pending()`, ` br i1 %pend, label %thrown, label %clean`, `clean:`, ` %pr = call ptr @scr_fiber_promise(ptr %self)`);
1814
- switch (ret.kind) {
1815
- case "void":
1816
- this.declare(`declare void @scr_promise_fulfill_void(ptr)`);
1817
- tr.push(` call void @scr_promise_fulfill_void(ptr %pr)`);
1818
- break;
1819
- case "f64":
1820
- case "date":
1821
- this.declare(`declare void @scr_promise_fulfill_f64(ptr, double)`);
1822
- tr.push(` call void @scr_promise_fulfill_f64(ptr %pr, double %r)`);
1823
- break;
1824
- case "bool":
1825
- this.declare(`declare void @scr_promise_fulfill_bool(ptr, i1 zeroext)`);
1826
- tr.push(` call void @scr_promise_fulfill_bool(ptr %pr, i1 %r)`);
1827
- break;
1828
- case "string":
1829
- this.declare(`declare void @scr_promise_fulfill_str(ptr, ptr)`);
1830
- tr.push(` call void @scr_promise_fulfill_str(ptr %pr, ptr %r) ; moves in`);
1831
- break;
1832
- case "dyn":
1833
- this.declare(`declare void @scr_promise_resolve_dyn(ptr, ptr)`);
1834
- tr.push(` call void @scr_promise_resolve_dyn(ptr %pr, ptr %r) ; moves in`);
1835
- break;
1836
- default: {
1837
- const v = vAdapters(this.shapeHost, ret);
1838
- this.declare(`declare void @scr_promise_fulfill_ref(ptr, ptr, ptr, ptr, ptr)`);
1839
- tr.push(` call void @scr_promise_fulfill_ref(ptr %pr, ptr %r, ptr ${v.retain}, ptr ${v.release}, ptr ${traceArg(this.shapeHost, ret)})`);
1840
- }
1841
- }
2127
+ for (const line of this.asyncFulfillLines(ret, "%pr"))
2128
+ tr.push(line);
1842
2129
  tr.push(` ret void`, `thrown:`);
1843
2130
  if (ret.kind !== "void" && isRefCounted(ret)) {
1844
2131
  // An escaping throw means %r is the never-read dummy (NULL).
@@ -2077,6 +2364,40 @@ export class LlEmitter {
2077
2364
  this.immortalValues.add(name);
2078
2365
  return name;
2079
2366
  }
2367
+ /** The immortal ABSENT state of an undefined-armed record field slot
2368
+ * (IR fieldAbsent). */
2369
+ absentInstanceRef(unionId) {
2370
+ if (undefinedArmTag({ kind: "union", unionId }, this.unionsById) < 0) {
2371
+ throw new InternalCompilerError(`llvm emitter bug: absent state of ${unionId} without undefined`);
2372
+ }
2373
+ let sym = this.absentInstances.get(unionId);
2374
+ if (!sym) {
2375
+ sym = `sc_absent_${this.absentInstances.size}`;
2376
+ this.absentInstances.set(unionId, sym);
2377
+ }
2378
+ const name = `@${sym}`;
2379
+ this.immortalValues.add(name);
2380
+ return name;
2381
+ }
2382
+ /** i1: a union value loaded from a field slot is the ABSENT state — the
2383
+ * undefined arm's tag with the absent payload marker. */
2384
+ fieldAbsentTest(value, unionId) {
2385
+ return emitFieldAbsentTest(this.B, value, undefinedArmTag({ kind: "union", unionId }, this.unionsById));
2386
+ }
2387
+ /** Loads a record field slot as a VALUE: an undefined-armed union slot
2388
+ * holding the ABSENT state surfaces as the ordinary undefined arm, so
2389
+ * absence never travels beyond its slot (a later `{ f: r.f }` is an
2390
+ * explicit, present property). */
2391
+ loadRecordField(ptr, t) {
2392
+ const v = this.loadField(ptr, t);
2393
+ if (t.kind !== "union" || undefinedArmTag(t, this.unionsById) < 0)
2394
+ return v;
2395
+ const absent = this.fieldAbsentTest(v, t.unionId);
2396
+ const unit = this.unitInstanceRef(t.unionId, undefinedArmTag(t, this.unionsById));
2397
+ const out = this.B.tmp();
2398
+ this.B.line(`${out} = select i1 ${absent}, ptr ${unit}, ptr ${v}`);
2399
+ return out;
2400
+ }
2080
2401
  declare(decl) {
2081
2402
  this.decls.add(decl);
2082
2403
  }
@@ -2136,8 +2457,7 @@ export class LlEmitter {
2136
2457
  const t = this.B.tmp();
2137
2458
  this.B.line(`${t} = load ptr, ptr ${v.name}`);
2138
2459
  if (v.boxed) {
2139
- this.declare(`declare void @scr_box_release(ptr)`);
2140
- this.B.line(`call void @scr_box_release(ptr ${t})`);
2460
+ this.B.line(`call void ${boxReleaseSym(this.shapeHost)}(ptr ${t})`);
2141
2461
  }
2142
2462
  else {
2143
2463
  this.releaseValue(t, v.type);
@@ -2153,8 +2473,7 @@ export class LlEmitter {
2153
2473
  const t = this.B.tmp();
2154
2474
  this.B.line(`${t} = load ptr, ptr ${e.slot}`);
2155
2475
  if (e.boxed) {
2156
- this.declare(`declare void @scr_box_release(ptr)`);
2157
- this.B.line(`call void @scr_box_release(ptr ${t})`);
2476
+ this.B.line(`call void ${boxReleaseSym(this.shapeHost)}(ptr ${t})`);
2158
2477
  }
2159
2478
  else {
2160
2479
  this.releaseValue(t, e.type); // runtime releases are NULL-tolerant
@@ -2206,6 +2525,20 @@ export class LlEmitter {
2206
2525
  * using the result). Callers own the surrounding pending branch; a
2207
2526
  * `throw` unwinds unconditionally. */
2208
2527
  emitUnwind() {
2528
+ if (this.mod.workers && this.tryStack.length > 0) {
2529
+ this.declare(`declare zeroext i1 @scr_context_stopping()`);
2530
+ const stopped = this.B.tmp();
2531
+ this.B.line(`${stopped} = call zeroext i1 @scr_context_stopping()`);
2532
+ const stop = this.B.newLabel("context.stop");
2533
+ const ordinary = this.B.newLabel("context.throw");
2534
+ this.B.condBr(stopped, stop, ordinary);
2535
+ this.B.startBlock(stop);
2536
+ const handlers = this.tryStack;
2537
+ this.tryStack = [];
2538
+ this.emitUnwind();
2539
+ this.tryStack = handlers;
2540
+ this.B.startBlock(ordinary);
2541
+ }
2209
2542
  const target = this.tryStack[this.tryStack.length - 1];
2210
2543
  const frameDepth = target?.frameDepth ?? 0;
2211
2544
  const scopeDepth = target?.scopeDepth ?? 0;
@@ -2220,13 +2553,13 @@ export class LlEmitter {
2220
2553
  terminator = `br label %${this.currentWasiCoro.finalLabel}`;
2221
2554
  }
2222
2555
  else {
2223
- const t = this.currentReturnType;
2556
+ const t = this.llType(this.currentReturnType);
2224
2557
  terminator =
2225
- t.kind === "void"
2558
+ t === "void"
2226
2559
  ? "ret void"
2227
- : t.kind === "f64" || t.kind === "date"
2560
+ : t === "double"
2228
2561
  ? `ret double ${f64Lit(0)}`
2229
- : t.kind === "bool"
2562
+ : t === "i1"
2230
2563
  ? "ret i1 false"
2231
2564
  : "ret ptr null";
2232
2565
  }
@@ -2269,6 +2602,34 @@ export class LlEmitter {
2269
2602
  this.emitUnwind();
2270
2603
  B.startBlock(lk);
2271
2604
  }
2605
+ workerLoopBudget() {
2606
+ if (!this.mod.workers)
2607
+ return null;
2608
+ const slot = this.B.slot();
2609
+ this.B.entryAllocas.push(`${slot} = alloca i32 ; worker cancellation budget`);
2610
+ this.B.line(`store i32 0, ptr ${slot}`);
2611
+ return slot;
2612
+ }
2613
+ emitWorkerLoopCheck(slot) {
2614
+ if (slot === null)
2615
+ return;
2616
+ const B = this.B;
2617
+ const budget = B.tmp(), due = B.tmp(), remaining = B.tmp();
2618
+ const poll = B.newLabel("worker.poll"), next = B.newLabel("worker.next");
2619
+ B.line(`${budget} = load i32, ptr ${slot}`);
2620
+ B.line(`${remaining} = sub i32 ${budget}, 1`);
2621
+ B.line(`store i32 ${remaining}, ptr ${slot}`);
2622
+ B.line(`${due} = icmp eq i32 ${budget}, 0`);
2623
+ B.condBr(due, poll, next);
2624
+ B.startBlock(poll);
2625
+ B.line(`store i32 63, ptr ${slot}`);
2626
+ // Calls and throwing operations retain their own checks. Amortize the
2627
+ // extra cancellation poll across bounded loop batches so scalar work
2628
+ // does not cross the runtime ABI on every iteration.
2629
+ this.emitPendingCheck();
2630
+ B.br(next);
2631
+ B.startBlock(next);
2632
+ }
2272
2633
  /** Moves an already-evaluated value into the runtime's exception cell —
2273
2634
  * the `throw` statement's kind dispatch (stmts.ts's), shared with
2274
2635
  * every synthetic thrower. Ownership of a refcounted payload must have
@@ -2344,14 +2705,10 @@ export class LlEmitter {
2344
2705
  this.unionTagSwitch(v.name, def, (arm) => {
2345
2706
  let valueName = "false";
2346
2707
  if (arm.kind === "f64" || arm.kind === "procStream") {
2347
- valueName = B.tmp();
2348
- this.declare(`declare double @scr_union_get_f64(ptr)`);
2349
- B.line(`${valueName} = call double @scr_union_get_f64(ptr ${v.name})`);
2708
+ valueName = this.unionGetF64(v.name);
2350
2709
  }
2351
2710
  else if (arm.kind === "bool") {
2352
- valueName = B.tmp();
2353
- this.declare(`declare zeroext i1 @scr_union_get_bool(ptr)`);
2354
- B.line(`${valueName} = call zeroext i1 @scr_union_get_bool(ptr ${v.name})`);
2711
+ valueName = this.unionGetBool(v.name);
2355
2712
  }
2356
2713
  else if (arm.kind === "string") {
2357
2714
  valueName = this.unionPeek(v.name);
@@ -2530,24 +2887,34 @@ export class LlEmitter {
2530
2887
  B.terminate(`unreachable`);
2531
2888
  }
2532
2889
  /** The +1 extraction of a union's single narrowed arm (unionNarrow /
2533
- * the nullish-family reads): scalars via the runtime getters, ref arms
2890
+ * the nullish-family reads): scalars via inline payload loads, ref arms
2534
2891
  * a retained peek. */
2535
2892
  unionExtract(uName, arm) {
2536
- const B = this.B;
2537
- if (arm.kind === "f64" || arm.kind === "procStream") {
2538
- const t = B.tmp();
2539
- this.declare(`declare double @scr_union_get_f64(ptr)`);
2540
- B.line(`${t} = call double @scr_union_get_f64(ptr ${uName})`);
2541
- return t;
2542
- }
2543
- if (arm.kind === "bool") {
2544
- const t = B.tmp();
2545
- this.declare(`declare zeroext i1 @scr_union_get_bool(ptr)`);
2546
- B.line(`${t} = call zeroext i1 @scr_union_get_bool(ptr ${uName})`);
2547
- return t;
2548
- }
2893
+ if (arm.kind === "f64" || arm.kind === "procStream")
2894
+ return this.unionGetF64(uName);
2895
+ if (arm.kind === "bool")
2896
+ return this.unionGetBool(uName);
2549
2897
  return this.retainValue(this.unionPeek(uName), arm);
2550
2898
  }
2899
+ /** A scalar f64 arm's payload (scr_union_get_f64 inlined). Inline loads
2900
+ * keep stack boxes free of escaping calls, so LLVM can scalarize them. */
2901
+ unionGetF64(uName) {
2902
+ const p = this.B.tmp();
2903
+ const t = this.B.tmp();
2904
+ this.B.line(`${p} = getelementptr inbounds %ScrUnion, ptr ${uName}, i64 0, i32 5`);
2905
+ this.B.line(`${t} = load double, ptr ${p}`);
2906
+ return t;
2907
+ }
2908
+ /** A bool arm's payload (scr_union_get_bool inlined: nonzero slot). */
2909
+ unionGetBool(uName) {
2910
+ const p = this.B.tmp();
2911
+ const bits = this.B.tmp();
2912
+ const t = this.B.tmp();
2913
+ this.B.line(`${p} = getelementptr inbounds %ScrUnion, ptr ${uName}, i64 0, i32 5`);
2914
+ this.B.line(`${bits} = load i64, ptr ${p}`);
2915
+ this.B.line(`${t} = icmp ne i64 ${bits}, 0`);
2916
+ return t;
2917
+ }
2551
2918
  /** Constructs a union box around an OWNED (+1, already moved) value —
2552
2919
  * the scr_union_new_* dispatch of unionWrap and the wrap-into-join
2553
2920
  * sites (shift). */
@@ -2726,17 +3093,26 @@ export class LlEmitter {
2726
3093
  this.B.line(`${b} = load ptr, ptr ${slot}`);
2727
3094
  return b;
2728
3095
  }
3096
+ /** The payload slot of a capture box (ScrBox.slot, field 5). */
3097
+ boxSlot(box) {
3098
+ const slot = this.B.tmp();
3099
+ this.B.line(`${slot} = getelementptr inbounds %ScrBox, ptr ${box}, i32 0, i32 5`);
3100
+ return slot;
3101
+ }
2729
3102
  boxGet(box, t) {
2730
3103
  const B = this.B;
2731
3104
  const acc = boxAccess(t);
2732
3105
  const r = B.tmp();
3106
+ // Scalar payloads are read in place (scr_box_get_f64/bool are a
3107
+ // memcpy and a nonzero test of the same slot), so LLVM can keep a
3108
+ // captured number in a register across a loop of calls.
2733
3109
  if (acc === "f64") {
2734
- this.declare(`declare double @scr_box_get_f64(ptr)`);
2735
- B.line(`${r} = call double @scr_box_get_f64(ptr ${box})`);
3110
+ B.line(`${r} = load double, ptr ${this.boxSlot(box)}`);
2736
3111
  }
2737
3112
  else if (acc === "bool") {
2738
- this.declare(`declare zeroext i1 @scr_box_get_bool(ptr)`);
2739
- B.line(`${r} = call zeroext i1 @scr_box_get_bool(ptr ${box})`);
3113
+ const raw = B.tmp();
3114
+ B.line(`${raw} = load i64, ptr ${this.boxSlot(box)}`);
3115
+ B.line(`${r} = icmp ne i64 ${raw}, 0`);
2740
3116
  }
2741
3117
  else {
2742
3118
  this.declare(`declare ptr @scr_box_get_ref(ptr)`);
@@ -2749,18 +3125,45 @@ export class LlEmitter {
2749
3125
  const B = this.B;
2750
3126
  const acc = boxAccess(t);
2751
3127
  if (acc === "f64") {
2752
- this.declare(`declare void @scr_box_set_f64(ptr, double)`);
2753
- B.line(`call void @scr_box_set_f64(ptr ${box}, double ${value})`);
3128
+ B.line(`store double ${value}, ptr ${this.boxSlot(box)}`);
2754
3129
  }
2755
3130
  else if (acc === "bool") {
2756
- this.declare(`declare void @scr_box_set_bool(ptr, i1 zeroext)`);
2757
- B.line(`call void @scr_box_set_bool(ptr ${box}, i1 ${value})`);
3131
+ const raw = B.tmp();
3132
+ B.line(`${raw} = zext i1 ${value} to i64`);
3133
+ B.line(`store i64 ${raw}, ptr ${this.boxSlot(box)}`);
2758
3134
  }
2759
3135
  else {
2760
3136
  this.declare(`declare void @scr_box_set_ref(ptr, ptr)`);
2761
3137
  B.line(`call void @scr_box_set_ref(ptr ${box}, ptr ${value})`);
2762
3138
  }
2763
3139
  }
3140
+ /** The box a new environment captures for `localId` (borrowed). An
3141
+ * unchanged parameter gets one box on first capture, holding its own
3142
+ * reference to the value; the function scope releases the cached box. */
3143
+ captureBox(localId) {
3144
+ const cache = this.lazyCaptureBoxes.get(localId);
3145
+ if (cache === undefined)
3146
+ return this.loadBox(`%${mangleLocal(localId)}`);
3147
+ const B = this.B;
3148
+ const type = this.currentLocals.get(localId).type;
3149
+ const current = this.loadBox(cache);
3150
+ const missing = B.tmp();
3151
+ B.line(`${missing} = icmp eq ptr ${current}, null`);
3152
+ const create = B.newLabel("lazy.box"), join = B.newLabel("lazy.box.j");
3153
+ B.condBr(missing, create, join);
3154
+ B.startBlock(create);
3155
+ const stack = this.lazyStackBoxes.get(localId);
3156
+ const box = stack ?? B.tmp();
3157
+ if (!stack)
3158
+ B.line(`${box} = ${boxNewCall(this.shapeHost, type)}`);
3159
+ const value = B.tmp();
3160
+ B.line(`${value} = load ${this.llType(type)}, ptr %${mangleLocal(localId)}`);
3161
+ this.boxSet(box, type, isRefCounted(type) ? this.retainValue(value, type) : value);
3162
+ B.line(`store ptr ${box}, ptr ${cache}`);
3163
+ B.br(join);
3164
+ B.startBlock(join);
3165
+ return this.loadBox(cache);
3166
+ }
2764
3167
  retainBox(box) {
2765
3168
  this.needsRetainBox = true;
2766
3169
  const t = this.B.tmp();
@@ -2805,6 +3208,12 @@ export class LlEmitter {
2805
3208
  B.line(`${empty} = icmp eq ptr ${value}, null`);
2806
3209
  this.throwIfUninitialized(empty, name);
2807
3210
  }
3211
+ /** Throws the TDZ ReferenceError for `name` unless the i1 `flag` is set. */
3212
+ checkInitializedFlag(flag, name) {
3213
+ const empty = this.B.tmp();
3214
+ this.B.line(`${empty} = xor i1 ${flag}, true`);
3215
+ this.throwIfUninitialized(empty, name);
3216
+ }
2808
3217
  throwIfUninitialized(empty, name) {
2809
3218
  const B = this.B;
2810
3219
  const lt = B.newLabel("tdz.t");
@@ -2973,10 +3382,36 @@ export class LlEmitter {
2973
3382
  this.unwindCleanups.clear();
2974
3383
  this.jumpTargets = [];
2975
3384
  this.currentLocals = new Map(fn.locals.map((l) => [l.id, l]));
3385
+ this.lazyCaptureBoxes.clear();
3386
+ this.lazyStackBoxes.clear();
3387
+ // An unchanged captured parameter keeps its value in a plain slot; the
3388
+ // binding is boxed only for closures (captureBox).
3389
+ const lazyParameters = this.lazyCaptures.parameters(fn);
3390
+ for (const id of lazyParameters) {
3391
+ const { boxed: _boxed, ...plain } = this.currentLocals.get(id);
3392
+ this.currentLocals.set(id, plain);
3393
+ this.lazyCaptureBoxes.set(id, "");
3394
+ }
3395
+ this.currentConstantCallbacks =
3396
+ this.debug === null ? (this.constantCallbacks.get(fn.name) ?? new Map()) : new Map();
3397
+ // Preserve concrete source bindings for debugger inspection. Suspended
3398
+ // functions keep the established array lifetime across continuations.
3399
+ this.streamingSplitsEnabled = this.debug === null && !fn.async && !fn.generator;
3400
+ this.stackCallbacks.reset(this.streamingSplitsEnabled);
3401
+ this.stackCaptures.reset(fn, this.streamingSplitsEnabled, lazyParameters);
3402
+ this.privateSplitLocals = this.streamingSplitsEnabled ? findPrivateSplitLocals(fn) : new Map();
3403
+ this.storedSplits.clear();
3404
+ this.splitSpans.clear();
3405
+ this.stringSlices.clear();
2976
3406
  const initializerBindings = this.initializerBindings.get(fn.name) ?? [];
2977
3407
  const numericFn = withInitializerBindings(fn, initializerBindings);
2978
3408
  this.numericLocals = new Map(numericFn.locals.map((l) => [l.id, l]));
2979
3409
  this.borrowedParameters.clear();
3410
+ this.projectedParameters.clear();
3411
+ const projectedParameterIndexes = this.callLifetimes.parameters.get(fn.name);
3412
+ if (projectedParameterIndexes)
3413
+ for (const index of projectedParameterIndexes)
3414
+ this.projectedParameters.add(fn.params[index].localId);
2980
3415
  this.stableCallBindings = this.callLifetimes.bindings.get(fn.name) ?? new Set();
2981
3416
  const borrowedParameterIndexes = this.callLifetimes.borrowed.get(fn.name);
2982
3417
  if (borrowedParameterIndexes) {
@@ -2986,14 +3421,25 @@ export class LlEmitter {
2986
3421
  this.captureIds = new Set([...(fn.captures ?? []), ...(fn.classCaptures ?? [])].map((c) => c.localId));
2987
3422
  this.integerLoopBindings.clear();
2988
3423
  this.countedLoopsEnabled = this.debug === null && !fn.async && !fn.generator;
3424
+ this.byteWindowsEnabled = this.countedLoopsEnabled;
3425
+ this.byteWindowEntries =
3426
+ this.byteWindowsEnabled && numericFn.locals.some((l) => l.type.kind === "bytes")
3427
+ ? findInitializedByteLoopBindings(fn)
3428
+ : new Map();
2989
3429
  this.integerViews.clear();
2990
3430
  this.fieldPointerTags.clear();
2991
3431
  this.integerArrayBindings.clear();
2992
- this.localArrayReads = findLocalArrayReads(fn, this.fnByName, this.unionsById, this.referenceEffects.functions, this.callLifetimes, this.optionalArrayReads);
3432
+ this.loopArrayBorrows =
3433
+ this.debug === null
3434
+ ? findLoopArrayBorrows(fn, this.callLifetimes, this.referenceEffects, this.optionalArrayReads)
3435
+ : new Set();
3436
+ this.localArrayReads = findLocalArrayReads(fn, this.fnByName, this.unionsById, this.referenceEffects.functions, this.callLifetimes, this.optionalArrayReads, this.loopArrayBorrows);
2993
3437
  this.callArrayReads = findCallArrayReads(fn, this.optionalArrayReads, this.referenceEffects.functions, this.callLifetimes);
3438
+ this.scalarStringSlices =
3439
+ this.debug === null ? findScalarStringSlices(fn, this.callLifetimes) : new Map();
2994
3440
  this.mapReadLifetimes = findMapReadLifetimes(fn, this.unionsById, this.callLifetimes);
2995
3441
  this.localStackUnions = findLocalStackUnions(fn, this.callLifetimes, this.unionsById);
2996
- this.localUnionStorageProofs = findLocalUnionStorage(fn, this.callLifetimes, this.unionsById);
3442
+ this.localUnionStorageProofs = findLocalUnionStorage(fn, this.callLifetimes, this.unionsById, (e) => this.nullableFieldGet(e) !== null);
2997
3443
  this.localUnionStorage = new Map();
2998
3444
  this.integerRanges = analyzeIntegerRanges(numericFn);
2999
3445
  this.bytesBounds = findBytesBounds(numericFn, this.integerRanges);
@@ -3052,7 +3498,7 @@ export class LlEmitter {
3052
3498
  B.line(`${value} = load double, ptr @${mangleGlobal(global.id)}`);
3053
3499
  this.storeIntegerView(global.id, { name: value, type: global.type });
3054
3500
  }
3055
- for (const local of fn.locals) {
3501
+ for (const local of this.currentLocals.values()) {
3056
3502
  // Boxed locals' slots hold their capture BOX (a ptr); captured
3057
3503
  // (env-borrowed) locals bind the incoming box below. A caught-typed
3058
3504
  // local is a catch binding: its slot holds the ScrCaught snapshot
@@ -3101,6 +3547,21 @@ export class LlEmitter {
3101
3547
  // params (callees own their params — callers passed +1). Boxed params
3102
3548
  // allocate the shared binding and move the raw value in.
3103
3549
  const fnScope = [];
3550
+ for (const id of this.lazyCaptureBoxes.keys()) {
3551
+ const cache = B.slot();
3552
+ B.entryAllocas.push(`${cache} = alloca ptr ; lazy box ${this.currentLocals.get(id).name}`);
3553
+ B.line(`store ptr null, ptr ${cache}`);
3554
+ fnScope.push({ slot: cache, type: this.currentLocals.get(id).type, boxed: true });
3555
+ this.lazyCaptureBoxes.set(id, cache);
3556
+ // Environments that finish during the call can share an immortal
3557
+ // frame box. Its payload owner is released with the function scope.
3558
+ const stack = this.stackCaptures.emit(this, id, this.currentLocals.get(id).type);
3559
+ if (stack) {
3560
+ this.lazyStackBoxes.set(id, stack.box);
3561
+ if (stack.owner)
3562
+ fnScope.push(stack.owner);
3563
+ }
3564
+ }
3104
3565
  const borrowed = this.callLifetimes.borrowed.get(fn.name);
3105
3566
  for (const [index, p] of fn.params.entries()) {
3106
3567
  const local = this.currentLocals.get(p.localId);
@@ -3129,6 +3590,8 @@ export class LlEmitter {
3129
3590
  B.line(`call void @scr_stack_enter(ptr %source_frame, ptr ${this.cstr(frame)})`);
3130
3591
  B.returnEpilogue = `call void @scr_stack_leave(ptr %source_frame)`;
3131
3592
  }
3593
+ if (this.mod.workers)
3594
+ this.emitPendingCheck();
3132
3595
  this.emitStmts(fn.body);
3133
3596
  // Implicit exit of a void function: release the function scope unless
3134
3597
  // the body already terminated its final block (return, or a throw
@@ -3279,6 +3742,20 @@ export class LlEmitter {
3279
3742
  switch (s.kind) {
3280
3743
  case "varDecl": {
3281
3744
  const b = this.binding(s.localId);
3745
+ const slice = this.scalarStringSlices.get(s.localId);
3746
+ if (slice) {
3747
+ const snapshot = emitStringSliceSnapshot(this, slice);
3748
+ this.stringSlices.set(s.localId, snapshot);
3749
+ this.scopes[this.scopes.length - 1].push({ slot: snapshot.source, type: STRING });
3750
+ break;
3751
+ }
3752
+ const split = this.privateSplitLocals.get(s.localId);
3753
+ if (split) {
3754
+ const snapshot = storeSplitSnapshot(this, emitSplitSnapshot(this, split));
3755
+ this.storedSplits.set(s.localId, snapshot);
3756
+ this.scopes[this.scopes.length - 1].push({ slot: snapshot.sourceSlot, type: STRING }, { slot: snapshot.separatorSlot, type: STRING });
3757
+ break;
3758
+ }
3282
3759
  const localUnion = this.localStackUnions.get(s.localId);
3283
3760
  if (localUnion) {
3284
3761
  const union = emitStackUnion(this, localUnion);
@@ -3321,6 +3798,19 @@ export class LlEmitter {
3321
3798
  break;
3322
3799
  }
3323
3800
  if (b.kind === "boxed") {
3801
+ const stack = this.stackCaptures.emit(this, s.localId, b.type);
3802
+ if (stack) {
3803
+ B.line(`store ptr ${stack.box}, ptr ${b.slot}`);
3804
+ if (stack.owner)
3805
+ this.scopes[this.scopes.length - 1].push(stack.owner);
3806
+ if (s.init) {
3807
+ const value = this.emitExpr(s.init);
3808
+ if (isRefCounted(value.type))
3809
+ this.moveTemp(value);
3810
+ this.boxSet(stack.box, b.type, value.name);
3811
+ }
3812
+ break;
3813
+ }
3324
3814
  // Box FIRST, then evaluate the initializer: a named function
3325
3815
  // expression's closure captures this box during init evaluation.
3326
3816
  // A SCALAR TDZ box rides an ARR-kind box: the value lives in a
@@ -3357,13 +3847,16 @@ export class LlEmitter {
3357
3847
  }
3358
3848
  break;
3359
3849
  }
3360
- // A lexical const alias cannot outlive its unchanged local owner.
3850
+ if (this.loopArrayBorrows.has(s) && s.init.kind === "arrayGet") {
3851
+ emitBorrowedArrayRead(this, s.init, b.slot);
3852
+ break;
3853
+ }
3854
+ // A stable lexical alias cannot outlive its unchanged local owner.
3361
3855
  // Whole-value uses still retain their own copies, so returning or
3362
3856
  // storing the alias preserves the ordinary heap representation.
3363
3857
  // Boxed captures, reassigned sources and projections need ownership.
3364
3858
  if (b.kind === "local" &&
3365
3859
  b.local &&
3366
- !b.local.mutable &&
3367
3860
  isRefCounted(b.type) &&
3368
3861
  this.stableCallBindings.has(s.localId) &&
3369
3862
  this.canBorrowCallArgument(s.init)) {
@@ -3428,9 +3921,7 @@ export class LlEmitter {
3428
3921
  const acc = elemAccess(s.arr.type.elem);
3429
3922
  if (acc === "ref")
3430
3923
  this.moveTemp(v);
3431
- const argTy = acc === "f64" ? "double" : acc === "bool" ? "i1" : "ptr";
3432
- this.declare(`declare void @scr_arr_set_${acc}(ptr, double, ${argTy === "i1" ? "i1 zeroext" : argTy})`);
3433
- B.line(`call void @scr_arr_set_${acc}(ptr ${arr.name}, double ${idx.name}, ${argTy} ${v.name})`);
3924
+ emitDenseArraySet(this, arr.name, idx, s.index, acc, s.arr.type.elem, v.name);
3434
3925
  break;
3435
3926
  }
3436
3927
  case "arraySetLength": {
@@ -3478,6 +3969,12 @@ export class LlEmitter {
3478
3969
  // release — a release can trigger a cycle collection, which must
3479
3970
  // never see a heap edge whose count was already given up).
3480
3971
  // Classes and records share the struct layout, so one emission.
3972
+ const nullable = s.kind === "fieldSet" ? this.nullableFields.get(s.className, s.field) : null;
3973
+ if (nullable && s.kind === "fieldSet") {
3974
+ const obj = this.emitExpr(s.obj);
3975
+ this.emitNullableFieldStore(obj.name, s.className, s.field, nullable, s.value);
3976
+ break;
3977
+ }
3481
3978
  const obj = isRefCounted(s.value.type)
3482
3979
  ? this.emitExpr(s.obj)
3483
3980
  : this.emitStableReceiver(s.obj, [s.value]);
@@ -3561,7 +4058,7 @@ export class LlEmitter {
3561
4058
  for (const f of shape.fields) {
3562
4059
  const lit = this.internLiteral(f.name);
3563
4060
  const hit = B.tmp();
3564
- B.line(`${hit} = call zeroext i1 @scr_str_eq(ptr ${key.name}, ptr ${lit}) ; ${llvmCommentText(f.name)}`);
4061
+ B.line(`${hit} = call zeroext i1 @sc_str_eq(ptr ${key.name}, ptr ${lit}) ; ${llvmCommentText(f.name)}`);
3565
4062
  const lh = B.newLabel("rks.h");
3566
4063
  const ln = B.newLabel("rks.n");
3567
4064
  B.condBr(hit, lh, ln);
@@ -3617,7 +4114,7 @@ export class LlEmitter {
3617
4114
  // typeEquals(f.type, iv) — the frontend fences everything else.
3618
4115
  const lit = this.internLiteral(f.name);
3619
4116
  const hit = B.tmp();
3620
- B.line(`${hit} = call zeroext i1 @scr_str_eq(ptr ${key.name}, ptr ${lit}) ; ${llvmCommentText(f.name)}`);
4117
+ B.line(`${hit} = call zeroext i1 @sc_str_eq(ptr ${key.name}, ptr ${lit}) ; ${llvmCommentText(f.name)}`);
3621
4118
  const lh = B.newLabel("rks.h");
3622
4119
  const ln = B.newLabel("rks.n");
3623
4120
  B.condBr(hit, lh, ln);
@@ -3694,11 +4191,13 @@ export class LlEmitter {
3694
4191
  break;
3695
4192
  }
3696
4193
  case "while": {
4194
+ const workerBudget = this.workerLoopBudget();
3697
4195
  const lc = B.newLabel("loop.c");
3698
4196
  const lb = B.newLabel("loop.b");
3699
4197
  const le = B.newLabel("loop.e");
3700
4198
  B.br(lc);
3701
4199
  B.startBlock(lc);
4200
+ this.emitWorkerLoopCheck(workerBudget);
3702
4201
  B.condBr(this.emitCondition(s.cond), lb, le);
3703
4202
  B.startBlock(lb);
3704
4203
  this.jumpTargets.push({
@@ -3717,12 +4216,14 @@ export class LlEmitter {
3717
4216
  break;
3718
4217
  }
3719
4218
  case "doWhile": {
4219
+ const workerBudget = this.workerLoopBudget();
3720
4220
  // Body first (runs at least once); continue jumps to the CONDITION.
3721
4221
  const lb = B.newLabel("loop.b");
3722
4222
  const lc = B.newLabel("loop.c");
3723
4223
  const le = B.newLabel("loop.e");
3724
4224
  B.br(lb);
3725
4225
  B.startBlock(lb);
4226
+ this.emitWorkerLoopCheck(workerBudget);
3726
4227
  this.jumpTargets.push({
3727
4228
  kind: "loop",
3728
4229
  brkLabel: le,
@@ -3751,6 +4252,33 @@ export class LlEmitter {
3751
4252
  const countedLoop = !integerLoop && this.countedLoopsEnabled
3752
4253
  ? matchIntegerCountedForLoop(s, this.numericLocals, this.integerRanges)
3753
4254
  : null;
4255
+ const window = countedLoop && this.byteWindowsEnabled
4256
+ ? matchByteWindow(s, countedLoop, this.numericLocals, this.captureIds, this.integerRanges, this.byteWindowEntries.get(s) ?? new Set())
4257
+ : null;
4258
+ if (window) {
4259
+ const valid = emitByteWindowGuard(this, window);
4260
+ const fast = B.newLabel("bytes.window.fast"), slow = B.newLabel("bytes.window.slow"), done = B.newLabel("bytes.window.done");
4261
+ B.condBr(valid, fast, slow);
4262
+ const originalRanges = this.integerRanges, originalBounds = this.bytesBounds;
4263
+ this.byteWindowsEnabled = false;
4264
+ try {
4265
+ B.startBlock(slow);
4266
+ this.emitStmt(s);
4267
+ B.br(done);
4268
+ B.startBlock(fast);
4269
+ this.integerRanges = window.ranges;
4270
+ this.bytesBounds = new Set([...originalBounds, ...window.bounds]);
4271
+ this.emitStmt(s);
4272
+ B.br(done);
4273
+ }
4274
+ finally {
4275
+ this.integerRanges = originalRanges;
4276
+ this.bytesBounds = originalBounds;
4277
+ this.byteWindowsEnabled = true;
4278
+ }
4279
+ B.startBlock(done);
4280
+ break;
4281
+ }
3754
4282
  let countedJoin = null;
3755
4283
  if (countedLoop?.guarded) {
3756
4284
  const bound = this.emitExpr(countedLoop.limit);
@@ -3839,8 +4367,10 @@ export class LlEmitter {
3839
4367
  };
3840
4368
  const lu = s.update || capturedLets.length > 0 ? B.newLabel("loop.u") : lc;
3841
4369
  freshenBindings();
4370
+ const workerBudget = this.workerLoopBudget();
3842
4371
  B.br(lc);
3843
4372
  B.startBlock(lc);
4373
+ this.emitWorkerLoopCheck(workerBudget);
3844
4374
  if (integerLoop && integerSlot) {
3845
4375
  const receiver = this.emitStableReceiver(integerLoop.limitReceiver, []);
3846
4376
  const lenPtr = B.tmp();
@@ -3922,23 +4452,52 @@ export class LlEmitter {
3922
4452
  if (s.iterable.type.kind !== "array")
3923
4453
  throw new LlvmUnsupportedError(`forOf:${s.iterable.type.kind}`, s.loc);
3924
4454
  const elem = s.iterable.type.elem;
3925
- const arr = this.emitExpr(s.iterable);
3926
- const idxSlot = B.slot();
3927
- B.entryAllocas.push(`${idxSlot} = alloca double`);
3928
- B.line(`store double ${f64Lit(0)}, ptr ${idxSlot}`);
4455
+ const stored = s.iterable.kind === "varRef" ? this.storedSplits.get(s.iterable.localId) : undefined;
4456
+ const direct = this.streamingSplitsEnabled ? stringSplit(s.iterable) : null;
4457
+ const snapshot = stored
4458
+ ? loadSplitSnapshot(this, stored)
4459
+ : direct
4460
+ ? emitSplitSnapshot(this, direct)
4461
+ : null;
4462
+ const arr = snapshot ? null : this.emitExpr(s.iterable);
4463
+ const cursor = snapshot ? emitSplitCursor(this, snapshot) : null;
4464
+ const scratch = snapshot ? emitSplitScratch(this) : null;
4465
+ const deferred = snapshot && canDeferSplitPiece(s, this.currentLocals.get(s.localId));
4466
+ const spanLength = deferred ? B.slot() : null;
4467
+ if (spanLength)
4468
+ B.entryAllocas.push(`${spanLength} = alloca ${this.sizeType}`);
4469
+ const idxSlot = snapshot ? null : B.slot();
4470
+ if (idxSlot) {
4471
+ B.entryAllocas.push(`${idxSlot} = alloca double`);
4472
+ B.line(`store double ${f64Lit(0)}, ptr ${idxSlot}`);
4473
+ }
3929
4474
  const lc = B.newLabel("fof.c");
3930
4475
  const lb = B.newLabel("fof.b");
3931
4476
  const lu = B.newLabel("fof.u");
3932
4477
  const le = B.newLabel("fof.e");
4478
+ const workerBudget = this.workerLoopBudget();
3933
4479
  B.br(lc);
3934
4480
  B.startBlock(lc);
3935
- const i = B.tmp();
3936
- const len = B.tmp();
4481
+ this.emitWorkerLoopCheck(workerBudget);
3937
4482
  const inBounds = B.tmp();
3938
- this.declare(`declare double @scr_arr_len(ptr)`);
3939
- B.line(`${i} = load double, ptr ${idxSlot}`);
3940
- B.line(`${len} = call double @scr_arr_len(ptr ${arr.name})`);
3941
- B.line(`${inBounds} = fcmp olt double ${i}, ${len}`);
4483
+ let cur;
4484
+ if (snapshot && cursor && scratch) {
4485
+ cur = spanLength
4486
+ ? emitSplitSpanNext(this, snapshot, cursor, spanLength)
4487
+ : emitSplitNext(this, snapshot, cursor, scratch);
4488
+ B.line(`${inBounds} = icmp ne ptr ${cur}, null`);
4489
+ }
4490
+ else {
4491
+ const i = B.tmp(), lenPtr = B.tmp(), rawLen = B.tmp(), len = B.tmp();
4492
+ B.line(`${i} = load double, ptr ${idxSlot}`);
4493
+ // The inline form of scr_arr_len, like the `length` intrinsic.
4494
+ B.line(`${lenPtr} = getelementptr inbounds %ScrArr, ptr ${arr.name}, i32 0, i32 1`);
4495
+ this.markMemoryPointer(lenPtr, "array:header");
4496
+ B.line(`${rawLen} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}`);
4497
+ B.line(`${len} = uitofp ${this.sizeType} ${rawLen} to double`);
4498
+ B.line(`${inBounds} = fcmp olt double ${i}, ${len}`);
4499
+ cur = i;
4500
+ }
3942
4501
  B.condBr(inBounds, lb, le);
3943
4502
  B.startBlock(lb);
3944
4503
  this.jumpTargets.push({
@@ -3956,11 +4515,18 @@ export class LlEmitter {
3956
4515
  this.scopes.push([]);
3957
4516
  const localInfo = this.currentLocals.get(s.localId);
3958
4517
  const slot = `%${mangleLocal(s.localId)}`;
3959
- const acc = elemAccess(elem);
3960
- const accTy = acc === "f64" ? "double" : acc === "bool" ? "i1" : "ptr";
3961
- this.declare(`declare ${acc === "bool" ? "zeroext i1" : accTy} @scr_arr_get_${acc}(ptr, double)`);
3962
- const cur = B.tmp();
3963
- B.line(`${cur} = call ${accTy} @scr_arr_get_${acc}(ptr ${arr.name}, double ${i})`);
4518
+ const borrowedElement = !snapshot && this.loopArrayBorrows.has(s);
4519
+ if (!snapshot) {
4520
+ const acc = elemAccess(elem);
4521
+ const getter = borrowedElement ? "scr_arr_borrow_ref" : `scr_arr_get_${acc}`;
4522
+ cur = emitDenseArrayGet(this, arr.name, { name: cur, type: { kind: "f64" } }, undefined, acc, elem, getter, !borrowedElement);
4523
+ }
4524
+ if (spanLength) {
4525
+ const length = B.tmp();
4526
+ B.line(`${length} = load ${this.sizeType}, ptr ${spanLength}`);
4527
+ this.splitSpans.set(s.localId, { bytes: cur, length, scratch: scratch });
4528
+ cur = "null";
4529
+ }
3964
4530
  if (localInfo?.boxed) {
3965
4531
  // Captured loop variable: a fresh box per iteration, matching the
3966
4532
  // fresh const binding. The box takes ownership of a ref element's
@@ -3973,10 +4539,12 @@ export class LlEmitter {
3973
4539
  }
3974
4540
  else {
3975
4541
  B.line(`store ${this.llType(elem)} ${cur}, ptr ${slot}`);
3976
- if (isRefCounted(elem))
4542
+ if (isRefCounted(elem) && !borrowedElement)
3977
4543
  this.scopes[this.scopes.length - 1].push({ slot, type: elem });
3978
4544
  }
3979
4545
  this.emitStmts(s.body);
4546
+ if (spanLength)
4547
+ this.splitSpans.delete(s.localId);
3980
4548
  const endedWithJump = endsWithJump(s.body);
3981
4549
  const scope = this.scopes.pop();
3982
4550
  if (!endedWithJump)
@@ -3984,11 +4552,12 @@ export class LlEmitter {
3984
4552
  this.jumpTargets.pop();
3985
4553
  B.br(lu);
3986
4554
  B.startBlock(lu);
3987
- const i2 = B.tmp();
3988
- const i3 = B.tmp();
3989
- B.line(`${i2} = load double, ptr ${idxSlot}`);
3990
- B.line(`${i3} = fadd double ${i2}, ${f64Lit(1)}`);
3991
- B.line(`store double ${i3}, ptr ${idxSlot}`);
4555
+ if (idxSlot) {
4556
+ const i2 = B.tmp(), i3 = B.tmp();
4557
+ B.line(`${i2} = load double, ptr ${idxSlot}`);
4558
+ B.line(`${i3} = fadd double ${i2}, ${f64Lit(1)}`);
4559
+ B.line(`store double ${i3}, ptr ${idxSlot}`);
4560
+ }
3992
4561
  B.br(lc);
3993
4562
  B.startBlock(le);
3994
4563
  break;
@@ -4346,7 +4915,7 @@ export class LlEmitter {
4346
4915
  const hit = B.tmp();
4347
4916
  if (c.test.type.kind === "string") {
4348
4917
  this.declare(`declare zeroext i1 @scr_str_eq(ptr, ptr)`);
4349
- B.line(`${hit} = call zeroext i1 @scr_str_eq(ptr ${disc.name}, ptr ${t.name})`);
4918
+ B.line(`${hit} = call zeroext i1 @sc_str_eq(ptr ${disc.name}, ptr ${t.name})`);
4350
4919
  }
4351
4920
  else if (c.test.type.kind === "bool") {
4352
4921
  B.line(`${hit} = icmp eq i1 ${disc.name}, ${t.name}`);
@@ -4517,10 +5086,20 @@ export class LlEmitter {
4517
5086
  if (e.kind === "strLit")
4518
5087
  return { name: this.internLiteral(e.value), type: e.type };
4519
5088
  if (e.kind === "varRef") {
5089
+ this.materializeSplitLocal(e.localId);
4520
5090
  const binding = this.binding(e.localId);
4521
- // Capture boxes can carry TDZ and caught-value conversion semantics.
4522
- if (binding.kind === "boxed")
4523
- return this.emitExpr(e);
5091
+ if (binding.kind === "boxed") {
5092
+ // A projection consumes this pointer before any user code runs.
5093
+ // Keep TDZ checks and caught-value conversion on the owned path.
5094
+ if (!this.canBorrowReceiver(e))
5095
+ return this.emitExpr(e);
5096
+ const box = this.loadBox(binding.slot);
5097
+ const payload = this.B.tmp();
5098
+ const value = this.B.tmp();
5099
+ this.B.line(`${payload} = getelementptr inbounds %ScrBox, ptr ${box}, i32 0, i32 5`);
5100
+ this.B.line(`${value} = load ptr, ptr ${payload}`);
5101
+ return { name: value, type: e.type };
5102
+ }
4524
5103
  if (binding.kind === "global")
4525
5104
  this.checkGlobalTdz(e.localId);
4526
5105
  const value = this.B.tmp();
@@ -4534,17 +5113,26 @@ export class LlEmitter {
4534
5113
  this.ownSlot(result.owner.slot, result.owner.type);
4535
5114
  return result.value;
4536
5115
  }
5116
+ const arrayRead = this.optionalArrayReads.get(e);
5117
+ if (arrayRead)
5118
+ return emitProjectedArrayRead(this, arrayRead);
4537
5119
  if (canStackUnion(e, this.unionsById))
4538
5120
  return emitStackUnion(this, e).value;
4539
5121
  if (e.kind === "unionNarrow") {
4540
- const union = this.emitReadReceiver(e.value);
5122
+ const union = this.emitUnionProjection(e.value);
4541
5123
  return { name: this.unionPeek(union.name), type: e.type };
4542
5124
  }
5125
+ // A nullable-pointer field has no box to borrow. Projections use
5126
+ // emitUnionProjection; other borrowers get an owned heap box.
5127
+ if (this.nullableFieldGet(e))
5128
+ return this.emitExpr(e);
4543
5129
  if (e.kind === "recordGet" || e.kind === "fieldGet") {
4544
5130
  const receiver = this.emitReadReceiver(e.obj);
4545
- const { ptr, type } = e.kind === "recordGet"
4546
- ? this.recordFieldPtr(receiver.name, e.shapeId, e.field)
4547
- : this.classFieldPtr(receiver.name, e.className, e.field);
5131
+ if (e.kind === "recordGet") {
5132
+ const { ptr, type } = this.recordFieldPtr(receiver.name, e.shapeId, e.field);
5133
+ return { name: this.loadRecordField(ptr, type), type: e.type };
5134
+ }
5135
+ const { ptr, type } = this.classFieldPtr(receiver.name, e.className, e.field);
4548
5136
  return { name: this.loadField(ptr, type), type: e.type };
4549
5137
  }
4550
5138
  if (e.kind === "ternary" && this.canBorrowReceiver(e.then) && this.canBorrowReceiver(e.else_)) {
@@ -4574,10 +5162,163 @@ export class LlEmitter {
4574
5162
  }
4575
5163
  return this.emitExpr(e);
4576
5164
  }
5165
+ /** A private stack union source with an independently owned payload. */
5166
+ canStackReceiver(e) {
5167
+ return this.isStackUnionSource(e);
5168
+ }
5169
+ /** True for union sources that emitReadReceiver can produce as a private
5170
+ * stack box with an independently owned payload (no heap union). */
5171
+ isStackUnionSource(e) {
5172
+ if (e.type.kind !== "union")
5173
+ return false;
5174
+ return (canStackUnion(e, this.unionsById) ||
5175
+ matchMapRead(e, this.unionsById) !== null ||
5176
+ this.optionalArrayReads.get(e) !== null);
5177
+ }
5178
+ /** The nullable-pointer representation behind a union-typed class field
5179
+ * read, or null for every other expression. */
5180
+ nullableFieldGet(e) {
5181
+ if (e.kind !== "fieldGet" || e.type.kind !== "union")
5182
+ return null;
5183
+ return this.nullableFields.get(e.className, e.field);
5184
+ }
5185
+ /** A union operand consumed only by a projection (tag test, payload
5186
+ * extraction, arm compare, truthiness). A nullable-pointer field read
5187
+ * becomes a private stack box whose payload is borrowed from the field:
5188
+ * the consumer must finish before any later expression can store to the
5189
+ * field, and must never retain, release, or store the box. Every other
5190
+ * operand takes the ordinary borrowed-receiver path. */
5191
+ emitUnionProjection(e) {
5192
+ const nullable = this.nullableFieldGet(e);
5193
+ if (!nullable || e.kind !== "fieldGet")
5194
+ return this.emitReadReceiver(e);
5195
+ const B = this.B;
5196
+ const receiver = this.emitReadReceiver(e.obj);
5197
+ const { ptr } = this.classFieldPtr(receiver.name, e.className, e.field);
5198
+ const p = B.tmp();
5199
+ B.line(`${p} = load ptr, ptr ${ptr}${this.fieldAliasAttachment(ptr)}`);
5200
+ return { name: this.stackNullableBox(p, nullable), type: e.type };
5201
+ }
5202
+ /** A private stack box over a nullable-pointer field read whose payload
5203
+ * is retained into the current frame: an independent owner, so the box
5204
+ * may outlive later stores to the field (call arguments, local copies).
5205
+ * The box itself must still never reach an RC entry point. */
5206
+ emitOwnedNullableStack(e) {
5207
+ const nullable = this.nullableFieldGet(e);
5208
+ if (!nullable || e.kind !== "fieldGet")
5209
+ throw new InternalCompilerError("llvm emitter bug: owned nullable stack of non-field");
5210
+ const receiver = this.emitReadReceiver(e.obj);
5211
+ const { ptr } = this.classFieldPtr(receiver.name, e.className, e.field);
5212
+ const p = this.B.tmp();
5213
+ this.B.line(`${p} = load ptr, ptr ${ptr}${this.fieldAliasAttachment(ptr)}`);
5214
+ const owner = this.own({ name: this.retainValue(p, nullable.arm), type: nullable.arm });
5215
+ return { box: this.stackNullableBox(owner.name, nullable), owner };
5216
+ }
5217
+ /** A private stack box over a borrowed nullable pointer. */
5218
+ stackNullableBox(p, nullable) {
5219
+ const B = this.B;
5220
+ const box = B.slot();
5221
+ B.entryAllocas.push(`${box} = alloca %ScrUnion`);
5222
+ const isNull = B.tmp(), tag = B.tmp(), tagPtr = B.tmp(), slot = B.tmp();
5223
+ B.line(`${isNull} = icmp eq ptr ${p}, null`);
5224
+ B.line(`${tag} = select i1 ${isNull}, i32 ${nullable.unitTag}, i32 ${nullable.refTag}`);
5225
+ B.line(`${tagPtr} = getelementptr inbounds %ScrUnion, ptr ${box}, i64 0, i32 1`);
5226
+ B.line(`store i32 ${tag}, ptr ${tagPtr}`);
5227
+ B.line(`${slot} = getelementptr inbounds %ScrUnion, ptr ${box}, i64 0, i32 5`);
5228
+ B.line(`store i64 0, ptr ${slot}`);
5229
+ B.line(`store ptr ${p}, ptr ${slot}`);
5230
+ return box;
5231
+ }
5232
+ /** The owned (+1) union value of a nullable-pointer slot: the unit arm's
5233
+ * immortal instance, or a fresh heap box around a retained instance. */
5234
+ nullableToOwnedUnion(p, nullable) {
5235
+ const B = this.B;
5236
+ const slot = B.slot();
5237
+ B.entryAllocas.push(`${slot} = alloca ptr`);
5238
+ const isNull = B.tmp();
5239
+ const unit = B.newLabel("nf.u"), ref = B.newLabel("nf.r"), join = B.newLabel("nf.j");
5240
+ B.line(`${isNull} = icmp eq ptr ${p}, null`);
5241
+ B.condBr(isNull, unit, ref);
5242
+ B.startBlock(unit);
5243
+ B.line(`store ptr ${this.unitInstanceRef(nullable.unionId, nullable.unitTag)}, ptr ${slot}`);
5244
+ B.br(join);
5245
+ B.startBlock(ref);
5246
+ const owned = this.retainValue(p, nullable.arm);
5247
+ const box = this.unionNewOwned(nullable.refTag, { name: owned, type: nullable.arm });
5248
+ B.line(`store ptr ${box}, ptr ${slot}`);
5249
+ B.br(join);
5250
+ B.startBlock(join);
5251
+ const t = B.tmp();
5252
+ B.line(`${t} = load ptr, ptr ${slot}`);
5253
+ return t;
5254
+ }
5255
+ /** The owned (+1) instance pointer (NULL for the unit arm) of a union
5256
+ * box, which stays owned by its existing owner. */
5257
+ unionToNullable(u, nullable) {
5258
+ const B = this.B;
5259
+ const tag = this.unionTag(u);
5260
+ const isRef = B.tmp(), sel = B.tmp();
5261
+ B.line(`${isRef} = icmp eq i32 ${tag}, ${nullable.refTag}`);
5262
+ const payload = this.unionPeek(u);
5263
+ B.line(`${sel} = select i1 ${isRef}, ptr ${payload}, ptr null`);
5264
+ return this.retainValue(sel, nullable.arm);
5265
+ }
5266
+ /** `obj.f = value` for a nullable-pointer field: the new instance pointer
5267
+ * is produced without a union box where the value is a wrap, and as a
5268
+ * retained payload of a borrowed box otherwise. Unlink-then-release like
5269
+ * every field store. */
5270
+ emitNullableFieldStore(obj, className, field, nullable, value) {
5271
+ const B = this.B;
5272
+ let next;
5273
+ if (value.kind === "unionWrap" &&
5274
+ value.unionId === nullable.unionId &&
5275
+ (value.tag === nullable.refTag
5276
+ ? typeEquals(value.value.type, nullable.arm)
5277
+ : value.tag === nullable.unitTag)) {
5278
+ if (value.tag === nullable.refTag) {
5279
+ const inner = this.emitExpr(value.value);
5280
+ this.moveTemp(inner);
5281
+ next = inner.name;
5282
+ }
5283
+ else {
5284
+ // Unit payloads carry nothing; a void payload runs for effects.
5285
+ if (!isUnitType(value.value.type))
5286
+ this.emitExpr(value.value);
5287
+ next = "null";
5288
+ }
5289
+ }
5290
+ else if (value.kind === "varRef" && !this.canBorrowReceiver(value)) {
5291
+ next = this.unionToNullable(this.emitExpr(value).name, nullable);
5292
+ }
5293
+ else {
5294
+ next = this.unionToNullable(this.emitUnionProjection(value).name, nullable);
5295
+ }
5296
+ const { ptr } = this.classFieldPtr(obj, className, field);
5297
+ const old = B.tmp();
5298
+ B.line(`${old} = load ptr, ptr ${ptr}`);
5299
+ B.line(`store ptr ${next}, ptr ${ptr}${this.fieldAliasAttachment(ptr)}`);
5300
+ this.releaseValue(old, nullable.arm);
5301
+ }
5302
+ /** A union binding whose slot may hold a private stack box: stack locals
5303
+ * and projection-only parameters (callers may pass stack boxes). Such a
5304
+ * box must only be projected: never retained, released, or stored. */
5305
+ isStackUnionLocal(localId) {
5306
+ return (this.projectedParameters.has(localId) ||
5307
+ this.localStackUnions.has(localId) ||
5308
+ this.localUnionStorage.has(localId) ||
5309
+ this.localArrayReads.has(localId) ||
5310
+ this.mapReadLifetimes.locals.has(localId));
5311
+ }
4577
5312
  canBorrowReceiver(e) {
4578
5313
  switch (e.kind) {
4579
- case "varRef":
4580
- return this.binding(e.localId).kind !== "boxed";
5314
+ case "varRef": {
5315
+ const binding = this.binding(e.localId);
5316
+ return (binding.kind !== "boxed" ||
5317
+ (!binding.local.tdz &&
5318
+ binding.type.kind === e.type.kind &&
5319
+ e.type.kind !== "caught" &&
5320
+ isRefCounted(e.type)));
5321
+ }
4581
5322
  case "unionNarrow":
4582
5323
  return this.canBorrowReceiver(e.value);
4583
5324
  case "fieldGet":
@@ -4593,6 +5334,11 @@ export class LlEmitter {
4593
5334
  return false;
4594
5335
  }
4595
5336
  }
5337
+ materializeSplitLocal(localId) {
5338
+ const span = this.splitSpans.get(localId);
5339
+ if (span)
5340
+ materializeSplitPiece(this, localId, span);
5341
+ }
4596
5342
  emitExpr(e) {
4597
5343
  return emitExpr(this, e);
4598
5344
  }