@scriptc/compiler 0.3.3 → 0.3.5

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 (352) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +844 -36
  2. package/dist/backend/cycle-mutability.d.ts +5 -0
  3. package/dist/backend/cycle-mutability.js +62 -3
  4. package/dist/backend/cycle-mutability.js.map +1 -1
  5. package/dist/backend/executable-features.js +1 -0
  6. package/dist/backend/executable-features.js.map +1 -1
  7. package/dist/backend/library-pack.js +1 -0
  8. package/dist/backend/library-pack.js.map +1 -1
  9. package/dist/backend/llvm/array-search.js +1 -1
  10. package/dist/backend/llvm/array-search.js.map +1 -1
  11. package/dist/backend/llvm/call-lifetimes.d.ts +11 -1
  12. package/dist/backend/llvm/call-lifetimes.js +30 -3
  13. package/dist/backend/llvm/call-lifetimes.js.map +1 -1
  14. package/dist/backend/llvm/call-lifetimes.test.js +31 -1
  15. package/dist/backend/llvm/call-lifetimes.test.js.map +1 -1
  16. package/dist/backend/llvm/common.d.ts +5 -0
  17. package/dist/backend/llvm/common.js +7 -0
  18. package/dist/backend/llvm/common.js.map +1 -1
  19. package/dist/backend/llvm/constant-callbacks.d.ts +2 -21
  20. package/dist/backend/llvm/constant-callbacks.js +36 -219
  21. package/dist/backend/llvm/constant-callbacks.js.map +1 -1
  22. package/dist/backend/llvm/constant-callbacks.test.js +22 -0
  23. package/dist/backend/llvm/constant-callbacks.test.js.map +1 -1
  24. package/dist/backend/llvm/dense-array-access.d.ts +9 -0
  25. package/dist/backend/llvm/dense-array-access.js +29 -3
  26. package/dist/backend/llvm/dense-array-access.js.map +1 -1
  27. package/dist/backend/llvm/dyn.d.ts +46 -1
  28. package/dist/backend/llvm/dyn.js +218 -33
  29. package/dist/backend/llvm/dyn.js.map +1 -1
  30. package/dist/backend/llvm/emitter.d.ts +87 -1
  31. package/dist/backend/llvm/emitter.js +568 -61
  32. package/dist/backend/llvm/emitter.js.map +1 -1
  33. package/dist/backend/llvm/expr-async.js +17 -7
  34. package/dist/backend/llvm/expr-async.js.map +1 -1
  35. package/dist/backend/llvm/expr-bytes.d.ts +9 -0
  36. package/dist/backend/llvm/expr-bytes.js +159 -64
  37. package/dist/backend/llvm/expr-bytes.js.map +1 -1
  38. package/dist/backend/llvm/expr-callbacks.d.ts +11 -0
  39. package/dist/backend/llvm/expr-callbacks.js +53 -0
  40. package/dist/backend/llvm/expr-callbacks.js.map +1 -1
  41. package/dist/backend/llvm/expr-calls.d.ts +3 -1
  42. package/dist/backend/llvm/expr-calls.js +137 -3
  43. package/dist/backend/llvm/expr-calls.js.map +1 -1
  44. package/dist/backend/llvm/expr-containers.js +63 -29
  45. package/dist/backend/llvm/expr-containers.js.map +1 -1
  46. package/dist/backend/llvm/expr-primitives.js +9 -1
  47. package/dist/backend/llvm/expr-primitives.js.map +1 -1
  48. package/dist/backend/llvm/expr-stream-bridges.js +4 -0
  49. package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
  50. package/dist/backend/llvm/integer-clones.d.ts +103 -0
  51. package/dist/backend/llvm/integer-clones.js +1336 -0
  52. package/dist/backend/llvm/integer-clones.js.map +1 -0
  53. package/dist/backend/llvm/integer-clones.test.d.ts +1 -0
  54. package/dist/backend/llvm/integer-clones.test.js +219 -0
  55. package/dist/backend/llvm/integer-clones.test.js.map +1 -0
  56. package/dist/backend/llvm/intern-tables.test.d.ts +1 -0
  57. package/dist/backend/llvm/intern-tables.test.js +77 -0
  58. package/dist/backend/llvm/intern-tables.test.js.map +1 -0
  59. package/dist/backend/llvm/lib-datagram.js +24 -0
  60. package/dist/backend/llvm/lib-datagram.js.map +1 -1
  61. package/dist/backend/llvm/lib-dispatch.js +96 -1
  62. package/dist/backend/llvm/lib-dispatch.js.map +1 -1
  63. package/dist/backend/llvm/lib-filesystem.js +51 -0
  64. package/dist/backend/llvm/lib-filesystem.js.map +1 -1
  65. package/dist/backend/llvm/lib-network.js +16 -0
  66. package/dist/backend/llvm/lib-network.js.map +1 -1
  67. package/dist/backend/llvm/lib-process.js +24 -0
  68. package/dist/backend/llvm/lib-process.js.map +1 -1
  69. package/dist/backend/llvm/lib-shared.d.ts +5 -0
  70. package/dist/backend/llvm/lib-shared.js +114 -3
  71. package/dist/backend/llvm/lib-shared.js.map +1 -1
  72. package/dist/backend/llvm/lib-stream.js +87 -0
  73. package/dist/backend/llvm/lib-stream.js.map +1 -1
  74. package/dist/backend/llvm/lib-test.js +9 -0
  75. package/dist/backend/llvm/lib-test.js.map +1 -1
  76. package/dist/backend/llvm/lib-tls.js +6 -0
  77. package/dist/backend/llvm/lib-tls.js.map +1 -1
  78. package/dist/backend/llvm/local-array-reads.js +1 -1
  79. package/dist/backend/llvm/local-array-reads.js.map +1 -1
  80. package/dist/backend/llvm/map-entry-fusion.d.ts +28 -0
  81. package/dist/backend/llvm/map-entry-fusion.js +233 -0
  82. package/dist/backend/llvm/map-entry-fusion.js.map +1 -0
  83. package/dist/backend/llvm/map-entry-fusion.test.d.ts +1 -0
  84. package/dist/backend/llvm/map-entry-fusion.test.js +183 -0
  85. package/dist/backend/llvm/map-entry-fusion.test.js.map +1 -0
  86. package/dist/backend/llvm/map-number-lookup.d.ts +6 -0
  87. package/dist/backend/llvm/map-number-lookup.js +23 -1
  88. package/dist/backend/llvm/map-number-lookup.js.map +1 -1
  89. package/dist/backend/llvm/map-number-lookup.test.js +60 -5
  90. package/dist/backend/llvm/map-number-lookup.test.js.map +1 -1
  91. package/dist/backend/llvm/map-read-lifetimes.d.ts +11 -0
  92. package/dist/backend/llvm/map-read-lifetimes.js +92 -1
  93. package/dist/backend/llvm/map-read-lifetimes.js.map +1 -1
  94. package/dist/backend/llvm/map-read-lifetimes.test.js +131 -3
  95. package/dist/backend/llvm/map-read-lifetimes.test.js.map +1 -1
  96. package/dist/backend/llvm/publish.d.ts +21 -0
  97. package/dist/backend/llvm/publish.js +101 -11
  98. package/dist/backend/llvm/publish.js.map +1 -1
  99. package/dist/backend/llvm/publish.test.d.ts +1 -0
  100. package/dist/backend/llvm/publish.test.js +94 -0
  101. package/dist/backend/llvm/publish.test.js.map +1 -0
  102. package/dist/backend/llvm/record-results.js +76 -2
  103. package/dist/backend/llvm/record-results.js.map +1 -1
  104. package/dist/backend/llvm/record-sources.d.ts +7 -0
  105. package/dist/backend/llvm/record-sources.js +14 -0
  106. package/dist/backend/llvm/record-sources.js.map +1 -0
  107. package/dist/backend/llvm/reference-writes.js +5 -2
  108. package/dist/backend/llvm/reference-writes.js.map +1 -1
  109. package/dist/backend/llvm/regions.d.ts +4 -1
  110. package/dist/backend/llvm/regions.js +12 -3
  111. package/dist/backend/llvm/regions.js.map +1 -1
  112. package/dist/backend/llvm/shapes.d.ts +9 -3
  113. package/dist/backend/llvm/shapes.js +18 -14
  114. package/dist/backend/llvm/shapes.js.map +1 -1
  115. package/dist/backend/llvm/shapes.test.js +5 -3
  116. package/dist/backend/llvm/shapes.test.js.map +1 -1
  117. package/dist/backend/llvm/string-lifetimes.test.js +75 -1
  118. package/dist/backend/llvm/string-lifetimes.test.js.map +1 -1
  119. package/dist/backend/llvm/string-reads.js +2 -2
  120. package/dist/backend/llvm/string-reads.js.map +1 -1
  121. package/dist/backend/llvm/string-slices.js +2 -2
  122. package/dist/backend/llvm/string-slices.js.map +1 -1
  123. package/dist/backend/llvm/walkers.d.ts +17 -0
  124. package/dist/backend/llvm/walkers.js +123 -0
  125. package/dist/backend/llvm/walkers.js.map +1 -1
  126. package/dist/backend/may-throw.js +4 -0
  127. package/dist/backend/may-throw.js.map +1 -1
  128. package/dist/backend/native/contracts.d.ts +5 -1
  129. package/dist/backend/native/executable.js +16 -6
  130. package/dist/backend/native/executable.js.map +1 -1
  131. package/dist/backend/native/runtime-inputs.d.ts +1 -1
  132. package/dist/backend/native/runtime-inputs.js +1 -0
  133. package/dist/backend/native/runtime-inputs.js.map +1 -1
  134. package/dist/backend/native-codegen-core.d.ts +4 -4
  135. package/dist/backend/native-codegen-core.js +4 -6
  136. package/dist/backend/native-codegen-core.js.map +1 -1
  137. package/dist/backend/native-codegen.test.js +7 -8
  138. package/dist/backend/native-codegen.test.js.map +1 -1
  139. package/dist/backend/native-link-info-core.d.ts +3 -0
  140. package/dist/backend/native-link-info-core.js.map +1 -1
  141. package/dist/backend/publication.js +3 -1
  142. package/dist/backend/publication.js.map +1 -1
  143. package/dist/backend/runtime-abi.d.ts +2 -2
  144. package/dist/backend/runtime-abi.js +2 -2
  145. package/dist/backend/runtime-pack-targets.test.js +2 -1
  146. package/dist/backend/runtime-pack-targets.test.js.map +1 -1
  147. package/dist/backend/runtime-pack.test.js +2 -1
  148. package/dist/backend/runtime-pack.test.js.map +1 -1
  149. package/dist/backend/target-diagnostics.js +7 -0
  150. package/dist/backend/target-diagnostics.js.map +1 -1
  151. package/dist/backend/warm-cache.js +1 -0
  152. package/dist/backend/warm-cache.js.map +1 -1
  153. package/dist/executable/executable-cache.d.ts +1 -0
  154. package/dist/executable/executable-cache.js +1 -0
  155. package/dist/executable/executable-cache.js.map +1 -1
  156. package/dist/executable/executable-cache.test.js +1 -0
  157. package/dist/executable/executable-cache.test.js.map +1 -1
  158. package/dist/frontend/experimental-import.d.ts +5 -1
  159. package/dist/frontend/experimental-import.js +28 -3
  160. package/dist/frontend/experimental-import.js.map +1 -1
  161. package/dist/frontend/lowering/assert-instances.d.ts +38 -0
  162. package/dist/frontend/lowering/assert-instances.js +269 -0
  163. package/dist/frontend/lowering/assert-instances.js.map +1 -0
  164. package/dist/frontend/lowering/builtin-calls.js +3 -0
  165. package/dist/frontend/lowering/builtin-calls.js.map +1 -1
  166. package/dist/frontend/lowering/builtins/callable-aliases.js +54 -8
  167. package/dist/frontend/lowering/builtins/callable-aliases.js.map +1 -1
  168. package/dist/frontend/lowering/builtins/child-process-async.d.ts +8 -6
  169. package/dist/frontend/lowering/builtins/child-process-async.js +45 -29
  170. package/dist/frontend/lowering/builtins/child-process-async.js.map +1 -1
  171. package/dist/frontend/lowering/builtins/compression.d.ts +18 -0
  172. package/dist/frontend/lowering/builtins/compression.js +175 -2
  173. package/dist/frontend/lowering/builtins/compression.js.map +1 -1
  174. package/dist/frontend/lowering/builtins/constants.js +7 -2
  175. package/dist/frontend/lowering/builtins/constants.js.map +1 -1
  176. package/dist/frontend/lowering/builtins/crypto.d.ts +6 -0
  177. package/dist/frontend/lowering/builtins/crypto.js +111 -1
  178. package/dist/frontend/lowering/builtins/crypto.js.map +1 -1
  179. package/dist/frontend/lowering/builtins/errors.js +23 -0
  180. package/dist/frontend/lowering/builtins/errors.js.map +1 -1
  181. package/dist/frontend/lowering/builtins/filesystem-calls.d.ts +2 -0
  182. package/dist/frontend/lowering/builtins/filesystem-calls.js +5 -0
  183. package/dist/frontend/lowering/builtins/filesystem-calls.js.map +1 -1
  184. package/dist/frontend/lowering/builtins/filesystem.js +60 -0
  185. package/dist/frontend/lowering/builtins/filesystem.js.map +1 -1
  186. package/dist/frontend/lowering/builtins/fs-streams.d.ts +26 -0
  187. package/dist/frontend/lowering/builtins/fs-streams.js +131 -0
  188. package/dist/frontend/lowering/builtins/fs-streams.js.map +1 -0
  189. package/dist/frontend/lowering/builtins/module-calls.js +5 -1
  190. package/dist/frontend/lowering/builtins/module-calls.js.map +1 -1
  191. package/dist/frontend/lowering/builtins/timers.d.ts +3 -0
  192. package/dist/frontend/lowering/builtins/timers.js +109 -23
  193. package/dist/frontend/lowering/builtins/timers.js.map +1 -1
  194. package/dist/frontend/lowering/call-signatures.js +6 -0
  195. package/dist/frontend/lowering/call-signatures.js.map +1 -1
  196. package/dist/frontend/lowering/class-dynamic-dispatch.js +216 -20
  197. package/dist/frontend/lowering/class-dynamic-dispatch.js.map +1 -1
  198. package/dist/frontend/lowering/containers/bytes.js +33 -12
  199. package/dist/frontend/lowering/containers/bytes.js.map +1 -1
  200. package/dist/frontend/lowering/devirtualize.js +20 -7
  201. package/dist/frontend/lowering/devirtualize.js.map +1 -1
  202. package/dist/frontend/lowering/intern-tables.d.ts +13 -0
  203. package/dist/frontend/lowering/intern-tables.js +536 -0
  204. package/dist/frontend/lowering/intern-tables.js.map +1 -0
  205. package/dist/frontend/lowering/lower-assert.js +700 -61
  206. package/dist/frontend/lowering/lower-assert.js.map +1 -1
  207. package/dist/frontend/lowering/lower-async-iteration.js +11 -2
  208. package/dist/frontend/lowering/lower-async-iteration.js.map +1 -1
  209. package/dist/frontend/lowering/lower-calls.js +75 -6
  210. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  211. package/dist/frontend/lowering/lower-classes.d.ts +3 -0
  212. package/dist/frontend/lowering/lower-classes.js +43 -0
  213. package/dist/frontend/lowering/lower-classes.js.map +1 -1
  214. package/dist/frontend/lowering/lower-dgram.js +132 -67
  215. package/dist/frontend/lowering/lower-dgram.js.map +1 -1
  216. package/dist/frontend/lowering/lower-event-emitter.d.ts +9 -1
  217. package/dist/frontend/lowering/lower-event-emitter.js +78 -0
  218. package/dist/frontend/lowering/lower-event-emitter.js.map +1 -1
  219. package/dist/frontend/lowering/lower-exprs.d.ts +4 -0
  220. package/dist/frontend/lowering/lower-exprs.js +144 -58
  221. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  222. package/dist/frontend/lowering/lower-inspect.d.ts +6 -0
  223. package/dist/frontend/lowering/lower-inspect.js +138 -39
  224. package/dist/frontend/lowering/lower-inspect.js.map +1 -1
  225. package/dist/frontend/lowering/lower-mock.d.ts +11 -0
  226. package/dist/frontend/lowering/lower-mock.js +504 -0
  227. package/dist/frontend/lowering/lower-mock.js.map +1 -0
  228. package/dist/frontend/lowering/lower-modules.js +7 -0
  229. package/dist/frontend/lowering/lower-modules.js.map +1 -1
  230. package/dist/frontend/lowering/lower-server.d.ts +4 -0
  231. package/dist/frontend/lowering/lower-server.js +448 -30
  232. package/dist/frontend/lowering/lower-server.js.map +1 -1
  233. package/dist/frontend/lowering/lower-stmts.js +35 -8
  234. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  235. package/dist/frontend/lowering/lower-stream.d.ts +10 -6
  236. package/dist/frontend/lowering/lower-stream.js +794 -27
  237. package/dist/frontend/lowering/lower-stream.js.map +1 -1
  238. package/dist/frontend/lowering/lower-test.js +86 -8
  239. package/dist/frontend/lowering/lower-test.js.map +1 -1
  240. package/dist/frontend/lowering/lowerer.d.ts +25 -1
  241. package/dist/frontend/lowering/lowerer.js +74 -9
  242. package/dist/frontend/lowering/lowerer.js.map +1 -1
  243. package/dist/frontend/lowering/pairs-snapshot.d.ts +6 -0
  244. package/dist/frontend/lowering/pairs-snapshot.js +49 -0
  245. package/dist/frontend/lowering/pairs-snapshot.js.map +1 -1
  246. package/dist/frontend/lowering/record-sources.d.ts +15 -0
  247. package/dist/frontend/lowering/record-sources.js +46 -0
  248. package/dist/frontend/lowering/record-sources.js.map +1 -0
  249. package/dist/frontend/lowering/surfaces.d.ts +4 -0
  250. package/dist/frontend/lowering/surfaces.js +94 -16
  251. package/dist/frontend/lowering/surfaces.js.map +1 -1
  252. package/dist/frontend/lowering/zlib-constants.d.ts +1 -0
  253. package/dist/frontend/lowering/zlib-constants.js +181 -0
  254. package/dist/frontend/lowering/zlib-constants.js.map +1 -0
  255. package/dist/frontend/mock-shapes.d.ts +10 -0
  256. package/dist/frontend/mock-shapes.js +76 -0
  257. package/dist/frontend/mock-shapes.js.map +1 -0
  258. package/dist/frontend/pipeline.js +5 -3
  259. package/dist/frontend/pipeline.js.map +1 -1
  260. package/dist/frontend/program.js +6 -1
  261. package/dist/frontend/program.js.map +1 -1
  262. package/dist/frontend/resolve.d.ts +4 -0
  263. package/dist/frontend/resolve.js +14 -0
  264. package/dist/frontend/resolve.js.map +1 -1
  265. package/dist/frontend/tsc-codes.js +1 -0
  266. package/dist/frontend/tsc-codes.js.map +1 -1
  267. package/dist/frontend/type-mapper.d.ts +8 -0
  268. package/dist/frontend/type-mapper.js +155 -9
  269. package/dist/frontend/type-mapper.js.map +1 -1
  270. package/dist/index.js +2 -1
  271. package/dist/index.js.map +1 -1
  272. package/dist/ir/analysis.d.ts +10 -0
  273. package/dist/ir/analysis.js +35 -0
  274. package/dist/ir/analysis.js.map +1 -1
  275. package/dist/ir/analysis.test.js +69 -1
  276. package/dist/ir/analysis.test.js.map +1 -1
  277. package/dist/ir/builtin-effects.js +72 -0
  278. package/dist/ir/builtin-effects.js.map +1 -1
  279. package/dist/ir/builtin-signatures.d.ts +1 -1
  280. package/dist/ir/builtin-signatures.js +238 -15
  281. package/dist/ir/builtin-signatures.js.map +1 -1
  282. package/dist/ir/capture-values.d.ts +107 -0
  283. package/dist/ir/capture-values.js +850 -0
  284. package/dist/ir/capture-values.js.map +1 -0
  285. package/dist/ir/capture-values.test.d.ts +1 -0
  286. package/dist/ir/capture-values.test.js +268 -0
  287. package/dist/ir/capture-values.test.js.map +1 -0
  288. package/dist/ir/field-values.d.ts +16 -0
  289. package/dist/ir/field-values.js +128 -0
  290. package/dist/ir/field-values.js.map +1 -0
  291. package/dist/ir/field-values.test.d.ts +1 -0
  292. package/dist/ir/field-values.test.js +160 -0
  293. package/dist/ir/field-values.test.js.map +1 -0
  294. package/dist/ir/final-fields.d.ts +4 -1
  295. package/dist/ir/final-fields.js +11 -3
  296. package/dist/ir/final-fields.js.map +1 -1
  297. package/dist/ir/function-references.test.js +1 -1
  298. package/dist/ir/function-references.test.js.map +1 -1
  299. package/dist/ir/init-only-globals.d.ts +16 -0
  300. package/dist/ir/init-only-globals.js +121 -0
  301. package/dist/ir/init-only-globals.js.map +1 -0
  302. package/dist/ir/init-only-globals.test.d.ts +1 -0
  303. package/dist/ir/init-only-globals.test.js +115 -0
  304. package/dist/ir/init-only-globals.test.js.map +1 -0
  305. package/dist/ir/initializer-bindings.d.ts +18 -1
  306. package/dist/ir/initializer-bindings.js +91 -1
  307. package/dist/ir/initializer-bindings.js.map +1 -1
  308. package/dist/ir/initializer-bindings.test.js +57 -2
  309. package/dist/ir/initializer-bindings.test.js.map +1 -1
  310. package/dist/ir/int32-slots.d.ts +22 -0
  311. package/dist/ir/int32-slots.js +376 -13
  312. package/dist/ir/int32-slots.js.map +1 -1
  313. package/dist/ir/int32-slots.test.js +91 -8
  314. package/dist/ir/int32-slots.test.js.map +1 -1
  315. package/dist/ir/integer-loop-facts.d.ts +19 -1
  316. package/dist/ir/integer-loop-facts.js +155 -7
  317. package/dist/ir/integer-loop-facts.js.map +1 -1
  318. package/dist/ir/integer-ranges.d.ts +8 -5
  319. package/dist/ir/integer-ranges.js +147 -55
  320. package/dist/ir/integer-ranges.js.map +1 -1
  321. package/dist/ir/integer-ranges.test.js +225 -7
  322. package/dist/ir/integer-ranges.test.js.map +1 -1
  323. package/dist/ir/ir.d.ts +29 -2
  324. package/dist/ir/ir.js +17 -0
  325. package/dist/ir/ir.js.map +1 -1
  326. package/dist/ir/property-bags.d.ts +14 -0
  327. package/dist/ir/property-bags.js +121 -0
  328. package/dist/ir/property-bags.js.map +1 -0
  329. package/dist/ir/receiver-readers.d.ts +21 -0
  330. package/dist/ir/receiver-readers.js +217 -0
  331. package/dist/ir/receiver-readers.js.map +1 -0
  332. package/dist/ir/runtime-features.d.ts +1 -0
  333. package/dist/ir/runtime-features.js +34 -2
  334. package/dist/ir/runtime-features.js.map +1 -1
  335. package/dist/ir/validate.js +221 -5
  336. package/dist/ir/validate.js.map +1 -1
  337. package/dist/ir/validate.test.js +21 -0
  338. package/dist/ir/validate.test.js.map +1 -1
  339. package/dist/library/int-infer.js +3 -0
  340. package/dist/library/int-infer.js.map +1 -1
  341. package/dist/library/ir-policy.js +1 -0
  342. package/dist/library/ir-policy.js.map +1 -1
  343. package/dist/native/cli.js +1 -0
  344. package/dist/native/cli.js.map +1 -1
  345. package/dist/native/compiler.js +1 -3
  346. package/dist/native/compiler.js.map +1 -1
  347. package/dist/native/prepare.js +1 -0
  348. package/dist/native/prepare.js.map +1 -1
  349. package/dist/native/sanitizer.test.js +1 -0
  350. package/dist/native/sanitizer.test.js.map +1 -1
  351. package/package.json +20 -20
  352. package/surface-manifest.json +896 -49
