@scriptc/compiler 0.3.4 → 0.3.6

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 (316) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +409 -42
  2. package/dist/backend/llvm/array-search.js +1 -1
  3. package/dist/backend/llvm/array-search.js.map +1 -1
  4. package/dist/backend/llvm/call-lifetimes.d.ts +6 -0
  5. package/dist/backend/llvm/call-lifetimes.js +19 -0
  6. package/dist/backend/llvm/call-lifetimes.js.map +1 -1
  7. package/dist/backend/llvm/constant-callbacks.d.ts +2 -21
  8. package/dist/backend/llvm/constant-callbacks.js +36 -219
  9. package/dist/backend/llvm/constant-callbacks.js.map +1 -1
  10. package/dist/backend/llvm/constant-callbacks.test.js +22 -0
  11. package/dist/backend/llvm/constant-callbacks.test.js.map +1 -1
  12. package/dist/backend/llvm/dense-array-access.d.ts +9 -0
  13. package/dist/backend/llvm/dense-array-access.js +27 -1
  14. package/dist/backend/llvm/dense-array-access.js.map +1 -1
  15. package/dist/backend/llvm/dyn.d.ts +6 -0
  16. package/dist/backend/llvm/dyn.js +71 -7
  17. package/dist/backend/llvm/dyn.js.map +1 -1
  18. package/dist/backend/llvm/emitter.d.ts +120 -2
  19. package/dist/backend/llvm/emitter.js +820 -142
  20. package/dist/backend/llvm/emitter.js.map +1 -1
  21. package/dist/backend/llvm/expr-async.js +17 -7
  22. package/dist/backend/llvm/expr-async.js.map +1 -1
  23. package/dist/backend/llvm/expr-bytes.js +1 -1
  24. package/dist/backend/llvm/expr-callbacks.d.ts +11 -0
  25. package/dist/backend/llvm/expr-callbacks.js +53 -0
  26. package/dist/backend/llvm/expr-callbacks.js.map +1 -1
  27. package/dist/backend/llvm/expr-calls.js +88 -2
  28. package/dist/backend/llvm/expr-calls.js.map +1 -1
  29. package/dist/backend/llvm/expr-containers.js +50 -27
  30. package/dist/backend/llvm/expr-containers.js.map +1 -1
  31. package/dist/backend/llvm/expr-dispatch.js +1 -0
  32. package/dist/backend/llvm/expr-dispatch.js.map +1 -1
  33. package/dist/backend/llvm/expr-dynamic.js +25 -1
  34. package/dist/backend/llvm/expr-dynamic.js.map +1 -1
  35. package/dist/backend/llvm/expr-primitives.d.ts +1 -1
  36. package/dist/backend/llvm/expr-primitives.js +20 -1
  37. package/dist/backend/llvm/expr-primitives.js.map +1 -1
  38. package/dist/backend/llvm/expr-stream-bridges.js +4 -0
  39. package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
  40. package/dist/backend/llvm/integer-clones.d.ts +103 -0
  41. package/dist/backend/llvm/integer-clones.js +1336 -0
  42. package/dist/backend/llvm/integer-clones.js.map +1 -0
  43. package/dist/backend/llvm/integer-clones.test.d.ts +1 -0
  44. package/dist/backend/llvm/integer-clones.test.js +219 -0
  45. package/dist/backend/llvm/integer-clones.test.js.map +1 -0
  46. package/dist/backend/llvm/lib-datagram.js +14 -0
  47. package/dist/backend/llvm/lib-datagram.js.map +1 -1
  48. package/dist/backend/llvm/lib-dispatch.js +32 -1
  49. package/dist/backend/llvm/lib-dispatch.js.map +1 -1
  50. package/dist/backend/llvm/lib-filesystem.js +61 -1
  51. package/dist/backend/llvm/lib-filesystem.js.map +1 -1
  52. package/dist/backend/llvm/lib-network.js +21 -3
  53. package/dist/backend/llvm/lib-network.js.map +1 -1
  54. package/dist/backend/llvm/lib-process.js +34 -0
  55. package/dist/backend/llvm/lib-process.js.map +1 -1
  56. package/dist/backend/llvm/lib-shared.js +77 -2
  57. package/dist/backend/llvm/lib-shared.js.map +1 -1
  58. package/dist/backend/llvm/lib-test.js +16 -3
  59. package/dist/backend/llvm/lib-test.js.map +1 -1
  60. package/dist/backend/llvm/lib-tls.js +6 -0
  61. package/dist/backend/llvm/lib-tls.js.map +1 -1
  62. package/dist/backend/llvm/local-array-reads.js +1 -1
  63. package/dist/backend/llvm/local-array-reads.js.map +1 -1
  64. package/dist/backend/llvm/map-entry-fusion.d.ts +28 -0
  65. package/dist/backend/llvm/map-entry-fusion.js +233 -0
  66. package/dist/backend/llvm/map-entry-fusion.js.map +1 -0
  67. package/dist/backend/llvm/map-entry-fusion.test.d.ts +1 -0
  68. package/dist/backend/llvm/map-entry-fusion.test.js +183 -0
  69. package/dist/backend/llvm/map-entry-fusion.test.js.map +1 -0
  70. package/dist/backend/llvm/map-number-lookup.d.ts +2 -0
  71. package/dist/backend/llvm/map-number-lookup.js +1 -1
  72. package/dist/backend/llvm/map-number-lookup.js.map +1 -1
  73. package/dist/backend/llvm/map-number-lookup.test.js +10 -11
  74. package/dist/backend/llvm/map-number-lookup.test.js.map +1 -1
  75. package/dist/backend/llvm/map-read-lifetimes.d.ts +2 -0
  76. package/dist/backend/llvm/map-read-lifetimes.js +3 -0
  77. package/dist/backend/llvm/map-read-lifetimes.js.map +1 -1
  78. package/dist/backend/llvm/map-read-lifetimes.test.js +2 -2
  79. package/dist/backend/llvm/nullable-unions.d.ts +6 -3
  80. package/dist/backend/llvm/nullable-unions.js +2 -0
  81. package/dist/backend/llvm/nullable-unions.js.map +1 -1
  82. package/dist/backend/llvm/publish.d.ts +16 -0
  83. package/dist/backend/llvm/publish.js +81 -10
  84. package/dist/backend/llvm/publish.js.map +1 -1
  85. package/dist/backend/llvm/publish.test.d.ts +1 -0
  86. package/dist/backend/llvm/publish.test.js +94 -0
  87. package/dist/backend/llvm/publish.test.js.map +1 -0
  88. package/dist/backend/llvm/record-results.d.ts +1 -1
  89. package/dist/backend/llvm/record-results.js +78 -4
  90. package/dist/backend/llvm/record-results.js.map +1 -1
  91. package/dist/backend/llvm/record-sources.d.ts +7 -0
  92. package/dist/backend/llvm/record-sources.js +14 -0
  93. package/dist/backend/llvm/record-sources.js.map +1 -0
  94. package/dist/backend/llvm/reference-writes.js +1 -0
  95. package/dist/backend/llvm/reference-writes.js.map +1 -1
  96. package/dist/backend/llvm/shapes.d.ts +12 -3
  97. package/dist/backend/llvm/shapes.js +23 -14
  98. package/dist/backend/llvm/shapes.js.map +1 -1
  99. package/dist/backend/llvm/shapes.test.js +5 -3
  100. package/dist/backend/llvm/shapes.test.js.map +1 -1
  101. package/dist/backend/llvm/speculative-integers.d.ts +74 -0
  102. package/dist/backend/llvm/speculative-integers.js +571 -0
  103. package/dist/backend/llvm/speculative-integers.js.map +1 -0
  104. package/dist/backend/llvm/speculative-integers.test.d.ts +1 -0
  105. package/dist/backend/llvm/speculative-integers.test.js +148 -0
  106. package/dist/backend/llvm/speculative-integers.test.js.map +1 -0
  107. package/dist/backend/llvm/string-lifetimes.test.js +75 -1
  108. package/dist/backend/llvm/string-lifetimes.test.js.map +1 -1
  109. package/dist/backend/llvm/walkers.d.ts +17 -0
  110. package/dist/backend/llvm/walkers.js +123 -0
  111. package/dist/backend/llvm/walkers.js.map +1 -1
  112. package/dist/backend/may-throw.js +2 -0
  113. package/dist/backend/may-throw.js.map +1 -1
  114. package/dist/backend/native/contracts.d.ts +2 -1
  115. package/dist/backend/native/executable.js +6 -1
  116. package/dist/backend/native/executable.js.map +1 -1
  117. package/dist/backend/native/runtime-inputs.d.ts +1 -1
  118. package/dist/backend/native/runtime-inputs.js +1 -0
  119. package/dist/backend/native/runtime-inputs.js.map +1 -1
  120. package/dist/backend/runtime-abi.d.ts +2 -2
  121. package/dist/backend/runtime-abi.js +2 -2
  122. package/dist/backend/runtime-pack-targets.test.js +1 -1
  123. package/dist/backend/runtime-pack.d.ts +7 -3
  124. package/dist/backend/runtime-pack.js +12 -6
  125. package/dist/backend/runtime-pack.js.map +1 -1
  126. package/dist/backend/runtime-pack.test.js +16 -7
  127. package/dist/backend/runtime-pack.test.js.map +1 -1
  128. package/dist/contracts/check.js +1 -0
  129. package/dist/contracts/check.js.map +1 -1
  130. package/dist/coverage/surface-manifest.js +3 -3
  131. package/dist/coverage/surface-manifest.js.map +1 -1
  132. package/dist/diagnostics/diagnostic.d.ts +1 -1
  133. package/dist/diagnostics/diagnostic.js +6 -2
  134. package/dist/diagnostics/diagnostic.js.map +1 -1
  135. package/dist/frontend/lowering/builtin-calls.js +13 -0
  136. package/dist/frontend/lowering/builtin-calls.js.map +1 -1
  137. package/dist/frontend/lowering/builtins/callable-aliases.js +82 -8
  138. package/dist/frontend/lowering/builtins/callable-aliases.js.map +1 -1
  139. package/dist/frontend/lowering/builtins/child-process-async.d.ts +8 -6
  140. package/dist/frontend/lowering/builtins/child-process-async.js +45 -29
  141. package/dist/frontend/lowering/builtins/child-process-async.js.map +1 -1
  142. package/dist/frontend/lowering/builtins/compression.d.ts +9 -0
  143. package/dist/frontend/lowering/builtins/compression.js +145 -30
  144. package/dist/frontend/lowering/builtins/compression.js.map +1 -1
  145. package/dist/frontend/lowering/builtins/crypto.js +12 -1
  146. package/dist/frontend/lowering/builtins/crypto.js.map +1 -1
  147. package/dist/frontend/lowering/builtins/errors.js +5 -3
  148. package/dist/frontend/lowering/builtins/errors.js.map +1 -1
  149. package/dist/frontend/lowering/builtins/filesystem-calls.d.ts +6 -0
  150. package/dist/frontend/lowering/builtins/filesystem-calls.js +16 -0
  151. package/dist/frontend/lowering/builtins/filesystem-calls.js.map +1 -1
  152. package/dist/frontend/lowering/builtins/filesystem.d.ts +8 -0
  153. package/dist/frontend/lowering/builtins/filesystem.js +199 -2
  154. package/dist/frontend/lowering/builtins/filesystem.js.map +1 -1
  155. package/dist/frontend/lowering/builtins/fs-streams.d.ts +26 -0
  156. package/dist/frontend/lowering/builtins/fs-streams.js +131 -0
  157. package/dist/frontend/lowering/builtins/fs-streams.js.map +1 -0
  158. package/dist/frontend/lowering/builtins/hash-stream.d.ts +12 -0
  159. package/dist/frontend/lowering/builtins/hash-stream.js +80 -0
  160. package/dist/frontend/lowering/builtins/hash-stream.js.map +1 -0
  161. package/dist/frontend/lowering/builtins/module-calls.js +17 -3
  162. package/dist/frontend/lowering/builtins/module-calls.js.map +1 -1
  163. package/dist/frontend/lowering/builtins/process.js +9 -2
  164. package/dist/frontend/lowering/builtins/process.js.map +1 -1
  165. package/dist/frontend/lowering/builtins/readline.d.ts +45 -13
  166. package/dist/frontend/lowering/builtins/readline.js +812 -47
  167. package/dist/frontend/lowering/builtins/readline.js.map +1 -1
  168. package/dist/frontend/lowering/builtins/workers.d.ts +4 -0
  169. package/dist/frontend/lowering/builtins/workers.js +58 -0
  170. package/dist/frontend/lowering/builtins/workers.js.map +1 -1
  171. package/dist/frontend/lowering/class-dynamic-dispatch.js +150 -8
  172. package/dist/frontend/lowering/class-dynamic-dispatch.js.map +1 -1
  173. package/dist/frontend/lowering/class-protocols.d.ts +55 -0
  174. package/dist/frontend/lowering/class-protocols.js +735 -0
  175. package/dist/frontend/lowering/class-protocols.js.map +1 -1
  176. package/dist/frontend/lowering/containers/array-from-async.d.ts +18 -0
  177. package/dist/frontend/lowering/containers/array-from-async.js +282 -0
  178. package/dist/frontend/lowering/containers/array-from-async.js.map +1 -0
  179. package/dist/frontend/lowering/containers/bytes.js +33 -12
  180. package/dist/frontend/lowering/containers/bytes.js.map +1 -1
  181. package/dist/frontend/lowering/containers/collection-grouping.js +2 -1
  182. package/dist/frontend/lowering/containers/collection-grouping.js.map +1 -1
  183. package/dist/frontend/lowering/containers/collection-methods.js +27 -23
  184. package/dist/frontend/lowering/containers/collection-methods.js.map +1 -1
  185. package/dist/frontend/lowering/devirtualize.js +20 -7
  186. package/dist/frontend/lowering/devirtualize.js.map +1 -1
  187. package/dist/frontend/lowering/divergences.js +188 -15
  188. package/dist/frontend/lowering/divergences.js.map +1 -1
  189. package/dist/frontend/lowering/lower-calls.js +129 -25
  190. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  191. package/dist/frontend/lowering/lower-classes.js +46 -1
  192. package/dist/frontend/lowering/lower-classes.js.map +1 -1
  193. package/dist/frontend/lowering/lower-dgram.js +55 -15
  194. package/dist/frontend/lowering/lower-dgram.js.map +1 -1
  195. package/dist/frontend/lowering/lower-event-emitter.d.ts +9 -1
  196. package/dist/frontend/lowering/lower-event-emitter.js +88 -1
  197. package/dist/frontend/lowering/lower-event-emitter.js.map +1 -1
  198. package/dist/frontend/lowering/lower-exprs.d.ts +4 -0
  199. package/dist/frontend/lowering/lower-exprs.js +239 -71
  200. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  201. package/dist/frontend/lowering/lower-inspect.js +49 -3
  202. package/dist/frontend/lowering/lower-inspect.js.map +1 -1
  203. package/dist/frontend/lowering/lower-mock.d.ts +11 -1
  204. package/dist/frontend/lowering/lower-mock.js +377 -61
  205. package/dist/frontend/lowering/lower-mock.js.map +1 -1
  206. package/dist/frontend/lowering/lower-modules.js +1 -0
  207. package/dist/frontend/lowering/lower-modules.js.map +1 -1
  208. package/dist/frontend/lowering/lower-server.d.ts +2 -1
  209. package/dist/frontend/lowering/lower-server.js +234 -21
  210. package/dist/frontend/lowering/lower-server.js.map +1 -1
  211. package/dist/frontend/lowering/lower-stmts.js +60 -13
  212. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  213. package/dist/frontend/lowering/lower-stream.d.ts +1 -3
  214. package/dist/frontend/lowering/lower-stream.js +325 -20
  215. package/dist/frontend/lowering/lower-stream.js.map +1 -1
  216. package/dist/frontend/lowering/lower-test.js +147 -48
  217. package/dist/frontend/lowering/lower-test.js.map +1 -1
  218. package/dist/frontend/lowering/lowerer.d.ts +13 -1
  219. package/dist/frontend/lowering/lowerer.js +10 -2
  220. package/dist/frontend/lowering/lowerer.js.map +1 -1
  221. package/dist/frontend/lowering/null-prototype.d.ts +13 -0
  222. package/dist/frontend/lowering/null-prototype.js +33 -0
  223. package/dist/frontend/lowering/null-prototype.js.map +1 -0
  224. package/dist/frontend/lowering/record-sources.d.ts +15 -0
  225. package/dist/frontend/lowering/record-sources.js +46 -0
  226. package/dist/frontend/lowering/record-sources.js.map +1 -0
  227. package/dist/frontend/lowering/surfaces.d.ts +3 -3
  228. package/dist/frontend/lowering/surfaces.js +59 -5
  229. package/dist/frontend/lowering/surfaces.js.map +1 -1
  230. package/dist/frontend/mock-shapes.d.ts +5 -0
  231. package/dist/frontend/mock-shapes.js +27 -2
  232. package/dist/frontend/mock-shapes.js.map +1 -1
  233. package/dist/frontend/program.js +5 -4
  234. package/dist/frontend/program.js.map +1 -1
  235. package/dist/frontend/type-mapper.d.ts +3 -0
  236. package/dist/frontend/type-mapper.js +148 -4
  237. package/dist/frontend/type-mapper.js.map +1 -1
  238. package/dist/index.js +18 -8
  239. package/dist/index.js.map +1 -1
  240. package/dist/ir/analysis.d.ts +10 -0
  241. package/dist/ir/analysis.js +35 -0
  242. package/dist/ir/analysis.js.map +1 -1
  243. package/dist/ir/analysis.test.js +69 -1
  244. package/dist/ir/analysis.test.js.map +1 -1
  245. package/dist/ir/builtin-effects.js +72 -0
  246. package/dist/ir/builtin-effects.js.map +1 -1
  247. package/dist/ir/builtin-signatures.d.ts +1 -1
  248. package/dist/ir/builtin-signatures.js +258 -13
  249. package/dist/ir/builtin-signatures.js.map +1 -1
  250. package/dist/ir/capture-values.d.ts +107 -0
  251. package/dist/ir/capture-values.js +850 -0
  252. package/dist/ir/capture-values.js.map +1 -0
  253. package/dist/ir/capture-values.test.d.ts +1 -0
  254. package/dist/ir/capture-values.test.js +268 -0
  255. package/dist/ir/capture-values.test.js.map +1 -0
  256. package/dist/ir/field-values.d.ts +16 -0
  257. package/dist/ir/field-values.js +128 -0
  258. package/dist/ir/field-values.js.map +1 -0
  259. package/dist/ir/field-values.test.d.ts +1 -0
  260. package/dist/ir/field-values.test.js +160 -0
  261. package/dist/ir/field-values.test.js.map +1 -0
  262. package/dist/ir/final-fields.d.ts +4 -1
  263. package/dist/ir/final-fields.js +11 -3
  264. package/dist/ir/final-fields.js.map +1 -1
  265. package/dist/ir/function-references.test.js +2 -1
  266. package/dist/ir/function-references.test.js.map +1 -1
  267. package/dist/ir/init-only-globals.d.ts +16 -0
  268. package/dist/ir/init-only-globals.js +121 -0
  269. package/dist/ir/init-only-globals.js.map +1 -0
  270. package/dist/ir/init-only-globals.test.d.ts +1 -0
  271. package/dist/ir/init-only-globals.test.js +115 -0
  272. package/dist/ir/init-only-globals.test.js.map +1 -0
  273. package/dist/ir/initializer-bindings.d.ts +18 -1
  274. package/dist/ir/initializer-bindings.js +91 -1
  275. package/dist/ir/initializer-bindings.js.map +1 -1
  276. package/dist/ir/initializer-bindings.test.js +57 -2
  277. package/dist/ir/initializer-bindings.test.js.map +1 -1
  278. package/dist/ir/int32-slots.d.ts +22 -0
  279. package/dist/ir/int32-slots.js +398 -13
  280. package/dist/ir/int32-slots.js.map +1 -1
  281. package/dist/ir/int32-slots.test.js +119 -8
  282. package/dist/ir/int32-slots.test.js.map +1 -1
  283. package/dist/ir/integer-loop-facts.d.ts +27 -1
  284. package/dist/ir/integer-loop-facts.js +239 -7
  285. package/dist/ir/integer-loop-facts.js.map +1 -1
  286. package/dist/ir/integer-ranges.d.ts +8 -5
  287. package/dist/ir/integer-ranges.js +147 -59
  288. package/dist/ir/integer-ranges.js.map +1 -1
  289. package/dist/ir/integer-ranges.test.js +285 -7
  290. package/dist/ir/integer-ranges.test.js.map +1 -1
  291. package/dist/ir/ir.d.ts +22 -1
  292. package/dist/ir/ir.js +4 -0
  293. package/dist/ir/ir.js.map +1 -1
  294. package/dist/ir/property-bags.d.ts +14 -0
  295. package/dist/ir/property-bags.js +121 -0
  296. package/dist/ir/property-bags.js.map +1 -0
  297. package/dist/ir/receiver-readers.d.ts +21 -0
  298. package/dist/ir/receiver-readers.js +217 -0
  299. package/dist/ir/receiver-readers.js.map +1 -0
  300. package/dist/ir/runtime-features.d.ts +3 -1
  301. package/dist/ir/runtime-features.js +102 -7
  302. package/dist/ir/runtime-features.js.map +1 -1
  303. package/dist/ir/runtime-features.test.js +62 -0
  304. package/dist/ir/runtime-features.test.js.map +1 -1
  305. package/dist/ir/traverse.js +4 -0
  306. package/dist/ir/traverse.js.map +1 -1
  307. package/dist/ir/validate.js +71 -3
  308. package/dist/ir/validate.js.map +1 -1
  309. package/dist/ir/validate.test.js +59 -0
  310. package/dist/ir/validate.test.js.map +1 -1
  311. package/dist/library/int-infer.js +3 -0
  312. package/dist/library/int-infer.js.map +1 -1
  313. package/dist/library/ir-policy.js +5 -0
  314. package/dist/library/ir-policy.js.map +1 -1
  315. package/package.json +20 -20
  316. package/surface-manifest.json +57 -19
