@scriptc/compiler 0.2.6 → 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 (368) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +5 -18
  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/link-plan-core.d.ts +2 -1
  11. package/dist/backend/link-plan-core.js +15 -3
  12. package/dist/backend/link-plan-core.js.map +1 -1
  13. package/dist/backend/link-plan.d.ts +2 -1
  14. package/dist/backend/link-plan.js.map +1 -1
  15. package/dist/backend/llvm/alloc.d.ts +40 -0
  16. package/dist/backend/llvm/alloc.js +205 -0
  17. package/dist/backend/llvm/alloc.js.map +1 -0
  18. package/dist/backend/llvm/alloc.test.d.ts +1 -0
  19. package/dist/backend/llvm/alloc.test.js +102 -0
  20. package/dist/backend/llvm/alloc.test.js.map +1 -0
  21. package/dist/backend/llvm/call-lifetimes.d.ts +15 -3
  22. package/dist/backend/llvm/call-lifetimes.js +42 -18
  23. package/dist/backend/llvm/call-lifetimes.js.map +1 -1
  24. package/dist/backend/llvm/call-lifetimes.test.js +170 -0
  25. package/dist/backend/llvm/call-lifetimes.test.js.map +1 -1
  26. package/dist/backend/llvm/classes.d.ts +4 -1
  27. package/dist/backend/llvm/classes.js +35 -32
  28. package/dist/backend/llvm/classes.js.map +1 -1
  29. package/dist/backend/llvm/classes.test.js +36 -3
  30. package/dist/backend/llvm/classes.test.js.map +1 -1
  31. package/dist/backend/llvm/common.d.ts +13 -0
  32. package/dist/backend/llvm/common.js +30 -0
  33. package/dist/backend/llvm/common.js.map +1 -1
  34. package/dist/backend/llvm/constant-callbacks.d.ts +19 -3
  35. package/dist/backend/llvm/constant-callbacks.js +289 -52
  36. package/dist/backend/llvm/constant-callbacks.js.map +1 -1
  37. package/dist/backend/llvm/constant-callbacks.test.js +140 -3
  38. package/dist/backend/llvm/constant-callbacks.test.js.map +1 -1
  39. package/dist/backend/llvm/dense-array-access.d.ts +29 -0
  40. package/dist/backend/llvm/dense-array-access.js +335 -0
  41. package/dist/backend/llvm/dense-array-access.js.map +1 -0
  42. package/dist/backend/llvm/dense-array-access.test.d.ts +1 -0
  43. package/dist/backend/llvm/dense-array-access.test.js +149 -0
  44. package/dist/backend/llvm/dense-array-access.test.js.map +1 -0
  45. package/dist/backend/llvm/dyn.d.ts +34 -0
  46. package/dist/backend/llvm/dyn.js +189 -1
  47. package/dist/backend/llvm/dyn.js.map +1 -1
  48. package/dist/backend/llvm/emitter.d.ts +124 -4
  49. package/dist/backend/llvm/emitter.js +623 -105
  50. package/dist/backend/llvm/emitter.js.map +1 -1
  51. package/dist/backend/llvm/emitter.test.js +68 -6
  52. package/dist/backend/llvm/emitter.test.js.map +1 -1
  53. package/dist/backend/llvm/expr-async.js +58 -4
  54. package/dist/backend/llvm/expr-async.js.map +1 -1
  55. package/dist/backend/llvm/expr-async.test.d.ts +1 -0
  56. package/dist/backend/llvm/expr-async.test.js +62 -0
  57. package/dist/backend/llvm/expr-async.test.js.map +1 -0
  58. package/dist/backend/llvm/expr-calls.js +64 -6
  59. package/dist/backend/llvm/expr-calls.js.map +1 -1
  60. package/dist/backend/llvm/expr-containers.js +16 -5
  61. package/dist/backend/llvm/expr-containers.js.map +1 -1
  62. package/dist/backend/llvm/expr-control.js +92 -2
  63. package/dist/backend/llvm/expr-control.js.map +1 -1
  64. package/dist/backend/llvm/expr-dispatch.js +2 -0
  65. package/dist/backend/llvm/expr-dispatch.js.map +1 -1
  66. package/dist/backend/llvm/expr-dynamic.js +174 -25
  67. package/dist/backend/llvm/expr-dynamic.js.map +1 -1
  68. package/dist/backend/llvm/expr-primitives.d.ts +1 -1
  69. package/dist/backend/llvm/expr-primitives.js +52 -24
  70. package/dist/backend/llvm/expr-primitives.js.map +1 -1
  71. package/dist/backend/llvm/expr-primitives.test.js +1 -1
  72. package/dist/backend/llvm/expr-records.js +3 -3
  73. package/dist/backend/llvm/expr-records.js.map +1 -1
  74. package/dist/backend/llvm/expr-stream-bridges.js +59 -2
  75. package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
  76. package/dist/backend/llvm/expr-stream-bridges.test.js +1 -1
  77. package/dist/backend/llvm/integer-remainder.d.ts +11 -0
  78. package/dist/backend/llvm/integer-remainder.js +91 -0
  79. package/dist/backend/llvm/integer-remainder.js.map +1 -1
  80. package/dist/backend/llvm/layouts.d.ts +2 -1
  81. package/dist/backend/llvm/layouts.js +3 -2
  82. package/dist/backend/llvm/layouts.js.map +1 -1
  83. package/dist/backend/llvm/lazy-captures.d.ts +16 -0
  84. package/dist/backend/llvm/lazy-captures.js +96 -0
  85. package/dist/backend/llvm/lazy-captures.js.map +1 -0
  86. package/dist/backend/llvm/lazy-captures.test.d.ts +1 -0
  87. package/dist/backend/llvm/lazy-captures.test.js +78 -0
  88. package/dist/backend/llvm/lazy-captures.test.js.map +1 -0
  89. package/dist/backend/llvm/lib-filesystem.js +3 -4
  90. package/dist/backend/llvm/lib-filesystem.js.map +1 -1
  91. package/dist/backend/llvm/local-array-reads.d.ts +5 -1
  92. package/dist/backend/llvm/local-array-reads.js +78 -17
  93. package/dist/backend/llvm/local-array-reads.js.map +1 -1
  94. package/dist/backend/llvm/local-array-reads.test.js +78 -1
  95. package/dist/backend/llvm/local-array-reads.test.js.map +1 -1
  96. package/dist/backend/llvm/local-union-storage.d.ts +5 -1
  97. package/dist/backend/llvm/local-union-storage.js +48 -6
  98. package/dist/backend/llvm/local-union-storage.js.map +1 -1
  99. package/dist/backend/llvm/local-union-storage.test.js +91 -4
  100. package/dist/backend/llvm/local-union-storage.test.js.map +1 -1
  101. package/dist/backend/llvm/map-read-lifetimes.test.js +1 -1
  102. package/dist/backend/llvm/nullable-fields.d.ts +28 -0
  103. package/dist/backend/llvm/nullable-fields.js +45 -0
  104. package/dist/backend/llvm/nullable-fields.js.map +1 -0
  105. package/dist/backend/llvm/nullable-fields.test.d.ts +1 -0
  106. package/dist/backend/llvm/nullable-fields.test.js +48 -0
  107. package/dist/backend/llvm/nullable-fields.test.js.map +1 -0
  108. package/dist/backend/llvm/serialized-ir-emission.test.d.ts +1 -0
  109. package/dist/backend/llvm/serialized-ir-emission.test.js +52 -0
  110. package/dist/backend/llvm/serialized-ir-emission.test.js.map +1 -0
  111. package/dist/backend/llvm/shapes.d.ts +69 -2
  112. package/dist/backend/llvm/shapes.js +259 -27
  113. package/dist/backend/llvm/shapes.js.map +1 -1
  114. package/dist/backend/llvm/shapes.test.js +114 -5
  115. package/dist/backend/llvm/shapes.test.js.map +1 -1
  116. package/dist/backend/llvm/stack-callbacks.js +2 -3
  117. package/dist/backend/llvm/stack-callbacks.js.map +1 -1
  118. package/dist/backend/llvm/stack-captures.d.ts +3 -1
  119. package/dist/backend/llvm/stack-captures.js +5 -3
  120. package/dist/backend/llvm/stack-captures.js.map +1 -1
  121. package/dist/backend/llvm/stack-unions.test.js +38 -1
  122. package/dist/backend/llvm/stack-unions.test.js.map +1 -1
  123. package/dist/backend/llvm/string-construction.test.js +1 -1
  124. package/dist/backend/llvm/string-key-hash.d.ts +8 -0
  125. package/dist/backend/llvm/string-key-hash.js +59 -0
  126. package/dist/backend/llvm/string-key-hash.js.map +1 -0
  127. package/dist/backend/llvm/string-key-hash.test.d.ts +1 -0
  128. package/dist/backend/llvm/string-key-hash.test.js +34 -0
  129. package/dist/backend/llvm/string-key-hash.test.js.map +1 -0
  130. package/dist/backend/llvm/string-lifetimes.test.js +41 -4
  131. package/dist/backend/llvm/string-lifetimes.test.js.map +1 -1
  132. package/dist/backend/llvm/switch-dispatch.js +1 -1
  133. package/dist/backend/llvm/switch-dispatch.js.map +1 -1
  134. package/dist/backend/llvm/switch-dispatch.test.js +2 -2
  135. package/dist/backend/llvm/switch-dispatch.test.js.map +1 -1
  136. package/dist/backend/may-throw.js +3 -0
  137. package/dist/backend/may-throw.js.map +1 -1
  138. package/dist/backend/may-throw.test.js +1 -1
  139. package/dist/backend/native/cache-warm.d.ts +2 -1
  140. package/dist/backend/native/cache-warm.js.map +1 -1
  141. package/dist/backend/native/contracts.d.ts +3 -2
  142. package/dist/backend/native/contracts.js.map +1 -1
  143. package/dist/backend/native/runtime-inputs.d.ts +1 -1
  144. package/dist/backend/native/runtime-inputs.js +1 -0
  145. package/dist/backend/native/runtime-inputs.js.map +1 -1
  146. package/dist/backend/native-codegen-core.d.ts +2 -1
  147. package/dist/backend/native-codegen-core.js.map +1 -1
  148. package/dist/backend/native-codegen.d.ts +5 -0
  149. package/dist/backend/native-codegen.js +4 -0
  150. package/dist/backend/native-codegen.js.map +1 -1
  151. package/dist/backend/native-codegen.test.js +23 -0
  152. package/dist/backend/native-codegen.test.js.map +1 -1
  153. package/dist/backend/native-link-info-core.d.ts +3 -2
  154. package/dist/backend/native-link-info-core.js.map +1 -1
  155. package/dist/backend/native-link-info.d.ts +2 -1
  156. package/dist/backend/native-link-info.js.map +1 -1
  157. package/dist/backend/native-link-info.test.js +2 -2
  158. package/dist/backend/native-link-info.test.js.map +1 -1
  159. package/dist/backend/native-tools.d.ts +3 -1
  160. package/dist/backend/native-tools.js +0 -1
  161. package/dist/backend/native-tools.js.map +1 -1
  162. package/dist/backend/optimization.d.ts +25 -0
  163. package/dist/backend/optimization.js +21 -0
  164. package/dist/backend/optimization.js.map +1 -0
  165. package/dist/backend/optimization.test.d.ts +1 -0
  166. package/dist/backend/optimization.test.js +24 -0
  167. package/dist/backend/optimization.test.js.map +1 -0
  168. package/dist/backend/runtime-abi.d.ts +2 -2
  169. package/dist/backend/runtime-abi.js +2 -2
  170. package/dist/backend/runtime-pack-core.d.ts +15 -4
  171. package/dist/backend/runtime-pack-core.js +23 -12
  172. package/dist/backend/runtime-pack-core.js.map +1 -1
  173. package/dist/backend/runtime-pack-native.d.ts +7 -3
  174. package/dist/backend/runtime-pack-native.js +0 -3
  175. package/dist/backend/runtime-pack-native.js.map +1 -1
  176. package/dist/backend/runtime-pack-targets.test.js +1 -1
  177. package/dist/backend/runtime-pack.d.ts +20 -2
  178. package/dist/backend/runtime-pack.js +23 -0
  179. package/dist/backend/runtime-pack.js.map +1 -1
  180. package/dist/backend/runtime-pack.test.js +115 -2
  181. package/dist/backend/runtime-pack.test.js.map +1 -1
  182. package/dist/backend/target-diagnostics.test.js +1 -1
  183. package/dist/backend/targets.d.ts +2 -1
  184. package/dist/backend/targets.js +1 -1
  185. package/dist/backend/targets.js.map +1 -1
  186. package/dist/cli/command.js +5 -4
  187. package/dist/cli/command.js.map +1 -1
  188. package/dist/cli/host.d.ts +2 -1
  189. package/dist/cli/usage.d.ts +1 -1
  190. package/dist/cli/usage.js +5 -2
  191. package/dist/cli/usage.js.map +1 -1
  192. package/dist/compile-types.d.ts +5 -2
  193. package/dist/diagnostics/diagnostic.js +10 -8
  194. package/dist/diagnostics/diagnostic.js.map +1 -1
  195. package/dist/executable/executable-cache.d.ts +2 -2
  196. package/dist/executable/executable-cache.js +7 -4
  197. package/dist/executable/executable-cache.js.map +1 -1
  198. package/dist/executable/executable-cache.test.js +20 -0
  199. package/dist/executable/executable-cache.test.js.map +1 -1
  200. package/dist/frontend/lowering/abstract-equality.js +5 -49
  201. package/dist/frontend/lowering/abstract-equality.js.map +1 -1
  202. package/dist/frontend/lowering/array-values.d.ts +23 -0
  203. package/dist/frontend/lowering/array-values.js +129 -0
  204. package/dist/frontend/lowering/array-values.js.map +1 -1
  205. package/dist/frontend/lowering/builtins/errors.js +17 -2
  206. package/dist/frontend/lowering/builtins/errors.js.map +1 -1
  207. package/dist/frontend/lowering/builtins/process.d.ts +6 -17
  208. package/dist/frontend/lowering/builtins/process.js +56 -29
  209. package/dist/frontend/lowering/builtins/process.js.map +1 -1
  210. package/dist/frontend/lowering/caught-value-dispatch.d.ts +11 -0
  211. package/dist/frontend/lowering/caught-value-dispatch.js +87 -0
  212. package/dist/frontend/lowering/caught-value-dispatch.js.map +1 -0
  213. package/dist/frontend/lowering/class-assertions.d.ts +4 -0
  214. package/dist/frontend/lowering/class-assertions.js +12 -4
  215. package/dist/frontend/lowering/class-assertions.js.map +1 -1
  216. package/dist/frontend/lowering/class-callbacks.d.ts +3 -3
  217. package/dist/frontend/lowering/class-callbacks.js +18 -5
  218. package/dist/frontend/lowering/class-callbacks.js.map +1 -1
  219. package/dist/frontend/lowering/class-dynamic-dispatch.d.ts +1 -0
  220. package/dist/frontend/lowering/class-dynamic-dispatch.js +4 -1
  221. package/dist/frontend/lowering/class-dynamic-dispatch.js.map +1 -1
  222. package/dist/frontend/lowering/class-method-mutations.d.ts +5 -0
  223. package/dist/frontend/lowering/class-method-mutations.js +472 -0
  224. package/dist/frontend/lowering/class-method-mutations.js.map +1 -0
  225. package/dist/frontend/lowering/class-method-values.js +25 -5
  226. package/dist/frontend/lowering/class-method-values.js.map +1 -1
  227. package/dist/frontend/lowering/class-prototypes.js +8 -2
  228. package/dist/frontend/lowering/class-prototypes.js.map +1 -1
  229. package/dist/frontend/lowering/class-unions.d.ts +16 -0
  230. package/dist/frontend/lowering/class-unions.js +127 -0
  231. package/dist/frontend/lowering/class-unions.js.map +1 -0
  232. package/dist/frontend/lowering/coercions/builders.js +8 -1
  233. package/dist/frontend/lowering/coercions/builders.js.map +1 -1
  234. package/dist/frontend/lowering/coercions/function-adapters.js +2 -0
  235. package/dist/frontend/lowering/coercions/function-adapters.js.map +1 -1
  236. package/dist/frontend/lowering/coercions/identity.d.ts +7 -0
  237. package/dist/frontend/lowering/coercions/identity.js +26 -0
  238. package/dist/frontend/lowering/coercions/identity.js.map +1 -0
  239. package/dist/frontend/lowering/coercions/structural.js +9 -6
  240. package/dist/frontend/lowering/coercions/structural.js.map +1 -1
  241. package/dist/frontend/lowering/collection-narrowing.d.ts +6 -0
  242. package/dist/frontend/lowering/collection-narrowing.js +31 -0
  243. package/dist/frontend/lowering/collection-narrowing.js.map +1 -0
  244. package/dist/frontend/lowering/containers/array-callback-call.d.ts +7 -0
  245. package/dist/frontend/lowering/containers/array-callback-call.js +52 -0
  246. package/dist/frontend/lowering/containers/array-callback-call.js.map +1 -0
  247. package/dist/frontend/lowering/containers/array-callbacks.d.ts +1 -1
  248. package/dist/frontend/lowering/containers/array-callbacks.js +363 -49
  249. package/dist/frontend/lowering/containers/array-callbacks.js.map +1 -1
  250. package/dist/frontend/lowering/containers/array-concat.d.ts +7 -0
  251. package/dist/frontend/lowering/containers/array-concat.js +130 -0
  252. package/dist/frontend/lowering/containers/array-concat.js.map +1 -0
  253. package/dist/frontend/lowering/containers/array-construction.d.ts +2 -0
  254. package/dist/frontend/lowering/containers/array-construction.js +95 -3
  255. package/dist/frontend/lowering/containers/array-construction.js.map +1 -1
  256. package/dist/frontend/lowering/containers/array-methods.d.ts +1 -11
  257. package/dist/frontend/lowering/containers/array-methods.js +43 -132
  258. package/dist/frontend/lowering/containers/array-methods.js.map +1 -1
  259. package/dist/frontend/lowering/containers/collection-methods.js +10 -1
  260. package/dist/frontend/lowering/containers/collection-methods.js.map +1 -1
  261. package/dist/frontend/lowering/containers/indexed-objects.js +56 -70
  262. package/dist/frontend/lowering/containers/indexed-objects.js.map +1 -1
  263. package/dist/frontend/lowering/cycle-initialization.d.ts +41 -0
  264. package/dist/frontend/lowering/cycle-initialization.js +277 -0
  265. package/dist/frontend/lowering/cycle-initialization.js.map +1 -0
  266. package/dist/frontend/lowering/expressions/evaluation-safety.js +4 -0
  267. package/dist/frontend/lowering/expressions/evaluation-safety.js.map +1 -1
  268. package/dist/frontend/lowering/expressions/object-literals.js +138 -32
  269. package/dist/frontend/lowering/expressions/object-literals.js.map +1 -1
  270. package/dist/frontend/lowering/expressions/union-field-write.d.ts +4 -4
  271. package/dist/frontend/lowering/expressions/union-field-write.js +54 -44
  272. package/dist/frontend/lowering/expressions/union-field-write.js.map +1 -1
  273. package/dist/frontend/lowering/iterator-adapters.d.ts +20 -0
  274. package/dist/frontend/lowering/iterator-adapters.js +605 -0
  275. package/dist/frontend/lowering/iterator-adapters.js.map +1 -0
  276. package/dist/frontend/lowering/lower-array-sort.d.ts +1 -1
  277. package/dist/frontend/lowering/lower-array-sort.js +44 -5
  278. package/dist/frontend/lowering/lower-array-sort.js.map +1 -1
  279. package/dist/frontend/lowering/lower-array-sort.test.d.ts +1 -0
  280. package/dist/frontend/lowering/lower-array-sort.test.js +31 -0
  281. package/dist/frontend/lowering/lower-array-sort.test.js.map +1 -0
  282. package/dist/frontend/lowering/lower-assert.js +7 -0
  283. package/dist/frontend/lowering/lower-assert.js.map +1 -1
  284. package/dist/frontend/lowering/lower-builtin-values.js +1 -0
  285. package/dist/frontend/lowering/lower-builtin-values.js.map +1 -1
  286. package/dist/frontend/lowering/lower-calls.d.ts +3 -3
  287. package/dist/frontend/lowering/lower-calls.js +162 -121
  288. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  289. package/dist/frontend/lowering/lower-classes.d.ts +2 -0
  290. package/dist/frontend/lowering/lower-classes.js +45 -43
  291. package/dist/frontend/lowering/lower-classes.js.map +1 -1
  292. package/dist/frontend/lowering/lower-exprs.d.ts +9 -2
  293. package/dist/frontend/lowering/lower-exprs.js +390 -157
  294. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  295. package/dist/frontend/lowering/lower-functions.js +32 -19
  296. package/dist/frontend/lowering/lower-functions.js.map +1 -1
  297. package/dist/frontend/lowering/lower-inspect.js +26 -2
  298. package/dist/frontend/lowering/lower-inspect.js.map +1 -1
  299. package/dist/frontend/lowering/lower-modules.js +10 -2
  300. package/dist/frontend/lowering/lower-modules.js.map +1 -1
  301. package/dist/frontend/lowering/lower-stmts.js +46 -58
  302. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  303. package/dist/frontend/lowering/lowerer.d.ts +37 -0
  304. package/dist/frontend/lowering/lowerer.js +283 -18
  305. package/dist/frontend/lowering/lowerer.js.map +1 -1
  306. package/dist/frontend/lowering/native-ffi.js +3 -1
  307. package/dist/frontend/lowering/native-ffi.js.map +1 -1
  308. package/dist/frontend/lowering/runtime-optional-provenance.d.ts +15 -0
  309. package/dist/frontend/lowering/runtime-optional-provenance.js +40 -0
  310. package/dist/frontend/lowering/runtime-optional-provenance.js.map +1 -0
  311. package/dist/frontend/lowering/sanitize-class-types.test.js +2 -2
  312. package/dist/frontend/lowering/strict-equality.d.ts +9 -0
  313. package/dist/frontend/lowering/strict-equality.js +116 -0
  314. package/dist/frontend/lowering/strict-equality.js.map +1 -0
  315. package/dist/frontend/lowering/surfaces.js +3 -0
  316. package/dist/frontend/lowering/surfaces.js.map +1 -1
  317. package/dist/frontend/lowering/union-comparison.d.ts +5 -0
  318. package/dist/frontend/lowering/union-comparison.js +47 -0
  319. package/dist/frontend/lowering/union-comparison.js.map +1 -0
  320. package/dist/frontend/program.d.ts +38 -2
  321. package/dist/frontend/program.js +440 -59
  322. package/dist/frontend/program.js.map +1 -1
  323. package/dist/frontend/program.test.js +59 -6
  324. package/dist/frontend/program.test.js.map +1 -1
  325. package/dist/frontend/type-mapper.d.ts +1 -1
  326. package/dist/frontend/type-mapper.js +89 -14
  327. package/dist/frontend/type-mapper.js.map +1 -1
  328. package/dist/frontend/type-mapper.test.js +15 -3
  329. package/dist/frontend/type-mapper.test.js.map +1 -1
  330. package/dist/index.js +25 -9
  331. package/dist/index.js.map +1 -1
  332. package/dist/ir/analysis.d.ts +3 -1
  333. package/dist/ir/analysis.js +4 -0
  334. package/dist/ir/analysis.js.map +1 -1
  335. package/dist/ir/builtin-signatures.js +2 -3
  336. package/dist/ir/builtin-signatures.js.map +1 -1
  337. package/dist/ir/constant-tables.test.js +1 -1
  338. package/dist/ir/initializer-bindings.test.js +1 -1
  339. package/dist/ir/ir.d.ts +78 -15
  340. package/dist/ir/ir.js +20 -6
  341. package/dist/ir/ir.js.map +1 -1
  342. package/dist/ir/scalar-records.test.js +1 -1
  343. package/dist/ir/serialize.d.ts +1 -1
  344. package/dist/ir/serialize.js +3 -3
  345. package/dist/ir/serialize.js.map +1 -1
  346. package/dist/ir/traverse.js +8 -0
  347. package/dist/ir/traverse.js.map +1 -1
  348. package/dist/ir/validate.js +118 -20
  349. package/dist/ir/validate.js.map +1 -1
  350. package/dist/ir/validate.test.js +269 -8
  351. package/dist/ir/validate.test.js.map +1 -1
  352. package/dist/library/fence-eval.test.js +1 -1
  353. package/dist/library/int-infer.test.js +4 -4
  354. package/dist/library/library-cache.test.js +5 -5
  355. package/dist/library/library-profile.d.ts +2 -1
  356. package/dist/library/library-profile.js +4 -3
  357. package/dist/library/library-profile.js.map +1 -1
  358. package/dist/native/compiler.js.map +1 -1
  359. package/dist/native/compiler.test.js +2 -2
  360. package/dist/native/library.d.ts +2 -1
  361. package/dist/native/library.js.map +1 -1
  362. package/dist/native/prepare.js +27 -3
  363. package/dist/native/prepare.js.map +1 -1
  364. package/dist/native/sanitizer.d.ts +3 -2
  365. package/dist/native/sanitizer.js +3 -13
  366. package/dist/native/sanitizer.js.map +1 -1
  367. package/package.json +20 -20
  368. package/surface-manifest.json +8 -2