@@ -6,7 +6,7 @@ import { CYC_BUF, CYC_HDR, FIRST_EMITTED_CYC_KIND, inlineAllocSupported } from "
6
6
  import { hasLoneSurrogate, llvmBytes as llBytes, wtf8Bytes } from "../literals.js";
7
7
  import { InternalCompilerError } from "../../errors.js";
8
8
  import { createHash } from "node:crypto";
9
- import { endsWithJump, matchStringSelfConcat, streamTypedRefEligible, undefinedArmTag, } from "../../ir/analysis.js";
9
+ import { endsWithJump, matchFieldSelfConcat, matchStringSelfConcat, readsArraySlot, streamTypedRefEligible, undefinedArmTag, } from "../../ir/analysis.js";
10
10
  import { emitLibraryIdentityLines } from "../library-identity-markers.js";
11
11
  import { loopTestsEveryIteration } from "../loop-polls.js";
12
12
  import { CAUGHT, ffiCallbackType, isDynTypedRefType, isFfiContextParam, isRefCounted, isUnitType, POINTER_KINDS, RUNTIME_EMITTER_CLASS, RUNTIME_EVENT_CLASS, RUNTIME_EVENT_TARGET_CLASS, RUNTIME_ERROR_CLASSES, RUNTIME_STREAM_CLASSES, typeEquals, typeKey, STRING, VOID, } from "../../ir/ir.js";
@@ -21,16 +21,21 @@ import { scalarizeNumericRecords } from "../../ir/scalar-records.js";
21
21
  import { RESULT_ENTRY_PREFIX, addRecordResultEntries } from "./record-results.js";
22
22
  import { specializeNumericCalls } from "../../ir/numeric-call-specialization.js";
23
23
  import { everyExpr, everyExprChild, everyStmtChild, everyStmtList } from "../../ir/traverse.js";
24
- import { analyzeIntegerRanges, INT32_RANGE } from "../../ir/integer-ranges.js";
24
+ import { analyzeIntegerRanges, INT32_RANGE, } from "../../ir/integer-ranges.js";
25
25
  import { unshareNodes } from "../../ir/unshare.js";
26
- import { analyzeInt32Slots } from "../../ir/int32-slots.js";
26
+ import { copyStableCaptures } from "../../ir/capture-values.js";
27
+ import { analyzeInt32Slots, dynamicFieldStores } from "../../ir/int32-slots.js";
27
28
  import { analyzeFinalFields, constructorClass } from "../../ir/final-fields.js";