@@ -6,13 +6,14 @@ 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";
13
13
  import { moduleRuntimeFeatures } from "../../ir/runtime-features.js";
14
14
  import { matchIntegerArrayForLoop, matchIntegerBytesForLoop, matchIntegerCountedForLoop, } from "../../ir/integer-loops.js";
15
15
  import { emitCountedLoopLimit } from "./counted-loops.js";
16
+ import { emitSafeIntegerCheck, emitSpeculativeInteger, planSpeculativeLoop, speculativeReads, speculativeWriteTarget, SPECULATIVE_RANGE, } from "./speculative-integers.js";
16
17
  import { emitLiteralSwitch } from "./switch-dispatch.js";
17
18
  import { findBytesBounds } from "./bytes-bounds.js";
18
19
  import { emitByteWindowGuard, findInitializedByteLoopBindings, matchByteWindow, } from "./byte-windows.js";
@@ -21,16 +22,21 @@ import { scalarizeNumericRecords } from "../../ir/scalar-records.js";
21
22
  import { RESULT_ENTRY_PREFIX, addRecordResultEntries } from "./record-results.js";
22
23
  import { specializeNumericCalls } from "../../ir/numeric-call-specialization.js";
23
24
  import { everyExpr, everyExprChild, everyStmtChild, everyStmtList } from "../../ir/traverse.js";
24
- import { analyzeIntegerRanges, INT32_RANGE } from "../../ir/integer-ranges.js";
25
+ import { analyzeIntegerRanges, INT32_RANGE, } from "../../ir/integer-ranges.js";
25
26
  import { unshareNodes } from "../../ir/unshare.js";
26
- import { analyzeInt32Slots } from "../../ir/int32-slots.js";
27
+ import { copyStableCaptures } from "../../ir/capture-values.js";
28
+ import { analyzeInt32Slots, dynamicFieldStores } from "../../ir/int32-slots.js";
27
29
  import { analyzeFinalFields, constructorClass } from "../../ir/final-fields.js";
28
30
  import { foldConstantGlobals } from "../../ir/constant-globals.js";
31
+ import { dropUnwrittenPropertyBags } from "../../ir/property-bags.js";
32
+ import { freezeInitOnlyGlobals } from "../../ir/init-only-globals.js";
33
+ import { hoistFinalFieldReads } from "../../ir/field-values.js";
29
34
  import { findIntegerViews } from "./integer-views.js";
30
35
  import { findInt32Abis, INT32_ABI_SUFFIX, SCALAR_UNION_DUMMY, SCALAR_UNION_RESULT, } from "./int32-abi.js";
31
36
  import { findInt32Locals } from "./int32-locals.js";
37
+ import { emitIntegerClone, emitIntegerDispatcher, findIntegerClones, NO_INTEGER_CLONES, } from "./integer-clones.js";
32
38
  import { exactInteger, narrowInteger } from "./integer-values.js";
33
- import { findInitializerBindings, withInitializerBindings } from "../../ir/initializer-bindings.js";
39
+ import { conditionalLoopWrites, findInitializerBindings, initializerLocalCandidates, withInitializerBindings, withInitializerLocals, } from "../../ir/initializer-bindings.js";
34
40
  import { findConstantNumericTables } from "../../ir/constant-tables.js";
35
41
  import { allocateFfiCallbackAdapters, hasForeignFfiCallback, hasRetainedFfiCallback, } from "../ffi-callbacks.js";
36
42
  import { RUNTIME_ABI_MARKER } from "../runtime-abi.js";
@@ -45,8 +51,9 @@ import { NullableRefFields } from "./nullable-fields.js";
45
51
  import { InlineUnionFields, loadInlineUnion, presentBits, stackUnionBox, storeInlineUnion, unionBoxBits, } from "./inline-unions.js";
46
52
  import { emitLocalArrayRead, emitProjectedArrayRead, emitBorrowedArrayRead, findLocalArrayReads, findCallArrayReads, OptionalArrayReads, } from "./local-array-reads.js";
47
53
  import { CheckedNarrows, emitCheckedNarrowReceiver } from "./checked-narrows.js";
48
- import { emitDenseArrayGet, emitDenseArraySet } from "./dense-array-access.js";
54
+ import { emitDenseArrayGet, emitDenseArraySet, emitDenseSlotUnlink } from "./dense-array-access.js";
49
55
  import { emitStackMapRead, findMapReadLifetimes, findReadNumberMapWrites, matchMapRead, } from "./map-read-lifetimes.js";
