@scriptc/compiler 0.2.2 → 0.2.4

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 (243) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +99 -27
  2. package/dist/backend/cycle-analysis.js +63 -50
  3. package/dist/backend/cycle-analysis.js.map +1 -1
  4. package/dist/backend/cycle-analysis.test.js +51 -0
  5. package/dist/backend/cycle-analysis.test.js.map +1 -1
  6. package/dist/backend/llvm/borrowed-inputs.d.ts +15 -0
  7. package/dist/backend/llvm/borrowed-inputs.js +28 -0
  8. package/dist/backend/llvm/borrowed-inputs.js.map +1 -0
  9. package/dist/backend/llvm/borrowed-receivers.d.ts +9 -0
  10. package/dist/backend/llvm/borrowed-receivers.js +58 -0
  11. package/dist/backend/llvm/borrowed-receivers.js.map +1 -0
  12. package/dist/backend/llvm/byte-numbers.d.ts +10 -0
  13. package/dist/backend/llvm/byte-numbers.js +235 -0
  14. package/dist/backend/llvm/byte-numbers.js.map +1 -0
  15. package/dist/backend/llvm/bytes-bounds.d.ts +9 -0
  16. package/dist/backend/llvm/bytes-bounds.js +189 -0
  17. package/dist/backend/llvm/bytes-bounds.js.map +1 -0
  18. package/dist/backend/llvm/call-lifetimes.d.ts +27 -0
  19. package/dist/backend/llvm/call-lifetimes.js +199 -0
  20. package/dist/backend/llvm/call-lifetimes.js.map +1 -0
  21. package/dist/backend/llvm/call-lifetimes.test.d.ts +1 -0
  22. package/dist/backend/llvm/call-lifetimes.test.js +259 -0
  23. package/dist/backend/llvm/call-lifetimes.test.js.map +1 -0
  24. package/dist/backend/llvm/checked-value-lifetimes.d.ts +9 -0
  25. package/dist/backend/llvm/checked-value-lifetimes.js +35 -0
  26. package/dist/backend/llvm/checked-value-lifetimes.js.map +1 -0
  27. package/dist/backend/llvm/counted-loops.d.ts +6 -0
  28. package/dist/backend/llvm/counted-loops.js +28 -0
  29. package/dist/backend/llvm/counted-loops.js.map +1 -0
  30. package/dist/backend/llvm/dyn.d.ts +2 -2
  31. package/dist/backend/llvm/dyn.js +79 -35
  32. package/dist/backend/llvm/dyn.js.map +1 -1
  33. package/dist/backend/llvm/emitter.d.ts +56 -7
  34. package/dist/backend/llvm/emitter.js +310 -37
  35. package/dist/backend/llvm/emitter.js.map +1 -1
  36. package/dist/backend/llvm/emitter.test.js +24 -5
  37. package/dist/backend/llvm/emitter.test.js.map +1 -1
  38. package/dist/backend/llvm/expr-async.js +4 -3
  39. package/dist/backend/llvm/expr-async.js.map +1 -1
  40. package/dist/backend/llvm/expr-bytes.d.ts +4 -4
  41. package/dist/backend/llvm/expr-bytes.js +68 -35
  42. package/dist/backend/llvm/expr-bytes.js.map +1 -1
  43. package/dist/backend/llvm/expr-calls.js +50 -5
  44. package/dist/backend/llvm/expr-calls.js.map +1 -1
  45. package/dist/backend/llvm/expr-containers.d.ts +2 -1
  46. package/dist/backend/llvm/expr-containers.js +136 -57
  47. package/dist/backend/llvm/expr-containers.js.map +1 -1
  48. package/dist/backend/llvm/expr-context.d.ts +11 -0
  49. package/dist/backend/llvm/expr-control.js +26 -5
  50. package/dist/backend/llvm/expr-control.js.map +1 -1
  51. package/dist/backend/llvm/expr-dynamic.js +33 -31
  52. package/dist/backend/llvm/expr-dynamic.js.map +1 -1
  53. package/dist/backend/llvm/expr-primitives.js +61 -27
  54. package/dist/backend/llvm/expr-primitives.js.map +1 -1
  55. package/dist/backend/llvm/expr-records.js +9 -3
  56. package/dist/backend/llvm/expr-records.js.map +1 -1
  57. package/dist/backend/llvm/expr-stream-bridges.js +5 -10
  58. package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
  59. package/dist/backend/llvm/integer-values.d.ts +10 -0
  60. package/dist/backend/llvm/integer-values.js +41 -0
  61. package/dist/backend/llvm/integer-values.js.map +1 -0
  62. package/dist/backend/llvm/integer-views.d.ts +7 -0
  63. package/dist/backend/llvm/integer-views.js +67 -0
  64. package/dist/backend/llvm/integer-views.js.map +1 -0
  65. package/dist/backend/llvm/integer-views.test.d.ts +1 -0
  66. package/dist/backend/llvm/integer-views.test.js +34 -0
  67. package/dist/backend/llvm/integer-views.test.js.map +1 -0
  68. package/dist/backend/llvm/lib-dispatch.js +3 -1
  69. package/dist/backend/llvm/lib-dispatch.js.map +1 -1
  70. package/dist/backend/llvm/lib-filesystem.js +5 -5
  71. package/dist/backend/llvm/lib-filesystem.js.map +1 -1
  72. package/dist/backend/llvm/lib-shared.js +35 -1
  73. package/dist/backend/llvm/lib-shared.js.map +1 -1
  74. package/dist/backend/llvm/local-array-reads.d.ts +42 -0
  75. package/dist/backend/llvm/local-array-reads.js +226 -0
  76. package/dist/backend/llvm/local-array-reads.js.map +1 -0
  77. package/dist/backend/llvm/local-array-reads.test.d.ts +1 -0
  78. package/dist/backend/llvm/local-array-reads.test.js +446 -0
  79. package/dist/backend/llvm/local-array-reads.test.js.map +1 -0
  80. package/dist/backend/llvm/map-read-lifetimes.d.ts +36 -0
  81. package/dist/backend/llvm/map-read-lifetimes.js +128 -0
  82. package/dist/backend/llvm/map-read-lifetimes.js.map +1 -0
  83. package/dist/backend/llvm/map-read-lifetimes.test.d.ts +1 -0
  84. package/dist/backend/llvm/map-read-lifetimes.test.js +127 -0
  85. package/dist/backend/llvm/map-read-lifetimes.test.js.map +1 -0
  86. package/dist/backend/llvm/reference-effects.d.ts +22 -0
  87. package/dist/backend/llvm/reference-effects.js +148 -0
  88. package/dist/backend/llvm/reference-effects.js.map +1 -0
  89. package/dist/backend/llvm/reference-effects.test.d.ts +1 -0
  90. package/dist/backend/llvm/reference-effects.test.js +74 -0
  91. package/dist/backend/llvm/reference-effects.test.js.map +1 -0
  92. package/dist/backend/llvm/shapes.d.ts +7 -4
  93. package/dist/backend/llvm/shapes.js +24 -6
  94. package/dist/backend/llvm/shapes.js.map +1 -1
  95. package/dist/backend/llvm/split.js +15 -5
  96. package/dist/backend/llvm/split.js.map +1 -1
  97. package/dist/backend/llvm/split.test.js +17 -0
  98. package/dist/backend/llvm/split.test.js.map +1 -1
  99. package/dist/backend/llvm/stack-unions.d.ts +20 -0
  100. package/dist/backend/llvm/stack-unions.js +61 -0
  101. package/dist/backend/llvm/stack-unions.js.map +1 -0
  102. package/dist/backend/llvm/stack-unions.test.d.ts +1 -0
  103. package/dist/backend/llvm/stack-unions.test.js +262 -0
  104. package/dist/backend/llvm/stack-unions.test.js.map +1 -0
  105. package/dist/backend/llvm/string-construction.d.ts +10 -0
  106. package/dist/backend/llvm/string-construction.js +38 -0
  107. package/dist/backend/llvm/string-construction.js.map +1 -0
  108. package/dist/backend/llvm/string-construction.test.d.ts +1 -0
  109. package/dist/backend/llvm/string-construction.test.js +65 -0
  110. package/dist/backend/llvm/string-construction.test.js.map +1 -0
  111. package/dist/backend/llvm/string-lifetimes.d.ts +12 -0
  112. package/dist/backend/llvm/string-lifetimes.js +72 -0
  113. package/dist/backend/llvm/string-lifetimes.js.map +1 -0
  114. package/dist/backend/llvm/string-lifetimes.test.d.ts +1 -0
  115. package/dist/backend/llvm/string-lifetimes.test.js +239 -0
  116. package/dist/backend/llvm/string-lifetimes.test.js.map +1 -0
  117. package/dist/backend/mangle.d.ts +2 -0
  118. package/dist/backend/mangle.js +4 -0
  119. package/dist/backend/mangle.js.map +1 -1
  120. package/dist/backend/may-throw.js +67 -29
  121. package/dist/backend/may-throw.js.map +1 -1
  122. package/dist/backend/may-throw.test.js +104 -1
  123. package/dist/backend/may-throw.test.js.map +1 -1
  124. package/dist/backend/native-toolchain.d.ts +2 -2
  125. package/dist/backend/native-toolchain.js +2 -0
  126. package/dist/backend/native-toolchain.js.map +1 -1
  127. package/dist/frontend/input-tracker.js +3 -3
  128. package/dist/frontend/input-tracker.js.map +1 -1
  129. package/dist/frontend/input-tracker.test.js +38 -2
  130. package/dist/frontend/input-tracker.test.js.map +1 -1
  131. package/dist/frontend/lowering/collection-ingestion.d.ts +31 -0
  132. package/dist/frontend/lowering/collection-ingestion.js +224 -0
  133. package/dist/frontend/lowering/collection-ingestion.js.map +1 -0
  134. package/dist/frontend/lowering/containers/bytes.js +2 -0
  135. package/dist/frontend/lowering/containers/bytes.js.map +1 -1
  136. package/dist/frontend/lowering/containers/string-and-regexp.js +6 -0
  137. package/dist/frontend/lowering/containers/string-and-regexp.js.map +1 -1
  138. package/dist/frontend/lowering/import-meta.d.ts +10 -0
  139. package/dist/frontend/lowering/import-meta.js +52 -0
  140. package/dist/frontend/lowering/import-meta.js.map +1 -0
  141. package/dist/frontend/lowering/lower-assert.js +105 -41
  142. package/dist/frontend/lowering/lower-assert.js.map +1 -1
  143. package/dist/frontend/lowering/lower-builtin-values.d.ts +2 -0
  144. package/dist/frontend/lowering/lower-builtin-values.js +22 -1
  145. package/dist/frontend/lowering/lower-builtin-values.js.map +1 -1
  146. package/dist/frontend/lowering/lower-builtins.d.ts +3 -13
  147. package/dist/frontend/lowering/lower-builtins.js +152 -46
  148. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  149. package/dist/frontend/lowering/lower-calls.js +6 -3
  150. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  151. package/dist/frontend/lowering/lower-classes.js +7 -7
  152. package/dist/frontend/lowering/lower-classes.js.map +1 -1
  153. package/dist/frontend/lowering/lower-containers.d.ts +5 -1
  154. package/dist/frontend/lowering/lower-containers.js +52 -37
  155. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  156. package/dist/frontend/lowering/lower-exprs.js +47 -43
  157. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  158. package/dist/frontend/lowering/lower-inspect.js +43 -0
  159. package/dist/frontend/lowering/lower-inspect.js.map +1 -1
  160. package/dist/frontend/lowering/lower-stmts.js +72 -68
  161. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  162. package/dist/frontend/lowering/lower-system-errors.d.ts +6 -0
  163. package/dist/frontend/lowering/lower-system-errors.js +29 -0
  164. package/dist/frontend/lowering/lower-system-errors.js.map +1 -0
  165. package/dist/frontend/lowering/lowerer.d.ts +4 -3
  166. package/dist/frontend/lowering/lowerer.js +60 -25
  167. package/dist/frontend/lowering/lowerer.js.map +1 -1
  168. package/dist/frontend/lowering/surfaces.d.ts +2 -2
  169. package/dist/frontend/lowering/surfaces.js +69 -21
  170. package/dist/frontend/lowering/surfaces.js.map +1 -1
  171. package/dist/frontend/module-syntax.d.ts +1 -1
  172. package/dist/frontend/module-syntax.js +8 -1
  173. package/dist/frontend/module-syntax.js.map +1 -1
  174. package/dist/frontend/module-syntax.test.js +10 -0
  175. package/dist/frontend/module-syntax.test.js.map +1 -1
  176. package/dist/frontend/npm.d.ts +4 -0
  177. package/dist/frontend/npm.js +86 -33
  178. package/dist/frontend/npm.js.map +1 -1
  179. package/dist/frontend/npm.test.js +103 -0
  180. package/dist/frontend/npm.test.js.map +1 -1
  181. package/dist/frontend/pipeline-native.js +2 -1
  182. package/dist/frontend/pipeline-native.js.map +1 -1
  183. package/dist/frontend/services-node.js +3 -2
  184. package/dist/frontend/services-node.js.map +1 -1
  185. package/dist/frontend/services.d.ts +5 -1
  186. package/dist/frontend/services.js +9 -1
  187. package/dist/frontend/services.js.map +1 -1
  188. package/dist/frontend/services.test.js +21 -0
  189. package/dist/frontend/services.test.js.map +1 -1
  190. package/dist/frontend/ts7/ast-node.d.ts +2 -0
  191. package/dist/frontend/ts7/ast-node.js +30 -4
  192. package/dist/frontend/ts7/ast-node.js.map +1 -1
  193. package/dist/frontend/ts7/ast-node.test.js +60 -1
  194. package/dist/frontend/ts7/ast-node.test.js.map +1 -1
  195. package/dist/frontend/type-mapper.js +8 -2
  196. package/dist/frontend/type-mapper.js.map +1 -1
  197. package/dist/index.js +3 -2
  198. package/dist/index.js.map +1 -1
  199. package/dist/ir/analysis.js +4 -1
  200. package/dist/ir/analysis.js.map +1 -1
  201. package/dist/ir/byte-numbers.d.ts +20 -0
  202. package/dist/ir/byte-numbers.js +65 -0
  203. package/dist/ir/byte-numbers.js.map +1 -0
  204. package/dist/ir/byte-numbers.test.d.ts +1 -0
  205. package/dist/ir/byte-numbers.test.js +42 -0
  206. package/dist/ir/byte-numbers.test.js.map +1 -0
  207. package/dist/ir/integer-loops.d.ts +26 -0
  208. package/dist/ir/integer-loops.js +130 -1
  209. package/dist/ir/integer-loops.js.map +1 -1
  210. package/dist/ir/integer-loops.test.d.ts +1 -0
  211. package/dist/ir/integer-loops.test.js +91 -0
  212. package/dist/ir/integer-loops.test.js.map +1 -0
  213. package/dist/ir/integer-ranges.d.ts +8 -6
  214. package/dist/ir/integer-ranges.js +343 -89
  215. package/dist/ir/integer-ranges.js.map +1 -1
  216. package/dist/ir/integer-ranges.test.js +54 -3
  217. package/dist/ir/integer-ranges.test.js.map +1 -1
  218. package/dist/ir/ir.d.ts +29 -22
  219. package/dist/ir/ir.js +59 -14
  220. package/dist/ir/ir.js.map +1 -1
  221. package/dist/ir/ir.test.js +18 -0
  222. package/dist/ir/ir.test.js.map +1 -1
  223. package/dist/ir/validate.js +208 -105
  224. package/dist/ir/validate.js.map +1 -1
  225. package/dist/ir/validate.test.js +247 -0
  226. package/dist/ir/validate.test.js.map +1 -1
  227. package/dist/library/fence-eval.js +9 -2
  228. package/dist/library/fence-eval.js.map +1 -1
  229. package/dist/library/fence-eval.test.d.ts +1 -0
  230. package/dist/library/fence-eval.test.js +21 -0
  231. package/dist/library/fence-eval.test.js.map +1 -0
  232. package/dist/native/runtime-typescript.d.ts +4 -0
  233. package/dist/native/runtime-typescript.js +61 -0
  234. package/dist/native/runtime-typescript.js.map +1 -0
  235. package/dist/native/runtime-typescript.test.d.ts +1 -0
  236. package/dist/native/runtime-typescript.test.js +41 -0
  237. package/dist/native/runtime-typescript.test.js.map +1 -0
  238. package/dist/native/toolchain.js +6 -3
  239. package/dist/native/toolchain.js.map +1 -1
  240. package/dist/native/toolchain.test.js +43 -5
  241. package/dist/native/toolchain.test.js.map +1 -1
  242. package/package.json +20 -20
  243. package/surface-manifest.json +271 -17