@@ -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,9 +20,9 @@ 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, STRING, 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";
@@ -44,7 +45,9 @@ import { analyzeCallLifetimes } from "./call-lifetimes.js";
44
45
  import { canStackUnion, emitStackUnion, findLocalStackUnions } from "./stack-unions.js";
45
46
  import { allocateLocalUnion, findLocalUnionStorage, storeLocalUnion, } from "./local-union-storage.js";
46
47
  import { BlockBuilder } from "./blocks.js";
47
- import { emitLocalArrayRead, emitBorrowedArrayRead, 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";
48
51
  import { emitStackMapRead, findMapReadLifetimes, matchMapRead, } from "./map-read-lifetimes.js";
49
52
  import { emitBorrowedFieldSequence } from "./borrowed-receivers.js";
50
53
  import { emitBorrowedInput } from "./borrowed-inputs.js";
@@ -52,10 +55,11 @@ import { ReferenceEffects } from "./reference-effects.js";
52
55
  import { LlvmDebugInfo } from "./debug-info.js";
53
56
  import { StackCallbacks } from "./stack-callbacks.js";
54
57
  import { findLoopArrayBorrows } from "./loop-array-borrows.js";
55
- import { findConstantCallbacks } from "./constant-callbacks.js";
58
+ import { AmbientReceiverReaders, findConstantCallbacks } from "./constant-callbacks.js";
56
59
  import { findScalarStringSlices, emitStringSliceSnapshot, } from "./string-slices.js";
60
+ import { LazyCaptures } from "./lazy-captures.js";
57
61
  import { StackCaptures } from "./stack-captures.js";
58
- import { f64Lit, ffiNativeTypeLl, ffiNativeParamLl, ffiNativeReturnLl, llvmCommentText, } from "./common.js";
62
+ import { ABSENT_FIELD_PAYLOAD, emitFieldAbsentTest, f64Lit, ffiNativeTypeLl, ffiNativeParamLl, ffiNativeReturnLl, llvmCommentText, } from "./common.js";
59
63
  import { ffiExtendsNarrowIntegers } from "../targets.js";
60
64
  import { emitLiteralExpr, emitOperatorExpr, emitStringExpr, emitContainerExpr, emitRecordExpr, } from "./expr-primitives.js";
61
65
  import { emitControlExpr } from "./expr-control.js";
@@ -79,7 +83,8 @@ import { buildClassGraph, classMembershipIntervals, emitClassMembershipHelper, c
79
83
  import { LlDyn } from "./dyn.js";
80
84
  import { LlvmUnsupportedError } from "./unsupported.js";
81
85
  import { LlWalkers } from "./walkers.js";
82
- 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";
83
88
  export { LlvmUnsupportedError } from "./unsupported.js";
84
89
  function ffiCallbackDummyLl(callback) {
85
90
  switch (callback.returns) {
@@ -182,6 +187,8 @@ export class LlEmitter {
182
187
  * immortal (rc == SIZE_MAX) static per (union, unit tag).
183
188
  * RC entry points and the collector skip immortals. */
184
189
  unitInstances = new Map();
190
+ /** unionId → symbol of the union's immortal ABSENT field-slot instance. */
191
+ absentInstances = new Map();
185
192
  immortalValues = new Set();
186
193
  /** Regex literal templates: "<flags>/<pattern>" → { symbol, interned
187
194
  * source/flags literal refs } — one immortal ScrRegex template per distinct
@@ -215,7 +222,13 @@ export class LlEmitter {
215
222
  needsBadTag = false;
216
223
  needsBadKey = false;
217
224
  needsRetainBox = false;
225
+ /** Inline RC fast-path helpers requested by call sites (shapes.ts); null
226
+ * when inline RC is off. */
227
+ rcHelpers;
218
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();
219
232
  referenceEffects;
220
233
  optionalArrayReads;
221
234
  callArrayReads = new Map();
@@ -224,8 +237,16 @@ export class LlEmitter {
224
237
  constantCallbacks;
225
238
  currentConstantCallbacks = new Map();
226
239
  stackCallbacks;
240
+ receiverReaders;
227
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();
228
247
  borrowedParameters = new Set();
248
+ /** Parameters proven projection-only: callers may pass stack boxes. */
249
+ projectedParameters = new Set();
229
250
  stableCallBindings = new Set();
230
251
  /** Manifest-bound native imports, used by ffiCall emission. */
231
252
  ffiByName = new Map();
@@ -265,6 +286,7 @@ export class LlEmitter {
265
286
  liveDynUnionRefAdapters = new Map();
266
287
  dynPromiseAdapters = new Map();
267
288
  unionsById = new Map();
289
+ nullableFields;
268
290
  recordsById = new Map();
269
291
  recordCloneShapes = new Set();
270
292
  tracedShapes;
@@ -370,6 +392,7 @@ export class LlEmitter {
370
392
  this.wasi = options.wasi === true;
371
393
  this.emitLibraryIdentity = options.emitLibraryIdentity !== false;
372
394
  this.runtimeAbiMarker = options.runtimeAbiMarker === true;
395
+ this.rcHelpers = options.inlineRc === true ? new Set() : null;
373
396
  // ScrCycHdr.color stays 12 bytes behind a wasm32 object and 16 bytes
374
397
  // behind a 64-bit object. The wasm32 header pads before color so its
375
398
  // payload remains double-aligned without changing this ABI offset.
@@ -396,12 +419,15 @@ export class LlEmitter {
396
419
  }
397
420
  for (const u of mod.unions ?? [])
398
421
  this.unionsById.set(u.id, u);
422
+ this.nullableFields = new NullableRefFields(mod.classes ?? [], this.unionsById);
399
423
  this.optionalArrayReads = new OptionalArrayReads(this.fnByName, this.unionsById);
400
424
  this.referenceEffects = new ReferenceEffects(this.fnByName, (call) => this.optionalArrayReads.get(call) !== null);
401
- this.callLifetimes = analyzeCallLifetimes(this.fnByName);
425
+ this.callLifetimes = analyzeCallLifetimes(this.fnByName, (className, field) => this.nullableFields.get(className, field) !== null);
402
426
  this.constantCallbacks = findConstantCallbacks(mod, this.callLifetimes);
403
427
  this.stackCallbacks = new StackCallbacks(this.fnByName);
428
+ this.receiverReaders = new AmbientReceiverReaders(this.fnByName);
404
429
  this.stackCaptures = new StackCaptures(this.fnByName, this.callLifetimes, this.stackCallbacks);
430
+ this.lazyCaptures = new LazyCaptures(this.fnByName);
405
431
  for (const r of mod.records ?? [])
406
432
  this.recordsById.set(r.id, r);
407
433
  const traced = computeTraced(mod);
@@ -414,6 +440,17 @@ export class LlEmitter {
414
440
  tracedUnions: this.tracedUnions,
415
441
  recordsById: this.recordsById,
416
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,
417
454
  unionsById: this.unionsById,
418
455
  declare: (decl) => this.declare(decl),
419
456
  needOom: () => this.needOom(),
@@ -421,6 +458,7 @@ export class LlEmitter {
421
458
  internLiteral: (text) => this.internLiteral(text),
422
459
  cstr: (text) => this.cstr(text),
423
460
  unitInstanceRef: (unionId, tag) => this.unitInstanceRef(unionId, tag),
461
+ absentInstanceRef: (unionId) => this.absentInstanceRef(unionId),
424
462
  liveDynRefAdapter: (type) => this.liveDynRefAdapter(type),
425
463
  liveDynUnionRefAdapter: (type) => this.liveDynUnionRefAdapter(type),
426
464
  dynPromiseAdapter: (type) => this.dynPromiseAdapter(type),
@@ -797,8 +835,11 @@ export class LlEmitter {
797
835
  // Function bodies first (the literal/unit/fn-value tables fill as they
798
836
  // emit), then the file assembles around them — the runtime ABI’s order.
799
837
  const fnDefs = [];
800
- for (const fn of this.mod.functions)
801
- 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
+ }
802
843
  const errorViews = [];
803
844
  for (const property of ["name", "message"])
804
845
  if (this.mod.functions.some((fn) => fn.name === `%error.${property}.read`)) {
@@ -817,7 +858,7 @@ export class LlEmitter {
817
858
  this.declare(`declare void @scr_error_register_dyn(ptr, ptr)`);
818
859
  errorViews.push(` call void @scr_error_register_dyn(ptr @${mangleVtInstance(meta.def.name)}, ptr @${name})`);
819
860
  }
820
- 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);
821
862
  const shapes = layouts.records;
822
863
  const classShapes = layouts.classes;
823
864
  const classObjDefs = layouts.classObjects;
@@ -1132,13 +1173,19 @@ export class LlEmitter {
1132
1173
  `%ScrLogArg = type { i32, i64 }`,
1133
1174
  `%ScrVt = type { ${this.sizeType}, ${this.sizeType}, ptr }`,
1134
1175
  `%ScrUnion = type { ${this.sizeType}, i32, ptr, ptr, ptr, i64 }`,
1135
- `%ScrClosure = type { ${this.sizeType}, ptr, ${this.sizeType}, ptr, i32 }`,
1176
+ `%ScrClosure = type { ${this.sizeType}, ptr, ${this.sizeType}, ptr, i32, i32, ptr }`,
1136
1177
  `%ScrFfiTable = type { ptr, ${this.sizeType}, ${this.sizeType}, ptr, i8, ptr, ptr, ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, ptr, ptr }`,
1137
- `%ScrRegex = type { ${this.sizeType}, ptr, ptr, ptr, double, ptr }`,
1178
+ `%ScrRegex = type { ${this.sizeType}, ptr, ptr, ptr, double, ptr, ptr }`,
1138
1179
  // ScrArr mirrors scr_runtime.h field-for-field. Live dynamic stream
1139
1180
  // commits swap its mutable dense, sparse, presence, and property
1140
1181
  // storage while preserving the target object's identity.
1141
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 }`]),
1142
1189
  // The runtime error prefix { rc, vt, name, message, code, cause } and the
1143
1190
  // class-object shape { rc, pre, post, ctor, name } — field reads on
1144
1191
  // builtin errors and classval loads GEP through these.
@@ -1184,12 +1231,13 @@ export class LlEmitter {
1184
1231
  // data (string literals, unit arms, template arrays, vtables) stays
1185
1232
  // shared.
1186
1233
  const tl = this.mod.workers === true || this.mod.lib?.threadInstances === true ? "thread_local " : "";
1187
- 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_v7()`] : []), ...(this.runtimeAbiMarker && this.mod.lib === undefined
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
1188
1235
  ? [`declare void @${RUNTIME_ABI_MARKER}()`]
1189
1236
  : []));
1190
1237
  for (const d of this.decls)
1191
1238
  out.push(d);
1192
1239
  out.push(``);
1240
+ const flushedDecls = new Set(this.decls);
1193
1241
  for (const table of this.constantNumericTables.values()) {
1194
1242
  const n = table.values.length;
1195
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:`,
@@ -1200,8 +1248,11 @@ export class LlEmitter {
1200
1248
  for (const [text, lit] of this.literals) {
1201
1249
  // Immortal interned ScrStr: { rc = SIZE_MAX, len, cap = len, bytes\0 } —
1202
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);
1203
1254
  out.push(`@${lit.sym} = internal global { ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, [${lit.len + 1} x i8] } ` +
1204
- `{ ${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)}" }`);
1205
1256
  }
1206
1257
  if (this.literals.size > 0)
1207
1258
  out.push(``);
@@ -1214,11 +1265,23 @@ export class LlEmitter {
1214
1265
  }
1215
1266
  if (this.unitInstances.size > 0)
1216
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(``);
1217
1279
  for (const [key, re] of this.regexInstances) {
1218
1280
  // One immortal ScrRegex per (pattern, flags) literal, pointing at
1219
- // the interned source/flags strings. The bc slot starts null (lazy
1220
- // compile, cached by the runtime) — a mutable global, not constant.
1221
- 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")}`);
1222
1285
  }
1223
1286
  if (this.regexInstances.size > 0)
1224
1287
  out.push(``);
@@ -1367,6 +1430,7 @@ export class LlEmitter {
1367
1430
  // symbols specified by the library IR instead.
1368
1431
  for (const line of this.emitLibDefs(globals, globalReleaseLines, stamps))
1369
1432
  out.push(line);
1433
+ out.push(...this.inlineRcTail(flushedDecls));
1370
1434
  out.push(`attributes #0 = { sanitize_address }`);
1371
1435
  if (this.wasi)
1372
1436
  out.push(`attributes #1 = { sanitize_address presplitcoroutine }`);
@@ -1378,7 +1442,7 @@ export class LlEmitter {
1378
1442
  out.push(this.debug.render());
1379
1443
  return out;
1380
1444
  }
1381
- 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_v7()`] : []), ` 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,
1382
1446
  // Event-surface programs (signal/exit listeners) fill the loop's
1383
1447
  // nullable event hooks before %main — scr_events.c links only when
1384
1448
  // this line is emitted (native/executable.ts gates on the same predicate).
@@ -1486,7 +1550,7 @@ export class LlEmitter {
1486
1550
  ? [` %test_exit = call i32 @scr_test_exit_code()`, ` ret i32 %test_exit`]
1487
1551
  : usesIsland
1488
1552
  ? [` %island_exit = call i32 @scr_island_exit_code()`, ` ret i32 %island_exit`]
1489
- : [` %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),
1490
1554
  // sanitize_address is inert under the plain pipeline; the sanitized
1491
1555
  // lane's -fsanitize=address link activates instrumentation over the
1492
1556
  // emitted functions too (the runtime TUs get theirs from clang).
@@ -1743,10 +1807,62 @@ export class LlEmitter {
1743
1807
  // emitted once with internal linkage, like the record retains.
1744
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`, `}`, ``);
1745
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
+ }
1746
1854
  // The declarations these helpers added must land in the extern block,
1747
1855
  // which already flushed — append here instead (LLVM is order-free).
1748
1856
  return defs.length > 0 ? [...defs] : defs;
1749
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
+ }
1750
1866
  /** Env-signature wrappers + interned immortal closures for declared
1751
1867
  * functions used as values (the runtime ABI’s sc_w_/sc_fc_ pair): every
1752
1868
  * mention of `f` yields the same pointer, so `f === f` holds. */
@@ -1761,7 +1877,7 @@ export class LlEmitter {
1761
1877
  // never the inner return type.
1762
1878
  const ret = fn.async === true || fn.generator !== undefined ? "ptr" : this.llType(fn.returnType);
1763
1879
  const call = `call ${ret} @${this.callTarget(name)}(${args})`;
1764
- 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)} }`, ``);
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 }`, ``);
1765
1881
  }
1766
1882
  return out;
1767
1883
  }
@@ -1815,6 +1931,154 @@ export class LlEmitter {
1815
1931
  });
1816
1932
  return { definitions, pack, lifted, fieldTys, ret, tr, spawnParams, argPackLines };
1817
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
+ }
1818
2082
  /** Per-async-function machinery — async.ts's scaffolding, .ll
1819
2083
  * flavored: an argument-pack struct type, a fiber trampoline (unpacks,
1820
2084
  * frees the pack, runs the ordinary compiled body, settles the
@@ -1829,9 +2093,20 @@ export class LlEmitter {
1829
2093
  this.declare(`declare void @llvm.coro.destroy(ptr)`);
1830
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`, `}`, ``);
1831
2095
  }
2096
+ const fiberless = this.fiberlessAsyncFunctions();
1832
2097
  for (const fn of this.mod.functions) {
1833
2098
  if (fn.async !== true || fn.generator !== undefined)
1834
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
+ }
1835
2110
  const { definitions, ret, tr, spawnParams, argPackLines } = this.emitArgPackAndTrampolinePrologue(fn);
1836
2111
  for (const line of definitions)
1837
2112
  out.push(line);
@@ -1849,34 +2124,8 @@ export class LlEmitter {
1849
2124
  this.declare(`declare void @scr_closure_release(ptr)`);
1850
2125
  }
1851
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)`);
1852
- switch (ret.kind) {
1853
- case "void":
1854
- this.declare(`declare void @scr_promise_fulfill_void(ptr)`);
1855
- tr.push(` call void @scr_promise_fulfill_void(ptr %pr)`);
1856
- break;
1857
- case "f64":
1858
- case "date":
1859
- this.declare(`declare void @scr_promise_fulfill_f64(ptr, double)`);
1860
- tr.push(` call void @scr_promise_fulfill_f64(ptr %pr, double %r)`);
1861
- break;
1862
- case "bool":
1863
- this.declare(`declare void @scr_promise_fulfill_bool(ptr, i1 zeroext)`);
1864
- tr.push(` call void @scr_promise_fulfill_bool(ptr %pr, i1 %r)`);
1865
- break;
1866
- case "string":
1867
- this.declare(`declare void @scr_promise_fulfill_str(ptr, ptr)`);
1868
- tr.push(` call void @scr_promise_fulfill_str(ptr %pr, ptr %r) ; moves in`);
1869
- break;
1870
- case "dyn":
1871
- this.declare(`declare void @scr_promise_resolve_dyn(ptr, ptr)`);
1872
- tr.push(` call void @scr_promise_resolve_dyn(ptr %pr, ptr %r) ; moves in`);
1873
- break;
1874
- default: {
1875
- const v = vAdapters(this.shapeHost, ret);
1876
- this.declare(`declare void @scr_promise_fulfill_ref(ptr, ptr, ptr, ptr, ptr)`);
1877
- tr.push(` call void @scr_promise_fulfill_ref(ptr %pr, ptr %r, ptr ${v.retain}, ptr ${v.release}, ptr ${traceArg(this.shapeHost, ret)})`);
1878
- }
1879
- }
2127
+ for (const line of this.asyncFulfillLines(ret, "%pr"))
2128
+ tr.push(line);
1880
2129
  tr.push(` ret void`, `thrown:`);
1881
2130
  if (ret.kind !== "void" && isRefCounted(ret)) {
1882
2131
  // An escaping throw means %r is the never-read dummy (NULL).
@@ -2115,6 +2364,40 @@ export class LlEmitter {
2115
2364
  this.immortalValues.add(name);
2116
2365
  return name;
2117
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
+ }
2118
2401
  declare(decl) {
2119
2402
  this.decls.add(decl);
2120
2403
  }
@@ -2174,8 +2457,7 @@ export class LlEmitter {
2174
2457
  const t = this.B.tmp();
2175
2458
  this.B.line(`${t} = load ptr, ptr ${v.name}`);
2176
2459
  if (v.boxed) {
2177
- this.declare(`declare void @scr_box_release(ptr)`);
2178
- this.B.line(`call void @scr_box_release(ptr ${t})`);
2460
+ this.B.line(`call void ${boxReleaseSym(this.shapeHost)}(ptr ${t})`);
2179
2461
  }
2180
2462
  else {
2181
2463
  this.releaseValue(t, v.type);
@@ -2191,8 +2473,7 @@ export class LlEmitter {
2191
2473
  const t = this.B.tmp();
2192
2474
  this.B.line(`${t} = load ptr, ptr ${e.slot}`);
2193
2475
  if (e.boxed) {
2194
- this.declare(`declare void @scr_box_release(ptr)`);
2195
- this.B.line(`call void @scr_box_release(ptr ${t})`);
2476
+ this.B.line(`call void ${boxReleaseSym(this.shapeHost)}(ptr ${t})`);
2196
2477
  }
2197
2478
  else {
2198
2479
  this.releaseValue(t, e.type); // runtime releases are NULL-tolerant
@@ -2272,13 +2553,13 @@ export class LlEmitter {
2272
2553
  terminator = `br label %${this.currentWasiCoro.finalLabel}`;
2273
2554
  }
2274
2555
  else {
2275
- const t = this.currentReturnType;
2556
+ const t = this.llType(this.currentReturnType);
2276
2557
  terminator =
2277
- t.kind === "void"
2558
+ t === "void"
2278
2559
  ? "ret void"
2279
- : t.kind === "f64" || t.kind === "date"
2560
+ : t === "double"
2280
2561
  ? `ret double ${f64Lit(0)}`
2281
- : t.kind === "bool"
2562
+ : t === "i1"
2282
2563
  ? "ret i1 false"
2283
2564
  : "ret ptr null";
2284
2565
  }
@@ -2424,14 +2705,10 @@ export class LlEmitter {
2424
2705
  this.unionTagSwitch(v.name, def, (arm) => {
2425
2706
  let valueName = "false";
2426
2707
  if (arm.kind === "f64" || arm.kind === "procStream") {
2427
- valueName = B.tmp();
2428
- this.declare(`declare double @scr_union_get_f64(ptr)`);
2429
- B.line(`${valueName} = call double @scr_union_get_f64(ptr ${v.name})`);
2708
+ valueName = this.unionGetF64(v.name);
2430
2709
  }
2431
2710
  else if (arm.kind === "bool") {
2432
- valueName = B.tmp();
2433
- this.declare(`declare zeroext i1 @scr_union_get_bool(ptr)`);
2434
- B.line(`${valueName} = call zeroext i1 @scr_union_get_bool(ptr ${v.name})`);
2711
+ valueName = this.unionGetBool(v.name);
2435
2712
  }
2436
2713
  else if (arm.kind === "string") {
2437
2714
  valueName = this.unionPeek(v.name);
@@ -2610,24 +2887,34 @@ export class LlEmitter {
2610
2887
  B.terminate(`unreachable`);
2611
2888
  }
2612
2889
  /** The +1 extraction of a union's single narrowed arm (unionNarrow /
2613
- * the nullish-family reads): scalars via the runtime getters, ref arms
2890
+ * the nullish-family reads): scalars via inline payload loads, ref arms
2614
2891
  * a retained peek. */