56
+ import { findMapEntryFusions } from "./map-entry-fusion.js";
50
57
  import { emitBorrowedFieldSequence, guardedValue } from "./borrowed-receivers.js";
51
58
  import { borrowableInputs, emitBorrowedInput, emitBorrowedInputs } from "./borrowed-inputs.js";
52
59
  import { analyzeWalks, findWalkBorrows } from "./walk-borrows.js";
@@ -72,7 +79,7 @@ import { PublishWalkers } from "./publish.js";
72
79
  import { computeImmutableFields, stableGlobals } from "../immutability.js";
73
80
  import { computePublishedTypes } from "../publication.js";
74
81
  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";
82
+ import { dynKind, raceAdapterFor, genResultThunkFor, childExitThunkFor, childExitSignalThunkFor, childDataThunkFor, execFileThunkFor, ipcMessageThunkFor, ipcSendThunkFor, emitterFixedAdapter, emitterOnceAdapter, wrapEmitterListener, unwrapNullableClosure, closeBindThunkFor, closeOverrideWrapFor, } from "./expr-callbacks.js";
76
83
  import { streamDataAdapter, streamDoneFnFor, cryptoBytesThunkFor, cryptoIntThunkFor, fsRenameThunkFor, streamCbThunkFor, zlibBytesThunkFor, } from "./expr-stream-callbacks.js";
77
84
  import { emitStableReceiver, resolveThunkFor, tagInSet, arrPush, emitArrayCopyLoop, emitStrIntrinsic, emitArrIntrinsic, emitDiscardedSplice, wrapNullable, emitMapNew, mapSet, emitMapLikeIntrinsic, emitSetNew, } from "./expr-containers.js";
78
85
  import { emitIntegerLoopIndex, emitBytesIndex, emitBytesData, emitBytesLength, emitBytesGet, emitToUint32, emitBytesSet, emitBytesIntrinsic, } from "./expr-bytes.js";
@@ -95,7 +102,7 @@ import { literalCapWord, literalCapWord32 } from "./string-key-hash.js";
95
102
  import { MAP_ITER_DECL, NUMBER_MAP_ENTRY_DECL, STRING_MAP_ENTRY_DECL, } from "./map-number-lookup.js";
96
103
  import { STRING_READ_HELPERS, stringScanDecl } from "./string-reads.js";
97
104
  import { emitConcatInputs, emitMixedConcat, numberPart, stringParts, } from "./string-construction.js";
98
- import { arrNewCall, boxAccess, boxNewCall, boxReleaseSym, computeTraced, elemAccess, emitInlineRcHelpers, emitNullableRetainWrappers, FN_ATTRS, inlineRcDecls, limitInlineDynReleases, llFieldType, releaseSym, retainSym, traceArg, typedRefConstructor, vAdapters, } from "./shapes.js";
105
+ import { arrNewCall, boxAccess, boxNewCall, boxReleaseSym, computeTraced, elemAccess, emitInlineRcHelpers, emitNullableRetainWrappers, FN_ATTRS, inlineRcDecls, limitInlineDynReleases, llFieldType, mapFlagsOffset, releaseSym, retainSym, traceArg, typedRefConstructor, vAdapters, } from "./shapes.js";
99
106
  import { INLINE_LITERAL_BYTES, literalEqualityDefinition } from "./string-literal-equality.js";
100
107
  export { LlvmUnsupportedError } from "./unsupported.js";