@@ -22,16 +22,26 @@ import { deflateRawSync } from "node:zlib";
22
22
  import { endsWithJump, matchStringSelfConcat, streamTypedRefEligible } from "../../ir/analysis.js";
23
23
  import { emitLibraryIdentityLines } from "../library-identity-markers.js";
24
24
  import { CAUGHT, ffiCallbackType, isDynTypedRefType, isFfiContextParam, isRefCounted, isUnitType, moduleRuntimeFeatures, moduleEmbedsBuiltin, moduleEmbedsCompressedNpm, NPM_COMPRESS_MIN, POINTER_KINDS, RUNTIME_EMITTER_CLASS, RUNTIME_ERROR_CLASSES, RUNTIME_STREAM_CLASSES, typeKey, VOID } from "../../ir/ir.js";
25
- import { matchIntegerBytesForLoop } from "../../ir/integer-loops.js";
25
+ import { matchIntegerArrayForLoop, matchIntegerBytesForLoop, matchIntegerCountedForLoop } from "../../ir/integer-loops.js";
26
+ import { emitCountedLoopLimit } from "./counted-loops.js";
27
+ import { findBytesBounds } from "./bytes-bounds.js";
26
28
  import { scalarizeNumericRecords } from "../../ir/scalar-records.js";
27
29
  import { everyStmtList } from "../../ir/traverse.js";