28
29
  import { foldConstantGlobals } from "../../ir/constant-globals.js";
30
+ import { dropUnwrittenPropertyBags } from "../../ir/property-bags.js";
31
+ import { freezeInitOnlyGlobals } from "../../ir/init-only-globals.js";
32
+ import { hoistFinalFieldReads } from "../../ir/field-values.js";
29
33
  import { findIntegerViews } from "./integer-views.js";
30
34
  import { findInt32Abis, INT32_ABI_SUFFIX, SCALAR_UNION_DUMMY, SCALAR_UNION_RESULT, } from "./int32-abi.js";
31
35
  import { findInt32Locals } from "./int32-locals.js";
36
+ import { emitIntegerClone, emitIntegerDispatcher, findIntegerClones, NO_INTEGER_CLONES, } from "./integer-clones.js";
32
37
  import { exactInteger, narrowInteger } from "./integer-values.js";
33
- import { findInitializerBindings, withInitializerBindings } from "../../ir/initializer-bindings.js";
38
+ import { conditionalLoopWrites, findInitializerBindings, initializerLocalCandidates, withInitializerBindings, withInitializerLocals, } from "../../ir/initializer-bindings.js";
34
39
  import { findConstantNumericTables } from "../../ir/constant-tables.js";
35
40
  import { allocateFfiCallbackAdapters, hasForeignFfiCallback, hasRetainedFfiCallback, } from "../ffi-callbacks.js";
36
41
  import { RUNTIME_ABI_MARKER } from "../runtime-abi.js";
@@ -45,10 +50,11 @@ import { NullableRefFields } from "./nullable-fields.js";
45
50
  import { InlineUnionFields, loadInlineUnion, presentBits, stackUnionBox, storeInlineUnion, unionBoxBits, } from "./inline-unions.js";
46
51
  import { emitLocalArrayRead, emitProjectedArrayRead, emitBorrowedArrayRead, findLocalArrayReads, findCallArrayReads, OptionalArrayReads, } from "./local-array-reads.js";
47
52
  import { CheckedNarrows, emitCheckedNarrowReceiver } from "./checked-narrows.js";
48
- import { emitDenseArrayGet, emitDenseArraySet } from "./dense-array-access.js";
49
- import { emitStackMapRead, findMapReadLifetimes, matchMapRead, } from "./map-read-lifetimes.js";
53
+ import { emitDenseArrayGet, emitDenseArraySet, emitDenseSlotUnlink } from "./dense-array-access.js";
54
+ import { emitStackMapRead, findMapReadLifetimes, findReadNumberMapWrites, matchMapRead, } from "./map-read-lifetimes.js";
55
+ import { findMapEntryFusions } from "./map-entry-fusion.js";
50
56
  import { emitBorrowedFieldSequence, guardedValue } from "./borrowed-receivers.js";
51
- import { borrowableInputs, emitBorrowedInput } from "./borrowed-inputs.js";
57
+ import { borrowableInputs, emitBorrowedInput, emitBorrowedInputs } from "./borrowed-inputs.js";
52
58
  import { analyzeWalks, findWalkBorrows } from "./walk-borrows.js";
53
59
  import { findReboundParameters } from "./rebound-parameters.js";
54
60
  import { ReferenceEffects } from "./reference-effects.js";
@@ -61,7 +67,7 @@ import { AmbientReceiverReaders, findConstantCallbacks } from "./constant-callba
61
67
  import { findScalarStringSlices, emitStringSliceSnapshot, } from "./string-slices.js";
62
68
  import { LazyCaptures } from "./lazy-captures.js";
63
69
  import { StackCaptures } from "./stack-captures.js";
64
- import { ABSENT_FIELD_PAYLOAD, emitFieldAbsentTest, exactIndexLines, f64Lit, ffiNativeTypeLl, ffiNativeParamLl, ffiNativeReturnLl, llvmCommentText, } from "./common.js";
70
+ import { ABSENT_FIELD_PAYLOAD, emitFieldAbsentTest, exactIndexLines, f64Lit, ffiNativeTypeLl, ffiNativeParamLl, ffiNativeReturnLl, llvmCommentText, sizeToDouble, } from "./common.js";
65
71
  import { ffiExtendsNarrowIntegers } from "../targets.js";
66
72
  import { emitLiteralExpr, emitOperatorExpr, emitStringExpr, emitContainerExpr, emitRecordExpr, } from "./expr-primitives.js";
67
73
  import { emitControlExpr } from "./expr-control.js";
@@ -72,7 +78,7 @@ import { PublishWalkers } from "./publish.js";
72
78
  import { computeImmutableFields, stableGlobals } from "../immutability.js";
73
79
  import { computePublishedTypes } from "../publication.js";
74
80
  import { emitExpr } from "./expr-dispatch.js";
75
- import { dynKind, raceAdapterFor, genResultThunkFor, childExitThunkFor, childExitSignalThunkFor, childDataThunkFor, execFileThunkFor, ipcMessageThunkFor, ipcSendThunkFor, emitterFixedAdapter, wrapEmitterListener, unwrapNullableClosure, closeBindThunkFor, closeOverrideWrapFor, } from "./expr-callbacks.js";
81
+ import { dynKind, raceAdapterFor, genResultThunkFor, childExitThunkFor, childExitSignalThunkFor, childDataThunkFor, execFileThunkFor, ipcMessageThunkFor, ipcSendThunkFor, emitterFixedAdapter, emitterOnceAdapter, wrapEmitterListener, unwrapNullableClosure, closeBindThunkFor, closeOverrideWrapFor, } from "./expr-callbacks.js";
76
82
  import { streamDataAdapter, streamDoneFnFor, cryptoBytesThunkFor, cryptoIntThunkFor, fsRenameThunkFor, streamCbThunkFor, zlibBytesThunkFor, } from "./expr-stream-callbacks.js";
77
83
  import { emitStableReceiver, resolveThunkFor, tagInSet, arrPush, emitArrayCopyLoop, emitStrIntrinsic, emitArrIntrinsic, emitDiscardedSplice, wrapNullable, emitMapNew, mapSet, emitMapLikeIntrinsic, emitSetNew, } from "./expr-containers.js";
78
84
  import { emitIntegerLoopIndex, emitBytesIndex, emitBytesData, emitBytesLength, emitBytesGet, emitToUint32, emitBytesSet, emitBytesIntrinsic, } from "./expr-bytes.js";
@@ -85,7 +91,7 @@ import { emitNetworkHttpLibCall } from "./lib-network.js";
85
91
  import { emitAssertInspectLibCall, emitIoLibCall, emitGenericLibCall, emitLibCall, } from "./lib-dispatch.js";
86
92
  import { buildClassGraph, classMembershipIntervals, emitClassMembershipHelper, classEnvironmentIndex, classFieldIndex, classStructSym, programLifetimeClass, } from "./classes.js";
87
93
  import { LifetimeRegions, REGION_TYPES } from "./regions.js";
88
- import { LlDyn } from "./dyn.js";
94
+ import { LlDyn, recordDynKeepsIdentity } from "./dyn.js";
89
95
  import { VirtualBorrows } from "./virtual-borrows.js";
90
96
  import { LlvmUnsupportedError } from "./unsupported.js";
91
97
  import { LlWalkers } from "./walkers.js";
@@ -129,14 +135,14 @@ export function emitLlvmModule(input, options = {}) {
129
135
  const mod = unshareNodes(input);
130
136
  // Keep source storage intact for debugger inspection in dev builds.
131
137
  return new LlEmitter(options.debugSources === undefined
132
- ? addRecordResultEntries(scalarizeNumericRecords(specializeNumericCalls(foldConstantGlobals(mod), mangleFunction)))
138
+ ? addRecordResultEntries(scalarizeNumericRecords(specializeNumericCalls(foldConstantGlobals(copyStableCaptures(hoistFinalFieldReads(freezeInitOnlyGlobals(dropUnwrittenPropertyBags(mod))))), mangleFunction)))
133
139
  : mod, options).emit();
134
140
  }
135
141
  /** Keep large translation units below the host's single-string limit. */