2615
2892
  unionExtract(uName, arm) {
2616
- const B = this.B;
2617
- if (arm.kind === "f64" || arm.kind === "procStream") {
2618
- const t = B.tmp();
2619
- this.declare(`declare double @scr_union_get_f64(ptr)`);
2620
- B.line(`${t} = call double @scr_union_get_f64(ptr ${uName})`);
2621
- return t;
2622
- }
2623
- if (arm.kind === "bool") {
2624
- const t = B.tmp();
2625
- this.declare(`declare zeroext i1 @scr_union_get_bool(ptr)`);
2626
- B.line(`${t} = call zeroext i1 @scr_union_get_bool(ptr ${uName})`);
2627
- return t;
2628
- }
2893
+ if (arm.kind === "f64" || arm.kind === "procStream")
2894
+ return this.unionGetF64(uName);
2895
+ if (arm.kind === "bool")
2896
+ return this.unionGetBool(uName);
2629
2897
  return this.retainValue(this.unionPeek(uName), arm);
2630
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
+ }
2631
2918
  /** Constructs a union box around an OWNED (+1, already moved) value —
2632
2919
  * the scr_union_new_* dispatch of unionWrap and the wrap-into-join
2633
2920
  * sites (shift). */
@@ -2806,17 +3093,26 @@ export class LlEmitter {
2806
3093
  this.B.line(`${b} = load ptr, ptr ${slot}`);
2807
3094
  return b;
2808
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
+ }
2809
3102
  boxGet(box, t) {
2810
3103
  const B = this.B;
2811
3104
  const acc = boxAccess(t);
2812
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.
2813
3109
  if (acc === "f64") {
2814
- this.declare(`declare double @scr_box_get_f64(ptr)`);
2815
- B.line(`${r} = call double @scr_box_get_f64(ptr ${box})`);
3110
+ B.line(`${r} = load double, ptr ${this.boxSlot(box)}`);
2816
3111
  }
2817
3112
  else if (acc === "bool") {
2818
- this.declare(`declare zeroext i1 @scr_box_get_bool(ptr)`);
2819
- 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`);
2820
3116
  }
2821
3117
  else {
2822
3118
  this.declare(`declare ptr @scr_box_get_ref(ptr)`);
@@ -2829,18 +3125,45 @@ export class LlEmitter {
2829
3125
  const B = this.B;
2830
3126
  const acc = boxAccess(t);
2831
3127
  if (acc === "f64") {
2832
- this.declare(`declare void @scr_box_set_f64(ptr, double)`);
2833
- B.line(`call void @scr_box_set_f64(ptr ${box}, double ${value})`);
3128
+ B.line(`store double ${value}, ptr ${this.boxSlot(box)}`);
2834
3129
  }
2835
3130
  else if (acc === "bool") {
2836
- this.declare(`declare void @scr_box_set_bool(ptr, i1 zeroext)`);
2837
- 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)}`);
2838
3134
  }