101
108
  function ffiCallbackDummyLl(callback) {
@@ -129,14 +136,14 @@ export function emitLlvmModule(input, options = {}) {
129
136
  const mod = unshareNodes(input);
130
137
  // Keep source storage intact for debugger inspection in dev builds.
131
138
  return new LlEmitter(options.debugSources === undefined
132
- ? addRecordResultEntries(scalarizeNumericRecords(specializeNumericCalls(foldConstantGlobals(mod), mangleFunction)))
139
+ ? addRecordResultEntries(scalarizeNumericRecords(specializeNumericCalls(foldConstantGlobals(copyStableCaptures(hoistFinalFieldReads(freezeInitOnlyGlobals(dropUnwrittenPropertyBags(mod))))), mangleFunction)))
133
140
  : mod, options).emit();
134
141
  }
135
142
  /** Keep large translation units below the host's single-string limit. */
136
143
  export function emitLlvmModuleSource(input, options = {}) {
137
144
  const mod = unshareNodes(input);
138
145
  const parts = new LlEmitter(options.debugSources === undefined
139
- ? addRecordResultEntries(scalarizeNumericRecords(specializeNumericCalls(foldConstantGlobals(mod), mangleFunction)))
146
+ ? addRecordResultEntries(scalarizeNumericRecords(specializeNumericCalls(foldConstantGlobals(copyStableCaptures(hoistFinalFieldReads(freezeInitOnlyGlobals(dropUnwrittenPropertyBags(mod))))), mangleFunction)))
140
147
  : mod, options).emitParts();
141
148
  const length = parts.reduce((total, part) => total + part.length, Math.max(0, parts.length - 1));
142
149
  if (length <= 256 * 1024 * 1024)
@@ -147,6 +154,9 @@ function llStrBytes(text) {
147
154
  return llBytes(wtf8Bytes(text));
148
155
  }
149
156
  const NO_BORROWED = new Set();
157
+ /** ScrExcKind tags of a generator slot holding a number or a boolean. */
158
+ const SCR_EXC_F64 = 1;
159
+ const SCR_EXC_BOOL = 2;
150
160
  /** Whether any node of `e` names the local (reads, writes, declarations,
151
161
  * captures). */
152
162
  function mentionsLocal(e, localId) {
@@ -226,6 +236,12 @@ export class LlEmitter {
226
236
  }
227
237
  return `, !tbaa !${tag}`;
228
238
  }
239
+ /** The `!range` attachment of an array length load: a length is at most
240
+ * 2^32 - 1 (the runtime's SCR_ARR_MAX_LENGTH), so conversions to double
241
+ * and back fold. 32-bit targets load the full range anyway. */
242
+ get arrayLengthRange() {
243
+ return this.sizeType === "i64" ? ", !range !{i64 0, i64 4294967296}" : "";
244
+ }
229
245
  fieldAliasAttachment(ptr) {
230
246
  const tag = this.fieldPointerTags.get(ptr);
231
247
  return tag === undefined ? "" : `, !tbaa !${tag}`;
@@ -312,6 +328,8 @@ export class LlEmitter {
312
328
  /** Interned NUL-terminated C-string constants (scr_jb_puts labels, the
313
329
  * stringify indent text): UTF-8 text → { symbol, byte length }. */
314
330
  cstrs = new Map();
331
+ /** Interned ScrMapOps descriptors (scr_map.h): initializer → symbol. */
332
+ mapOpsConsts = new Map();
315
333
  /** Type-directed walker functions (JSON serializers, the indent
316
334
  * rewriter, union ToString/join) — interned per typeKey/unionId, defs
317
335
  * flushed with the shape helpers. */
@@ -355,6 +373,8 @@ export class LlEmitter {
355
373
  callArrayReads = new Map();
356
374
  mapReadLifetimes = { locals: new Map(), arguments: new Map() };
357
375
  readNumberMapWrites = new Set();
376
+ /** The current function's fused string-key Map get-then-set pairs (map-entry-fusion.ts). */
377
+ mapEntryFusion = null;
358
378
  callLifetimes;
359
379
  constantCallbacks;
360
380
  currentConstantCallbacks = new Map();
@@ -402,6 +422,9 @@ export class LlEmitter {
402
422
  stableGlobals = new Set();
403
423
  tdzGlobals = new Map();
404
424
  constantNumericTables;
425
+ /** Constant-key reads on checked-dynamic objects, each with its own
426
+ * zero-initialized ScrDynReadIc (scr_runtime.h). */
427
+ dynReadIcSites = 0;
405
428
  /** May-throw analysis (computeMayThrow): pending
406
429
  * checks are emitted only after calls that can actually raise. */
407
430
  mayThrow;
@@ -518,8 +541,20 @@ export class LlEmitter {
518
541
  initializerBindings;
519
542
  /** Whole-program int32 storage and parameter/result facts. */
520
543
  int32Slots;
544
+ /** The program has no runtime store into a checked-dynamic value
545
+ * (dynamicFieldStores), so live views of classes and records never
546
+ * commit back into their objects. Library builds keep their commits:
547
+ * their values cross a native boundary. */
548
+ liveViewsReadOnly;
521
549
  /** Functions whose direct calls use i32 parameters or results. */
522
550
  int32Abis;
551
+ /** Pure number functions with speculative integer clones. */
552
+ integerClones;
553
+ /** The IR function being emitted. */
554
+ currentFunctionName = "";
555
+ integerParamRange(fnName, localId) {
556
+ return this.int32Slots.facts(fnName).params?.get(localId) ?? null;
557
+ }
523
558
  /** Immutable module constants (contract types) whose graphs become
524
559
  * immortal once initialized: their reads need no reference counting. */
525
560
  immortalGlobals;
@@ -531,6 +566,17 @@ export class LlEmitter {
531
566
  /** Active induction bindings retain their width independently of size_t. */
532
567
  integerLoopBindings = new Map();
533
568
  countedLoopsEnabled = false;
569
+ /** Speculative integer loops (speculative-integers.ts) run in this function. */
570
+ speculativeLoopsEnabled = false;
571
+ /** The fast copy of a speculative integer loop being emitted: its
572
+ * integer-computed writes and where each resumes in the ordinary copy. */
573
+ speculation = null;
574
+ /** The ordinary copy being emitted: resume labels still to place. */
575
+ speculationResume = null;
576
+ /** The function being emitted, for speculative globals. */
577
+ speculativeFunction = "";
578
+ /** Every function that writes each number global (computed on demand). */
579
+ globalWriters = null;
534
580
  /** Whole-program final reference fields (ir/final-fields.ts). */
535
581
  finalFields;
536
582
  /** The current function may borrow final fields of borrowable objects:
@@ -543,6 +589,9 @@ export class LlEmitter {
543
589
  byteWindowEntries = new Map();
544
590
  bytesBounds = new Set();
545
591
  integerViews = new Map();
592
+ /** Module globals the current function owns outright (initializer
593
+ * bindings): their function-local slot, which stands in for the global. */
594
+ privateGlobalSlots = new Map();
546
595
  /** Number locals of the current function stored only as an i32
547
596
  * (int32-locals.ts); their slots are `alloca i32`. */
548
597
  int32Locals = new Map();
@@ -576,6 +625,9 @@ export class LlEmitter {
576
625
  * switched-coroutine intrinsics (wasm32 async bodies and generators,
577
626
  * native synchronous generators; see coroLowered). */
578
627
  currentCoro = null;
628
+ /** Inline generator slot accesses need the %ScrGenHead/%ScrGenSlot
629
+ * mirrors in the module header. */
630
+ usesGenSlots = false;
579
631
  /** The generator channels of the function being emitted (null outside
580
632
  * generator bodies): yieldExpr emission reads them, and emitTryCatch's
581
633
  * catch prologue emits the GENRET sentinel re-unwind exactly here. */
@@ -600,6 +652,9 @@ export class LlEmitter {
600
652
  this.constantNumericTables = findConstantNumericTables(mod);
601
653
  this.initializerBindings = findInitializerBindings(mod);
602
654
  this.int32Slots = analyzeInt32Slots(mod);
655
+ const dynamicStores = dynamicFieldStores(mod);
656
+ this.liveViewsReadOnly =
657
+ mod.lib === undefined && !dynamicStores.all && dynamicStores.names.size === 0;
603
658
  this.int32Abis = this.debug === null ? findInt32Abis(mod, this.int32Slots) : new Map();
604
659
  this.immortalGlobals = new Set(mod.contracts?.immortalGlobals ?? []);
605
660
  this.programLifetime = new Set(mod.contracts?.programLifetime ?? []);
@@ -743,6 +798,20 @@ export class LlEmitter {
743
798
  return shape !== undefined && !recordDynKeepsIdentity(shape);
744
799
  });
745
800
  this.constantCallbacks = findConstantCallbacks(mod, this.callLifetimes);
801
+ // Integer clones signal a stack overflow through their result, which
802
+ // needs the single-threaded guard; worker programs also poll for
803
+ // termination, and debug builds and stack traces keep every frame.
804
+ // Clones read number arrays their function borrows (64-bit layouts).
805
+ this.integerClones =
806
+ this.debug === null &&
807
+ this.stackChecks !== null &&
808
+ mod.workers !== true &&
809
+ mod.lib === undefined &&
810
+ !this.stackTraces
811
+ ? findIntegerClones(mod, this.sizeType === "i64"
812
+ ? (fn, index) => this.callLifetimes.borrowed.get(fn)?.has(index) === true
813
+ : undefined)
814
+ : NO_INTEGER_CLONES;
746
815
  this.stackCallbacks = new StackCallbacks(this.fnByName);
747
816
  this.receiverReaders = new AmbientReceiverReaders(this.fnByName);
748
817
  this.stackCaptures = new StackCaptures(this.fnByName, this.callLifetimes, this.stackCallbacks);
@@ -780,6 +849,7 @@ export class LlEmitter {
780
849
  needBadTag: () => this.needBadTag(),
781
850
  internLiteral: (text) => this.internLiteral(text),
782
851
  cstr: (text) => this.cstr(text),
852
+ mapOps: (operands) => this.mapOps(operands),
783
853
  unitInstanceRef: (unionId, tag) => this.unitInstanceRef(unionId, tag),
784
854
  retypeGuardRef: (unionId) => this.retypeGuardRef(unionId),
785
855
  fieldAbsentTestIn: (B, value, unionId) => this.fieldAbsentTestIn(B, value, unionId),
@@ -880,6 +950,10 @@ export class LlEmitter {
880
950
  virtualSlotBorrowed(rootName, slotIndex) {
881
951
  return this.virtualBorrows.slot(rootName, slotIndex);
882
952
  }
953
+ /** Optimized emission: no debug-info (dev) posture. */
954
+ get optimizing() {
955
+ return this.debug === null;
956
+ }
883
957
  /** The vtable entry for one implementation (VirtualBorrows.entry). */
884
958
  virtualEntry(implFn) {
885
959
  return this.virtualBorrows.entry(implFn);
@@ -1376,7 +1450,25 @@ export class LlEmitter {
1376
1450
  ` call void @scr_run_exit_listeners(double %${prefix}hd)`,
1377
1451
  ];
1378
1452
  };
1379
- const globalReleases = globalReleaseLines("g");
1453
+ // The entry's exits release the module globals only where the thread
1454
+ // frees its heap when its script ends (scr_exit_frees: workers, and
1455
+ // builds that check for leaks); the main thread of other builds leaves
1456
+ // it to the operating system.
1457
+ const exitReleaseLines = (prefix) => {
1458
+ const releases = globalReleaseLines(prefix);
1459
+ if (releases.length === 0)
1460
+ return releases;
1461
+ this.declare(`declare zeroext i1 @scr_exit_frees()`);
1462
+ return [
1463
+ ` %${prefix}.frees = call zeroext i1 @scr_exit_frees()`,
1464
+ ` br i1 %${prefix}.frees, label %${prefix}.release, label %${prefix}.kept`,
1465
+ `${prefix}.release:`,
1466
+ ...releases,
1467
+ ` br label %${prefix}.kept`,
1468
+ `${prefix}.kept:`,
1469
+ ];
1470
+ };
1471
+ const globalReleases = exitReleaseLines("g");
1380
1472
  const asyncEntry = this.fnByName.get(this.mod.entry)?.async === true;
1381
1473
  const entryMayThrow = this.mayThrow.has(this.mod.entry);
1382
1474
  // The event loop runs when timers appeared OR any async/generator
@@ -1388,12 +1480,12 @@ export class LlEmitter {
1388
1480
  const runsLoop = this.usesTimers ||
1389
1481
  this.ffiHasForeignCallback ||
1390
1482
  this.mod.functions.some((f) => f.async === true || f.generator !== undefined);
1391
- const uncaughtReleases = entryMayThrow && !asyncEntry ? globalReleaseLines("gu") : [];
1392
- const loopReleasesU = runsLoop ? globalReleaseLines("gl") : [];
1393
- const loopReleasesR = runsLoop ? globalReleaseLines("gr") : [];
1394
- const topRejectReleases = asyncEntry ? globalReleaseLines("gt") : [];
1395
- const topPendingReleases = asyncEntry ? globalReleaseLines("gp") : [];
1396
- const loopReportedReleases = runsLoop ? globalReleaseLines("gq") : [];
1483
+ const uncaughtReleases = entryMayThrow && !asyncEntry ? exitReleaseLines("gu") : [];
1484
+ const loopReleasesU = runsLoop ? exitReleaseLines("gl") : [];
1485
+ const loopReleasesR = runsLoop ? exitReleaseLines("gr") : [];
1486
+ const topRejectReleases = asyncEntry ? exitReleaseLines("gt") : [];
1487
+ const topPendingReleases = asyncEntry ? exitReleaseLines("gp") : [];
1488
+ const loopReportedReleases = runsLoop ? exitReleaseLines("gq") : [];
1397
1489
  // main's epilogues read the flag / the loop entry points — declared
1398
1490
  // HERE, before the extern block flushes. They run once, so they keep
1399
1491
  // the out-of-line scr_exc_pending call; per-call checks inline the
@@ -1519,22 +1611,20 @@ export class LlEmitter {
1519
1611
  // preserving the target object's identity.
1520
1612
  `%ScrArr = type { ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, i8, i8, i16, i32, ptr, ptr, ptr, ptr }`,
1521
1613
  `%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 }`]),
1614
+ // The ScrMap prefix { rc, key_kind, val_kind, traced } — the inline
1615
+ // RC fast paths test the traced byte (shapes.ts).
1616
+ ...(this.rcHelpers === null ? [] : [`%ScrMapRc = type { ${this.sizeType}, i8, i8, i8 }`]),
1528
1617
  // The complete ScrMap and its { key, val, hash } entry, read by the
1529
1618
  // 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.
1619
+ // iteration steps (sc_map_iter_entry): field 8 is the i32 entry
1620
+ // count, field 10 the i64 absent-key filter, entries is field 11,
1621
+ // nbuckets and buckets fields 13 and 14, the direct index and its i32
1622
+ // length fields 15 and 16.
1533
1623
  ...(this.decls.has(NUMBER_MAP_ENTRY_DECL) ||
1534
1624
  this.decls.has(STRING_MAP_ENTRY_DECL) ||
1535
1625
  this.decls.has(MAP_ITER_DECL)
1536
1626
  ? [
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} }`,
1627
+ `%ScrMapIx = type { ${this.sizeType}, i8, i8, i8, i8, i32, ptr, ptr, i32, i32, i64, ptr, i32, i32, ptr, ptr, i32, i32 }`,
1538
1628
  ]
1539
1629
  : []),
1540
1630
  // String-keyed reference reads load the value slot of the entry
@@ -1572,7 +1662,7 @@ export class LlEmitter {
1572
1662
  // typed-array access GEPs through this directly: the IR type already
1573
1663
  // fixes elem, so the hot path needs neither a runtime kind load nor
1574
1664
  // the generic scr_bytes_get/set call.
1575
- `%ScrBytes = type { ${this.sizeType}, ${this.sizeType}, i32, ptr, ptr, i8, i8, i8, ptr }`,
1665
+ `%ScrBytes = type { ${this.sizeType}, ${this.sizeType}, i32, ptr, ptr, i8, i8, i8, i8, ptr }`,
1576
1666
  // The capture box { rc, kind, obj_retain, obj_release, obj_trace,
1577
1667
  // slot } — TDZ reads peek the payload slot (offset 40) directly.
1578
1668
  `%ScrBox = type { ${this.sizeType}, i32, ptr, ptr, ptr, i64 }`,
@@ -1583,6 +1673,15 @@ export class LlEmitter {
1583
1673
  `%ScrDynPath = type { ptr, ptr, ${this.sizeType} }`,
1584
1674
  `%ScrIslandModule = type { ptr, ptr, ${this.sizeType}, ${this.sizeType}, i32, ptr, ${this.sizeType}, ${this.sizeType} }`,
1585
1675
  `%ScrIslandEdge = type { ptr, ptr, ptr, i32 }`,
1676
+ // A generator's prefix { rc, receiver, fiber, state, out } and its
1677
+ // value slot { kind, f64, b, payload, release_fn } (scr_async.c pins
1678
+ // both): number and boolean yields and drives address OUT directly.
1679
+ ...(this.usesGenSlots
1680
+ ? [
1681
+ `%ScrGenSlot = type { i32, double, i8, ptr, ptr }`,
1682
+ `%ScrGenHead = type { ${this.sizeType}, ptr, ptr, i32, %ScrGenSlot }`,
1683
+ ]
1684
+ : []),
1586
1685
  ];
1587
1686
  for (const line of shapes.typeDefs)
1588
1687
  out.push(line);
@@ -1607,6 +1706,11 @@ export class LlEmitter {
1607
1706
  out.push(d);
1608
1707
  out.push(``);
1609
1708
  const flushedDecls = new Set(this.decls);
1709
+ {
1710
+ const tl = this.mod.workers === true || this.mod.lib?.threadInstances === true ? "thread_local " : "";
1711
+ for (let site = 0; site < this.dynReadIcSites; site++)
1712
+ out.push(`@sc_dyn_read_ic_${site} = internal ${tl}global { ptr, ${this.sizeType}, ${this.sizeType}, ptr } zeroinitializer`);
1713
+ }
1610
1714
  for (const table of this.constantNumericTables.values()) {
1611
1715
  const n = table.values.length;
1612
1716
  let tableTmp = 0;
@@ -1720,6 +1824,11 @@ export class LlEmitter {
1720
1824
  }
1721
1825
  if (this.cstrs.size > 0)
1722
1826
  out.push(``);
1827
+ for (const [body, sym] of this.mapOpsConsts) {
1828
+ out.push(`@${sym} = internal constant { ptr, ptr, ptr, ptr, ptr, ptr, ptr } { ${body} }`);
1829
+ }
1830
+ if (this.mapOpsConsts.size > 0)
1831
+ out.push(``);
1723
1832
  for (const line of ffiCallbacks.globals)
1724
1833
  out.push(line);
1725
1834
  if (ffiCallbacks.globals.length > 0)
@@ -2196,19 +2305,25 @@ export class LlEmitter {
2196
2305
  const sz = this.sizeType;
2197
2306
  let mapTmp = 0;
2198
2307
  const mapIndex = exactIndexLines("%k", "%nd", sz, () => `%ix${mapTmp++}`);
2199
- 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`,
2308
+ 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"
2309
+ ? [` %nd32 = load i32, ptr %ndp`, ` %nd = zext i32 %nd32 to i64`]
2310
+ : [` %nd = load i32, ptr %ndp`]),
2200
2311
  // A non-integral key inside the table is as absent as one outside it.
2201
2312
  ...mapIndex.lines.map((line) => ` ${line}`), ` br i1 ${mapIndex.ok}, label %slot, label %outside`, `slot:`, sz === "i64"
2202
2313
  ? ` %i = add i64 ${mapIndex.wide}, 0`
2203
- : ` %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:`, ` %hashed = call ptr @sc_map_entry_f64_hashed(ptr %m, double %k)`, ` ret ptr %hashed`, `}`, ``,
2314
+ : ` %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`, `}`, ``,
2204
2315
  // Hashing (scr_map_find_f64): the SameValueZero key word, its
2205
2316
  // scr_key_mix hash (zero remapped to one) and the absent-key filter.
2206
- `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 11`, ` %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 14`, ` %nb = load ${sz}, ptr %nbp`, ` %eps = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 13`, ` %ents = load ptr, ptr %eps`, ` %linear = icmp eq ${sz} %nb, 0`, ` br i1 %linear, label %scan, label %buckets`,
2317
+ `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"
2318
+ ? [` %nb32 = load i32, ptr %nbp`, ` %nb = zext i32 %nb32 to i64`]
2319
+ : [` %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`,
2207
2320
  // Small maps keep no bucket table: scan the cached entry hashes.
2208
- `scan:`, ` %nep = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 9`, ` %ne = load ${sz}, 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`,
2321
+ `scan:`, ` %nep = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 8`, ...(sz === "i64"
2322
+ ? [` %ne32 = load i32, ptr %nep`, ` %ne = zext i32 %ne32 to i64`]
2323
+ : [` %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`,
2209
2324
  // Compact tables tag each 24-bit entry index (+1) with the hash's
2210
2325
  // top byte; wider tables take the runtime lookup.
2211
- `buckets:`, ` %wide = icmp ugt ${sz} %nb, 16777216`, ` br i1 %wide, label %runtime, label %probe`, `probe:`, ` %bp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 15`, ` %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`, `}`, ``);
2326
+ `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`, `}`, ``);
2212
2327
  }
2213
2328
  // Map/Set iteration steps read the entries array inline: an index is
2214
2329
  // live when it is below the entry count and its entry's cached hash is
@@ -2217,7 +2332,7 @@ export class LlEmitter {
2217
2332
  // the iteration are visited.
2218
2333
  if (this.decls.has(MAP_ITER_DECL)) {
2219
2334
  const sz = this.sizeType;
2220
- 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`, `}`, ``);
2335
+ 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`, `}`, ``);
2221
2336
  }
2222
2337
  // String-key Map/Set lookups test the map's absent-key filter inline
2223
2338
  // (scr_map.c scr_map_filter_bits): a key whose two filter bits are not
@@ -2230,7 +2345,7 @@ export class LlEmitter {
2230
2345
  if (this.decls.has(STRING_MAP_ENTRY_DECL)) {
2231
2346
  defs.push(`define internal ptr @sc_map_entry_str(ptr %m, ptr %k) ${FN_ATTRS} {`, `entry:`, ...(this.sizeType === "i64"
2232
2347
  ? [
2233
- ` %fp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 11`,
2348
+ ` %fp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 10`,
2234
2349
  ` %filter = load i64, ptr %fp`,
2235
2350
  ` %capp = getelementptr inbounds %ScrStr, ptr %k, i32 0, i32 2`,
2236
2351
  ` %cap = load i64, ptr %capp`,
@@ -3036,6 +3151,18 @@ export class LlEmitter {
3036
3151
  needBadTag() {
3037
3152
  this.needsBadTag = true;
3038
3153
  }
3154
+ /** The ScrMapOps descriptor holding these operands (ShapeHost.mapOps). */
3155
+ mapOps(operands) {
3156
+ if (operands.every((op) => op === "null"))
3157
+ return "null";
3158
+ const body = operands.map((op) => `ptr ${op}`).join(", ");
3159
+ let sym = this.mapOpsConsts.get(body);
3160
+ if (!sym) {
3161
+ sym = this.contentSymbol("sc_map_ops_", body);
3162
+ this.mapOpsConsts.set(body, sym);
3163
+ }
3164
+ return `@${sym}`;
3165
+ }
3039
3166
  /** The interned immortal instance for a UNIT arm of a union — asserts
3040
3167
  * the arm really is payload-less (undefined/null). Public: class
3041
3168
  * emission initializes undefined-admitting fields through it
@@ -3616,6 +3743,259 @@ export class LlEmitter {
3616
3743
  this.B.line(`store i32 0, ptr ${slot}`);
3617
3744
  return slot;
3618
3745
  }
3746
+ /** Emit a loop's condition, body and update through `emitCopy`, twice
3747
+ * when speculative-integers.ts plans a fast integer copy. Both copies
3748
+ * share the loop's setup and exit; the ordinary copy comes first so the
3749
+ * fast copy is only emitted when every resume point exists. */
3750
+ emitLoopCopies(loop, allowed, exit, emitCopy) {
3751
+ const plan = allowed &&
3752
+ this.speculativeLoopsEnabled &&
3753
+ this.speculation === null &&
3754
+ this.speculationResume === null
3755
+ ? planSpeculativeLoop(loop, (id) => this.speculativeLocal(id), (id) => this.integerLoopBindings.has(id) || this.int32Locals.has(id), (e) => {
3756
+ const range = this.integerRanges.get(e);
3757
+ return (range !== undefined &&
3758
+ range !== null &&
3759
+ range.min >= -Number.MAX_SAFE_INTEGER &&
3760
+ range.max <= Number.MAX_SAFE_INTEGER);
3761
+ }, (id) => this.speculativeLocal(id) || this.speculativeInvariantGlobal(id))
3762
+ : null;
3763
+ if (!plan) {
3764
+ emitCopy(exit);
3765
+ return;
3766
+ }
3767
+ const B = this.B;
3768
+ const entries = new Map();
3769
+ let ok = "true";
3770
+ for (const [id, live] of plan.locals) {
3771
+ if (!live)
3772
+ continue;
3773
+ const value = B.tmp();
3774
+ B.line(`${value} = load double, ptr ${this.binding(id).slot}`);
3775
+ const [integer, exact] = emitSafeIntegerCheck(this, value);
3776
+ entries.set(id, integer);
3777
+ if (ok === "true")
3778
+ ok = exact;
3779
+ else {
3780
+ const both = B.tmp();
3781
+ B.line(`${both} = and i1 ${ok}, ${exact}`);
3782
+ ok = both;
3783
+ }
3784
+ }
3785
+ const fast = B.newLabel("spec.fast"), slow = B.newLabel("spec.slow");
3786
+ B.condBr(ok, fast, slow);
3787
+ const resume = new Map();
3788
+ for (const s of plan.writes)
3789
+ resume.set(s, { label: B.newLabel("spec.resume"), placed: false });
3790
+ B.startBlock(slow);
3791
+ this.speculationResume = resume;
3792
+ try {
3793
+ emitCopy(exit);
3794
+ }
3795
+ finally {
3796
+ this.speculationResume = null;
3797
+ }
3798
+ B.startBlock(fast);
3799
+ if (![...resume.values()].every((r) => r.placed)) {
3800
+ B.br(slow);
3801
+ return;
3802
+ }
3803
+ for (const id of plan.locals.keys()) {
3804
+ const shadow = B.slot();
3805
+ B.entryAllocas.push(`${shadow} = alloca i64 ; speculative integer ${this.currentLocals.get(id)?.name ?? id}`);
3806
+ const entry = entries.get(id);
3807
+ if (entry !== undefined) {
3808
+ // The same double, now visibly an integer conversion: the double
3809
+ // phis of the fast copy fold into integer phis.
3810
+ const exact = B.tmp();
3811
+ B.line(`store i64 ${entry}, ptr ${shadow}`);
3812
+ B.line(`${exact} = sitofp i64 ${entry} to double`);
3813
+ B.line(`store double ${exact}, ptr ${this.binding(id).slot}`);
3814
+ }
3815
+ else
3816
+ B.line(`store i64 0, ptr ${shadow}`);
3817
+ this.integerLoopBindings.set(id, {
3818
+ slot: shadow,
3819
+ type: "i64",
3820
+ signed: true,
3821
+ range: SPECULATIVE_RANGE,
3822
+ });
3823
+ }
3824
+ // A global's store stays in the loop (calls may read it). When nothing
3825
+ // in the loop can observe it, its double is written once on leaving the
3826
+ // copy instead, so the loop keeps it in a register as the double copy
3827
+ // would.
3828
+ const deferred = new Set();
3829
+ // Worker loops also leave through their cancellation poll.
3830
+ if (plan.observerFree && !this.mod.workers)
3831
+ for (const s of plan.writes) {
3832
+ const id = speculativeWriteTarget(s).localId;
3833
+ if (!this.currentLocals.has(id))
3834
+ deferred.add(id);
3835
+ }
3836
+ this.speculation = {
3837
+ writes: plan.writes,
3838
+ resume: new Map([...resume].map(([s, r]) => [s, r.label])),
3839
+ deferred,
3840
+ };
3841
+ try {
3842
+ if (deferred.size === 0)
3843
+ emitCopy(exit);
3844
+ else {
3845
+ const leave = B.newLabel("spec.leave");
3846
+ emitCopy(leave);
3847
+ B.startBlock(leave);
3848
+ this.writeBackSpeculativeGlobals(deferred);
3849
+ B.br(exit);
3850
+ }
3851
+ }
3852
+ finally {
3853
+ this.speculation = null;
3854
+ for (const id of plan.locals.keys())
3855
+ this.integerLoopBindings.delete(id);
3856
+ }
3857
+ }
3858
+ writeBackSpeculativeGlobals(deferred) {
3859
+ const B = this.B;
3860
+ for (const id of deferred) {
3861
+ const integer = B.tmp(), number = B.tmp();
3862
+ B.line(`${integer} = load i64, ptr ${this.integerLoopBindings.get(id).slot}`);
3863
+ B.line(`${number} = sitofp i64 ${integer} to double`);
3864
+ B.line(`store double ${number}, ptr ${this.binding(id).slot}`);
3865
+ }
3866
+ }
3867
+ /** A number local whose only storage is its own double slot, so an i64
3868
+ * shadow beside it can stand for it within one loop. */
3869
+ speculativeLocal(id) {
3870
+ const local = this.currentLocals.get(id);
3871
+ if (!local)
3872
+ return this.speculativeGlobal(id);
3873
+ if (local.type.kind !== "f64" || local.boxed || local.tdz)
3874
+ return false;
3875
+ return !(this.captureIds.has(id) ||
3876
+ this.int32Locals.has(id) ||
3877
+ this.integerLoopBindings.has(id) ||
3878
+ this.integerViews.has(id) ||
3879
+ this.localUnionStorage.has(id) ||
3880
+ this.reboundOwnerSlots.has(id) ||
3881
+ this.lazyCaptureBoxes.has(id) ||
3882
+ this.privateSplitLocals.has(id));
3883
+ }
3884
+ /** A module-level number written only by the file's top-level code that
3885
+ * is emitting this loop. Calls may still read it: the fast copy keeps its
3886
+ * double current. An init function runs once and is never re-entered, so
3887
+ * no call from the loop can write the global behind the shadow. */
3888
+ speculativeGlobal(id) {
3889
+ if (!this.speculativeFunction.startsWith("%init."))
3890
+ return false;
3891
+ const writers = this.numberGlobalWriters(id);
3892
+ return (writers !== null &&
3893
+ (writers.size === 0 || (writers.size === 1 && writers.has(this.speculativeFunction))));
3894
+ }
3895
+ /** A module-level number a loop of any function only reads, written by
3896
+ * nothing but top-level code: an init function never runs during a loop
3897
+ * of another function, so the entry value stays its value. */
3898
+ speculativeInvariantGlobal(id) {
3899
+ const writers = this.numberGlobalWriters(id);
3900
+ return writers !== null && [...writers].every((name) => name.startsWith("%init."));
3901
+ }
3902
+ /** The functions writing a number global a shadow may stand for, or null
3903
+ * when the global is not such a number. */
3904
+ numberGlobalWriters(id) {
3905
+ if (this.globalTypes.get(id)?.kind !== "f64")
3906
+ return null;
3907
+ if (this.tdzGlobals.has(id) || this.integerViews.has(id))
3908
+ return null;
3909
+ if (!this.globalWriters) {
3910
+ const writers = new Map();
3911
+ for (const fn of this.mod.functions) {
3912
+ const note = (localId) => {
3913
+ if (!localId.startsWith("%g."))
3914
+ return;
3915
+ const set = writers.get(localId) ?? new Set();
3916
+ set.add(fn.name);
3917
+ writers.set(localId, set);
3918
+ };
3919
+ everyStmtList(fn.body, {
3920
+ stmt: (s) => {
3921
+ if (s.kind === "assign" || s.kind === "varDecl")
3922
+ note(s.localId);
3923
+ return true;
3924
+ },
3925
+ expr: (e) => {
3926
+ if (e.kind === "assignExpr" || e.kind === "incDec")
3927
+ note(e.localId);
3928
+ return true;
3929
+ },
3930
+ });
3931
+ }
3932
+ this.globalWriters = writers;
3933
+ }
3934
+ return this.globalWriters.get(id) ?? new Set();
3935
+ }
3936
+ /** A speculated write in the fast copy: compute the value as a safe
3937
+ * integer, store it to the shadow and its exact double to the local.
3938
+ * Any failed check re-executes the statement in the ordinary copy. */
3939
+ emitSpeculativeWrite(s, resume) {
3940
+ const B = this.B;
3941
+ const target = speculativeWriteTarget(s);
3942
+ const binding = this.integerLoopBindings.get(target.localId);
3943
+ const failed = B.newLabel("spec.bail");
3944
+ let bails = false;
3945
+ this.declare("declare i1 @llvm.expect.i1(i1, i1)");
3946
+ const bail = (condition) => {
3947
+ if (condition === "false")
3948
+ return;
3949
+ bails = true;
3950
+ const expected = B.tmp();
3951
+ B.line(`${expected} = call i1 @llvm.expect.i1(i1 ${condition}, i1 false)`);
3952
+ const next = B.newLabel("spec.ok");
3953
+ B.condBr(expected, failed, next);
3954
+ B.startBlock(next);
3955
+ };
3956
+ // Heap reads come first, before any check, and release their temporaries
3957
+ // at once: a bailing path then owns nothing, and reads have no effect
3958
+ // whose reordering before the arithmetic could be observed.
3959
+ const reads = new Map();
3960
+ if (target.value !== null) {
3961
+ this.frames.push([]);
3962
+ for (const read of speculativeReads(target.value))
3963
+ reads.set(read, this.emitExpr(read));
3964
+ this.releaseFrame(this.frames.pop());
3965
+ }
3966
+ this.frames.push([]);
3967
+ let result;
3968
+ if (target.value === null) {
3969
+ const old = B.tmp();
3970
+ result = B.tmp();
3971
+ B.line(`${old} = load i64, ptr ${binding.slot}`);
3972
+ B.line(`${result} = ${target.op === "+" ? "add" : "sub"} nsw i64 ${old}, 1`);
3973
+ const biased = B.tmp(), outside = B.tmp();
3974
+ B.line(`${biased} = add i64 ${result}, ${Number.MAX_SAFE_INTEGER}`);
3975
+ B.line(`${outside} = icmp ugt i64 ${biased}, ${2 * Number.MAX_SAFE_INTEGER}`);
3976
+ bail(outside);
3977
+ }
3978
+ else
3979
+ result = emitSpeculativeInteger(this, target.value, bail, reads);
3980
+ // Speculable values read only numbers past this point.
3981
+ if (this.frames.pop().length > 0)
3982
+ throw new InternalCompilerError("llvm emitter bug: speculative write owns a temporary");
3983
+ B.line(`store i64 ${result}, ptr ${binding.slot}`);
3984
+ const deferred = this.speculation.deferred;
3985
+ if (!deferred.has(target.localId)) {
3986
+ const number = B.tmp();
3987
+ B.line(`${number} = sitofp i64 ${result} to double`);
3988
+ B.line(`store double ${number}, ptr ${this.binding(target.localId).slot}`);
3989
+ }
3990
+ if (!bails)
3991
+ return;
3992
+ const done = B.newLabel("spec.done");
3993
+ B.br(done);
3994
+ B.startBlock(failed);
3995
+ this.writeBackSpeculativeGlobals(deferred);
3996
+ B.br(resume);
3997
+ B.startBlock(done);
3998
+ }
3619
3999
  /** The amortized cancellation poll, emitted on the loop's back edge (the
3620
4000
  * continue target), after the body. A poll in the header would be a loop
3621
4001
  * exit ahead of the body; LLVM then cannot rotate the loop, so loads that
@@ -4054,6 +4434,14 @@ export class LlEmitter {
4054
4434
  this.B.line(`${v} = load ptr, ptr ${p}`);
4055
4435
  return v;
4056
4436
  }
4437
+ /** The pointer to ScrMap.flags of an index-signature record's overflow
4438
+ * map (byte offset sizeof(size_t) + 3; SCR_MAP_NULL_PROTO is bit 0). */
4439
+ recordMapFlagsPtr(objName, shapeId) {
4440
+ const ovf = this.recordOvfPtr(objName, shapeId);
4441
+ const p = this.B.tmp();
4442
+ this.B.line(`${p} = getelementptr inbounds i8, ptr ${ovf}, i64 ${mapFlagsOffset(this.sizeType)}`);
4443
+ return p;
4444
+ }
4057
4445
  /** Loads a record field (i8-stored bools trunc to i1). An inline union
4058
4446
  * field answers a private stack box with the slot's own bits, ABSENT
4059
4447
  * included: it may only be borrowed. */
@@ -4172,7 +4560,11 @@ export class LlEmitter {
4172
4560
  const g = this.globalTypes.get(id);
4173
4561
  if (!g)
4174
4562
  throw new InternalCompilerError(`llvm emitter bug: unknown binding ${id}`);
4175
- return { kind: "global", slot: `@${mangleGlobal(id)}`, type: g };
4563
+ return {
4564
+ kind: "global",
4565
+ slot: this.privateGlobalSlots.get(id) ?? `@${mangleGlobal(id)}`,
4566
+ type: g,
4567
+ };
4176
4568
  }
4177
4569
  emitDebugLocal(local, slot, arg = 0, captured = false) {
4178
4570
  const debug = this.debug?.local(local, this.debugScope, arg, captured);
@@ -4435,6 +4827,55 @@ export class LlEmitter {
4435
4827
  }
4436
4828
  this.emitCoroSuspendPrepared();
4437
4829
  }
4830
+ /** A native generator's OUT slot (scr_async.c's ScrGen.out, mirrored by
4831
+ * %ScrGenHead and %ScrGenSlot { kind, f64, b, payload, release_fn }). */
4832
+ genOutSlot(gen) {
4833
+ this.usesGenSlots = true;
4834
+ const slot = this.B.tmp();
4835
+ this.B.line(`${slot} = getelementptr inbounds %ScrGenHead, ptr ${gen}, i32 0, i32 4`);
4836
+ return slot;
4837
+ }
4838
+ /** A yield on a number or boolean channel: scr_gen_slot_f64/bool inline.
4839
+ * Such a body's OUT never holds a reference at a yield (only its final
4840
+ * completion can park one), so the slot needs no release first. */
4841
+ emitGenSlotStore(slot, kind, value) {
4842
+ const B = this.B;
4843
+ const field = B.tmp();
4844
+ if (kind === "f64") {
4845
+ B.line(`store i32 ${SCR_EXC_F64}, ptr ${slot}`);
4846
+ B.line(`${field} = getelementptr inbounds %ScrGenSlot, ptr ${slot}, i32 0, i32 1`);
4847
+ B.line(`store double ${value}, ptr ${field}`);
4848
+ }
4849
+ else {
4850
+ const byte = B.tmp();
4851
+ B.line(`store i32 ${SCR_EXC_BOOL}, ptr ${slot}`);
4852
+ B.line(`${field} = getelementptr inbounds %ScrGenSlot, ptr ${slot}, i32 0, i32 2`);
4853
+ B.line(`${byte} = zext i1 ${value} to i8`);
4854
+ B.line(`store i8 ${byte}, ptr ${field}`);
4855
+ }
4856
+ }
4857
+ /** scr_gen_take_out_f64/bool inline: the slot's value when it holds that
4858
+ * kind (otherwise 0 or false), leaving the slot empty. */
4859
+ emitGenSlotTake(slot, kind) {
4860
+ const B = this.B;
4861
+ const tag = B.tmp(), matches = B.tmp(), field = B.tmp(), raw = B.tmp(), out = B.tmp();
4862
+ B.line(`${tag} = load i32, ptr ${slot}`);
4863
+ B.line(`${matches} = icmp eq i32 ${tag}, ${kind === "f64" ? SCR_EXC_F64 : SCR_EXC_BOOL}`);
4864
+ if (kind === "f64") {
4865
+ B.line(`${field} = getelementptr inbounds %ScrGenSlot, ptr ${slot}, i32 0, i32 1`);
4866
+ B.line(`${raw} = load double, ptr ${field}`);
4867
+ B.line(`${out} = select i1 ${matches}, double ${raw}, double 0.0`);
4868
+ }
4869
+ else {
4870
+ const bit = B.tmp();
4871
+ B.line(`${field} = getelementptr inbounds %ScrGenSlot, ptr ${slot}, i32 0, i32 2`);
4872
+ B.line(`${raw} = load i8, ptr ${field}`);
4873
+ B.line(`${bit} = trunc i8 ${raw} to i1`);
4874
+ B.line(`${out} = select i1 ${matches}, i1 ${bit}, i1 false`);
4875
+ }
4876
+ B.line(`store i32 0, ptr ${slot}`);
4877
+ return out;
4878
+ }
4438
4879
  /** Suspend after a target-specific runtime helper already queued the
4439
4880
  * continuation (used by module awaits, which skip the hop when settled). */
4440
4881
  emitCoroSuspendPrepared() {
@@ -4509,9 +4950,25 @@ export class LlEmitter {
4509
4950
  }
4510
4951
  }
4511
4952
  }
4953
+ /** Number locals declared as a copy of a capture or of a lazily boxed
4954
+ * parameter, at the top of the body. */
4955
+ doubleCopies(fn) {
4956
+ const copies = [];
4957
+ for (const s of fn.body) {
4958
+ if (s.kind !== "varDecl")
4959
+ break;
4960
+ if (s.init?.kind !== "varRef" || s.init.type.kind !== "f64")
4961
+ continue;
4962
+ const source = s.init.localId;
4963
+ if (this.captureIds.has(source) || this.lazyCaptureBoxes.has(source))
4964
+ copies.push(s.localId);
4965
+ }
4966
+ return copies;
4967
+ }
4512
4968
  emitFunction(fn) {
4513
4969
  const B = new BlockBuilder();
4514
4970
  this.B = B;
4971
+ this.currentFunctionName = fn.name;
4515
4972
  // Fiber switches restore the active exception cell before control
4516
4973
  // returns to a synchronous frame (emitPendingCheck).
4517
4974
  B.excCellInvariant = fn.async !== true && fn.generator === undefined;
@@ -4564,11 +5021,20 @@ export class LlEmitter {
4564
5021
  !this.reboundByFunction.get(fn.name)?.has(fn.params[index].localId))
4565
5022
  this.borrowedParameters.add(fn.params[index].localId);
4566
5023
  }
5024
+ // A generator frame owns its unchanged parameters across suspensions.
5025
+ const generatorParameters = this.callLifetimes.generatorParameters.get(fn.name);
5026
+ if (generatorParameters !== undefined)
5027
+ for (const id of generatorParameters)
5028
+ this.borrowedParameters.add(id);
4567
5029
  this.captureIds = new Set([...(fn.captures ?? []), ...(fn.classCaptures ?? [])].map((c) => c.localId));
4568
5030
  this.stableCaptureIds =
4569
5031
  fn.captures !== undefined ? this.lazyCaptures.stableCaptures(fn.name) : new Set();
4570
5032
  this.integerLoopBindings.clear();
4571
5033
  this.countedLoopsEnabled = this.debug === null && !fn.async && !fn.generator;
5034
+ this.speculativeLoopsEnabled = this.countedLoopsEnabled;
5035
+ this.speculativeFunction = fn.name;
5036
+ this.speculation = null;
5037
+ this.speculationResume = null;
4572
5038
  this.finalFieldBorrows =
4573
5039
  !fn.async && !fn.generator && constructorClass(fn.name) === null && this.finalFields.size > 0;
4574
5040
  this.byteWindowsEnabled = this.countedLoopsEnabled;
@@ -4589,6 +5055,7 @@ export class LlEmitter {
4589
5055
  this.debug === null ? findScalarStringSlices(fn, this.callLifetimes) : new Map();
4590
5056
  this.mapReadLifetimes = findMapReadLifetimes(fn, this.boxedUnionsById, this.callLifetimes);
4591
5057
  this.readNumberMapWrites = findReadNumberMapWrites(fn);
5058
+ this.mapEntryFusion = this.debug === null ? findMapEntryFusions(fn) : null;
4592
5059
  this.localStackUnions = findLocalStackUnions(fn, this.callLifetimes, this.boxedUnionsById);
4593
5060
  this.localStackObjects = this.stackObjects
4594
5061
  ? findLocalStackObjects(this, fn, this.callLifetimes)
@@ -4604,12 +5071,30 @@ export class LlEmitter {
4604
5071
  this.bytesBounds = findBytesBounds(numericFn, this.integerRanges);
4605
5072
  // Debug builds keep every number local a double for the debugger's
4606
5073
  // variable view. Lazily boxed captured parameters keep their double,
4607
- // which the box is created from.
5074
+ // which the box is created from, and so do plain copies of a capture or
5075
+ // such a parameter (copyStableCaptures): the source already holds the
5076
+ // double, which an i32 copy would convert and widen again on every call.
5077
+ // Initializer bindings proven int32 move into an i32 local of their
5078
+ // initializer: no other code observes the global, so the loop-carried
5079
+ // value never round-trips through the double slot.
5080
+ const initializerLocals = initializerLocalCandidates(initializerBindings);
4608
5081
  const int32Locals = this.debug === null
4609
- ? findInt32Locals(fn, this.integerRanges, this.int32Slots.facts(fn.name), new Set(this.lazyCaptureBoxes.keys()))
5082
+ ? findInt32Locals(withInitializerLocals(fn, initializerLocals), this.integerRanges, this.int32Slots.facts(fn.name), new Set([...this.lazyCaptureBoxes.keys(), ...this.doubleCopies(fn)]))
4610
5083
  : null;
4611
5084
  this.int32Locals = int32Locals?.locals ?? new Map();
4612
5085
  this.int32LocalWrites = int32Locals?.values ?? new Set();
5086
+ const localizedGlobals = new Set();
5087
+ for (const global of initializerLocals) {
5088
+ if (!this.int32Locals.has(global.id))
5089
+ continue;
5090
+ localizedGlobals.add(global.id);
5091
+ this.currentLocals.set(global.id, {
5092
+ id: global.id,
5093
+ name: global.name,
5094
+ type: global.type,
5095
+ mutable: global.mutable,
5096
+ });
5097
+ }
4613
5098
  this.chainSlots.clear();
4614
5099
  this.finallyStack = [];
4615
5100
  this.tryStack = [];
@@ -4651,6 +5136,18 @@ export class LlEmitter {
4651
5136
  B.line(`${handle} = call ptr @llvm.coro.begin(token ${id}, ptr ${mem})`);
4652
5137
  B.line(`call void @${started}(ptr ${handle})`);
4653
5138
  B.line(`${self} = call ptr @scr_fiber_self()`);
5139
+ // A native synchronous body owns its generator for the whole run (the
5140
+ // ScrGen owns the frame), so its OUT slot address is fixed.
5141
+ let out = null;
5142
+ const yieldKind = fn.generator?.yieldT.kind;
5143
+ if (!this.wasi &&
5144
+ fn.async !== true &&
5145
+ (yieldKind === "f64" || yieldKind === "date" || yieldKind === "bool")) {
5146
+ this.declare(`declare ptr @scr_gen_of_fiber(ptr)`);
5147
+ const gen = B.tmp();
5148
+ B.line(`${gen} = call ptr @scr_gen_of_fiber(ptr ${self})`);
5149
+ out = this.genOutSlot(gen);
5150
+ }
4654
5151
  this.currentCoro = {
4655
5152
  kind: fn.generator !== undefined ? "generator" : "async",
4656
5153
  id,
@@ -4659,6 +5156,7 @@ export class LlEmitter {
4659
5156
  finalLabel: B.newLabel("coro.final"),
4660
5157
  cleanupLabel: B.newLabel("coro.cleanup"),
4661
5158
  suspendLabel: B.newLabel("coro.suspend"),
5159
+ out,
4662
5160
  };
4663
5161
  }
4664
5162
  const paramIds = new Set(fn.params.map((p) => p.localId));
@@ -4679,6 +5177,32 @@ export class LlEmitter {
4679
5177
  B.line(`${value} = load double, ptr @${mangleGlobal(global.id)}`);
4680
5178
  this.storeIntegerView(global.id, { name: value, type: global.type });
4681
5179
  }
5180
+ // A number global only its guarded, synchronous initializer reads or
5181
+ // writes (findInitializerBindings) is invisible to every other function,
5182
+ // and reentry returns before reaching it, so a local slot can stand in
5183
+ // for it: LLVM keeps it in a register across calls and loops that would
5184
+ // otherwise force a store and reload per iteration. The global's
5185
+ // storage is never read again, so nothing writes the slot back. Bindings
5186
+ // proven int32 already live in an i32 local (localizedGlobals above);
5187
+ // this covers the remaining doubles.
5188
+ this.privateGlobalSlots.clear();
5189
+ if (this.debug === null && this.currentCoro === null && !fn.async && !fn.generator) {
5190
+ const conditional = initializerLocals.length ? conditionalLoopWrites(fn.body) : null;
5191
+ for (const global of initializerLocals) {
5192
+ if (localizedGlobals.has(global.id))
5193
+ continue;
5194
+ // A conditional update in a loop stays a memory update (see
5195
+ // conditionalLoopWrites).
5196
+ if (conditional?.has(global.id))
5197
+ continue;
5198
+ const slot = B.slot();
5199
+ const value = B.tmp();
5200
+ B.entryAllocas.push(`${slot} = alloca double ; module binding ${global.name}`);
5201
+ B.line(`${value} = load double, ptr @${mangleGlobal(global.id)}`);
5202
+ B.line(`store double ${value}, ptr ${slot}`);
5203
+ this.privateGlobalSlots.set(global.id, slot);
5204
+ }
5205
+ }
4682
5206
  for (const local of this.currentLocals.values()) {
4683
5207
  // Boxed locals' slots hold their capture BOX (a ptr); captured
4684
5208
  // (env-borrowed) locals bind the incoming box below. A caught-typed
@@ -4691,6 +5215,9 @@ export class LlEmitter {
4691
5215
  : this.llType(local.type);
4692
5216
  B.entryAllocas.push(`%${mangleLocal(local.id)} = alloca ${slotTy} ; ${local.name}`);
4693
5217
  this.emitDebugLocal(local, `%${mangleLocal(local.id)}`, fn.params.findIndex((p) => p.localId === local.id) + 1, this.captureIds.has(local.id));
5218
+ // A localized initializer binding starts at its global's zero value.
5219
+ if (localizedGlobals.has(local.id))
5220
+ B.line(`store i32 0, ptr %${mangleLocal(local.id)}`);
4694
5221
  // Refcounted/boxed locals start NULL (the C prologue's `= NULL`):
4695
5222
  // scope-exit releases run whether or not an assign ever did.
4696
5223
  if (paramIds.has(local.id) || this.captureIds.has(local.id))
@@ -4910,9 +5437,17 @@ export class LlEmitter {
4910
5437
  attrs = `inlinehint ${attrs}`;
4911
5438
  const debug = this.debugScope === null ? "" : ` !dbg ${this.debugScope}`;
4912
5439
  const symbol = borrowed ? mangleBorrowedFunction(fn.name) : mangleFunction(fn.name);
5440
+ this.privateGlobalSlots.clear();
4913
5441
  let body = `define internal ${ret} @${symbol}${int32Abi ? INT32_ABI_SUFFIX : ""}(${params.join(", ")}) ${attrs}${debug} { ; ${fn.name}\n${B.render()}\n}`;
4914
5442
  if (int32Abi)
4915
5443
  body += "\n" + this.emitInt32AbiWrapper(fn, symbol, int32Abi);
5444
+ if (this.integerClones.members.has(fn.name)) {
5445
+ body += "\n" + emitIntegerClone(fn, this, this.stackChecks?.has(fn.name) === true);
5446
+ if (this.integerClones.dispatched.has(fn.name))
5447
+ body +=
5448
+ "\n" +
5449
+ emitIntegerDispatcher(fn, this, `${symbol}${int32Abi ? INT32_ABI_SUFFIX : ""}`, int32Abi?.params ?? [], int32Abi?.result === true);
5450
+ }
4916
5451
  if (!borrowed)
4917
5452
  return body;
4918
5453
  const slot = this.implSlotBorrowed(fn.name);
@@ -5477,6 +6012,17 @@ export class LlEmitter {
5477
6012
  this.releaseScope(scope);
5478
6013
  }
5479
6014
  emitStmt(s) {
6015
+ const resume = this.speculationResume?.get(s);
6016
+ if (resume && !resume.placed && !this.B.isTerminated()) {
6017
+ // The fast copy's failed checks re-execute this statement here.
6018
+ resume.placed = true;
6019
+ this.B.br(resume.label);
6020
+ this.B.startBlock(resume.label);
6021
+ }
6022
+ if (this.speculation?.writes.has(s)) {
6023
+ this.emitSpeculativeWrite(s, this.speculation.resume.get(s));
6024
+ return;
6025
+ }
5480
6026
  const previous = this.B.debugLocation;
5481
6027
  this.B.debugLocation = this.debug?.location(s.loc, this.debugScope) ?? null;
5482
6028
  const loop = s.kind === "while" || s.kind === "doWhile" || s.kind === "for" || s.kind === "forOf";
@@ -5779,6 +6325,8 @@ export class LlEmitter {
5779
6325
  // the replaced element itself). A local receiver that the index and
5780
6326
  // value cannot rebind is read in place, as for field stores; the
5781
6327
  // released element cannot reach that binding.
6328
+ if (this.emitArraySelfConcat(s))
6329
+ break;
5782
6330
  const arr = this.emitStableReceiver(s.arr, [s.index, s.value]);
5783
6331
  const idx = this.emitExpr(s.index);
5784
6332
  const v = this.emitExpr(s.value);
@@ -5813,7 +6361,7 @@ export class LlEmitter {
5813
6361
  const resize = B.newLabel("arr.len.set"), done = B.newLabel("arr.len.done");
5814
6362
  B.line(`${lenPtr} = getelementptr inbounds %ScrArr, ptr ${arr.name}, i32 0, i32 1`);
5815
6363
  this.markMemoryPointer(lenPtr, "array:header");
5816
- B.line(`${len} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}`);
6364
+ B.line(`${len} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}${this.arrayLengthRange}`);
5817
6365
  B.line(`${lenNumber} = ${sizeToDouble(this.sizeType)} ${this.sizeType} ${len} to double`);
5818
6366
  B.line(`${same} = fcmp oeq double ${length.name}, ${lenNumber}`);
5819
6367
  B.condBr(same, done, resize);
@@ -5877,6 +6425,8 @@ export class LlEmitter {
5877
6425
  this.storeInt32Field(ptr, v, s.value);
5878
6426
  break;
5879
6427
  }
6428
+ if (this.emitFieldSelfConcat(s))
6429
+ break;
5880
6430
  const obj = this.emitStableReceiver(s.obj, [s.value]);
5881
6431
  const v = this.emitFieldValue(s.value, s.kind === "recordSet"
5882
6432
  ? this.recordFieldInline(s.shapeId, s.field)
@@ -6103,57 +6653,61 @@ export class LlEmitter {
6103
6653
  }
6104
6654
  case "while": {
6105
6655
  const workerBudget = this.workerLoopBudget(s);
6106
- const lc = B.newLabel("loop.c");
6107
- const lb = B.newLabel("loop.b");
6108
6656
  const le = B.newLabel("loop.e");
6109
- const lp = workerBudget !== null ? B.newLabel("loop.p") : lc;
6110
- B.br(lc);
6111
- B.startBlock(lc);
6112
- B.condBr(this.emitCondition(s.cond), lb, le);
6113
- B.startBlock(lb);
6114
- this.jumpTargets.push({
6115
- kind: "loop",
6116
- brkLabel: le,
6117
- contLabel: lp,
6118
- labels: s.labels,
6119
- frameDepth: this.frames.length,
6120
- scopeDepth: this.scopes.length,
6121
- finallyDepth: this.finallyStack.length,
6122
- });
6123
- this.emitBlock(s.body);
6124
- this.jumpTargets.pop();
6125
- B.br(lp);
6126
- if (lp !== lc) {
6127
- B.startBlock(lp);
6128
- this.emitWorkerLoopCheck(workerBudget);
6657
+ this.emitLoopCopies(s, true, le, (exit) => {
6658
+ const lc = B.newLabel("loop.c");
6659
+ const lb = B.newLabel("loop.b");
6660
+ const lp = workerBudget !== null ? B.newLabel("loop.p") : lc;
6129
6661
  B.br(lc);
6130
- }
6662
+ B.startBlock(lc);
6663
+ B.condBr(this.emitCondition(s.cond), lb, exit);
6664
+ B.startBlock(lb);
6665
+ this.jumpTargets.push({
6666
+ kind: "loop",
6667
+ brkLabel: exit,
6668
+ contLabel: lp,
6669
+ labels: s.labels,
6670
+ frameDepth: this.frames.length,
6671
+ scopeDepth: this.scopes.length,
6672
+ finallyDepth: this.finallyStack.length,
6673
+ });
6674
+ this.emitBlock(s.body);
6675
+ this.jumpTargets.pop();
6676
+ B.br(lp);
6677
+ if (lp !== lc) {
6678
+ B.startBlock(lp);
6679
+ this.emitWorkerLoopCheck(workerBudget);
6680
+ B.br(lc);
6681
+ }
6682
+ });
6131
6683
  B.startBlock(le);
6132
6684
  break;
6133
6685
  }
6134
6686
  case "doWhile": {
6135
6687
  const workerBudget = this.workerLoopBudget(s);
6136
6688
  // Body first (runs at least once); continue jumps to the CONDITION.
6137
- const lb = B.newLabel("loop.b");
6138
- const lc = B.newLabel("loop.c");
6139
6689
  const le = B.newLabel("loop.e");
6140
- B.br(lb);
6141
- B.startBlock(lb);
6142
- this.jumpTargets.push({
6143
- kind: "loop",
6144
- brkLabel: le,
6145
- contLabel: lc,
6146
- labels: s.labels,
6147
- frameDepth: this.frames.length,
6148
- scopeDepth: this.scopes.length,
6149
- finallyDepth: this.finallyStack.length,
6690
+ this.emitLoopCopies(s, true, le, (exit) => {
6691
+ const lb = B.newLabel("loop.b");
6692
+ const lc = B.newLabel("loop.c");
6693
+ B.br(lb);
6694
+ B.startBlock(lb);
6695
+ this.jumpTargets.push({
6696
+ kind: "loop",
6697
+ brkLabel: exit,
6698
+ contLabel: lc,
6699
+ labels: s.labels,
6700
+ frameDepth: this.frames.length,
6701
+ scopeDepth: this.scopes.length,
6702
+ finallyDepth: this.finallyStack.length,
6703
+ });
6704
+ this.emitBlock(s.body);
6705
+ this.jumpTargets.pop();
6706
+ B.br(lc);
6707
+ B.startBlock(lc);
6708
+ this.emitWorkerLoopCheck(workerBudget);
6709
+ B.condBr(this.emitCondition(s.cond), lb, exit);
6150
6710
  });
6151
- this.emitBlock(s.body);
6152
- this.jumpTargets.pop();
6153
- B.br(lc);
6154
- B.startBlock(lc);
6155
- this.emitWorkerLoopCheck(workerBudget);
6156
- B.condBr(this.emitCondition(s.cond), lb, le);
6157
6711
  B.startBlock(le);
6158
6712
  break;
6159
6713
  }
@@ -6285,8 +6839,6 @@ export class LlEmitter {
6285
6839
  for (const initializer of initializers)
6286
6840
  this.emitStmt(initializer);
6287
6841
  }
6288
- const lc = B.newLabel("loop.c");
6289
- const lb = B.newLabel("loop.b");
6290
6842
  const le = B.newLabel("loop.e");
6291
6843
  // JS `for (let i ...)`: each iteration gets a FRESH binding holding
6292
6844
  // a copy of the previous one (closures made in iteration k keep
@@ -6313,70 +6865,76 @@ export class LlEmitter {
6313
6865
  }
6314
6866
  };
6315
6867
  const workerBudget = this.workerLoopBudget(s);
6316
- const lu = s.update || capturedLets.length > 0 || workerBudget !== null ? B.newLabel("loop.u") : lc;
6317
6868
  freshenBindings();
6318
- B.br(lc);
6319
- B.startBlock(lc);
6320
- if (integerLoop && integerSlot) {
6321
- const receiver = this.emitStableReceiver(integerLoop.limitReceiver, []);
6322
- const lenPtr = B.tmp();
6323
- const len = B.tmp();
6324
- const index = B.tmp();
6325
- const inBounds = B.tmp();
6326
- B.line(`${lenPtr} = getelementptr inbounds %${integerArrayLoop ? "ScrArr" : "ScrBytes"}, ptr ${receiver.name}, i64 0, i32 1`);
6327
- if (integerArrayLoop)
6328
- this.markMemoryPointer(lenPtr, "array:header");
6329
- B.line(`${len} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}`);
6330
- B.line(`${index} = load ${this.sizeType}, ptr ${integerSlot}`);
6331
- B.line(`${inBounds} = icmp ult ${this.sizeType} ${index}, ${len}`);
6332
- B.condBr(inBounds, lb, le);
6333
- }
6334
- else if (countedLoop && integerSlot && countedLimit) {
6335
- const index = B.tmp();
6336
- const inBounds = B.tmp();
6337
- B.line(`${index} = load i64, ptr ${integerSlot}`);
6338
- B.line(`${inBounds} = icmp ${countedLoop.step > 0 ? "slt" : "sgt"} i64 ${index}, ${countedLimit}`);
6339
- B.condBr(inBounds, lb, le);
6340
- }
6341
- else if (s.cond)
6342
- B.condBr(this.emitCondition(s.cond), lb, le);
6343
- else
6344
- B.br(lb);
6345
- B.startBlock(lb);
6346
- this.jumpTargets.push({
6347
- kind: "loop",
6348
- brkLabel: le,
6349
- contLabel: lu,
6350
- labels: s.labels,
6351
- frameDepth: this.frames.length,
6352
- scopeDepth: this.scopes.length,
6353
- finallyDepth: this.finallyStack.length,
6354
- });
6355
- this.emitBlock(s.body);
6356
- this.jumpTargets.pop();
6357
- B.br(lu);
6358
- if (lu !== lc) {
6359
- B.startBlock(lu);
6360
- this.emitWorkerLoopCheck(workerBudget);
6361
- freshenBindings();
6869
+ this.emitLoopCopies(s, capturedLets.length === 0, le, (exit) => {
6870
+ const lc = B.newLabel("loop.c");
6871
+ const lb = B.newLabel("loop.b");
6872
+ const lu = s.update || capturedLets.length > 0 || workerBudget !== null
6873
+ ? B.newLabel("loop.u")
6874
+ : lc;
6875
+ B.br(lc);
6876
+ B.startBlock(lc);
6362
6877
  if (integerLoop && integerSlot) {
6363
- const old = B.tmp();
6364
- const next = B.tmp();
6365
- B.line(`${old} = load ${this.sizeType}, ptr ${integerSlot}`);
6366
- B.line(`${next} = add nuw ${this.sizeType} ${old}, 1`);
6367
- B.line(`store ${this.sizeType} ${next}, ptr ${integerSlot}`);
6878
+ const receiver = this.emitStableReceiver(integerLoop.limitReceiver, []);
6879
+ const lenPtr = B.tmp();
6880
+ const len = B.tmp();
6881
+ const index = B.tmp();
6882
+ const inBounds = B.tmp();
6883
+ B.line(`${lenPtr} = getelementptr inbounds %${integerArrayLoop ? "ScrArr" : "ScrBytes"}, ptr ${receiver.name}, i64 0, i32 1`);
6884
+ if (integerArrayLoop)
6885
+ this.markMemoryPointer(lenPtr, "array:header");
6886
+ B.line(`${len} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}`);
6887
+ B.line(`${index} = load ${this.sizeType}, ptr ${integerSlot}`);
6888
+ B.line(`${inBounds} = icmp ult ${this.sizeType} ${index}, ${len}`);
6889
+ B.condBr(inBounds, lb, exit);
6368
6890
  }
6369
- else if (countedLoop && integerSlot) {
6370
- const old = B.tmp();
6371
- const next = B.tmp();
6372
- B.line(`${old} = load i64, ptr ${integerSlot}`);
6373
- B.line(`${next} = add nsw i64 ${old}, ${countedLoop.step}`);
6374
- B.line(`store i64 ${next}, ptr ${integerSlot}`);
6891
+ else if (countedLoop && integerSlot && countedLimit) {
6892
+ const index = B.tmp();
6893
+ const inBounds = B.tmp();
6894
+ B.line(`${index} = load i64, ptr ${integerSlot}`);
6895
+ B.line(`${inBounds} = icmp ${countedLoop.step > 0 ? "slt" : "sgt"} i64 ${index}, ${countedLimit}`);
6896
+ B.condBr(inBounds, lb, exit);
6375
6897
  }
6376
- else if (s.update)
6377
- this.emitStmt(s.update);
6378
- B.br(lc);
6379
- }
6898
+ else if (s.cond)
6899
+ B.condBr(this.emitCondition(s.cond), lb, exit);
6900
+ else
6901
+ B.br(lb);
6902
+ B.startBlock(lb);
6903
+ this.jumpTargets.push({
6904
+ kind: "loop",
6905
+ brkLabel: exit,
6906
+ contLabel: lu,
6907
+ labels: s.labels,
6908
+ frameDepth: this.frames.length,
6909
+ scopeDepth: this.scopes.length,
6910
+ finallyDepth: this.finallyStack.length,
6911
+ });
6912
+ this.emitBlock(s.body);
6913
+ this.jumpTargets.pop();
6914
+ B.br(lu);
6915
+ if (lu !== lc) {
6916
+ B.startBlock(lu);
6917
+ this.emitWorkerLoopCheck(workerBudget);
6918
+ freshenBindings();
6919
+ if (integerLoop && integerSlot) {
6920
+ const old = B.tmp();
6921
+ const next = B.tmp();
6922
+ B.line(`${old} = load ${this.sizeType}, ptr ${integerSlot}`);
6923
+ B.line(`${next} = add nuw ${this.sizeType} ${old}, 1`);
6924
+ B.line(`store ${this.sizeType} ${next}, ptr ${integerSlot}`);
6925
+ }
6926
+ else if (countedLoop && integerSlot) {
6927
+ const old = B.tmp();
6928
+ const next = B.tmp();
6929
+ B.line(`${old} = load i64, ptr ${integerSlot}`);
6930
+ B.line(`${next} = add nsw i64 ${old}, ${countedLoop.step}`);
6931
+ B.line(`store i64 ${next}, ptr ${integerSlot}`);
6932
+ }
6933
+ else if (s.update)
6934
+ this.emitStmt(s.update);
6935
+ B.br(lc);
6936
+ }
6937
+ });
6380
6938
  B.startBlock(le);
6381
6939
  this.releaseScope(this.scopes.pop());
6382
6940
  if (integerLoop)
@@ -6439,7 +6997,7 @@ export class LlEmitter {
6439
6997
  // The inline form of scr_arr_len, like the `length` intrinsic.
6440
6998
  B.line(`${lenPtr} = getelementptr inbounds %ScrArr, ptr ${arr.name}, i32 0, i32 1`);
6441
6999
  this.markMemoryPointer(lenPtr, "array:header");
6442
- B.line(`${rawLen} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}`);
7000
+ B.line(`${rawLen} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}${this.arrayLengthRange}`);
6443
7001
  B.line(`${len} = ${sizeToDouble(this.sizeType)} ${this.sizeType} ${rawLen} to double`);
6444
7002
  B.line(`${inBounds} = fcmp olt double ${i}, ${len}`);
6445
7003
  cur = i;
@@ -7146,6 +7704,118 @@ export class LlEmitter {
7146
7704
  return null;
7147
7705
  return this.emitExpr(packed.elems[0]);
7148
7706
  }
7707
+ /**
7708
+ * Lower `recv.f = recv.f + suffix` on a plain string field like
7709
+ * emitStringSelfConcatAssign does for locals. The field's old value is
7710
+ * snapshotted (+1) and the suffix evaluated in JavaScript order; then the
7711
+ * field unlinks and releases its CURRENT value, so the snapshot is unique
7712
+ * unless a real alias exists and the concat appends in place instead of
7713
+ * copying the whole string on every append. Returns false (emitting
7714
+ * nothing) for every other store.
7715
+ */
7716
+ emitFieldSelfConcat(s) {
7717
+ const suffix = matchFieldSelfConcat(s);
7718
+ if (!suffix || s.value.kind !== "strConcat")
7719
+ return false;
7720
+ let fieldType;
7721
+ if (s.kind === "fieldSet") {
7722
+ if (this.inlineUnions.classField(s.className, s.field) ||
7723
+ this.classMeta.get(s.className)?.root.def.name === "%Error")
7724
+ return false;
7725
+ fieldType = classFieldIndex(this.classMetaOf(s.className), s.field).type;
7726
+ }
7727
+ else {
7728
+ fieldType = this.recordShape(s.shapeId).fields.find((f) => f.name === s.field)?.type;
7729
+ }
7730
+ if (fieldType?.kind !== "string")
7731
+ return false;
7732
+ const B = this.B;
7733
+ const obj = this.emitStableReceiver(s.obj, [s.value]);
7734
+ const snapshot = this.emitExpr(s.value.left);
7735
+ const parts = stringParts(suffix);
7736
+ const mixed = parts.length > 1 || numberPart(parts[0]) !== null;
7737
+ const inputs = mixed ? emitConcatInputs(this, parts) : [];
7738
+ const right = mixed ? null : this.emitExpr(suffix);
7739
+ if (s.kind === "fieldSet")
7740
+ this.emitStoreGuard(obj.name, s);
7741
+ else if (this.publishedTypes?.recordGuarded(s.shapeId) === true)
7742
+ this.emitPublishedFieldGuard(obj.name, null, s.field);
7743
+ const { ptr } = s.kind === "fieldSet"
7744
+ ? this.classFieldPtr(obj.name, s.className, s.field)
7745
+ : this.recordFieldPtr(obj.name, s.shapeId, s.field);
7746
+ // Unlink, then release: nothing between here and the store below can
7747
+ // run user code or observe the field (the concat only traps on OOM).
7748
+ // Only an owned snapshot may survive the field's release.
7749
+ const owned = this.ownsSnapshot(snapshot.name);
7750
+ if (owned) {
7751
+ const old = this.loadField(ptr, fieldType, false);
7752
+ this.storeField(ptr, fieldType, "null", false);
7753
+ this.releaseValue(old, fieldType);
7754
+ }
7755
+ let raw;
7756
+ if (right === null) {
7757
+ raw = emitMixedConcat(this, snapshot.name, inputs);
7758
+ }
7759
+ else {
7760
+ this.declare(`declare ptr @scr_str_concat(ptr, ptr)`);
7761
+ raw = B.tmp();
7762
+ B.line(`${raw} = call ptr @scr_str_concat(ptr ${snapshot.name}, ptr ${right.name})`);
7763
+ }
7764
+ // The +1 result moves straight into the (now empty) field.
7765
+ if (owned)
7766
+ this.storeField(ptr, fieldType, raw, false);
7767
+ else
7768
+ this.replaceField(ptr, fieldType, false, this.own({ name: raw, type: fieldType }));
7769
+ return true;
7770
+ }
7771
+ /** A snapshot the current statement frames own (+1), or an immortal
7772
+ * value: it stays valid after its container releases the same string. */
7773
+ ownsSnapshot(name) {
7774
+ return (this.isImmortal(name) || this.frames.some((f) => f.some((v) => v.name === name && !v.slot)));
7775
+ }
7776
+ /**
7777
+ * `a[i] = a[i] + suffix` on a string array (the canonical statement form
7778
+ * of `a[i] += suffix`): the old element is snapshotted (+1) and the
7779
+ * suffix evaluated in JavaScript order; then, when the slot still holds
7780
+ * the snapshot, the array lets go of it (emitDenseSlotUnlink) so the
7781
+ * concat can append in place, and the result is stored as usual.
7782
+ */
7783
+ emitArraySelfConcat(s) {
7784
+ const value = s.value;
7785
+ if (value.kind !== "strConcat" ||
7786
+ value.type.kind !== "string" ||
7787
+ value.left.type.kind !== "string" ||
7788
+ value.right.type.kind !== "string" ||
7789
+ s.arr.type.kind !== "array" ||
7790
+ s.arr.type.elem.kind !== "string" ||
7791
+ s.arr.kind !== "varRef" ||
7792
+ s.index.kind !== "varRef" ||
7793
+ !readsArraySlot(value.left, s.arr.localId, s.index.localId))
7794
+ return false;
7795
+ const elem = s.arr.type.elem;
7796
+ const B = this.B;
7797
+ const arr = this.emitStableReceiver(s.arr, [s.index, s.value]);
7798
+ const idx = this.emitExpr(s.index);
7799
+ const snapshot = this.emitExpr(value.left);
7800
+ const parts = stringParts(value.right);
7801
+ const mixed = parts.length > 1 || numberPart(parts[0]) !== null;
7802
+ const inputs = mixed ? emitConcatInputs(this, parts) : [];
7803
+ const right = mixed ? null : this.emitExpr(value.right);
7804
+ this.emitPublishedArrayGuard(arr.name, s.arr.type, 0, idx.name);
7805
+ if (this.ownsSnapshot(snapshot.name))
7806
+ emitDenseSlotUnlink(this, arr.name, idx, s.index, snapshot.name, this.internLiteral(""), elem);
7807
+ let raw;
7808
+ if (right === null) {
7809
+ raw = emitMixedConcat(this, snapshot.name, inputs);
7810
+ }
7811
+ else {
7812
+ this.declare(`declare ptr @scr_str_concat(ptr, ptr)`);
7813
+ raw = B.tmp();
7814
+ B.line(`${raw} = call ptr @scr_str_concat(ptr ${snapshot.name}, ptr ${right.name})`);
7815
+ }
7816
+ emitDenseArraySet(this, arr.name, idx, s.index, "ref", elem, raw);
7817
+ return true;
7818
+ }
7149
7819
  /** `local = local + suffix` when the suffix cannot read or write the
7150
7820
  * local (an unboxed binding is only reachable through this function's own
7151
7821
  * expressions): reading the binding after the suffix gives the value the
@@ -7373,6 +8043,7 @@ export class LlEmitter {
7373
8043
  declare: (decl) => this.declare(decl),
7374
8044
  cstr: (text) => this.cstr(text),
7375
8045
  sizeType: this.sizeType,
8046
+ rcAudit: this.shapeHost.objectAudit !== false,
7376
8047
  tracedShapes: this.tracedShapes,
7377
8048
  unionsById: this.unionsById,
7378
8049
  recordsById: this.recordsById,
@@ -8057,6 +8728,10 @@ export class LlEmitter {
8057
8728
  emitExpr(e) {
8058
8729
  return emitExpr(this, e);
8059
8730
  }
8731
+ /** A fresh read-site cache for one constant-key dyn read. */
8732
+ dynReadIcSite() {
8733
+ return `@sc_dyn_read_ic_${this.dynReadIcSites++}`;
8734
+ }
8060
8735
  dynKind(d) {
8061
8736
  return dynKind(this, d);
8062
8737
  }
@@ -8087,6 +8762,9 @@ export class LlEmitter {
8087
8762
  emitterFixedAdapter(cbT) {
8088
8763
  return emitterFixedAdapter(this, cbT);
8089
8764
  }
8765
+ emitterOnceAdapter(boxerT) {
8766
+ return emitterOnceAdapter(this, boxerT);
8767
+ }
8090
8768
  wrapEmitterListener(target, adapterFn) {
8091
8769
  return wrapEmitterListener(this, target, adapterFn);
8092
8770
  }