28
30
  import { analyzeIntegerRanges } from "../../ir/integer-ranges.js";
31
+ import { findIntegerViews } from "./integer-views.js";
29
32
  import { findConstantNumericTables } from "../../ir/constant-tables.js";
30
33
  import { allocateFfiCallbackAdapters, hasForeignFfiCallback, hasRetainedFfiCallback } from "../ffi-callbacks.js";
31
34
  import { RUNTIME_ABI_MARKER } from "../runtime-abi.js";
32
35
  import { computeMayThrow } from "../may-throw.js";
33
- import { mangleArgPack, mangleAsyncSpawn, mangleClassObj, mangleClassStruct, mangleFnClosure, mangleFunction, mangleGenDrop, mangleGenSpawn, mangleGlobal, mangleLocal, mangleRecordStruct, mangleTrampoline, mangleWrapper, mangleVtInstance } from "../mangle.js";
36
+ import { mangleArgPack, mangleAsyncSpawn, mangleBorrowedFunction, mangleClassObj, mangleClassStruct, mangleFnClosure, mangleFunction, mangleGenDrop, mangleGenSpawn, mangleGlobal, mangleLocal, mangleRecordStruct, mangleTrampoline, mangleWrapper, mangleVtInstance } from "../mangle.js";
37
+ import { analyzeCallLifetimes } from "./call-lifetimes.js";
38
+ import { emitStackUnion, findLocalStackUnions } from "./stack-unions.js";
34
39
  import { BlockBuilder } from "./blocks.js";
40
+ import { emitLocalArrayRead, findLocalArrayReads, findCallArrayReads, OptionalArrayReads } from "./local-array-reads.js";
41
+ import { emitStackMapRead, findMapReadLifetimes } from "./map-read-lifetimes.js";
42
+ import { emitBorrowedFieldSequence } from "./borrowed-receivers.js";
43
+ import { emitBorrowedInput } from "./borrowed-inputs.js";
44
+ import { ReferenceEffects } from "./reference-effects.js";
35
45
  import { LlvmDebugInfo } from "./debug-info.js";
36
46
  import { f64Lit, ffiNativeTypeLl, ffiNativeParamLl, ffiNativeReturnLl, llvmCommentText } from "./common.js";
37
47
  import { ffiExtendsNarrowIntegers } from "../targets.js";
@@ -101,6 +111,41 @@ function llStrBytes(text) {
101
111
  }