2839
3135
  else {
2840
3136
  this.declare(`declare void @scr_box_set_ref(ptr, ptr)`);
2841
3137
  B.line(`call void @scr_box_set_ref(ptr ${box}, ptr ${value})`);
2842
3138
  }
2843
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
+ }
2844
3167
  retainBox(box) {
2845
3168
  this.needsRetainBox = true;
2846
3169
  const t = this.B.tmp();
@@ -2885,6 +3208,12 @@ export class LlEmitter {
2885
3208
  B.line(`${empty} = icmp eq ptr ${value}, null`);
2886
3209
  this.throwIfUninitialized(empty, name);
2887
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
+ }
2888
3217
  throwIfUninitialized(empty, name) {
2889
3218
  const B = this.B;
2890
3219
  const lt = B.newLabel("tdz.t");
@@ -3053,13 +3382,23 @@ export class LlEmitter {
3053
3382
  this.unwindCleanups.clear();
3054
3383
  this.jumpTargets = [];
3055
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
+ }
3056
3395
  this.currentConstantCallbacks =
3057
3396
  this.debug === null ? (this.constantCallbacks.get(fn.name) ?? new Map()) : new Map();
3058
3397
  // Preserve concrete source bindings for debugger inspection. Suspended
3059
3398
  // functions keep the established array lifetime across continuations.
3060
3399
  this.streamingSplitsEnabled = this.debug === null && !fn.async && !fn.generator;
3061
3400
  this.stackCallbacks.reset(this.streamingSplitsEnabled);
3062
- this.stackCaptures.reset(fn, this.streamingSplitsEnabled);
3401
+ this.stackCaptures.reset(fn, this.streamingSplitsEnabled, lazyParameters);
3063
3402
  this.privateSplitLocals = this.streamingSplitsEnabled ? findPrivateSplitLocals(fn) : new Map();
3064
3403
  this.storedSplits.clear();
3065
3404
  this.splitSpans.clear();
@@ -3068,6 +3407,11 @@ export class LlEmitter {
3068
3407
  const numericFn = withInitializerBindings(fn, initializerBindings);
3069
3408
  this.numericLocals = new Map(numericFn.locals.map((l) => [l.id, l]));
3070
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);
3071
3415
  this.stableCallBindings = this.callLifetimes.bindings.get(fn.name) ?? new Set();
3072
3416
  const borrowedParameterIndexes = this.callLifetimes.borrowed.get(fn.name);
3073
3417
  if (borrowedParameterIndexes) {
@@ -3095,7 +3439,7 @@ export class LlEmitter {
3095
3439
  this.debug === null ? findScalarStringSlices(fn, this.callLifetimes) : new Map();
3096
3440
  this.mapReadLifetimes = findMapReadLifetimes(fn, this.unionsById, this.callLifetimes);
3097
3441
  this.localStackUnions = findLocalStackUnions(fn, this.callLifetimes, this.unionsById);
3098
- this.localUnionStorageProofs = findLocalUnionStorage(fn, this.callLifetimes, this.unionsById);
3442
+ this.localUnionStorageProofs = findLocalUnionStorage(fn, this.callLifetimes, this.unionsById, (e) => this.nullableFieldGet(e) !== null);
3099
3443
  this.localUnionStorage = new Map();
3100
3444
  this.integerRanges = analyzeIntegerRanges(numericFn);
3101
3445
  this.bytesBounds = findBytesBounds(numericFn, this.integerRanges);
@@ -3154,7 +3498,7 @@ export class LlEmitter {
3154
3498
  B.line(`${value} = load double, ptr @${mangleGlobal(global.id)}`);
3155
3499
  this.storeIntegerView(global.id, { name: value, type: global.type });
3156
3500
  }
3157
- for (const local of fn.locals) {
3501
+ for (const local of this.currentLocals.values()) {
3158
3502
  // Boxed locals' slots hold their capture BOX (a ptr); captured
3159
3503
  // (env-borrowed) locals bind the incoming box below. A caught-typed
3160
3504
  // local is a catch binding: its slot holds the ScrCaught snapshot
@@ -3203,6 +3547,21 @@ export class LlEmitter {
3203
3547
  // params (callees own their params — callers passed +1). Boxed params
3204
3548
  // allocate the shared binding and move the raw value in.
3205
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
+ }
3206
3565
  const borrowed = this.callLifetimes.borrowed.get(fn.name);
3207
3566
  for (const [index, p] of fn.params.entries()) {
3208
3567
  const local = this.currentLocals.get(p.localId);
@@ -3562,9 +3921,7 @@ export class LlEmitter {
3562
3921
  const acc = elemAccess(s.arr.type.elem);
3563
3922
  if (acc === "ref")
3564
3923
  this.moveTemp(v);
3565
- const argTy = acc === "f64" ? "double" : acc === "bool" ? "i1" : "ptr";
3566
- this.declare(`declare void @scr_arr_set_${acc}(ptr, double, ${argTy === "i1" ? "i1 zeroext" : argTy})`);
3567
- 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);
3568
3925
  break;
3569
3926
  }
3570
3927
  case "arraySetLength": {
@@ -3612,6 +3969,12 @@ export class LlEmitter {
3612
3969
  // release — a release can trigger a cycle collection, which must
3613
3970
  // never see a heap edge whose count was already given up).
3614
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
+ }
3615
3978
  const obj = isRefCounted(s.value.type)
3616
3979
  ? this.emitExpr(s.obj)
3617
3980
  : this.emitStableReceiver(s.obj, [s.value]);
@@ -3695,7 +4058,7 @@ export class LlEmitter {
3695
4058
  for (const f of shape.fields) {
3696
4059
  const lit = this.internLiteral(f.name);
3697
4060
  const hit = B.tmp();
3698
- 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)}`);
3699
4062
  const lh = B.newLabel("rks.h");
3700
4063
  const ln = B.newLabel("rks.n");
3701
4064
  B.condBr(hit, lh, ln);
@@ -3751,7 +4114,7 @@ export class LlEmitter {
3751
4114
  // typeEquals(f.type, iv) — the frontend fences everything else.
3752
4115
  const lit = this.internLiteral(f.name);
3753
4116
  const hit = B.tmp();
3754
- 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)}`);
3755
4118
  const lh = B.newLabel("rks.h");
3756
4119
  const ln = B.newLabel("rks.n");
3757
4120
  B.condBr(hit, lh, ln);
@@ -4125,10 +4488,13 @@ export class LlEmitter {
4125
4488
  B.line(`${inBounds} = icmp ne ptr ${cur}, null`);
4126
4489
  }
4127
4490
  else {
4128
- const i = B.tmp(), len = B.tmp();
4129
- this.declare(`declare double @scr_arr_len(ptr)`);
4491
+ const i = B.tmp(), lenPtr = B.tmp(), rawLen = B.tmp(), len = B.tmp();
4130
4492
  B.line(`${i} = load double, ptr ${idxSlot}`);
4131
- B.line(`${len} = call double @scr_arr_len(ptr ${arr.name})`);
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`);
4132
4498
  B.line(`${inBounds} = fcmp olt double ${i}, ${len}`);
4133
4499
  cur = i;
4134
4500
  }
@@ -4152,12 +4518,8 @@ export class LlEmitter {
4152
4518
  const borrowedElement = !snapshot && this.loopArrayBorrows.has(s);
4153
4519
  if (!snapshot) {
4154
4520
  const acc = elemAccess(elem);
4155
- const accTy = acc === "f64" ? "double" : acc === "bool" ? "i1" : "ptr";
4156
4521
  const getter = borrowedElement ? "scr_arr_borrow_ref" : `scr_arr_get_${acc}`;
4157
- this.declare(`declare ${acc === "bool" ? "zeroext i1" : accTy} @${getter}(ptr, double)`);
4158
- const value = B.tmp();
4159
- B.line(`${value} = call ${accTy} @${getter}(ptr ${arr.name}, double ${cur})`);
4160
- cur = value;
4522
+ cur = emitDenseArrayGet(this, arr.name, { name: cur, type: { kind: "f64" } }, undefined, acc, elem, getter, !borrowedElement);
4161
4523
  }
4162
4524
  if (spanLength) {
4163
4525
  const length = B.tmp();
@@ -4553,7 +4915,7 @@ export class LlEmitter {
4553
4915
  const hit = B.tmp();
4554
4916
  if (c.test.type.kind === "string") {
4555
4917
  this.declare(`declare zeroext i1 @scr_str_eq(ptr, ptr)`);
4556
- 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})`);
4557
4919
  }
4558
4920
  else if (c.test.type.kind === "bool") {
4559
4921
  B.line(`${hit} = icmp eq i1 ${disc.name}, ${t.name}`);
@@ -4751,17 +5113,26 @@ export class LlEmitter {
4751
5113
  this.ownSlot(result.owner.slot, result.owner.type);
4752
5114
  return result.value;
4753
5115
  }
5116
+ const arrayRead = this.optionalArrayReads.get(e);
5117
+ if (arrayRead)
5118
+ return emitProjectedArrayRead(this, arrayRead);
4754
5119
  if (canStackUnion(e, this.unionsById))
4755
5120
  return emitStackUnion(this, e).value;
4756
5121
  if (e.kind === "unionNarrow") {
4757
- const union = this.emitReadReceiver(e.value);
5122
+ const union = this.emitUnionProjection(e.value);
4758
5123
  return { name: this.unionPeek(union.name), type: e.type };
4759
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);
4760
5129
  if (e.kind === "recordGet" || e.kind === "fieldGet") {
4761
5130
  const receiver = this.emitReadReceiver(e.obj);
4762
- const { ptr, type } = e.kind === "recordGet"
4763
- ? this.recordFieldPtr(receiver.name, e.shapeId, e.field)
4764
- : 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);
4765
5136
  return { name: this.loadField(ptr, type), type: e.type };
4766
5137
  }
4767
5138
  if (e.kind === "ternary" && this.canBorrowReceiver(e.then) && this.canBorrowReceiver(e.else_)) {
@@ -4791,6 +5162,153 @@ export class LlEmitter {
4791
5162
  }
4792
5163
  return this.emitExpr(e);
4793
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
+ }
4794
5312
  canBorrowReceiver(e) {
4795
5313
  switch (e.kind) {
4796
5314
  case "varRef": {