136
142
  export function emitLlvmModuleSource(input, options = {}) {
137
143
  const mod = unshareNodes(input);
138
144
  const parts = new LlEmitter(options.debugSources === undefined
139
- ? addRecordResultEntries(scalarizeNumericRecords(specializeNumericCalls(foldConstantGlobals(mod), mangleFunction)))
145
+ ? addRecordResultEntries(scalarizeNumericRecords(specializeNumericCalls(foldConstantGlobals(copyStableCaptures(hoistFinalFieldReads(freezeInitOnlyGlobals(dropUnwrittenPropertyBags(mod))))), mangleFunction)))
140
146
  : mod, options).emitParts();
141
147
  const length = parts.reduce((total, part) => total + part.length, Math.max(0, parts.length - 1));
142
148
  if (length <= 256 * 1024 * 1024)
@@ -147,6 +153,9 @@ function llStrBytes(text) {
147
153
  return llBytes(wtf8Bytes(text));
148
154
  }
149
155
  const NO_BORROWED = new Set();
156
+ /** ScrExcKind tags of a generator slot holding a number or a boolean. */
157
+ const SCR_EXC_F64 = 1;
158
+ const SCR_EXC_BOOL = 2;
150
159
  /** Whether any node of `e` names the local (reads, writes, declarations,
151
160
  * captures). */
152
161
  function mentionsLocal(e, localId) {
@@ -226,6 +235,12 @@ export class LlEmitter {
226
235
  }
227
236
  return `, !tbaa !${tag}`;
228
237
  }
238
+ /** The `!range` attachment of an array length load: a length is at most
239
+ * 2^32 - 1 (the runtime's SCR_ARR_MAX_LENGTH), so conversions to double
240
+ * and back fold. 32-bit targets load the full range anyway. */
241
+ get arrayLengthRange() {
242
+ return this.sizeType === "i64" ? ", !range !{i64 0, i64 4294967296}" : "";
243
+ }
229
244
  fieldAliasAttachment(ptr) {
230
245
  const tag = this.fieldPointerTags.get(ptr);
231
246
  return tag === undefined ? "" : `, !tbaa !${tag}`;
@@ -312,6 +327,8 @@ export class LlEmitter {
312
327
  /** Interned NUL-terminated C-string constants (scr_jb_puts labels, the
313
328
  * stringify indent text): UTF-8 text → { symbol, byte length }. */
314
329
  cstrs = new Map();
330
+ /** Interned ScrMapOps descriptors (scr_map.h): initializer → symbol. */
331
+ mapOpsConsts = new Map();
315
332
  /** Type-directed walker functions (JSON serializers, the indent
316
333
  * rewriter, union ToString/join) — interned per typeKey/unionId, defs
317
334
  * flushed with the shape helpers. */
@@ -354,6 +371,9 @@ export class LlEmitter {
354
371
  checkedNarrows;
355
372
  callArrayReads = new Map();
356
373
  mapReadLifetimes = { locals: new Map(), arguments: new Map() };
374
+ readNumberMapWrites = new Set();
375
+ /** The current function's fused string-key Map get-then-set pairs (map-entry-fusion.ts). */
376
+ mapEntryFusion = null;
357
377
  callLifetimes;
358
378
  constantCallbacks;
359
379
  currentConstantCallbacks = new Map();
@@ -517,8 +537,20 @@ export class LlEmitter {
517
537
  initializerBindings;
518
538
  /** Whole-program int32 storage and parameter/result facts. */
519
539
  int32Slots;
540
+ /** The program has no runtime store into a checked-dynamic value
541
+ * (dynamicFieldStores), so live views of classes and records never
542
+ * commit back into their objects. Library builds keep their commits:
543
+ * their values cross a native boundary. */
544
+ liveViewsReadOnly;
520
545
  /** Functions whose direct calls use i32 parameters or results. */
521
546
  int32Abis;
547
+ /** Pure number functions with speculative integer clones. */
548
+ integerClones;
549
+ /** The IR function being emitted. */
550
+ currentFunctionName = "";
551
+ integerParamRange(fnName, localId) {
552
+ return this.int32Slots.facts(fnName).params?.get(localId) ?? null;
553
+ }
522
554
  /** Immutable module constants (contract types) whose graphs become
523
555
  * immortal once initialized: their reads need no reference counting. */
524
556
  immortalGlobals;
@@ -542,6 +574,9 @@ export class LlEmitter {
542
574
  byteWindowEntries = new Map();
543
575
  bytesBounds = new Set();
544
576
  integerViews = new Map();
577
+ /** Module globals the current function owns outright (initializer
578
+ * bindings): their function-local slot, which stands in for the global. */
579
+ privateGlobalSlots = new Map();
545
580
  /** Number locals of the current function stored only as an i32
546
581
  * (int32-locals.ts); their slots are `alloca i32`. */
547
582
  int32Locals = new Map();
@@ -575,6 +610,9 @@ export class LlEmitter {
575
610
  * switched-coroutine intrinsics (wasm32 async bodies and generators,
576
611
  * native synchronous generators; see coroLowered). */
577
612
  currentCoro = null;
613
+ /** Inline generator slot accesses need the %ScrGenHead/%ScrGenSlot
614
+ * mirrors in the module header. */
615
+ usesGenSlots = false;
578
616
  /** The generator channels of the function being emitted (null outside
579
617
  * generator bodies): yieldExpr emission reads them, and emitTryCatch's
580
618
  * catch prologue emits the GENRET sentinel re-unwind exactly here. */
@@ -599,6 +637,9 @@ export class LlEmitter {
599
637
  this.constantNumericTables = findConstantNumericTables(mod);
600
638
  this.initializerBindings = findInitializerBindings(mod);
601
639
  this.int32Slots = analyzeInt32Slots(mod);
640
+ const dynamicStores = dynamicFieldStores(mod);
641
+ this.liveViewsReadOnly =
642
+ mod.lib === undefined && !dynamicStores.all && dynamicStores.names.size === 0;
602
643
  this.int32Abis = this.debug === null ? findInt32Abis(mod, this.int32Slots) : new Map();
603
644
  this.immortalGlobals = new Set(mod.contracts?.immortalGlobals ?? []);
604
645
  this.programLifetime = new Set(mod.contracts?.programLifetime ?? []);
@@ -737,8 +778,25 @@ export class LlEmitter {
737
778
  this.callLifetimes = analyzeCallLifetimes(this.fnByName, (className, field) => this.nullableFields.get(className, field) !== null, walkParameters, new Set([...this.int32Abis].filter(([, abi]) => abi.union).map(([name]) => name)), {
738
779
  record: (shapeId, field) => this.recordFieldInline(shapeId, field),
739
780
  classField: (className, field) => this.inlineUnions.classField(className, field),
781
+ }, (shapeId) => {
782
+ const shape = this.recordsById.get(shapeId);
783
+ return shape !== undefined && !recordDynKeepsIdentity(shape);
740
784
  });
741
785
  this.constantCallbacks = findConstantCallbacks(mod, this.callLifetimes);
786
+ // Integer clones signal a stack overflow through their result, which
787
+ // needs the single-threaded guard; worker programs also poll for
788
+ // termination, and debug builds and stack traces keep every frame.
789
+ // Clones read number arrays their function borrows (64-bit layouts).
790
+ this.integerClones =
791
+ this.debug === null &&
792
+ this.stackChecks !== null &&
793
+ mod.workers !== true &&
794
+ mod.lib === undefined &&
795
+ !this.stackTraces
796
+ ? findIntegerClones(mod, this.sizeType === "i64"
797
+ ? (fn, index) => this.callLifetimes.borrowed.get(fn)?.has(index) === true
798
+ : undefined)
799
+ : NO_INTEGER_CLONES;
742
800
  this.stackCallbacks = new StackCallbacks(this.fnByName);
743
801
  this.receiverReaders = new AmbientReceiverReaders(this.fnByName);
744
802
  this.stackCaptures = new StackCaptures(this.fnByName, this.callLifetimes, this.stackCallbacks);
@@ -776,6 +834,7 @@ export class LlEmitter {
776
834
  needBadTag: () => this.needBadTag(),
777
835
  internLiteral: (text) => this.internLiteral(text),
778
836
  cstr: (text) => this.cstr(text),
837
+ mapOps: (operands) => this.mapOps(operands),
779
838
  unitInstanceRef: (unionId, tag) => this.unitInstanceRef(unionId, tag),
780
839
  retypeGuardRef: (unionId) => this.retypeGuardRef(unionId),
781
840
  fieldAbsentTestIn: (B, value, unionId) => this.fieldAbsentTestIn(B, value, unionId),
@@ -876,6 +935,10 @@ export class LlEmitter {
876
935
  virtualSlotBorrowed(rootName, slotIndex) {
877
936
  return this.virtualBorrows.slot(rootName, slotIndex);
878
937
  }
938
+ /** Optimized emission: no debug-info (dev) posture. */
939
+ get optimizing() {
940
+ return this.debug === null;
941
+ }
879
942
  /** The vtable entry for one implementation (VirtualBorrows.entry). */
880
943
  virtualEntry(implFn) {
881
944
  return this.virtualBorrows.entry(implFn);
@@ -1372,7 +1435,25 @@ export class LlEmitter {
1372
1435
  ` call void @scr_run_exit_listeners(double %${prefix}hd)`,
1373
1436
  ];
1374
1437
  };
1375
- const globalReleases = globalReleaseLines("g");
1438
+ // The entry's exits release the module globals only where the thread
1439
+ // frees its heap when its script ends (scr_exit_frees: workers, and
1440
+ // builds that check for leaks); the main thread of other builds leaves
1441
+ // it to the operating system.
1442
+ const exitReleaseLines = (prefix) => {
1443
+ const releases = globalReleaseLines(prefix);
1444
+ if (releases.length === 0)
1445
+ return releases;
1446
+ this.declare(`declare zeroext i1 @scr_exit_frees()`);
1447
+ return [
1448
+ ` %${prefix}.frees = call zeroext i1 @scr_exit_frees()`,
1449
+ ` br i1 %${prefix}.frees, label %${prefix}.release, label %${prefix}.kept`,
1450
+ `${prefix}.release:`,
1451
+ ...releases,
1452
+ ` br label %${prefix}.kept`,
1453
+ `${prefix}.kept:`,
1454
+ ];
1455
+ };
1456
+ const globalReleases = exitReleaseLines("g");
1376
1457
  const asyncEntry = this.fnByName.get(this.mod.entry)?.async === true;
1377
1458
  const entryMayThrow = this.mayThrow.has(this.mod.entry);
1378
1459
  // The event loop runs when timers appeared OR any async/generator
@@ -1384,12 +1465,12 @@ export class LlEmitter {
1384
1465
  const runsLoop = this.usesTimers ||
1385
1466
  this.ffiHasForeignCallback ||
1386
1467
  this.mod.functions.some((f) => f.async === true || f.generator !== undefined);
1387
- const uncaughtReleases = entryMayThrow && !asyncEntry ? globalReleaseLines("gu") : [];
1388
- const loopReleasesU = runsLoop ? globalReleaseLines("gl") : [];
1389
- const loopReleasesR = runsLoop ? globalReleaseLines("gr") : [];
1390
- const topRejectReleases = asyncEntry ? globalReleaseLines("gt") : [];
1391
- const topPendingReleases = asyncEntry ? globalReleaseLines("gp") : [];
1392
- const loopReportedReleases = runsLoop ? globalReleaseLines("gq") : [];
1468
+ const uncaughtReleases = entryMayThrow && !asyncEntry ? exitReleaseLines("gu") : [];
1469
+ const loopReleasesU = runsLoop ? exitReleaseLines("gl") : [];
1470
+ const loopReleasesR = runsLoop ? exitReleaseLines("gr") : [];
1471
+ const topRejectReleases = asyncEntry ? exitReleaseLines("gt") : [];
1472
+ const topPendingReleases = asyncEntry ? exitReleaseLines("gp") : [];
1473
+ const loopReportedReleases = runsLoop ? exitReleaseLines("gq") : [];
1393
1474
  // main's epilogues read the flag / the loop entry points — declared
1394
1475
  // HERE, before the extern block flushes. They run once, so they keep
1395
1476
  // the out-of-line scr_exc_pending call; per-call checks inline the
@@ -1413,21 +1494,17 @@ export class LlEmitter {
1413
1494
  if (!asyncEntry)
1414
1495
  return [];
1415
1496
  const lines = [];
1497
+ // finish_top_level noted 13 unless the program set process.exitCode.
1498
+ // A failed node:test run outranks either before exit listeners run.
1416
1499
  if (usesNodeTest) {
1417
- lines.push(` %tla_program_exit = call i32 @scr_test_exit_code()`);
1418
- }
1419
- if (usesNodeTest) {
1420
- lines.push(` %tla_program_exit_zero = icmp eq i32 %tla_program_exit, 0`, ` %tla_exit_status = select i1 %tla_program_exit_zero, i32 %tla_status, i32 %tla_program_exit`);
1500
+ lines.push(` %tla_program_exit = call i32 @scr_test_exit_code()`, ` %tla_program_exit_zero = icmp eq i32 %tla_program_exit, 0`, ` %tla_hint = call i32 @scr_exit_code_hint_get()`, ` %tla_exit_status = select i1 %tla_program_exit_zero, i32 %tla_hint, i32 %tla_program_exit`, ` call void @scr_exit_code_note(i32 %tla_exit_status)`);
1421
1501
  }
1422
- const exitStatus = usesNodeTest ? "%tla_exit_status" : "%tla_status";
1423
- // finish_top_level initially notes 13. Replace that hint before exit
1424
- // listeners run when a higher-priority verdict was already selected.
1425
- lines.push(` call void @scr_exit_code_note(i32 ${exitStatus})`);
1426
1502
  for (const line of exitListenerLines("xp"))
1427
1503
  lines.push(line);
1428
1504
  for (const line of topPendingReleases)
1429
1505
  lines.push(line);
1430
- lines.push(` ret i32 ${exitStatus}`);
1506
+ // An exit listener may set process.exitCode again, as in Node.
1507
+ lines.push(` %tla_exit_code = call i32 @scr_exit_code_hint_get()`, ` ret i32 %tla_exit_code`);
1431
1508
  return lines;
1432
1509
  };
1433
1510
  // LIBRARY mode: the runtime entry points the generated library
@@ -1519,22 +1596,20 @@ export class LlEmitter {
1519
1596
  // preserving the target object's identity.
1520
1597
  `%ScrArr = type { ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, i8, i8, i16, i32, ptr, ptr, ptr, ptr }`,
1521
1598
  `%ScrArrOps = type { ptr, ptr, ptr }`,
1522
- // The ScrMap prefix { rc, key_kind, val_kind, val_retain, val_release,
1523
- // val_trace, key_retain, key_release, key_trace } — the inline RC
1524
- // fast paths test the two trace slots (shapes.ts).
1525
- ...(this.rcHelpers === null
1526
- ? []
1527
- : [`%ScrMapRc = type { ${this.sizeType}, i32, i32, ptr, ptr, ptr, ptr, ptr, ptr }`]),
1599
+ // The ScrMap prefix { rc, key_kind, val_kind, traced } — the inline
1600
+ // RC fast paths test the traced byte (shapes.ts).
1601
+ ...(this.rcHelpers === null ? [] : [`%ScrMapRc = type { ${this.sizeType}, i8, i8, i8 }`]),
1528
1602
  // The complete ScrMap and its { key, val, hash } entry, read by the
1529
1603
  // inline number-key lookup (sc_map_entry_f64) and the inline
1530
- // iteration steps (sc_map_iter_entry): field 9 is the entry count,
1531
- // field 11 the i64 absent-key filter, entries is field 13, the direct
1532
- // index and its length fields 19 and 20.
1604
+ // iteration steps (sc_map_iter_entry): field 8 is the i32 entry
1605
+ // count, field 10 the i64 absent-key filter, entries is field 11,
1606
+ // nbuckets and buckets fields 13 and 14, the direct index and its i32
1607
+ // length fields 15 and 16.
1533
1608
  ...(this.decls.has(NUMBER_MAP_ENTRY_DECL) ||
1534
1609
  this.decls.has(STRING_MAP_ENTRY_DECL) ||
1535
1610
  this.decls.has(MAP_ITER_DECL)
1536
1611
  ? [
1537
- `%ScrMapIx = type { ${this.sizeType}, i32, i32, ptr, ptr, ptr, ptr, ptr, ptr, ${this.sizeType}, ${this.sizeType}, i64, ${this.sizeType}, ptr, ${this.sizeType}, ptr, ${this.sizeType}, ptr, ptr, ptr, ${this.sizeType} }`,
1612
+ `%ScrMapIx = type { ${this.sizeType}, i8, i8, i8, i8, i32, ptr, ptr, i32, i32, i64, ptr, i32, i32, ptr, ptr, i32, i32 }`,
1538
1613
  ]
1539
1614
  : []),
1540
1615
  // String-keyed reference reads load the value slot of the entry
@@ -1572,7 +1647,7 @@ export class LlEmitter {
1572
1647
  // typed-array access GEPs through this directly: the IR type already
1573
1648
  // fixes elem, so the hot path needs neither a runtime kind load nor
1574
1649
  // the generic scr_bytes_get/set call.
1575
- `%ScrBytes = type { ${this.sizeType}, ${this.sizeType}, i32, ptr, ptr, i8, i8, i8, ptr }`,
1650
+ `%ScrBytes = type { ${this.sizeType}, ${this.sizeType}, i32, ptr, ptr, i8, i8, i8, i8, ptr }`,
1576
1651
  // The capture box { rc, kind, obj_retain, obj_release, obj_trace,
1577
1652
  // slot } — TDZ reads peek the payload slot (offset 40) directly.
1578
1653
  `%ScrBox = type { ${this.sizeType}, i32, ptr, ptr, ptr, i64 }`,
@@ -1583,6 +1658,15 @@ export class LlEmitter {
1583
1658
  `%ScrDynPath = type { ptr, ptr, ${this.sizeType} }`,
1584
1659
  `%ScrIslandModule = type { ptr, ptr, ${this.sizeType}, ${this.sizeType}, i32, ptr, ${this.sizeType}, ${this.sizeType} }`,
1585
1660
  `%ScrIslandEdge = type { ptr, ptr, ptr, i32 }`,
1661
+ // A generator's prefix { rc, receiver, fiber, state, out } and its
1662
+ // value slot { kind, f64, b, payload, release_fn } (scr_async.c pins
1663
+ // both): number and boolean yields and drives address OUT directly.
1664
+ ...(this.usesGenSlots
1665
+ ? [
1666
+ `%ScrGenSlot = type { i32, double, i8, ptr, ptr }`,
1667
+ `%ScrGenHead = type { ${this.sizeType}, ptr, ptr, i32, %ScrGenSlot }`,
1668
+ ]
1669
+ : []),
1586
1670
  ];
1587
1671
  for (const line of shapes.typeDefs)
1588
1672
  out.push(line);
@@ -1720,6 +1804,11 @@ export class LlEmitter {
1720
1804
  }
1721
1805
  if (this.cstrs.size > 0)
1722
1806
  out.push(``);
1807
+ for (const [body, sym] of this.mapOpsConsts) {
1808
+ out.push(`@${sym} = internal constant { ptr, ptr, ptr, ptr, ptr, ptr, ptr } { ${body} }`);
1809
+ }
1810
+ if (this.mapOpsConsts.size > 0)
1811
+ out.push(``);
1723
1812
  for (const line of ffiCallbacks.globals)
1724
1813
  out.push(line);
1725
1814
  if (ffiCallbacks.globals.length > 0)
@@ -2185,18 +2274,36 @@ export class LlEmitter {
2185
2274
  // Number-key Map/Set lookups probe the runtime's direct index inline:
2186
2275
  // while ndense is nonzero every live key is an integer below it, so an
2187
2276
  // in-range integral key reads its slot (entry index + 1, 0 = absent) and
2188
- // any other key is absent. Maps without the index (ndense 0) and every
2189
- // non-integral probe outside the table take the runtime lookup, which
2190
- // keeps the complete SameValueZero semantics.
2277
+ // any other key is absent. Maps without the index (ndense 0) hash the
2278
+ // key in sc_map_entry_f64_hashed, which scans the entries of a small map
2279
+ // or probes a compact bucket table as scr_map_find_f64 does, and calls
2280
+ // the runtime lookup for a wide table. It stays out of line so that the
2281
+ // direct-index path stays small at every call site. Both only read
2282
+ // memory, so LLVM merges lookups of one key when nothing is stored in
2283
+ // between (a has() followed by get(), for example).
2191
2284
  if (this.decls.has(NUMBER_MAP_ENTRY_DECL)) {
2192
2285
  const sz = this.sizeType;
2193
2286
  let mapTmp = 0;
2194
2287
  const mapIndex = exactIndexLines("%k", "%nd", sz, () => `%ix${mapTmp++}`);
2195
- defs.push(`define internal ptr @sc_map_entry_f64(ptr %m, double %k) ${FN_ATTRS} {`, `entry:`, ` %ndp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 20`, ` %nd = load ${sz}, ptr %ndp`,
2288
+ defs.push(`define internal ptr @sc_map_entry_f64(ptr %m, double %k) ${FN_ATTRS} {`, `entry:`, ` %ndp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 16`, ...(sz === "i64"
2289
+ ? [` %nd32 = load i32, ptr %ndp`, ` %nd = zext i32 %nd32 to i64`]
2290
+ : [` %nd = load i32, ptr %ndp`]),
2196
2291
  // A non-integral key inside the table is as absent as one outside it.
2197
2292
  ...mapIndex.lines.map((line) => ` ${line}`), ` br i1 ${mapIndex.ok}, label %slot, label %outside`, `slot:`, sz === "i64"
2198
2293
  ? ` %i = add i64 ${mapIndex.wide}, 0`
2199
- : ` %i = trunc i64 ${mapIndex.wide} to ${sz}`, ` %dp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 19`, ` %dense = load ptr, ptr %dp`, ` %sp = getelementptr inbounds i32, ptr %dense, ${sz} %i`, ` %s = load i32, ptr %sp`, ` %empty = icmp eq i32 %s, 0`, ` br i1 %empty, label %miss, label %hit`, `hit:`, ` %e32 = sub i32 %s, 1`, sz === "i32" ? ` %e = add i32 %e32, 0` : ` %e = zext i32 %e32 to ${sz}`, ` %ep = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 13`, ` %entries = load ptr, ptr %ep`, ` %found = getelementptr inbounds %ScrMapEntry, ptr %entries, ${sz} %e`, ` ret ptr %found`, `miss:`, ` ret ptr null`, `outside:`, ` %dense.any = icmp ne ${sz} %nd, 0`, ` br i1 %dense.any, label %miss, label %slow`, `slow:`, ` %r = call ptr @scr_map_entry_f64(ptr %m, double %k)`, ` ret ptr %r`, `}`, ``);
2294
+ : ` %i = trunc i64 ${mapIndex.wide} to ${sz}`, ` %dp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 15`, ` %dense = load ptr, ptr %dp`, ` %sp = getelementptr inbounds i32, ptr %dense, ${sz} %i`, ` %s = load i32, ptr %sp`, ` %empty = icmp eq i32 %s, 0`, ` br i1 %empty, label %miss, label %hit`, `hit:`, ` %e32 = sub i32 %s, 1`, sz === "i32" ? ` %e = add i32 %e32, 0` : ` %e = zext i32 %e32 to ${sz}`, ` %ep = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 11`, ` %entries = load ptr, ptr %ep`, ` %found = getelementptr inbounds %ScrMapEntry, ptr %entries, ${sz} %e`, ` ret ptr %found`, `miss:`, ` ret ptr null`, `outside:`, ` %dense.any = icmp ne ${sz} %nd, 0`, ` br i1 %dense.any, label %miss, label %slow`, `slow:`, ` %hashed = call ptr @sc_map_entry_f64_hashed(ptr %m, double %k)`, ` ret ptr %hashed`, `}`, ``,
2295
+ // Hashing (scr_map_find_f64): the SameValueZero key word, its
2296
+ // scr_key_mix hash (zero remapped to one) and the absent-key filter.
2297
+ `define internal ptr @sc_map_entry_f64_hashed(ptr %m, double %k) noinline ${FN_ATTRS} {`, `entry:`, ` %nan = fcmp uno double %k, %k`, ` %zero = fcmp oeq double %k, 0.0`, ` %kz = select i1 %zero, double 0.0, double %k`, ` %kzb = bitcast double %kz to i64`, ` %kb = select i1 %nan, i64 9221120237041090560, i64 %kzb`, ` %x1s = lshr i64 %kb, 30`, ` %x1 = xor i64 %kb, %x1s`, ` %x2 = mul i64 %x1, -4658895280553007687`, ` %x3s = lshr i64 %x2, 27`, ` %x3 = xor i64 %x2, %x3s`, ` %x4 = mul i64 %x3, -7723592293110705685`, ` %x5s = lshr i64 %x4, 31`, ` %x5 = xor i64 %x4, %x5s`, ` %hz = icmp eq i64 %x5, 0`, ` %h = select i1 %hz, i64 1, i64 %x5`, ` %f1s = lshr i64 %h, 44`, ` %f1a = and i64 %f1s, 63`, ` %f1 = shl i64 1, %f1a`, ` %f2s = lshr i64 %h, 50`, ` %f2a = and i64 %f2s, 63`, ` %f2 = shl i64 1, %f2a`, ` %fb = or i64 %f1, %f2`, ` %fp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 10`, ` %filter = load i64, ptr %fp`, ` %fm = and i64 %filter, %fb`, ` %fin = icmp eq i64 %fm, %fb`, ` br i1 %fin, label %table, label %miss`, `table:`, ` %nbp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 13`, ...(sz === "i64"
2298
+ ? [` %nb32 = load i32, ptr %nbp`, ` %nb = zext i32 %nb32 to i64`]
2299
+ : [` %nb = load i32, ptr %nbp`]), ` %eps = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 11`, ` %ents = load ptr, ptr %eps`, ` %linear = icmp eq ${sz} %nb, 0`, ` br i1 %linear, label %scan, label %buckets`,
2300
+ // Small maps keep no bucket table: scan the cached entry hashes.
2301
+ `scan:`, ` %nep = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 8`, ...(sz === "i64"
2302
+ ? [` %ne32 = load i32, ptr %nep`, ` %ne = zext i32 %ne32 to i64`]
2303
+ : [` %ne = load i32, ptr %nep`]), ` br label %scan.loop`, `scan.loop:`, ` %si = phi ${sz} [ 0, %scan ], [ %si.next, %scan.next ]`, ` %sdone = icmp eq ${sz} %si, %ne`, ` br i1 %sdone, label %miss, label %scan.entry`, `scan.entry:`, ` %se = getelementptr inbounds %ScrMapEntry, ptr %ents, ${sz} %si`, ` %seh = getelementptr inbounds %ScrMapEntry, ptr %se, i32 0, i32 2`, ` %sh = load i64, ptr %seh`, ` %sk = load i64, ptr %se`, ` %shq = icmp eq i64 %sh, %h`, ` %skq = icmp eq i64 %sk, %kb`, ` %sq = and i1 %shq, %skq`, ` br i1 %sq, label %scan.found, label %scan.next`, `scan.found:`, ` ret ptr %se`, `scan.next:`, ` %si.next = add ${sz} %si, 1`, ` br label %scan.loop`,
2304
+ // Compact tables tag each 24-bit entry index (+1) with the hash's
2305
+ // top byte; wider tables take the runtime lookup.
2306
+ `buckets:`, ` %wide = icmp ugt ${sz} %nb, 16777216`, ` br i1 %wide, label %runtime, label %probe`, `probe:`, ` %bp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 14`, ` %bk = load ptr, ptr %bp`, ` %mask = sub ${sz} %nb, 1`, ` %ht = lshr i64 %h, 32`, ` %ht32 = trunc i64 %ht to i32`, ` %tag = and i32 %ht32, -16777216`, sz === "i64" ? ` %hi = add i64 %h, 0` : ` %hi = trunc i64 %h to ${sz}`, ` %i0 = and ${sz} %hi, %mask`, ` br label %probe.loop`, `probe.loop:`, ` %pi = phi ${sz} [ %i0, %probe ], [ %pi.next, %probe.next ]`, ` %bep = getelementptr inbounds i32, ptr %bk, ${sz} %pi`, ` %b = load i32, ptr %bep`, ` %bempty = icmp eq i32 %b, 0`, ` br i1 %bempty, label %miss, label %probe.tag`, `probe.tag:`, ` %bt = and i32 %b, -16777216`, ` %btq = icmp eq i32 %bt, %tag`, ` br i1 %btq, label %probe.entry, label %probe.next`, `probe.entry:`, ` %bix = and i32 %b, 16777215`, ` %bix1 = sub i32 %bix, 1`, sz === "i32" ? ` %bi = add i32 %bix1, 0` : ` %bi = zext i32 %bix1 to ${sz}`, ` %pe = getelementptr inbounds %ScrMapEntry, ptr %ents, ${sz} %bi`, ` %peh = getelementptr inbounds %ScrMapEntry, ptr %pe, i32 0, i32 2`, ` %ph = load i64, ptr %peh`, ` %pk = load i64, ptr %pe`, ` %phq = icmp eq i64 %ph, %h`, ` %pkq = icmp eq i64 %pk, %kb`, ` %pq = and i1 %phq, %pkq`, ` br i1 %pq, label %probe.found, label %probe.next`, `probe.found:`, ` ret ptr %pe`, `probe.next:`, ` %pi.inc = add ${sz} %pi, 1`, ` %pi.next = and ${sz} %pi.inc, %mask`, ` br label %probe.loop`, `miss:`, ` ret ptr null`, `runtime:`, ` %r = call ptr @scr_map_entry_f64(ptr %m, double %k) memory(read) nounwind willreturn`, ` ret ptr %r`, `}`, ``);
2200
2307
  }
2201
2308
  // Map/Set iteration steps read the entries array inline: an index is
2202
2309
  // live when it is below the entry count and its entry's cached hash is
@@ -2205,7 +2312,7 @@ export class LlEmitter {
2205
2312
  // the iteration are visited.
2206
2313
  if (this.decls.has(MAP_ITER_DECL)) {
2207
2314
  const sz = this.sizeType;
2208
- defs.push(`define internal ptr @sc_map_iter_entry(ptr %m, double %i) ${FN_ATTRS} {`, `entry:`, ` %np = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 9`, ` %n = load ${sz}, ptr %np`, ` %nf = uitofp ${sz} %n to double`, ` %lo = fcmp oge double %i, 0.0`, ` %hi = fcmp olt double %i, %nf`, ` %in = and i1 %lo, %hi`, ` br i1 %in, label %load, label %none`, `load:`, ` %ix = fptoui double %i to ${sz}`, ` %ep = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 13`, ` %entries = load ptr, ptr %ep`, ` %e = getelementptr inbounds %ScrMapEntry, ptr %entries, ${sz} %ix`, ` %hp = getelementptr inbounds %ScrMapEntry, ptr %e, i32 0, i32 2`, ` %h = load i64, ptr %hp`, ` %live = icmp ne i64 %h, 0`, ` br i1 %live, label %yes, label %none`, `yes:`, ` ret ptr %e`, `none:`, ` ret ptr null`, `}`, ``, `define internal double @sc_map_iter_count(ptr %m) ${FN_ATTRS} {`, `entry:`, ` %np = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 9`, ` %n = load ${sz}, ptr %np`, ` %nf = uitofp ${sz} %n to double`, ` ret double %nf`, `}`, ``);
2315
+ defs.push(`define internal ptr @sc_map_iter_entry(ptr %m, double %i) ${FN_ATTRS} {`, `entry:`, ` %np = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 8`, ` %n = load i32, ptr %np`, ` %nf = uitofp i32 %n to double`, ` %lo = fcmp oge double %i, 0.0`, ` %hi = fcmp olt double %i, %nf`, ` %in = and i1 %lo, %hi`, ` br i1 %in, label %load, label %none`, `load:`, ` %ix = fptoui double %i to ${sz}`, ` %ep = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 11`, ` %entries = load ptr, ptr %ep`, ` %e = getelementptr inbounds %ScrMapEntry, ptr %entries, ${sz} %ix`, ` %hp = getelementptr inbounds %ScrMapEntry, ptr %e, i32 0, i32 2`, ` %h = load i64, ptr %hp`, ` %live = icmp ne i64 %h, 0`, ` br i1 %live, label %yes, label %none`, `yes:`, ` ret ptr %e`, `none:`, ` ret ptr null`, `}`, ``, `define internal double @sc_map_iter_count(ptr %m) ${FN_ATTRS} {`, `entry:`, ` %np = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 8`, ` %n = load i32, ptr %np`, ` %nf = uitofp i32 %n to double`, ` ret double %nf`, `}`, ``);
2209
2316
  }
2210
2317
  // String-key Map/Set lookups test the map's absent-key filter inline
2211
2318
  // (scr_map.c scr_map_filter_bits): a key whose two filter bits are not
@@ -2218,7 +2325,7 @@ export class LlEmitter {
2218
2325
  if (this.decls.has(STRING_MAP_ENTRY_DECL)) {
2219
2326
  defs.push(`define internal ptr @sc_map_entry_str(ptr %m, ptr %k) ${FN_ATTRS} {`, `entry:`, ...(this.sizeType === "i64"
2220
2327
  ? [
2221
- ` %fp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 11`,
2328
+ ` %fp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 10`,
2222
2329
  ` %filter = load i64, ptr %fp`,
2223
2330
  ` %capp = getelementptr inbounds %ScrStr, ptr %k, i32 0, i32 2`,
2224
2331
  ` %cap = load i64, ptr %capp`,
@@ -3024,6 +3131,18 @@ export class LlEmitter {
3024
3131
  needBadTag() {
3025
3132
  this.needsBadTag = true;
3026
3133
  }
3134
+ /** The ScrMapOps descriptor holding these operands (ShapeHost.mapOps). */
3135
+ mapOps(operands) {
3136
+ if (operands.every((op) => op === "null"))
3137
+ return "null";
3138
+ const body = operands.map((op) => `ptr ${op}`).join(", ");
3139
+ let sym = this.mapOpsConsts.get(body);
3140
+ if (!sym) {
3141
+ sym = this.contentSymbol("sc_map_ops_", body);
3142
+ this.mapOpsConsts.set(body, sym);
3143
+ }
3144
+ return `@${sym}`;
3145
+ }
3027
3146
  /** The interned immortal instance for a UNIT arm of a union — asserts
3028
3147
  * the arm really is payload-less (undefined/null). Public: class
3029
3148
  * emission initializes undefined-admitting fields through it
@@ -3604,6 +3723,11 @@ export class LlEmitter {
3604
3723
  this.B.line(`store i32 0, ptr ${slot}`);
3605
3724
  return slot;
3606
3725
  }
3726
+ /** The amortized cancellation poll, emitted on the loop's back edge (the
3727
+ * continue target), after the body. A poll in the header would be a loop
3728
+ * exit ahead of the body; LLVM then cannot rotate the loop, so loads that
3729
+ * the body repeats on every iteration (a typed array's length, for one)
3730
+ * stay in the loop. */
3607
3731
  emitWorkerLoopCheck(slot) {
3608
3732
  if (slot === null)
3609
3733
  return;
@@ -4155,7 +4279,11 @@ export class LlEmitter {
4155
4279
  const g = this.globalTypes.get(id);
4156
4280
  if (!g)
4157
4281
  throw new InternalCompilerError(`llvm emitter bug: unknown binding ${id}`);
4158
- return { kind: "global", slot: `@${mangleGlobal(id)}`, type: g };
4282
+ return {
4283
+ kind: "global",
4284
+ slot: this.privateGlobalSlots.get(id) ?? `@${mangleGlobal(id)}`,
4285
+ type: g,
4286
+ };
4159
4287
  }
4160
4288
  emitDebugLocal(local, slot, arg = 0, captured = false) {
4161
4289
  const debug = this.debug?.local(local, this.debugScope, arg, captured);
@@ -4418,6 +4546,55 @@ export class LlEmitter {
4418
4546
  }
4419
4547
  this.emitCoroSuspendPrepared();
4420
4548
  }
4549
+ /** A native generator's OUT slot (scr_async.c's ScrGen.out, mirrored by
4550
+ * %ScrGenHead and %ScrGenSlot { kind, f64, b, payload, release_fn }). */
4551
+ genOutSlot(gen) {
4552
+ this.usesGenSlots = true;
4553
+ const slot = this.B.tmp();
4554
+ this.B.line(`${slot} = getelementptr inbounds %ScrGenHead, ptr ${gen}, i32 0, i32 4`);
4555
+ return slot;
4556
+ }
4557
+ /** A yield on a number or boolean channel: scr_gen_slot_f64/bool inline.
4558
+ * Such a body's OUT never holds a reference at a yield (only its final
4559
+ * completion can park one), so the slot needs no release first. */
4560
+ emitGenSlotStore(slot, kind, value) {
4561
+ const B = this.B;
4562
+ const field = B.tmp();
4563
+ if (kind === "f64") {
4564
+ B.line(`store i32 ${SCR_EXC_F64}, ptr ${slot}`);
4565
+ B.line(`${field} = getelementptr inbounds %ScrGenSlot, ptr ${slot}, i32 0, i32 1`);
4566
+ B.line(`store double ${value}, ptr ${field}`);
4567
+ }
4568
+ else {
4569
+ const byte = B.tmp();
4570
+ B.line(`store i32 ${SCR_EXC_BOOL}, ptr ${slot}`);
4571
+ B.line(`${field} = getelementptr inbounds %ScrGenSlot, ptr ${slot}, i32 0, i32 2`);
4572
+ B.line(`${byte} = zext i1 ${value} to i8`);
4573
+ B.line(`store i8 ${byte}, ptr ${field}`);
4574
+ }
4575
+ }
4576
+ /** scr_gen_take_out_f64/bool inline: the slot's value when it holds that
4577
+ * kind (otherwise 0 or false), leaving the slot empty. */
4578
+ emitGenSlotTake(slot, kind) {
4579
+ const B = this.B;
4580
+ const tag = B.tmp(), matches = B.tmp(), field = B.tmp(), raw = B.tmp(), out = B.tmp();
4581
+ B.line(`${tag} = load i32, ptr ${slot}`);
4582
+ B.line(`${matches} = icmp eq i32 ${tag}, ${kind === "f64" ? SCR_EXC_F64 : SCR_EXC_BOOL}`);
4583
+ if (kind === "f64") {
4584
+ B.line(`${field} = getelementptr inbounds %ScrGenSlot, ptr ${slot}, i32 0, i32 1`);
4585
+ B.line(`${raw} = load double, ptr ${field}`);
4586
+ B.line(`${out} = select i1 ${matches}, double ${raw}, double 0.0`);
4587
+ }
4588
+ else {
4589
+ const bit = B.tmp();
4590
+ B.line(`${field} = getelementptr inbounds %ScrGenSlot, ptr ${slot}, i32 0, i32 2`);
4591
+ B.line(`${raw} = load i8, ptr ${field}`);
4592
+ B.line(`${bit} = trunc i8 ${raw} to i1`);
4593
+ B.line(`${out} = select i1 ${matches}, i1 ${bit}, i1 false`);
4594
+ }
4595
+ B.line(`store i32 0, ptr ${slot}`);
4596
+ return out;
4597
+ }
4421
4598
  /** Suspend after a target-specific runtime helper already queued the
4422
4599
  * continuation (used by module awaits, which skip the hop when settled). */
4423
4600
  emitCoroSuspendPrepared() {
@@ -4492,9 +4669,25 @@ export class LlEmitter {
4492
4669
  }
4493
4670
  }
4494
4671
  }
4672
+ /** Number locals declared as a copy of a capture or of a lazily boxed
4673
+ * parameter, at the top of the body. */
4674
+ doubleCopies(fn) {
4675
+ const copies = [];
4676
+ for (const s of fn.body) {
4677
+ if (s.kind !== "varDecl")
4678
+ break;
4679
+ if (s.init?.kind !== "varRef" || s.init.type.kind !== "f64")
4680
+ continue;
4681
+ const source = s.init.localId;
4682
+ if (this.captureIds.has(source) || this.lazyCaptureBoxes.has(source))
4683
+ copies.push(s.localId);
4684
+ }
4685
+ return copies;
4686
+ }
4495
4687
  emitFunction(fn) {
4496
4688
  const B = new BlockBuilder();
4497
4689
  this.B = B;
4690
+ this.currentFunctionName = fn.name;
4498
4691
  // Fiber switches restore the active exception cell before control
4499
4692
  // returns to a synchronous frame (emitPendingCheck).
4500
4693
  B.excCellInvariant = fn.async !== true && fn.generator === undefined;
@@ -4547,6 +4740,11 @@ export class LlEmitter {
4547
4740
  !this.reboundByFunction.get(fn.name)?.has(fn.params[index].localId))
4548
4741
  this.borrowedParameters.add(fn.params[index].localId);
4549
4742
  }
4743
+ // A generator frame owns its unchanged parameters across suspensions.
4744
+ const generatorParameters = this.callLifetimes.generatorParameters.get(fn.name);
4745
+ if (generatorParameters !== undefined)
4746
+ for (const id of generatorParameters)
4747
+ this.borrowedParameters.add(id);
4550
4748
  this.captureIds = new Set([...(fn.captures ?? []), ...(fn.classCaptures ?? [])].map((c) => c.localId));
4551
4749
  this.stableCaptureIds =
4552
4750
  fn.captures !== undefined ? this.lazyCaptures.stableCaptures(fn.name) : new Set();
@@ -4571,6 +4769,8 @@ export class LlEmitter {
4571
4769
  this.scalarStringSlices =
4572
4770
  this.debug === null ? findScalarStringSlices(fn, this.callLifetimes) : new Map();
4573
4771
  this.mapReadLifetimes = findMapReadLifetimes(fn, this.boxedUnionsById, this.callLifetimes);
4772
+ this.readNumberMapWrites = findReadNumberMapWrites(fn);
4773
+ this.mapEntryFusion = this.debug === null ? findMapEntryFusions(fn) : null;
4574
4774
  this.localStackUnions = findLocalStackUnions(fn, this.callLifetimes, this.boxedUnionsById);
4575
4775
  this.localStackObjects = this.stackObjects
4576
4776
  ? findLocalStackObjects(this, fn, this.callLifetimes)
@@ -4586,12 +4786,30 @@ export class LlEmitter {
4586
4786
  this.bytesBounds = findBytesBounds(numericFn, this.integerRanges);
4587
4787
  // Debug builds keep every number local a double for the debugger's
4588
4788
  // variable view. Lazily boxed captured parameters keep their double,
4589
- // which the box is created from.
4789
+ // which the box is created from, and so do plain copies of a capture or
4790
+ // such a parameter (copyStableCaptures): the source already holds the
4791
+ // double, which an i32 copy would convert and widen again on every call.
4792
+ // Initializer bindings proven int32 move into an i32 local of their
4793
+ // initializer: no other code observes the global, so the loop-carried
4794
+ // value never round-trips through the double slot.
4795
+ const initializerLocals = initializerLocalCandidates(initializerBindings);
4590
4796
  const int32Locals = this.debug === null
4591
- ? findInt32Locals(fn, this.integerRanges, this.int32Slots.facts(fn.name), new Set(this.lazyCaptureBoxes.keys()))
4797
+ ? findInt32Locals(withInitializerLocals(fn, initializerLocals), this.integerRanges, this.int32Slots.facts(fn.name), new Set([...this.lazyCaptureBoxes.keys(), ...this.doubleCopies(fn)]))
4592
4798
  : null;
4593
4799
  this.int32Locals = int32Locals?.locals ?? new Map();
4594
4800
  this.int32LocalWrites = int32Locals?.values ?? new Set();
4801
+ const localizedGlobals = new Set();
4802
+ for (const global of initializerLocals) {
4803
+ if (!this.int32Locals.has(global.id))
4804
+ continue;
4805
+ localizedGlobals.add(global.id);
4806
+ this.currentLocals.set(global.id, {
4807
+ id: global.id,
4808
+ name: global.name,
4809
+ type: global.type,
4810
+ mutable: global.mutable,
4811
+ });
4812
+ }
4595
4813
  this.chainSlots.clear();
4596
4814
  this.finallyStack = [];
4597
4815
  this.tryStack = [];
@@ -4633,6 +4851,18 @@ export class LlEmitter {
4633
4851
  B.line(`${handle} = call ptr @llvm.coro.begin(token ${id}, ptr ${mem})`);
4634
4852
  B.line(`call void @${started}(ptr ${handle})`);
4635
4853
  B.line(`${self} = call ptr @scr_fiber_self()`);
4854
+ // A native synchronous body owns its generator for the whole run (the
4855
+ // ScrGen owns the frame), so its OUT slot address is fixed.
4856
+ let out = null;
4857
+ const yieldKind = fn.generator?.yieldT.kind;
4858
+ if (!this.wasi &&
4859
+ fn.async !== true &&
4860
+ (yieldKind === "f64" || yieldKind === "date" || yieldKind === "bool")) {
4861
+ this.declare(`declare ptr @scr_gen_of_fiber(ptr)`);
4862
+ const gen = B.tmp();
4863
+ B.line(`${gen} = call ptr @scr_gen_of_fiber(ptr ${self})`);
4864
+ out = this.genOutSlot(gen);
4865
+ }
4636
4866
  this.currentCoro = {
4637
4867
  kind: fn.generator !== undefined ? "generator" : "async",
4638
4868
  id,
@@ -4641,6 +4871,7 @@ export class LlEmitter {
4641
4871
  finalLabel: B.newLabel("coro.final"),
4642
4872
  cleanupLabel: B.newLabel("coro.cleanup"),
4643
4873
  suspendLabel: B.newLabel("coro.suspend"),
4874
+ out,
4644
4875
  };
4645
4876
  }
4646
4877
  const paramIds = new Set(fn.params.map((p) => p.localId));
@@ -4661,6 +4892,32 @@ export class LlEmitter {
4661
4892
  B.line(`${value} = load double, ptr @${mangleGlobal(global.id)}`);
4662
4893
  this.storeIntegerView(global.id, { name: value, type: global.type });
4663
4894
  }
4895
+ // A number global only its guarded, synchronous initializer reads or
4896
+ // writes (findInitializerBindings) is invisible to every other function,
4897
+ // and reentry returns before reaching it, so a local slot can stand in
4898
+ // for it: LLVM keeps it in a register across calls and loops that would
4899
+ // otherwise force a store and reload per iteration. The global's
4900
+ // storage is never read again, so nothing writes the slot back. Bindings
4901
+ // proven int32 already live in an i32 local (localizedGlobals above);
4902
+ // this covers the remaining doubles.
4903
+ this.privateGlobalSlots.clear();
4904
+ if (this.debug === null && this.currentCoro === null && !fn.async && !fn.generator) {
4905
+ const conditional = initializerLocals.length ? conditionalLoopWrites(fn.body) : null;
4906
+ for (const global of initializerLocals) {
4907
+ if (localizedGlobals.has(global.id))
4908
+ continue;
4909
+ // A conditional update in a loop stays a memory update (see
4910
+ // conditionalLoopWrites).
4911
+ if (conditional?.has(global.id))
4912
+ continue;
4913
+ const slot = B.slot();
4914
+ const value = B.tmp();
4915
+ B.entryAllocas.push(`${slot} = alloca double ; module binding ${global.name}`);
4916
+ B.line(`${value} = load double, ptr @${mangleGlobal(global.id)}`);
4917
+ B.line(`store double ${value}, ptr ${slot}`);
4918
+ this.privateGlobalSlots.set(global.id, slot);
4919
+ }
4920
+ }
4664
4921
  for (const local of this.currentLocals.values()) {
4665
4922
  // Boxed locals' slots hold their capture BOX (a ptr); captured
4666
4923
  // (env-borrowed) locals bind the incoming box below. A caught-typed
@@ -4673,6 +4930,9 @@ export class LlEmitter {
4673
4930
  : this.llType(local.type);
4674
4931
  B.entryAllocas.push(`%${mangleLocal(local.id)} = alloca ${slotTy} ; ${local.name}`);
4675
4932
  this.emitDebugLocal(local, `%${mangleLocal(local.id)}`, fn.params.findIndex((p) => p.localId === local.id) + 1, this.captureIds.has(local.id));
4933
+ // A localized initializer binding starts at its global's zero value.
4934
+ if (localizedGlobals.has(local.id))
4935
+ B.line(`store i32 0, ptr %${mangleLocal(local.id)}`);
4676
4936
  // Refcounted/boxed locals start NULL (the C prologue's `= NULL`):
4677
4937
  // scope-exit releases run whether or not an assign ever did.
4678
4938
  if (paramIds.has(local.id) || this.captureIds.has(local.id))
@@ -4892,9 +5152,17 @@ export class LlEmitter {
4892
5152
  attrs = `inlinehint ${attrs}`;
4893
5153
  const debug = this.debugScope === null ? "" : ` !dbg ${this.debugScope}`;
4894
5154
  const symbol = borrowed ? mangleBorrowedFunction(fn.name) : mangleFunction(fn.name);
5155
+ this.privateGlobalSlots.clear();
4895
5156
  let body = `define internal ${ret} @${symbol}${int32Abi ? INT32_ABI_SUFFIX : ""}(${params.join(", ")}) ${attrs}${debug} { ; ${fn.name}\n${B.render()}\n}`;
4896
5157
  if (int32Abi)
4897
5158
  body += "\n" + this.emitInt32AbiWrapper(fn, symbol, int32Abi);
5159
+ if (this.integerClones.members.has(fn.name)) {
5160
+ body += "\n" + emitIntegerClone(fn, this, this.stackChecks?.has(fn.name) === true);
5161
+ if (this.integerClones.dispatched.has(fn.name))
5162
+ body +=
5163
+ "\n" +
5164
+ emitIntegerDispatcher(fn, this, `${symbol}${int32Abi ? INT32_ABI_SUFFIX : ""}`, int32Abi?.params ?? [], int32Abi?.result === true);
5165
+ }
4898
5166
  if (!borrowed)
4899
5167
  return body;
4900
5168
  const slot = this.implSlotBorrowed(fn.name);
@@ -5761,6 +6029,8 @@ export class LlEmitter {
5761
6029
  // the replaced element itself). A local receiver that the index and
5762
6030
  // value cannot rebind is read in place, as for field stores; the
5763
6031
  // released element cannot reach that binding.
6032
+ if (this.emitArraySelfConcat(s))
6033
+ break;
5764
6034
  const arr = this.emitStableReceiver(s.arr, [s.index, s.value]);
5765
6035
  const idx = this.emitExpr(s.index);
5766
6036
  const v = this.emitExpr(s.value);
@@ -5795,8 +6065,8 @@ export class LlEmitter {
5795
6065
  const resize = B.newLabel("arr.len.set"), done = B.newLabel("arr.len.done");
5796
6066
  B.line(`${lenPtr} = getelementptr inbounds %ScrArr, ptr ${arr.name}, i32 0, i32 1`);
5797
6067
  this.markMemoryPointer(lenPtr, "array:header");
5798
- B.line(`${len} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}`);
5799
- B.line(`${lenNumber} = sitofp ${this.sizeType} ${len} to double`);
6068
+ B.line(`${len} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}${this.arrayLengthRange}`);
6069
+ B.line(`${lenNumber} = ${sizeToDouble(this.sizeType)} ${this.sizeType} ${len} to double`);
5800
6070
  B.line(`${same} = fcmp oeq double ${length.name}, ${lenNumber}`);
5801
6071
  B.condBr(same, done, resize);
5802
6072
  B.startBlock(resize);
@@ -5859,6 +6129,8 @@ export class LlEmitter {
5859
6129
  this.storeInt32Field(ptr, v, s.value);
5860
6130
  break;
5861
6131
  }
6132
+ if (this.emitFieldSelfConcat(s))
6133
+ break;
5862
6134
  const obj = this.emitStableReceiver(s.obj, [s.value]);
5863
6135
  const v = this.emitFieldValue(s.value, s.kind === "recordSet"
5864
6136
  ? this.recordFieldInline(s.shapeId, s.field)
@@ -6088,15 +6360,15 @@ export class LlEmitter {
6088
6360
  const lc = B.newLabel("loop.c");
6089
6361
  const lb = B.newLabel("loop.b");
6090
6362
  const le = B.newLabel("loop.e");
6363
+ const lp = workerBudget !== null ? B.newLabel("loop.p") : lc;
6091
6364
  B.br(lc);
6092
6365
  B.startBlock(lc);
6093
- this.emitWorkerLoopCheck(workerBudget);
6094
6366
  B.condBr(this.emitCondition(s.cond), lb, le);
6095
6367
  B.startBlock(lb);
6096
6368
  this.jumpTargets.push({
6097
6369
  kind: "loop",
6098
6370
  brkLabel: le,
6099
- contLabel: lc,
6371
+ contLabel: lp,
6100
6372
  labels: s.labels,
6101
6373
  frameDepth: this.frames.length,
6102
6374
  scopeDepth: this.scopes.length,
@@ -6104,7 +6376,12 @@ export class LlEmitter {
6104
6376
  });
6105
6377
  this.emitBlock(s.body);
6106
6378
  this.jumpTargets.pop();
6107
- B.br(lc);
6379
+ B.br(lp);
6380
+ if (lp !== lc) {
6381
+ B.startBlock(lp);
6382
+ this.emitWorkerLoopCheck(workerBudget);
6383
+ B.br(lc);
6384
+ }
6108
6385
  B.startBlock(le);
6109
6386
  break;
6110
6387
  }
@@ -6116,7 +6393,6 @@ export class LlEmitter {
6116
6393
  const le = B.newLabel("loop.e");
6117
6394
  B.br(lb);
6118
6395
  B.startBlock(lb);
6119
- this.emitWorkerLoopCheck(workerBudget);
6120
6396
  this.jumpTargets.push({
6121
6397
  kind: "loop",
6122
6398
  brkLabel: le,
@@ -6130,6 +6406,7 @@ export class LlEmitter {
6130
6406
  this.jumpTargets.pop();
6131
6407
  B.br(lc);
6132
6408
  B.startBlock(lc);
6409
+ this.emitWorkerLoopCheck(workerBudget);
6133
6410
  B.condBr(this.emitCondition(s.cond), lb, le);
6134
6411
  B.startBlock(le);
6135
6412
  break;
@@ -6289,12 +6566,11 @@ export class LlEmitter {
6289
6566
  B.line(`store ptr ${fresh}, ptr ${slot}`);
6290
6567
  }
6291
6568
  };
6292
- const lu = s.update || capturedLets.length > 0 ? B.newLabel("loop.u") : lc;
6293
- freshenBindings();
6294
6569
  const workerBudget = this.workerLoopBudget(s);
6570
+ const lu = s.update || capturedLets.length > 0 || workerBudget !== null ? B.newLabel("loop.u") : lc;
6571
+ freshenBindings();
6295
6572
  B.br(lc);
6296
6573
  B.startBlock(lc);
6297
- this.emitWorkerLoopCheck(workerBudget);
6298
6574
  if (integerLoop && integerSlot) {
6299
6575
  const receiver = this.emitStableReceiver(integerLoop.limitReceiver, []);
6300
6576
  const lenPtr = B.tmp();
@@ -6335,6 +6611,7 @@ export class LlEmitter {
6335
6611
  B.br(lu);
6336
6612
  if (lu !== lc) {
6337
6613
  B.startBlock(lu);
6614
+ this.emitWorkerLoopCheck(workerBudget);
6338
6615
  freshenBindings();
6339
6616
  if (integerLoop && integerSlot) {
6340
6617
  const old = B.tmp();
@@ -6402,7 +6679,6 @@ export class LlEmitter {
6402
6679
  const workerBudget = this.workerLoopBudget(s);
6403
6680
  B.br(lc);
6404
6681
  B.startBlock(lc);
6405
- this.emitWorkerLoopCheck(workerBudget);
6406
6682
  const inBounds = B.tmp();
6407
6683
  let cur;
6408
6684
  if (snapshot && cursor && scratch) {
@@ -6417,8 +6693,8 @@ export class LlEmitter {
6417
6693
  // The inline form of scr_arr_len, like the `length` intrinsic.
6418
6694
  B.line(`${lenPtr} = getelementptr inbounds %ScrArr, ptr ${arr.name}, i32 0, i32 1`);
6419
6695
  this.markMemoryPointer(lenPtr, "array:header");
6420
- B.line(`${rawLen} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}`);
6421
- B.line(`${len} = sitofp ${this.sizeType} ${rawLen} to double`);
6696
+ B.line(`${rawLen} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}${this.arrayLengthRange}`);
6697
+ B.line(`${len} = ${sizeToDouble(this.sizeType)} ${this.sizeType} ${rawLen} to double`);
6422
6698
  B.line(`${inBounds} = fcmp olt double ${i}, ${len}`);
6423
6699
  cur = i;
6424
6700
  }
@@ -6476,6 +6752,7 @@ export class LlEmitter {
6476
6752
  this.jumpTargets.pop();
6477
6753
  B.br(lu);
6478
6754
  B.startBlock(lu);
6755
+ this.emitWorkerLoopCheck(workerBudget);
6479
6756
  if (idxSlot) {
6480
6757
  const i2 = B.tmp(), i3 = B.tmp();
6481
6758
  B.line(`${i2} = load double, ptr ${idxSlot}`);
@@ -7123,6 +7400,118 @@ export class LlEmitter {
7123
7400
  return null;
7124
7401
  return this.emitExpr(packed.elems[0]);
7125
7402
  }
7403
+ /**
7404
+ * Lower `recv.f = recv.f + suffix` on a plain string field like
7405
+ * emitStringSelfConcatAssign does for locals. The field's old value is
7406
+ * snapshotted (+1) and the suffix evaluated in JavaScript order; then the
7407
+ * field unlinks and releases its CURRENT value, so the snapshot is unique
7408
+ * unless a real alias exists and the concat appends in place instead of
7409
+ * copying the whole string on every append. Returns false (emitting
7410
+ * nothing) for every other store.
7411
+ */
7412
+ emitFieldSelfConcat(s) {
7413
+ const suffix = matchFieldSelfConcat(s);
7414
+ if (!suffix || s.value.kind !== "strConcat")
7415
+ return false;
7416
+ let fieldType;
7417
+ if (s.kind === "fieldSet") {
7418
+ if (this.inlineUnions.classField(s.className, s.field) ||
7419
+ this.classMeta.get(s.className)?.root.def.name === "%Error")
7420
+ return false;
7421
+ fieldType = classFieldIndex(this.classMetaOf(s.className), s.field).type;
7422
+ }
7423
+ else {
7424
+ fieldType = this.recordShape(s.shapeId).fields.find((f) => f.name === s.field)?.type;
7425
+ }
7426
+ if (fieldType?.kind !== "string")
7427
+ return false;
7428
+ const B = this.B;
7429
+ const obj = this.emitStableReceiver(s.obj, [s.value]);
7430
+ const snapshot = this.emitExpr(s.value.left);
7431
+ const parts = stringParts(suffix);
7432
+ const mixed = parts.length > 1 || numberPart(parts[0]) !== null;
7433
+ const inputs = mixed ? emitConcatInputs(this, parts) : [];
7434
+ const right = mixed ? null : this.emitExpr(suffix);
7435
+ if (s.kind === "fieldSet")
7436
+ this.emitStoreGuard(obj.name, s);
7437
+ else if (this.publishedTypes?.recordGuarded(s.shapeId) === true)
7438
+ this.emitPublishedFieldGuard(obj.name, null, s.field);
7439
+ const { ptr } = s.kind === "fieldSet"
7440
+ ? this.classFieldPtr(obj.name, s.className, s.field)
7441
+ : this.recordFieldPtr(obj.name, s.shapeId, s.field);
7442
+ // Unlink, then release: nothing between here and the store below can
7443
+ // run user code or observe the field (the concat only traps on OOM).
7444
+ // Only an owned snapshot may survive the field's release.
7445
+ const owned = this.ownsSnapshot(snapshot.name);
7446
+ if (owned) {
7447
+ const old = this.loadField(ptr, fieldType, false);
7448
+ this.storeField(ptr, fieldType, "null", false);
7449
+ this.releaseValue(old, fieldType);
7450
+ }
7451
+ let raw;
7452
+ if (right === null) {
7453
+ raw = emitMixedConcat(this, snapshot.name, inputs);
7454
+ }
7455
+ else {
7456
+ this.declare(`declare ptr @scr_str_concat(ptr, ptr)`);
7457
+ raw = B.tmp();
7458
+ B.line(`${raw} = call ptr @scr_str_concat(ptr ${snapshot.name}, ptr ${right.name})`);
7459
+ }
7460
+ // The +1 result moves straight into the (now empty) field.
7461
+ if (owned)
7462
+ this.storeField(ptr, fieldType, raw, false);
7463
+ else
7464
+ this.replaceField(ptr, fieldType, false, this.own({ name: raw, type: fieldType }));
7465
+ return true;
7466
+ }
7467
+ /** A snapshot the current statement frames own (+1), or an immortal
7468
+ * value: it stays valid after its container releases the same string. */
7469
+ ownsSnapshot(name) {
7470
+ return (this.isImmortal(name) || this.frames.some((f) => f.some((v) => v.name === name && !v.slot)));
7471
+ }
7472
+ /**
7473
+ * `a[i] = a[i] + suffix` on a string array (the canonical statement form
7474
+ * of `a[i] += suffix`): the old element is snapshotted (+1) and the
7475
+ * suffix evaluated in JavaScript order; then, when the slot still holds
7476
+ * the snapshot, the array lets go of it (emitDenseSlotUnlink) so the
7477
+ * concat can append in place, and the result is stored as usual.
7478
+ */
7479
+ emitArraySelfConcat(s) {
7480
+ const value = s.value;
7481
+ if (value.kind !== "strConcat" ||
7482
+ value.type.kind !== "string" ||
7483
+ value.left.type.kind !== "string" ||
7484
+ value.right.type.kind !== "string" ||
7485
+ s.arr.type.kind !== "array" ||
7486
+ s.arr.type.elem.kind !== "string" ||
7487
+ s.arr.kind !== "varRef" ||
7488
+ s.index.kind !== "varRef" ||
7489
+ !readsArraySlot(value.left, s.arr.localId, s.index.localId))
7490
+ return false;
7491
+ const elem = s.arr.type.elem;
7492
+ const B = this.B;
7493
+ const arr = this.emitStableReceiver(s.arr, [s.index, s.value]);
7494
+ const idx = this.emitExpr(s.index);
7495
+ const snapshot = this.emitExpr(value.left);
7496
+ const parts = stringParts(value.right);
7497
+ const mixed = parts.length > 1 || numberPart(parts[0]) !== null;
7498
+ const inputs = mixed ? emitConcatInputs(this, parts) : [];
7499
+ const right = mixed ? null : this.emitExpr(value.right);
7500
+ this.emitPublishedArrayGuard(arr.name, s.arr.type, 0, idx.name);
7501
+ if (this.ownsSnapshot(snapshot.name))
7502
+ emitDenseSlotUnlink(this, arr.name, idx, s.index, snapshot.name, this.internLiteral(""), elem);
7503
+ let raw;
7504
+ if (right === null) {
7505
+ raw = emitMixedConcat(this, snapshot.name, inputs);
7506
+ }
7507
+ else {
7508
+ this.declare(`declare ptr @scr_str_concat(ptr, ptr)`);
7509
+ raw = B.tmp();
7510
+ B.line(`${raw} = call ptr @scr_str_concat(ptr ${snapshot.name}, ptr ${right.name})`);
7511
+ }
7512
+ emitDenseArraySet(this, arr.name, idx, s.index, "ref", elem, raw);
7513
+ return true;
7514
+ }
7126
7515
  /** `local = local + suffix` when the suffix cannot read or write the
7127
7516
  * local (an unboxed binding is only reachable through this function's own
7128
7517
  * expressions): reading the binding after the suffix gives the value the
@@ -7192,8 +7581,122 @@ export class LlEmitter {
7192
7581
  e.name === "region.leave" ||
7193
7582
  e.name === "region.release")
7194
7583
  return this.emitRegionIntrinsic(e);
7584
+ if (e.name.startsWith("itable."))
7585
+ return this.emitInternTableIntrinsic(e);
7195
7586
  return emitIntrinsicExpr(this, e);
7196
7587
  }
7588
+ /** InternTable's runtime calls (scr_itable.c). Table and entry handles
7589
+ * are f64 values carrying pointer bits; entry values are 64-bit slots
7590
+ * (double bits, a bool, or a pointer to an immortal value) that the
7591
+ * table neither retains nor releases. Lookups and inserts of array keys
7592
+ * throw a TypeError for keys longer than a table accepts. */
7593
+ emitInternTableIntrinsic(e) {
7594
+ const B = this.B;
7595
+ // The runtime copies keys and stores published values without counts,
7596
+ // so every operand is borrowed.
7597
+ const args = emitBorrowedInputs(this, e.args);
7598
+ if (B.isTerminated())
7599
+ return { name: "", type: e.type };
7600
+ const toPtr = (handle) => {
7601
+ const bits = B.tmp(), ptr = B.tmp();
7602
+ B.line(`${bits} = bitcast double ${handle} to i64`);
7603
+ B.line(`${ptr} = inttoptr i64 ${bits} to ptr`);
7604
+ return ptr;
7605
+ };
7606
+ const toHandle = (ptr) => {
7607
+ const bits = B.tmp(), handle = B.tmp();
7608
+ B.line(`${bits} = ptrtoint ptr ${ptr} to i64`);
7609
+ B.line(`${handle} = bitcast i64 ${bits} to double`);
7610
+ return { name: handle, type: e.type };
7611
+ };
7612
+ const keyCall = (op) => {
7613
+ const kind = e.args[1].type.kind;
7614
+ const key = kind === "string" ? "str" : kind === "array" ? "arr" : "f64";
7615
+ const keyTy = key === "f64" ? "double" : "ptr";
7616
+ const value = op === "insert" ? ", i64" : "";
7617
+ this.declare(`declare ptr @scr_itable_${op}_${key}(ptr, ${keyTy}${value})`);
7618
+ return `@scr_itable_${op}_${key}(ptr ${toPtr(args[0].name)}, ${keyTy} ${args[1].name}`;
7619
+ };
7620
+ switch (e.name) {
7621
+ case "itable.open": {
7622
+ this.declare(`declare ptr @scr_itable_open(ptr, i32)`);
7623
+ const kind = B.tmp(), table = B.tmp();
7624
+ B.line(`${kind} = fptosi double ${args[1].name} to i32`);
7625
+ B.line(`${table} = call ptr @scr_itable_open(ptr ${args[0].name}, i32 ${kind})`);
7626
+ return toHandle(table);
7627
+ }
7628
+ case "itable.find": {
7629
+ const call = keyCall("find");
7630
+ const entry = B.tmp();
7631
+ B.line(`${entry} = call ptr ${call})`);
7632
+ // Array keys longer than a table accepts throw a TypeError.
7633
+ if (e.args[1].type.kind === "array")
7634
+ this.emitPendingCheck();
7635
+ return toHandle(entry);
7636
+ }
7637
+ case "itable.insert": {
7638
+ const value = args[2];
7639
+ const ty = this.llType(value.type);
7640
+ let slot = value.name;
7641
+ if (ty === "double") {
7642
+ slot = B.tmp();
7643
+ B.line(`${slot} = bitcast double ${value.name} to i64`);
7644
+ }
7645
+ else if (ty === "i1") {
7646
+ slot = B.tmp();
7647
+ B.line(`${slot} = zext i1 ${value.name} to i64`);
7648
+ }
7649
+ else if (ty === "ptr") {
7650
+ slot = B.tmp();
7651
+ B.line(`${slot} = ptrtoint ptr ${value.name} to i64`);
7652
+ }
7653
+ else
7654
+ throw new InternalCompilerError(`llvm emitter bug: itable value of type ${ty}`);
7655
+ const call = keyCall("insert");
7656
+ const entry = B.tmp();
7657
+ B.line(`${entry} = call ptr ${call}, i64 ${slot})`);
7658
+ if (e.args[1].type.kind === "array")
7659
+ this.emitPendingCheck();
7660
+ return toHandle(entry);
7661
+ }
7662
+ case "itable.value": {
7663
+ // The handle's bits reach the address through memory: at -O0, x86
7664
+ // FastISel folds inttoptr(bitcast double) into the load's address
7665
+ // without moving the value out of its XMM register. Optimized
7666
+ // builds turn the stack round trip back into one register move.
7667
+ const carrier = B.slot();
7668
+ B.entryAllocas.push(`${carrier} = alloca double`);
7669
+ B.line(`store double ${args[0].name}, ptr ${carrier}`);
7670
+ const bits = B.tmp(), entry = B.tmp();
7671
+ B.line(`${bits} = load i64, ptr ${carrier}`);
7672
+ B.line(`${entry} = inttoptr i64 ${bits} to ptr`);
7673
+ const slot = B.tmp();
7674
+ B.line(`${slot} = load i64, ptr ${entry}, align 8`);
7675
+ const ty = this.llType(e.type);
7676
+ const result = B.tmp();
7677
+ if (ty === "double")
7678
+ B.line(`${result} = bitcast i64 ${slot} to double`);
7679
+ else if (ty === "i1")
7680
+ B.line(`${result} = trunc i64 ${slot} to i1`);
7681
+ else if (ty === "ptr")
7682
+ B.line(`${result} = inttoptr i64 ${slot} to ptr`);
7683
+ else
7684
+ throw new InternalCompilerError(`llvm emitter bug: itable value of type ${ty}`);
7685
+ // Stored values are immortal: owning one adds no count.
7686
+ return isRefCounted(e.type)
7687
+ ? this.own({ name: result, type: e.type })
7688
+ : { name: result, type: e.type };
7689
+ }
7690
+ case "itable.size": {
7691
+ this.declare(`declare double @scr_itable_size(ptr)`);
7692
+ const size = B.tmp();
7693
+ B.line(`${size} = call double @scr_itable_size(ptr ${toPtr(args[0].name)})`);
7694
+ return { name: size, type: e.type };
7695
+ }
7696
+ default:
7697
+ throw new InternalCompilerError(`llvm emitter bug: unknown intrinsic ${e.name}`);
7698
+ }
7699
+ }
7197
7700
  /** Lifetime regions in modules with lifetimeRegions (llvm/regions.ts).
7198
7701
  * Created with the class graph; null elsewhere. */
7199
7702
  regions = null;
@@ -7236,6 +7739,7 @@ export class LlEmitter {
7236
7739
  declare: (decl) => this.declare(decl),
7237
7740
  cstr: (text) => this.cstr(text),
7238
7741
  sizeType: this.sizeType,
7742
+ rcAudit: this.shapeHost.objectAudit !== false,
7239
7743
  tracedShapes: this.tracedShapes,
7240
7744
  unionsById: this.unionsById,
7241
7745
  recordsById: this.recordsById,
@@ -7950,6 +8454,9 @@ export class LlEmitter {
7950
8454
  emitterFixedAdapter(cbT) {
7951
8455
  return emitterFixedAdapter(this, cbT);
7952
8456
  }
8457
+ emitterOnceAdapter(boxerT) {
8458
+ return emitterOnceAdapter(this, boxerT);
8459
+ }
7953
8460
  wrapEmitterListener(target, adapterFn) {
7954
8461
  return wrapEmitterListener(this, target, adapterFn);
7955
8462
  }