102
112
  export class LlEmitter {
103
113
  mod;
114
+ localArrayReads = new Map();
115
+ localStackUnions = new Map();
116
+ integerArrayBindings = new Set();
117
+ fieldAliasTags = new Map();
118
+ fieldPointerTags = new Map();
119
+ /** Native class fields with different property names cannot occupy the
120
+ * same slot of one object. Inherited views of the same property share a
121
+ * tag. Array storage has its own disjoint categories; untagged runtime
122
+ * operations and record views conservatively alias all of them. */
123
+ markFieldPointer(ptr, field) {
124
+ this.markMemoryPointer(ptr, `field:${field}`);
125
+ }
126
+ markMemoryPointer(ptr, field) {
127
+ if (this.debug !== null)
128
+ return;
129
+ let tag = this.fieldAliasTags.get(field);
130
+ if (tag === undefined) {
131
+ tag = 2 + this.fieldAliasTags.size * 2;
132
+ this.fieldAliasTags.set(field, tag);
133
+ }
134
+ this.fieldPointerTags.set(ptr, tag);
135
+ }
136
+ fieldAliasAttachment(ptr) {
137
+ const tag = this.fieldPointerTags.get(ptr);
138
+ return tag === undefined ? "" : `, !tbaa !${tag}`;
139
+ }
140
+ fieldAliasMetadata() {
141
+ if (this.fieldAliasTags.size === 0)
142
+ return [];
143
+ const lines = ['!0 = !{!"scriptc native fields"}'];
144
+ for (const tag of this.fieldAliasTags.values()) {
145
+ lines.push(`!${tag - 1} = !{!"field.${tag}", !0, i64 0}`, `!${tag} = !{!${tag - 1}, !${tag - 1}, i64 0}`);
146
+ }
147
+ return lines;
148
+ }
104
149
  debug;
105
150
  debugScope = null;
106
151
  sizeType;
@@ -116,6 +161,7 @@ export class LlEmitter {
116
161
  * immortal (rc == SIZE_MAX) static per (union, unit tag).
117
162
  * RC entry points and the collector skip immortals. */
118
163
  unitInstances = new Map();
164
+ immortalValues = new Set();
119
165
  /** Regex literal templates: "<flags>/<pattern>" → { symbol, interned
120
166
  * source/flags literal refs } — one immortal ScrRegex template per distinct
121
167
  * (pattern, flags) pair; the bytecode slot starts null and the runtime
@@ -149,6 +195,13 @@ export class LlEmitter {
149
195
  needsBadKey = false;
150
196
  needsRetainBox = false;
151
197
  fnByName = new Map();
198
+ referenceEffects;
199
+ optionalArrayReads;
200
+ callArrayReads = new Map();
201
+ mapReadLifetimes = { locals: new Map(), arguments: new Map() };
202
+ callLifetimes;
203
+ borrowedParameters = new Set();
204
+ stableCallBindings = new Set();
152
205
  /** Manifest-bound native imports, used by ffiCall emission. */
153
206
  ffiByName = new Map();
154
207
  /** C-ABI callback trampolines and (for raw/no-userdata callbacks) their
@@ -230,9 +283,12 @@ export class LlEmitter {
230
283
  jumpTargets = [];
231
284
  currentLocals = new Map();
232
285
  captureIds = new Set();
233
- /** Active canonical byte-loop induction bindings: local id → size_t slot. */
286
+ /** Active induction bindings retain their width independently of size_t. */
234
287
  integerLoopBindings = new Map();
288
+ countedLoopsEnabled = false;
235
289
  integerRanges = new Map();
290
+ bytesBounds = new Set();
291
+ integerViews = new Map();
236
292
  /** Enclosing try-with-FINALLY regions, innermost last: a `return`
237
293
  * inside one runs every crossed finally (innermost first) before the
238
294
  * actual ret — the runtime ABI’s pending-return path, with the finally
@@ -298,6 +354,9 @@ export class LlEmitter {
298
354
  }
299
355
  for (const u of mod.unions ?? [])
300
356
  this.unionsById.set(u.id, u);
357
+ this.optionalArrayReads = new OptionalArrayReads(this.fnByName, this.unionsById);
358
+ this.referenceEffects = new ReferenceEffects(this.fnByName, (call) => this.optionalArrayReads.get(call) !== null);
359
+ this.callLifetimes = analyzeCallLifetimes(this.fnByName);
301
360
  for (const r of mod.records ?? [])
302
361
  this.recordsById.set(r.id, r);
303
362
  const traced = computeTraced(mod);
@@ -849,6 +908,7 @@ export class LlEmitter {
849
908
  this.declare(`declare void @scr_net_island_install()`);
850
909
  if (usesIsland) {
851
910
  this.declare(`declare void @scr_island_modules(ptr, ${this.sizeType}, ptr, ${this.sizeType})`);
911
+ this.declare(`declare void @scr_island_entry_module(ptr)`);
852
912
  this.declare(`declare i32 @scr_island_exit_code()`);
853
913
  if (moduleEmbedsCompressedNpm(this.mod)) {
854
914
  this.declare(`declare void @scr_island_set_inflate(ptr)`);
@@ -1116,6 +1176,7 @@ export class LlEmitter {
1116
1176
  }
1117
1177
  if (this.templateStringsInstances.size > 0)
1118
1178
  out.push(``);
1179
+ const islandEntryPath = usesIsland ? this.cstr(this.mod.sourceFile) : null;
1119
1180
  for (const [text, c] of this.cstrs) {
1120
1181
  // NUL-terminated byte-array constants for scr_jb_puts and indentation.
1121
1182
  out.push(`@${c.sym} = internal constant [${c.len + 1} x i8] c"${llStrBytes(text)}"`);
@@ -1247,6 +1308,7 @@ export class LlEmitter {
1247
1308
  if (hasNoInlineRecordClone)
1248
1309
  out.push(`attributes #2 = { noinline sanitize_address }`);
1249
1310
  out.push(``);
1311
+ out.push(...this.fieldAliasMetadata());
1250
1312
  if (this.debug !== null)
1251
1313
  out.push(this.debug.render());
1252
1314
  return out;
@@ -1260,6 +1322,7 @@ export class LlEmitter {
1260
1322
  // scr_watch.c links only when this line is emitted.
1261
1323
  ...(usesFsWatch ? [` call void @scr_watch_install()`] : []), ...(this.ffiHasForeignCallback ? [` call void @scr_ffi_install()`] : []), ...(snapshotsTlsCa ? [` call void @scr_tls_ca_install()`] : []), ...(usesFetch ? [` call void @scr_fetch_install()`] : []), ...(embedsZlib ? [` call void @scr_zlib_island_install()`] : []), ...(embedsNet ? [` call void @scr_net_island_install()`] : []), ...(usesNet ? [` call void @scr_net_install()`, ` call void @scr_net_dyn_install()`] : []), ...(usesHttp ? [` call void @scr_http_dyn_install()`] : []), ...(usesDgram ? [` call void @scr_dgram_install()`] : []), ...(usesHttp2 ? [` call void @scr_http2_dyn_install()`] : []), ...(usesStream ? [` call void @scr_stream_install()`] : []), ` call void @scr_lib_init(i32 %argc, ptr %argv)`, ...(usesIsland
1262
1324
  ? [
1325
+ ` call void @scr_island_entry_module(ptr ${islandEntryPath})`,
1263
1326
  ...(moduleEmbedsCompressedNpm(this.mod)
1264
1327
  ? [` call void @scr_island_set_inflate(ptr @scr_zlib_inflate_exact)`]
1265
1328
  : []),
@@ -1359,6 +1422,7 @@ export class LlEmitter {
1359
1422
  // lane's -fsanitize=address link activates instrumentation over the
1360
1423
  // emitted functions too (the runtime TUs get theirs from clang).
1361
1424
  `attributes #0 = { sanitize_address }`, ...(this.wasi ? [`attributes #1 = { sanitize_address presplitcoroutine }`] : []), ...(hasNoInlineRecordClone ? [`attributes #2 = { noinline sanitize_address }`] : []), ``);
1425
+ out.push(...this.fieldAliasMetadata());
1362
1426
  if (this.debug !== null)
1363
1427
  out.push(this.debug.render());
1364
1428
  return out;
@@ -1941,7 +2005,9 @@ export class LlEmitter {
1941
2005
  lit = { sym: `sc_lit_${this.literals.size}`, len: Buffer.byteLength(text, "utf8") };
1942
2006
  this.literals.set(text, lit);
1943
2007
  }
1944
- return `@${lit.sym}`;
2008
+ const name = `@${lit.sym}`;
2009
+ this.immortalValues.add(name);
2010
+ return name;
1945
2011
  }
1946
2012
  /** Interned NUL-terminated C-string constant (the scr_jb_puts /
1947
2013
  * stringify-indent currency) — `@`-ref, first-use order. */
@@ -1971,7 +2037,9 @@ export class LlEmitter {
1971
2037
  sym = `sc_unit_${this.unitInstances.size}`;
1972
2038
  this.unitInstances.set(key, sym);
1973
2039
  }
1974
- return `@${sym}`;
2040
+ const name = `@${sym}`;
2041
+ this.immortalValues.add(name);
2042
+ return name;
1975
2043
  }
1976
2044
  declare(decl) {
1977
2045
  this.decls.add(decl);
@@ -1987,7 +2055,7 @@ export class LlEmitter {
1987
2055
  }
1988
2056
  /** Registers an owned refcounted value on the current statement frame. */
1989
2057
  own(v) {
1990
- if (isRefCounted(v.type))
2058
+ if (isRefCounted(v.type) && !this.immortalValues.has(v.name))
1991
2059
  this.currentFrame().push(v);
1992
2060
  return v;
1993
2061
  }
@@ -1999,7 +2067,7 @@ export class LlEmitter {
1999
2067
  }
2000
2068
  /** Strike a refcounted temp from its frame: ownership is being moved. */
2001
2069
  moveTemp(v) {
2002
- if (!isRefCounted(v.type))
2070
+ if (!isRefCounted(v.type) || this.immortalValues.has(v.name))
2003
2071
  return;
2004
2072
  for (let i = this.frames.length - 1; i >= 0; i--) {
2005
2073
  const idx = this.frames[i].findIndex((e) => e.name === v.name);
@@ -2013,6 +2081,8 @@ export class LlEmitter {
2013
2081
  /** The retained (+1) read of a refcounted value — type-directed through
2014
2082
  * the `_v` table (immortals skip, exactly the C retain calls). */
2015
2083
  retainValue(name, type) {
2084
+ if (this.immortalValues.has(name))
2085
+ return name;
2016
2086
  const t = this.B.tmp();
2017
2087
  this.B.line(`${t} = call ptr ${retainSym(this.shapeHost, type)}(ptr ${name})`);
2018
2088
  return t;
@@ -2020,6 +2090,8 @@ export class LlEmitter {
2020
2090
  /** The release call for one owned refcounted value — type-directed like
2021
2091
  * releaseCallC (all runtime releases are NULL-tolerant). */
2022
2092
  releaseValue(name, type) {
2093
+ if (this.immortalValues.has(name))
2094
+ return;
2023
2095
  this.B.line(`call void ${releaseSym(this.shapeHost, type)}(ptr ${name})`);
2024
2096
  }
2025
2097
  releaseFrame(frame) {
@@ -2418,7 +2490,7 @@ export class LlEmitter {
2418
2490
  * a retained peek. */
2419
2491
  unionExtract(uName, arm) {
2420
2492
  const B = this.B;
2421
- if (arm.kind === "f64") {
2493
+ if (arm.kind === "f64" || arm.kind === "procStream") {
2422
2494
  const t = B.tmp();
2423
2495
  this.declare(`declare double @scr_union_get_f64(ptr)`);
2424
2496
  B.line(`${t} = call double @scr_union_get_f64(ptr ${uName})`);
@@ -2438,7 +2510,7 @@ export class LlEmitter {
2438
2510
  unionNewOwned(tag, v) {
2439
2511
  const B = this.B;
2440
2512
  const t = B.tmp();
2441
- if (v.type.kind === "f64") {
2513
+ if (v.type.kind === "f64" || v.type.kind === "procStream") {
2442
2514
  this.declare(`declare ptr @scr_union_new_f64(i32, double)`);
2443
2515
  B.line(`${t} = call ptr @scr_union_new_f64(i32 ${tag}, double ${v.name})`);
2444
2516
  return t;
@@ -2469,6 +2541,7 @@ export class LlEmitter {
2469
2541
  const { index, type } = classFieldIndex(meta, field);
2470
2542
  const p = this.B.tmp();
2471
2543
  this.B.line(`${p} = getelementptr inbounds %${classStructSym(className)}, ptr ${objName}, i64 0, i32 ${index}`);
2544
+ this.markFieldPointer(p, field);
2472
2545
  return { ptr: p, type };
2473
2546
  }
2474
2547
  /** `o->vt->pre` — the dynamic class's preorder number (borrowed object;
@@ -2559,7 +2632,7 @@ export class LlEmitter {
2559
2632
  const B = this.B;
2560
2633
  const fieldTy = llFieldType(t);
2561
2634
  const raw = B.tmp();
2562
- B.line(`${raw} = load ${fieldTy}, ptr ${ptr}`);
2635
+ B.line(`${raw} = load ${fieldTy}, ptr ${ptr}${this.fieldAliasAttachment(ptr)}`);
2563
2636
  if (fieldTy !== "i8")
2564
2637
  return raw;
2565
2638
  const b = B.tmp();
@@ -2571,12 +2644,12 @@ export class LlEmitter {
2571
2644
  const B = this.B;
2572
2645
  const fieldTy = llFieldType(t);
2573
2646
  if (fieldTy !== "i8") {
2574
- B.line(`store ${fieldTy} ${value}, ptr ${ptr}`);
2647
+ B.line(`store ${fieldTy} ${value}, ptr ${ptr}${this.fieldAliasAttachment(ptr)}`);
2575
2648
  return;
2576
2649
  }
2577
2650
  const z = B.tmp();
2578
2651
  B.line(`${z} = zext i1 ${value} to i8`);
2579
- B.line(`store i8 ${z}, ptr ${ptr}`);
2652
+ B.line(`store i8 ${z}, ptr ${ptr}${this.fieldAliasAttachment(ptr)}`);
2580
2653
  }
2581
2654
  // ── bindings ────────────────────────────────────────────────────────────
2582
2655
  /** A binding's storage: a module global, a plain local slot, or a boxed
@@ -2856,9 +2929,25 @@ export class LlEmitter {
2856
2929
  this.unwindCleanups.clear();
2857
2930
  this.jumpTargets = [];
2858
2931
  this.currentLocals = new Map(fn.locals.map((l) => [l.id, l]));
2932
+ this.borrowedParameters.clear();
2933
+ this.stableCallBindings = this.callLifetimes.bindings.get(fn.name) ?? new Set();
2934
+ const borrowedParameterIndexes = this.callLifetimes.borrowed.get(fn.name);
2935
+ if (borrowedParameterIndexes) {
2936
+ for (const index of borrowedParameterIndexes)
2937
+ this.borrowedParameters.add(fn.params[index].localId);
2938
+ }
2859
2939
  this.captureIds = new Set([...(fn.captures ?? []), ...(fn.classCaptures ?? [])].map((c) => c.localId));
2860
2940
  this.integerLoopBindings.clear();
2941
+ this.countedLoopsEnabled = this.debug === null && !fn.async && !fn.generator;
2942
+ this.integerViews.clear();
2943
+ this.fieldPointerTags.clear();
2944
+ this.integerArrayBindings.clear();
2945
+ this.localArrayReads = findLocalArrayReads(fn, this.fnByName, this.unionsById, this.referenceEffects.functions, this.callLifetimes, this.optionalArrayReads);
2946
+ this.callArrayReads = findCallArrayReads(fn, this.optionalArrayReads, this.referenceEffects.functions, this.callLifetimes);
2947
+ this.mapReadLifetimes = findMapReadLifetimes(fn, this.unionsById, this.callLifetimes);
2948
+ this.localStackUnions = findLocalStackUnions(fn, this.callLifetimes, this.unionsById);
2861
2949
  this.integerRanges = analyzeIntegerRanges(fn);
2950
+ this.bytesBounds = findBytesBounds(fn, this.integerRanges);
2862
2951
  this.chainSlots.clear();
2863
2952
  this.finallyStack = [];
2864
2953
  this.tryStack = [];
@@ -2900,6 +2989,13 @@ export class LlEmitter {
2900
2989
  };
2901
2990
  }
2902
2991
  const paramIds = new Set(fn.params.map((p) => p.localId));
2992
+ if (this.debug === null) {
2993
+ for (const id of findIntegerViews(fn)) {
2994
+ const slot = B.slot();
2995
+ B.entryAllocas.push(`${slot} = alloca i32 ; integer view ${this.currentLocals.get(id).name}`);
2996
+ this.integerViews.set(id, slot);
2997
+ }
2998
+ }
2903
2999
  for (const local of fn.locals) {
2904
3000
  // Boxed locals' slots hold their capture BOX (a ptr); captured
2905
3001
  // (env-borrowed) locals bind the incoming box below. A caught-typed
@@ -2949,7 +3045,8 @@ export class LlEmitter {
2949
3045
  // params (callees own their params — callers passed +1). Boxed params
2950
3046
  // allocate the shared binding and move the raw value in.
2951
3047
  const fnScope = [];
2952
- for (const p of fn.params) {
3048
+ const borrowed = this.callLifetimes.borrowed.get(fn.name);
3049
+ for (const [index, p] of fn.params.entries()) {
2953
3050
  const local = this.currentLocals.get(p.localId);
2954
3051
  const slot = `%${mangleLocal(p.localId)}`;
2955
3052
  if (local.boxed) {
@@ -2961,7 +3058,8 @@ export class LlEmitter {
2961
3058
  continue;
2962
3059
  }
2963
3060
  B.line(`store ${this.llType(p.type)} %p_${mangleLocal(p.localId)}, ptr ${slot}`);
2964
- if (isRefCounted(p.type))
3061
+ this.storeIntegerView(p.localId, { name: `%p_${mangleLocal(p.localId)}`, type: p.type });
3062
+ if (isRefCounted(p.type) && !borrowed?.has(index))
2965
3063
  fnScope.push({ slot, type: p.type });
2966
3064
  }
2967
3065
  this.scopes.push(fnScope);
@@ -3043,7 +3141,38 @@ export class LlEmitter {
3043
3141
  const ret = this.llType(fn.returnType);
3044
3142
  const attrs = coro !== null ? "#1" : FN_ATTRS;
3045
3143
  const debug = this.debugScope === null ? "" : ` !dbg ${this.debugScope}`;
3046
- return `define internal ${ret} @${mangleFunction(fn.name)}(${params.join(", ")}) ${attrs}${debug} { ; ${fn.name}\n${B.render()}\n}`;
3144
+ const symbol = borrowed ? mangleBorrowedFunction(fn.name) : mangleFunction(fn.name);
3145
+ const body = `define internal ${ret} @${symbol}(${params.join(", ")}) ${attrs}${debug} { ; ${fn.name}\n${B.render()}\n}`;
3146
+ return borrowed ? body + "\n" + this.emitOwnedCallAdapter(fn, borrowed) : body;
3147
+ }
3148
+ /** Keep the ordinary owned ABI for closures, virtual dispatch, generated
3149
+ * runtime adapters and library exports. Only direct IR calls select the
3150
+ * borrowing body. Ownership of other parameters moves into that body;
3151
+ * borrowed parameters are released here on normal and exceptional exits.
3152
+ * The pending exception remains for the adapter's caller to handle. */
3153
+ emitOwnedCallAdapter(fn, borrowed) {
3154
+ const B = new BlockBuilder();
3155
+ this.B = B;
3156
+ const params = fn.params.map((param, index) => `${this.llType(param.type)} %p${index}`);
3157
+ const ret = this.llType(fn.returnType);
3158
+ const call = `call ${ret} @${mangleBorrowedFunction(fn.name)}(${params.join(", ")})`;
3159
+ B.line(ret === "void" ? call : `%result = ${call}`);
3160
+ for (const index of borrowed)
3161
+ this.releaseValue(`%p${index}`, fn.params[index].type);
3162
+ B.terminate(ret === "void" ? "ret void" : `ret ${ret} %result`);
3163
+ return `define internal ${ret} @${mangleFunction(fn.name)}(${params.join(", ")}) ${FN_ATTRS} {\n${B.render()}\n}`;
3164
+ }
3165
+ /** A local stable binding keeps its value alive while later arguments
3166
+ * and the callee execute. Global, captured, TDZ and mutable bindings need
3167
+ * an owned snapshot, even when the callee only inspects the value. */
3168
+ canBorrowCallArgument(value) {
3169
+ if (value.kind === "strLit")
3170
+ return true;
3171
+ if (value.kind !== "varRef")
3172
+ return false;
3173
+ const binding = this.binding(value.localId);
3174
+ return binding.kind === "local" && binding.local !== undefined &&
3175
+ (!binding.local.mutable || this.stableCallBindings.has(value.localId) || this.borrowedParameters.has(value.localId)) && !binding.local.boxed && !binding.local.tdz;
3047
3176
  }
3048
3177
  // ── statements ──────────────────────────────────────────────────────────
3049
3178
  emitStmts(stmts) {
@@ -3087,6 +3216,31 @@ export class LlEmitter {
3087
3216
  switch (s.kind) {
3088
3217
  case "varDecl": {
3089
3218
  const b = this.binding(s.localId);
3219
+ const localUnion = this.localStackUnions.get(s.localId);
3220
+ if (localUnion) {
3221
+ const union = emitStackUnion(this, localUnion);
3222
+ B.line(`store ptr ${union.value.name}, ptr ${b.slot}`);
3223
+ if (union.payload) {
3224
+ this.moveTemp(union.payload);
3225
+ this.scopes[this.scopes.length - 1].push({ slot: union.payloadSlot, type: union.payload.type });
3226
+ }
3227
+ break;
3228
+ }
3229
+ const localRead = this.localArrayReads.get(s.localId);
3230
+ if (localRead) {
3231
+ const owner = emitLocalArrayRead(this, localRead, b.slot);
3232
+ if (owner)
3233
+ this.scopes[this.scopes.length - 1].push(owner);
3234
+ break;
3235
+ }
3236
+ const mapRead = this.mapReadLifetimes.locals.get(s.localId);
3237
+ if (mapRead) {
3238
+ const result = emitStackMapRead(this, mapRead);
3239
+ B.line(`store ptr ${result.value.name}, ptr ${b.slot}`);
3240
+ if (result.owner)
3241
+ this.scopes[this.scopes.length - 1].push(result.owner);
3242
+ break;
3243
+ }
3090
3244
  if (b.kind === "boxed") {
3091
3245
  // Box FIRST, then evaluate the initializer: a named function
3092
3246
  // expression's closure captures this box during init evaluation.
@@ -3124,9 +3278,20 @@ export class LlEmitter {
3124
3278
  }
3125
3279
  break;
3126
3280
  }
3281
+ // A lexical const alias cannot outlive its unchanged local owner.
3282
+ // Whole-value uses still retain their own copies, so returning or
3283
+ // storing the alias preserves the ordinary heap representation.
3284
+ // Boxed captures, reassigned sources and projections need ownership.
3285
+ if (b.kind === "local" && b.local && !b.local.mutable && isRefCounted(b.type) &&
3286
+ this.stableCallBindings.has(s.localId) && this.canBorrowCallArgument(s.init)) {
3287
+ const v = this.emitReadReceiver(s.init);
3288
+ B.line(`store ptr ${v.name}, ptr ${b.slot}`);
3289
+ break;
3290
+ }
3127
3291
  const v = this.emitExpr(s.init);
3128
3292
  this.moveTemp(v);
3129
3293
  B.line(`store ${this.llType(b.type)} ${v.name}, ptr ${b.slot}`);
3294
+ this.storeIntegerView(s.localId, v, s.init);
3130
3295
  if (isRefCounted(b.type)) {
3131
3296
  this.scopes[this.scopes.length - 1].push({ slot: b.slot, type: b.type });
3132
3297
  }
@@ -3157,6 +3322,7 @@ export class LlEmitter {
3157
3322
  this.releaseValue(old, b.type);
3158
3323
  }
3159
3324
  B.line(`store ${this.llType(b.type)} ${v.name}, ptr ${b.slot}`);
3325
+ this.storeIntegerView(s.localId, v, s.value);
3160
3326
  break;
3161
3327
  }
3162
3328
  case "exprStmt":
@@ -3166,7 +3332,7 @@ export class LlEmitter {
3166
3332
  // Evaluation order matches JS: array, index, then value. Ownership
3167
3333
  // of a refcounted value moves into the array (the runtime releases
3168
3334
  // the replaced element itself).
3169
- const arr = this.emitExpr(s.arr);
3335
+ const arr = emitBorrowedInput(this, s.arr);
3170
3336
  const idx = this.emitExpr(s.index);
3171
3337
  const v = this.emitExpr(s.value);
3172
3338
  if (s.arr.type.kind !== "array")
@@ -3180,7 +3346,7 @@ export class LlEmitter {
3180
3346
  break;
3181
3347
  }
3182
3348
  case "arraySetLength": {
3183
- const arr = this.emitExpr(s.arr);
3349
+ const arr = emitBorrowedInput(this, s.arr);
3184
3350
  const length = this.emitExpr(s.length);
3185
3351
  if (s.arr.type.kind !== "array")
3186
3352
  throw new InternalCompilerError("llvm emitter bug: arraySetLength on non-array");
@@ -3191,7 +3357,7 @@ export class LlEmitter {
3191
3357
  }
3192
3358
  case "arraySetUndefined":
3193
3359
  case "arrayDelete": {
3194
- const arr = this.emitExpr(s.arr);
3360
+ const arr = emitBorrowedInput(this, s.arr);
3195
3361
  const idx = this.emitExpr(s.index);
3196
3362
  if (s.arr.type.kind !== "array")
3197
3363
  throw new InternalCompilerError(`llvm emitter bug: ${s.kind} on non-array`);
@@ -3210,12 +3376,11 @@ export class LlEmitter {
3210
3376
  // IrBytesElem is static, so never rediscover it through the
3211
3377
  // generic runtime switch in a hot loop.
3212
3378
  const arr = this.emitStableReceiver(s.arr, [s.index, s.value]);
3213
- const integerIndex = this.emitIntegerLoopIndex(s.index);
3214
- const idx = integerIndex ?? this.emitExpr(s.index).name;
3379
+ const idx = this.emitExpr(s.index);
3215
3380
  const v = this.emitExpr(s.value);
3216
3381
  if (s.arr.type.kind !== "bytes")
3217
3382
  throw new InternalCompilerError("llvm emitter bug: bytesSet on non-bytes");
3218
- this.emitBytesSet(s.arr.type.elem, arr.name, idx, v.name, integerIndex !== null);
3383
+ this.emitBytesSet(s.arr.type.elem, arr.name, idx, v, s.index, s.value, this.bytesBounds.has(s));
3219
3384
  break;
3220
3385
  }
3221
3386
  case "fieldSet":
@@ -3225,7 +3390,7 @@ export class LlEmitter {
3225
3390
  // release — a release can trigger a cycle collection, which must
3226
3391
  // never see a heap edge whose count was already given up).
3227
3392
  // Classes and records share the struct layout, so one emission.
3228
- const obj = this.emitExpr(s.obj);
3393
+ const obj = isRefCounted(s.value.type) ? this.emitExpr(s.obj) : this.emitStableReceiver(s.obj, [s.value]);
3229
3394
  const v = this.emitExpr(s.value);
3230
3395
  const { ptr, type } = s.kind === "fieldSet"
3231
3396
  ? this.classFieldPtr(obj.name, s.className, s.field)
@@ -3486,14 +3651,51 @@ export class LlEmitter {
3486
3651
  case "for": {
3487
3652
  // The init's scope wraps the whole loop (break/continue must NOT
3488
3653
  // release it — scopeDepth captured after the push, C parity).
3489
- const integerLoop = this.debug === null ? matchIntegerBytesForLoop(s, this.currentLocals) : null;
3654
+ const integerArrayLoop = this.debug === null ? matchIntegerArrayForLoop(s, this.currentLocals, this.integerArrayBindings) : null;
3655
+ const integerLoop = integerArrayLoop ?? (this.debug === null ? matchIntegerBytesForLoop(s, this.currentLocals) : null);
3656
+ const countedLoop = !integerLoop && this.countedLoopsEnabled ? matchIntegerCountedForLoop(s, this.currentLocals, this.integerRanges) : null;
3657
+ let countedJoin = null;
3658
+ if (countedLoop?.guarded) {
3659
+ const bound = this.emitExpr(countedLoop.limit);
3660
+ const safe = B.tmp();
3661
+ const fast = B.newLabel("counted.fast");
3662
+ const slow = B.newLabel("counted.slow");
3663
+ countedJoin = B.newLabel("counted.done");
3664
+ B.line(`${safe} = fcmp ${countedLoop.step > 0 ? "ole" : "oge"} double ${bound.name}, ${f64Lit(countedLoop.guardLimit)}`);
3665
+ B.condBr(safe, fast, slow);
3666
+ B.startBlock(slow);
3667
+ this.countedLoopsEnabled = false;
3668
+ this.emitStmt(s);
3669
+ this.countedLoopsEnabled = true;
3670
+ B.br(countedJoin);
3671
+ B.startBlock(fast);
3672
+ }
3490
3673
  this.scopes.push([]);
3491
3674
  let integerSlot = null;
3675
+ let countedLimit = null;
3492
3676
  if (integerLoop) {
3493
3677
  integerSlot = B.slot();
3494
3678
  B.entryAllocas.push(`${integerSlot} = alloca ${this.sizeType} ; integer induction ${this.currentLocals.get(integerLoop.localId).name}`);
3495
- B.line(`store ${this.sizeType} 0, ptr ${integerSlot}`);
3496
- this.integerLoopBindings.set(integerLoop.localId, integerSlot);
3679
+ let start = "0";
3680
+ if (integerArrayLoop && s.init?.kind === "varDecl" && s.init.init) {
3681
+ const value = this.emitExpr(s.init.init);
3682
+ start = B.tmp();
3683
+ B.line(`${start} = fptoui double ${value.name} to ${this.sizeType}`);
3684
+ this.integerArrayBindings.add(integerLoop.localId);
3685
+ }
3686
+ B.line(`store ${this.sizeType} ${start}, ptr ${integerSlot}`);
3687
+ this.integerLoopBindings.set(integerLoop.localId, { slot: integerSlot, type: this.sizeType,
3688
+ range: { min: 0, max: integerArrayLoop ? 4294967294 : Number.MAX_SAFE_INTEGER - 1 } });
3689
+ }
3690
+ else if (countedLoop) {
3691
+ integerSlot = B.slot();
3692
+ B.entryAllocas.push(`${integerSlot} = alloca i64 ; integer induction ${this.currentLocals.get(countedLoop.localId).name}`);
3693
+ const startNumber = this.emitExpr(countedLoop.start);
3694
+ const start = B.tmp();
3695
+ B.line(`${start} = fptosi double ${startNumber.name} to i64`);
3696
+ B.line(`store i64 ${start}, ptr ${integerSlot}`);
3697
+ this.integerLoopBindings.set(countedLoop.localId, { slot: integerSlot, type: "i64", signed: true, range: countedLoop.range });
3698
+ countedLimit = emitCountedLoopLimit(this, countedLoop);
3497
3699
  }
3498
3700
  else if (s.init) {
3499
3701
  // A multi-declarator head shares the loop's scope. Emitting its
@@ -3540,12 +3742,21 @@ export class LlEmitter {
3540
3742
  const len = B.tmp();
3541
3743
  const index = B.tmp();
3542
3744
  const inBounds = B.tmp();
3543
- B.line(`${lenPtr} = getelementptr inbounds %ScrBytes, ptr ${receiver.name}, i64 0, i32 1`);
3544
- B.line(`${len} = load ${this.sizeType}, ptr ${lenPtr}`);
3745
+ B.line(`${lenPtr} = getelementptr inbounds %${integerArrayLoop ? "ScrArr" : "ScrBytes"}, ptr ${receiver.name}, i64 0, i32 1`);
3746
+ if (integerArrayLoop)
3747
+ this.markMemoryPointer(lenPtr, "array:header");
3748
+ B.line(`${len} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}`);
3545
3749
  B.line(`${index} = load ${this.sizeType}, ptr ${integerSlot}`);
3546
3750
  B.line(`${inBounds} = icmp ult ${this.sizeType} ${index}, ${len}`);
3547
3751
  B.condBr(inBounds, lb, le);
3548
3752
  }
3753
+ else if (countedLoop && integerSlot && countedLimit) {
3754
+ const index = B.tmp();
3755
+ const inBounds = B.tmp();
3756
+ B.line(`${index} = load i64, ptr ${integerSlot}`);
3757
+ B.line(`${inBounds} = icmp ${countedLoop.step > 0 ? "slt" : "sgt"} i64 ${index}, ${countedLimit}`);
3758
+ B.condBr(inBounds, lb, le);
3759
+ }
3549
3760
  else if (s.cond)
3550
3761
  B.condBr(this.emitCondition(s.cond), lb, le);
3551
3762
  else
@@ -3573,6 +3784,13 @@ export class LlEmitter {
3573
3784
  B.line(`${next} = add nuw ${this.sizeType} ${old}, 1`);
3574
3785
  B.line(`store ${this.sizeType} ${next}, ptr ${integerSlot}`);
3575
3786
  }
3787
+ else if (countedLoop && integerSlot) {
3788
+ const old = B.tmp();
3789
+ const next = B.tmp();
3790
+ B.line(`${old} = load i64, ptr ${integerSlot}`);
3791
+ B.line(`${next} = add nsw i64 ${old}, ${countedLoop.step}`);
3792
+ B.line(`store i64 ${next}, ptr ${integerSlot}`);
3793
+ }
3576
3794
  else if (s.update)
3577
3795
  this.emitStmt(s.update);
3578
3796
  B.br(lc);
@@ -3581,6 +3799,14 @@ export class LlEmitter {
3581
3799
  this.releaseScope(this.scopes.pop());
3582
3800
  if (integerLoop)
3583
3801
  this.integerLoopBindings.delete(integerLoop.localId);
3802
+ if (countedLoop)
3803
+ this.integerLoopBindings.delete(countedLoop.localId);
3804
+ if (integerArrayLoop)
3805
+ this.integerArrayBindings.delete(integerArrayLoop.localId);
3806
+ if (countedJoin) {
3807
+ B.br(countedJoin);
3808
+ B.startBlock(countedJoin);
3809
+ }
3584
3810
  break;
3585
3811
  }
3586
3812
  case "forOf": {
@@ -4066,12 +4292,15 @@ export class LlEmitter {
4066
4292
  * branch and moves the result into `slot`: the chosen value's ownership
4067
4293
  * transfers, every other temp the arm allocated releases inside the
4068
4294
  * branch. The shared core of ternary/logical. */
4069
- emitBranchInto(slot, expr) {
4295
+ emitBranchInto(slot, expr, integerSlot) {
4070
4296
  this.frames.push([]);
4071
4297
  const v = this.emitExpr(expr);
4072
4298
  this.moveTemp(v);
4073
4299
  this.B.line(`store ${this.llType(expr.type)} ${v.name}, ptr ${slot}`);
4300
+ if (integerSlot !== undefined && v.uint32 !== undefined)
4301
+ this.B.line(`store i32 ${v.uint32}, ptr ${integerSlot}`);
4074
4302
  this.releaseFrame(this.frames.pop());
4303
+ return v.uint32 !== undefined;
4075
4304
  }
4076
4305
  // ── expressions ─────────────────────────────────────────────────────────
4077
4306
  emitLiteralExpr(e) {
@@ -4083,7 +4312,7 @@ export class LlEmitter {
4083
4312
  // to this expression's frame so they survive those reads and still
4084
4313
  // die on the path that created them, including inside lazy branches.
4085
4314
  this.scopes.push([]);
4086
- const result = emitOperatorExpr(this, e);
4315
+ const result = emitBorrowedFieldSequence(this, e) ?? emitOperatorExpr(this, e);
4087
4316
  for (const local of this.scopes.pop()) {
4088
4317
  this.currentFrame().push({ name: local.slot, type: local.type, slot: true, ...(local.boxed ? { boxed: true } : {}) });
4089
4318
  }
@@ -4170,6 +4399,8 @@ export class LlEmitter {
4170
4399
  emitReadReceiver(e) {
4171
4400
  if (!isRefCounted(e.type))
4172
4401
  return this.emitExpr(e);
4402
+ if (e.kind === "strLit")
4403
+ return { name: this.internLiteral(e.value), type: e.type };
4173
4404
  if (e.kind === "varRef") {
4174
4405
  const binding = this.binding(e.localId);
4175
4406
  // Capture boxes can carry TDZ and caught-value conversion semantics.
@@ -4192,8 +4423,43 @@ export class LlEmitter {
4192
4423
  : this.classFieldPtr(receiver.name, e.className, e.field);
4193
4424
  return { name: this.loadField(ptr, type), type: e.type };
4194
4425
  }
4426
+ if (e.kind === "ternary" && this.canBorrowReceiver(e.then) && this.canBorrowReceiver(e.else_)) {
4427
+ // Checked projections lower to a ternary with an always-throwing
4428
+ // arm. Keep that check at the use, but borrow the successful arm.
4429
+ const B = this.B;
4430
+ const cond = this.emitExpr(e.cond);
4431
+ const slot = B.slot();
4432
+ B.entryAllocas.push(`${slot} = alloca ptr`);
4433
+ const yes = B.newLabel("read.then"), no = B.newLabel("read.else"), join = B.newLabel("read.join");
4434
+ B.condBr(cond.name, yes, no);
4435
+ for (const [label, value] of [[yes, e.then], [no, e.else_]]) {
4436
+ B.startBlock(label);
4437
+ this.frames.push([]);
4438
+ const result = this.emitReadReceiver(value);
4439
+ B.line(`store ptr ${result.name}, ptr ${slot}`);
4440
+ this.releaseFrame(this.frames.pop());
4441
+ B.br(join);
4442
+ }
4443
+ B.startBlock(join);
4444
+ const value = B.tmp();
4445
+ B.line(`${value} = load ptr, ptr ${slot}`);
4446
+ return { name: value, type: e.type };
4447
+ }
4195
4448
  return this.emitExpr(e);
4196
4449
  }
4450
+ canBorrowReceiver(e) {
4451
+ switch (e.kind) {
4452
+ case "varRef": return this.binding(e.localId).kind !== "boxed";
4453
+ case "unionNarrow": return this.canBorrowReceiver(e.value);
4454
+ case "fieldGet":
4455
+ case "recordGet": return this.canBorrowReceiver(e.obj);
4456
+ case "ternary": return this.canBorrowReceiver(e.then) && this.canBorrowReceiver(e.else_);
4457
+ // The runtime always sets a pending exception; its unreachable
4458
+ // typed dummy is null and owns no receiver.
4459
+ case "libCall": return e.fn === "error.nodeThrow";
4460
+ default: return false;
4461
+ }
4462
+ }
4197
4463
  emitExpr(e) {
4198
4464
  return emitExpr(this, e);
4199
4465
  }
@@ -4311,8 +4577,8 @@ export class LlEmitter {
4311
4577
  emitIntegerLoopIndex(expr) {
4312
4578
  return emitIntegerLoopIndex(this, expr);
4313
4579
  }
4314
- emitBytesIndex(receiver, index, integerIndex = false) {
4315
- return emitBytesIndex(this, receiver, index, integerIndex);
4580
+ emitBytesIndex(receiver, index, expr, inBounds = false) {
4581
+ return emitBytesIndex(this, receiver, index, expr, undefined, inBounds);
4316
4582
  }
4317
4583
  emitBytesData(receiver) {
4318
4584
  return emitBytesData(this, receiver);
@@ -4320,14 +4586,21 @@ export class LlEmitter {
4320
4586
  emitBytesLength(elem, receiver, bytes) {
4321
4587
  return emitBytesLength(this, elem, receiver, bytes);
4322
4588
  }
4323
- emitBytesGet(elem, receiver, index, integerIndex = false) {
4324
- return emitBytesGet(this, elem, receiver, index, integerIndex);
4589
+ emitBytesGet(elem, receiver, index, expr, inBounds = false) {
4590
+ return emitBytesGet(this, elem, receiver, index, expr, inBounds);
4325
4591
  }
4326
- emitToUint32(value, expr) {
4327
- return emitToUint32(this, value, expr);
4592
+ emitToUint32(value, expr, uint32) {
4593
+ return emitToUint32(this, value, expr, uint32);
4594
+ }
4595
+ storeIntegerView(localId, value, expr) {
4596
+ const slot = this.integerViews.get(localId);
4597
+ if (slot === undefined)
4598
+ return;
4599
+ const integer = this.emitToUint32(value.name, expr, value.uint32);
4600
+ this.B.line(`store i32 ${integer}, ptr ${slot}`);
4328
4601
  }
4329
- emitBytesSet(elem, receiver, index, value, integerIndex = false) {
4330
- return emitBytesSet(this, elem, receiver, index, value, integerIndex);
4602
+ emitBytesSet(elem, receiver, index, value, indexExpr, expr, inBounds = false) {
4603
+ return emitBytesSet(this, elem, receiver, index, value, indexExpr, expr, inBounds);
4331
4604
  }
4332
4605
  emitBytesIntrinsic(e) {
4333
4606
  return emitBytesIntrinsic(this, e);