@scriptc/compiler 0.2.3 → 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 (135) hide show
  1. package/dist/backend/llvm/borrowed-inputs.d.ts +15 -0
  2. package/dist/backend/llvm/borrowed-inputs.js +28 -0
  3. package/dist/backend/llvm/borrowed-inputs.js.map +1 -0
  4. package/dist/backend/llvm/byte-numbers.d.ts +10 -0
  5. package/dist/backend/llvm/byte-numbers.js +235 -0
  6. package/dist/backend/llvm/byte-numbers.js.map +1 -0
  7. package/dist/backend/llvm/bytes-bounds.d.ts +9 -0
  8. package/dist/backend/llvm/bytes-bounds.js +189 -0
  9. package/dist/backend/llvm/bytes-bounds.js.map +1 -0
  10. package/dist/backend/llvm/call-lifetimes.d.ts +7 -0
  11. package/dist/backend/llvm/call-lifetimes.js +27 -7
  12. package/dist/backend/llvm/call-lifetimes.js.map +1 -1
  13. package/dist/backend/llvm/call-lifetimes.test.js +54 -0
  14. package/dist/backend/llvm/call-lifetimes.test.js.map +1 -1
  15. package/dist/backend/llvm/checked-value-lifetimes.d.ts +9 -0
  16. package/dist/backend/llvm/checked-value-lifetimes.js +35 -0
  17. package/dist/backend/llvm/checked-value-lifetimes.js.map +1 -0
  18. package/dist/backend/llvm/counted-loops.d.ts +6 -0
  19. package/dist/backend/llvm/counted-loops.js +28 -0
  20. package/dist/backend/llvm/counted-loops.js.map +1 -0
  21. package/dist/backend/llvm/emitter.d.ts +25 -9
  22. package/dist/backend/llvm/emitter.js +131 -31
  23. package/dist/backend/llvm/emitter.js.map +1 -1
  24. package/dist/backend/llvm/emitter.test.js +4 -4
  25. package/dist/backend/llvm/emitter.test.js.map +1 -1
  26. package/dist/backend/llvm/expr-async.js +3 -2
  27. package/dist/backend/llvm/expr-async.js.map +1 -1
  28. package/dist/backend/llvm/expr-bytes.d.ts +4 -4
  29. package/dist/backend/llvm/expr-bytes.js +68 -35
  30. package/dist/backend/llvm/expr-bytes.js.map +1 -1
  31. package/dist/backend/llvm/expr-calls.js +21 -13
  32. package/dist/backend/llvm/expr-calls.js.map +1 -1
  33. package/dist/backend/llvm/expr-containers.d.ts +2 -1
  34. package/dist/backend/llvm/expr-containers.js +68 -46
  35. package/dist/backend/llvm/expr-containers.js.map +1 -1
  36. package/dist/backend/llvm/expr-context.d.ts +11 -0
  37. package/dist/backend/llvm/expr-control.js +26 -5
  38. package/dist/backend/llvm/expr-control.js.map +1 -1
  39. package/dist/backend/llvm/expr-dynamic.js +32 -30
  40. package/dist/backend/llvm/expr-dynamic.js.map +1 -1
  41. package/dist/backend/llvm/expr-primitives.js +55 -22
  42. package/dist/backend/llvm/expr-primitives.js.map +1 -1
  43. package/dist/backend/llvm/expr-records.js +8 -2
  44. package/dist/backend/llvm/expr-records.js.map +1 -1
  45. package/dist/backend/llvm/integer-values.d.ts +10 -0
  46. package/dist/backend/llvm/integer-values.js +41 -0
  47. package/dist/backend/llvm/integer-values.js.map +1 -0
  48. package/dist/backend/llvm/integer-views.d.ts +7 -0
  49. package/dist/backend/llvm/integer-views.js +67 -0
  50. package/dist/backend/llvm/integer-views.js.map +1 -0
  51. package/dist/backend/llvm/integer-views.test.d.ts +1 -0
  52. package/dist/backend/llvm/integer-views.test.js +34 -0
  53. package/dist/backend/llvm/integer-views.test.js.map +1 -0
  54. package/dist/backend/llvm/lib-dispatch.js +3 -1
  55. package/dist/backend/llvm/lib-dispatch.js.map +1 -1
  56. package/dist/backend/llvm/lib-filesystem.js +5 -5
  57. package/dist/backend/llvm/lib-filesystem.js.map +1 -1
  58. package/dist/backend/llvm/local-array-reads.d.ts +2 -4
  59. package/dist/backend/llvm/local-array-reads.js +4 -91
  60. package/dist/backend/llvm/local-array-reads.js.map +1 -1
  61. package/dist/backend/llvm/local-array-reads.test.js +11 -11
  62. package/dist/backend/llvm/local-array-reads.test.js.map +1 -1
  63. package/dist/backend/llvm/map-read-lifetimes.js +2 -2
  64. package/dist/backend/llvm/map-read-lifetimes.js.map +1 -1
  65. package/dist/backend/llvm/map-read-lifetimes.test.js +5 -1
  66. package/dist/backend/llvm/map-read-lifetimes.test.js.map +1 -1
  67. package/dist/backend/llvm/reference-effects.d.ts +22 -0
  68. package/dist/backend/llvm/reference-effects.js +148 -0
  69. package/dist/backend/llvm/reference-effects.js.map +1 -0
  70. package/dist/backend/llvm/reference-effects.test.d.ts +1 -0
  71. package/dist/backend/llvm/reference-effects.test.js +74 -0
  72. package/dist/backend/llvm/reference-effects.test.js.map +1 -0
  73. package/dist/backend/llvm/shapes.d.ts +7 -4
  74. package/dist/backend/llvm/shapes.js +23 -6
  75. package/dist/backend/llvm/shapes.js.map +1 -1
  76. package/dist/backend/llvm/stack-unions.test.js +23 -2
  77. package/dist/backend/llvm/stack-unions.test.js.map +1 -1
  78. package/dist/backend/llvm/string-construction.d.ts +10 -0
  79. package/dist/backend/llvm/string-construction.js +38 -0
  80. package/dist/backend/llvm/string-construction.js.map +1 -0
  81. package/dist/backend/llvm/string-construction.test.d.ts +1 -0
  82. package/dist/backend/llvm/string-construction.test.js +65 -0
  83. package/dist/backend/llvm/string-construction.test.js.map +1 -0
  84. package/dist/backend/llvm/string-lifetimes.d.ts +2 -2
  85. package/dist/backend/llvm/string-lifetimes.js +5 -3
  86. package/dist/backend/llvm/string-lifetimes.js.map +1 -1
  87. package/dist/backend/llvm/string-lifetimes.test.js +19 -10
  88. package/dist/backend/llvm/string-lifetimes.test.js.map +1 -1
  89. package/dist/frontend/lowering/collection-ingestion.js +9 -0
  90. package/dist/frontend/lowering/collection-ingestion.js.map +1 -1
  91. package/dist/frontend/lowering/containers/string-and-regexp.js +6 -0
  92. package/dist/frontend/lowering/containers/string-and-regexp.js.map +1 -1
  93. package/dist/frontend/lowering/lower-builtins.js +2 -0
  94. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  95. package/dist/frontend/lowering/lower-classes.js +3 -3
  96. package/dist/frontend/lowering/lower-classes.js.map +1 -1
  97. package/dist/frontend/lowering/lower-containers.js +12 -34
  98. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  99. package/dist/frontend/lowering/lower-exprs.js +12 -2
  100. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  101. package/dist/frontend/lowering/lowerer.js +10 -3
  102. package/dist/frontend/lowering/lowerer.js.map +1 -1
  103. package/dist/frontend/type-mapper.js +2 -2
  104. package/dist/frontend/type-mapper.js.map +1 -1
  105. package/dist/ir/analysis.js +3 -1
  106. package/dist/ir/analysis.js.map +1 -1
  107. package/dist/ir/byte-numbers.d.ts +20 -0
  108. package/dist/ir/byte-numbers.js +65 -0
  109. package/dist/ir/byte-numbers.js.map +1 -0
  110. package/dist/ir/byte-numbers.test.d.ts +1 -0
  111. package/dist/ir/byte-numbers.test.js +42 -0
  112. package/dist/ir/byte-numbers.test.js.map +1 -0
  113. package/dist/ir/integer-loops.d.ts +19 -0
  114. package/dist/ir/integer-loops.js +107 -1
  115. package/dist/ir/integer-loops.js.map +1 -1
  116. package/dist/ir/integer-loops.test.js +51 -1
  117. package/dist/ir/integer-loops.test.js.map +1 -1
  118. package/dist/ir/integer-ranges.d.ts +8 -6
  119. package/dist/ir/integer-ranges.js +343 -89
  120. package/dist/ir/integer-ranges.js.map +1 -1
  121. package/dist/ir/integer-ranges.test.js +54 -3
  122. package/dist/ir/integer-ranges.test.js.map +1 -1
  123. package/dist/ir/ir.d.ts +8 -5
  124. package/dist/ir/ir.js +16 -5
  125. package/dist/ir/ir.js.map +1 -1
  126. package/dist/ir/validate.js +47 -29
  127. package/dist/ir/validate.js.map +1 -1
  128. package/dist/ir/validate.test.js +20 -0
  129. package/dist/ir/validate.test.js.map +1 -1
  130. package/dist/native/toolchain.js +6 -3
  131. package/dist/native/toolchain.js.map +1 -1
  132. package/dist/native/toolchain.test.js +43 -5
  133. package/dist/native/toolchain.test.js.map +1 -1
  134. package/package.json +20 -20
  135. package/surface-manifest.json +1 -1
@@ -22,10 +22,13 @@ 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 { matchIntegerArrayForLoop, 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";
@@ -34,9 +37,11 @@ import { mangleArgPack, mangleAsyncSpawn, mangleBorrowedFunction, mangleClassObj
34
37
  import { analyzeCallLifetimes } from "./call-lifetimes.js";
35
38
  import { emitStackUnion, findLocalStackUnions } from "./stack-unions.js";
36
39
  import { BlockBuilder } from "./blocks.js";
37
- import { emitLocalArrayRead, findArrayPreservingFunctions, findLocalArrayReads, findCallArrayReads, OptionalArrayReads } from "./local-array-reads.js";
40
+ import { emitLocalArrayRead, findLocalArrayReads, findCallArrayReads, OptionalArrayReads } from "./local-array-reads.js";
38
41
  import { emitStackMapRead, findMapReadLifetimes } from "./map-read-lifetimes.js";
39
42
  import { emitBorrowedFieldSequence } from "./borrowed-receivers.js";
43
+ import { emitBorrowedInput } from "./borrowed-inputs.js";
44
+ import { ReferenceEffects } from "./reference-effects.js";
40
45
  import { LlvmDebugInfo } from "./debug-info.js";
41
46
  import { f64Lit, ffiNativeTypeLl, ffiNativeParamLl, ffiNativeReturnLl, llvmCommentText } from "./common.js";
42
47
  import { ffiExtendsNarrowIntegers } from "../targets.js";
@@ -156,6 +161,7 @@ export class LlEmitter {
156
161
  * immortal (rc == SIZE_MAX) static per (union, unit tag).
157
162
  * RC entry points and the collector skip immortals. */
158
163
  unitInstances = new Map();
164
+ immortalValues = new Set();
159
165
  /** Regex literal templates: "<flags>/<pattern>" → { symbol, interned
160
166
  * source/flags literal refs } — one immortal ScrRegex template per distinct
161
167
  * (pattern, flags) pair; the bytecode slot starts null and the runtime
@@ -189,12 +195,13 @@ export class LlEmitter {
189
195
  needsBadKey = false;
190
196
  needsRetainBox = false;
191
197
  fnByName = new Map();
192
- arrayPreservingFunctions;
198
+ referenceEffects;
193
199
  optionalArrayReads;
194
200
  callArrayReads = new Map();
195
201
  mapReadLifetimes = { locals: new Map(), arguments: new Map() };
196
202
  callLifetimes;
197
203
  borrowedParameters = new Set();
204
+ stableCallBindings = new Set();
198
205
  /** Manifest-bound native imports, used by ffiCall emission. */
199
206
  ffiByName = new Map();
200
207
  /** C-ABI callback trampolines and (for raw/no-userdata callbacks) their
@@ -276,9 +283,12 @@ export class LlEmitter {
276
283
  jumpTargets = [];
277
284
  currentLocals = new Map();
278
285
  captureIds = new Set();
279
- /** Active canonical byte-loop induction bindings: local id → size_t slot. */
286
+ /** Active induction bindings retain their width independently of size_t. */
280
287
  integerLoopBindings = new Map();
288
+ countedLoopsEnabled = false;
281
289
  integerRanges = new Map();
290
+ bytesBounds = new Set();
291
+ integerViews = new Map();
282
292
  /** Enclosing try-with-FINALLY regions, innermost last: a `return`
283
293
  * inside one runs every crossed finally (innermost first) before the
284
294
  * actual ret — the runtime ABI’s pending-return path, with the finally
@@ -345,7 +355,7 @@ export class LlEmitter {
345
355
  for (const u of mod.unions ?? [])
346
356
  this.unionsById.set(u.id, u);
347
357
  this.optionalArrayReads = new OptionalArrayReads(this.fnByName, this.unionsById);
348
- this.arrayPreservingFunctions = findArrayPreservingFunctions(this.fnByName, this.unionsById, this.optionalArrayReads);
358
+ this.referenceEffects = new ReferenceEffects(this.fnByName, (call) => this.optionalArrayReads.get(call) !== null);
349
359
  this.callLifetimes = analyzeCallLifetimes(this.fnByName);
350
360
  for (const r of mod.records ?? [])
351
361
  this.recordsById.set(r.id, r);
@@ -1995,7 +2005,9 @@ export class LlEmitter {
1995
2005
  lit = { sym: `sc_lit_${this.literals.size}`, len: Buffer.byteLength(text, "utf8") };
1996
2006
  this.literals.set(text, lit);
1997
2007
  }
1998
- return `@${lit.sym}`;
2008
+ const name = `@${lit.sym}`;
2009
+ this.immortalValues.add(name);
2010
+ return name;
1999
2011
  }
2000
2012
  /** Interned NUL-terminated C-string constant (the scr_jb_puts /
2001
2013
  * stringify-indent currency) — `@`-ref, first-use order. */
@@ -2025,7 +2037,9 @@ export class LlEmitter {
2025
2037
  sym = `sc_unit_${this.unitInstances.size}`;
2026
2038
  this.unitInstances.set(key, sym);
2027
2039
  }
2028
- return `@${sym}`;
2040
+ const name = `@${sym}`;
2041
+ this.immortalValues.add(name);
2042
+ return name;
2029
2043
  }
2030
2044
  declare(decl) {
2031
2045
  this.decls.add(decl);
@@ -2041,7 +2055,7 @@ export class LlEmitter {
2041
2055
  }
2042
2056
  /** Registers an owned refcounted value on the current statement frame. */
2043
2057
  own(v) {
2044
- if (isRefCounted(v.type))
2058
+ if (isRefCounted(v.type) && !this.immortalValues.has(v.name))
2045
2059
  this.currentFrame().push(v);
2046
2060
  return v;
2047
2061
  }
@@ -2053,7 +2067,7 @@ export class LlEmitter {
2053
2067
  }
2054
2068
  /** Strike a refcounted temp from its frame: ownership is being moved. */
2055
2069
  moveTemp(v) {
2056
- if (!isRefCounted(v.type))
2070
+ if (!isRefCounted(v.type) || this.immortalValues.has(v.name))
2057
2071
  return;
2058
2072
  for (let i = this.frames.length - 1; i >= 0; i--) {
2059
2073
  const idx = this.frames[i].findIndex((e) => e.name === v.name);
@@ -2067,6 +2081,8 @@ export class LlEmitter {
2067
2081
  /** The retained (+1) read of a refcounted value — type-directed through
2068
2082
  * the `_v` table (immortals skip, exactly the C retain calls). */
2069
2083
  retainValue(name, type) {
2084
+ if (this.immortalValues.has(name))
2085
+ return name;
2070
2086
  const t = this.B.tmp();
2071
2087
  this.B.line(`${t} = call ptr ${retainSym(this.shapeHost, type)}(ptr ${name})`);
2072
2088
  return t;
@@ -2074,6 +2090,8 @@ export class LlEmitter {
2074
2090
  /** The release call for one owned refcounted value — type-directed like
2075
2091
  * releaseCallC (all runtime releases are NULL-tolerant). */
2076
2092
  releaseValue(name, type) {
2093
+ if (this.immortalValues.has(name))
2094
+ return;
2077
2095
  this.B.line(`call void ${releaseSym(this.shapeHost, type)}(ptr ${name})`);
2078
2096
  }
2079
2097
  releaseFrame(frame) {
@@ -2912,20 +2930,24 @@ export class LlEmitter {
2912
2930
  this.jumpTargets = [];
2913
2931
  this.currentLocals = new Map(fn.locals.map((l) => [l.id, l]));
2914
2932
  this.borrowedParameters.clear();
2915
- const borrowedParameterIndexes = this.callLifetimes.parameters.get(fn.name);
2933
+ this.stableCallBindings = this.callLifetimes.bindings.get(fn.name) ?? new Set();
2934
+ const borrowedParameterIndexes = this.callLifetimes.borrowed.get(fn.name);
2916
2935
  if (borrowedParameterIndexes) {
2917
2936
  for (const index of borrowedParameterIndexes)
2918
2937
  this.borrowedParameters.add(fn.params[index].localId);
2919
2938
  }
2920
2939
  this.captureIds = new Set([...(fn.captures ?? []), ...(fn.classCaptures ?? [])].map((c) => c.localId));
2921
2940
  this.integerLoopBindings.clear();
2941
+ this.countedLoopsEnabled = this.debug === null && !fn.async && !fn.generator;
2942
+ this.integerViews.clear();
2922
2943
  this.fieldPointerTags.clear();
2923
2944
  this.integerArrayBindings.clear();
2924
- this.localArrayReads = findLocalArrayReads(fn, this.fnByName, this.unionsById, this.arrayPreservingFunctions, this.callLifetimes, this.optionalArrayReads);
2925
- this.callArrayReads = findCallArrayReads(fn, this.optionalArrayReads, this.arrayPreservingFunctions, this.callLifetimes);
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);
2926
2947
  this.mapReadLifetimes = findMapReadLifetimes(fn, this.unionsById, this.callLifetimes);
2927
2948
  this.localStackUnions = findLocalStackUnions(fn, this.callLifetimes, this.unionsById);
2928
2949
  this.integerRanges = analyzeIntegerRanges(fn);
2950
+ this.bytesBounds = findBytesBounds(fn, this.integerRanges);
2929
2951
  this.chainSlots.clear();
2930
2952
  this.finallyStack = [];
2931
2953
  this.tryStack = [];
@@ -2967,6 +2989,13 @@ export class LlEmitter {
2967
2989
  };
2968
2990
  }
2969
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
+ }
2970
2999
  for (const local of fn.locals) {
2971
3000
  // Boxed locals' slots hold their capture BOX (a ptr); captured
2972
3001
  // (env-borrowed) locals bind the incoming box below. A caught-typed
@@ -3016,7 +3045,7 @@ export class LlEmitter {
3016
3045
  // params (callees own their params — callers passed +1). Boxed params
3017
3046
  // allocate the shared binding and move the raw value in.
3018
3047
  const fnScope = [];
3019
- const borrowed = this.callLifetimes.parameters.get(fn.name);
3048
+ const borrowed = this.callLifetimes.borrowed.get(fn.name);
3020
3049
  for (const [index, p] of fn.params.entries()) {
3021
3050
  const local = this.currentLocals.get(p.localId);
3022
3051
  const slot = `%${mangleLocal(p.localId)}`;
@@ -3029,6 +3058,7 @@ export class LlEmitter {
3029
3058
  continue;
3030
3059
  }
3031
3060
  B.line(`store ${this.llType(p.type)} %p_${mangleLocal(p.localId)}, ptr ${slot}`);
3061
+ this.storeIntegerView(p.localId, { name: `%p_${mangleLocal(p.localId)}`, type: p.type });
3032
3062
  if (isRefCounted(p.type) && !borrowed?.has(index))
3033
3063
  fnScope.push({ slot, type: p.type });
3034
3064
  }
@@ -3132,7 +3162,7 @@ export class LlEmitter {
3132
3162
  B.terminate(ret === "void" ? "ret void" : `ret ${ret} %result`);
3133
3163
  return `define internal ${ret} @${mangleFunction(fn.name)}(${params.join(", ")}) ${FN_ATTRS} {\n${B.render()}\n}`;
3134
3164
  }
3135
- /** A local immutable binding keeps its value alive while later arguments
3165
+ /** A local stable binding keeps its value alive while later arguments
3136
3166
  * and the callee execute. Global, captured, TDZ and mutable bindings need
3137
3167
  * an owned snapshot, even when the callee only inspects the value. */
3138
3168
  canBorrowCallArgument(value) {
@@ -3142,7 +3172,7 @@ export class LlEmitter {
3142
3172
  return false;
3143
3173
  const binding = this.binding(value.localId);
3144
3174
  return binding.kind === "local" && binding.local !== undefined &&
3145
- (!binding.local.mutable || this.borrowedParameters.has(value.localId)) && !binding.local.boxed && !binding.local.tdz;
3175
+ (!binding.local.mutable || this.stableCallBindings.has(value.localId) || this.borrowedParameters.has(value.localId)) && !binding.local.boxed && !binding.local.tdz;
3146
3176
  }
3147
3177
  // ── statements ──────────────────────────────────────────────────────────
3148
3178
  emitStmts(stmts) {
@@ -3248,9 +3278,20 @@ export class LlEmitter {
3248
3278
  }
3249
3279
  break;
3250
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
+ }
3251
3291
  const v = this.emitExpr(s.init);
3252
3292
  this.moveTemp(v);
3253
3293
  B.line(`store ${this.llType(b.type)} ${v.name}, ptr ${b.slot}`);
3294
+ this.storeIntegerView(s.localId, v, s.init);
3254
3295
  if (isRefCounted(b.type)) {
3255
3296
  this.scopes[this.scopes.length - 1].push({ slot: b.slot, type: b.type });
3256
3297
  }
@@ -3281,6 +3322,7 @@ export class LlEmitter {
3281
3322
  this.releaseValue(old, b.type);
3282
3323
  }
3283
3324
  B.line(`store ${this.llType(b.type)} ${v.name}, ptr ${b.slot}`);
3325
+ this.storeIntegerView(s.localId, v, s.value);
3284
3326
  break;
3285
3327
  }
3286
3328
  case "exprStmt":
@@ -3290,7 +3332,7 @@ export class LlEmitter {
3290
3332
  // Evaluation order matches JS: array, index, then value. Ownership
3291
3333
  // of a refcounted value moves into the array (the runtime releases
3292
3334
  // the replaced element itself).
3293
- const arr = this.emitExpr(s.arr);
3335
+ const arr = emitBorrowedInput(this, s.arr);
3294
3336
  const idx = this.emitExpr(s.index);
3295
3337
  const v = this.emitExpr(s.value);
3296
3338
  if (s.arr.type.kind !== "array")
@@ -3304,7 +3346,7 @@ export class LlEmitter {
3304
3346
  break;
3305
3347
  }
3306
3348
  case "arraySetLength": {
3307
- const arr = this.emitExpr(s.arr);
3349
+ const arr = emitBorrowedInput(this, s.arr);
3308
3350
  const length = this.emitExpr(s.length);
3309
3351
  if (s.arr.type.kind !== "array")
3310
3352
  throw new InternalCompilerError("llvm emitter bug: arraySetLength on non-array");
@@ -3315,7 +3357,7 @@ export class LlEmitter {
3315
3357
  }
3316
3358
  case "arraySetUndefined":
3317
3359
  case "arrayDelete": {
3318
- const arr = this.emitExpr(s.arr);
3360
+ const arr = emitBorrowedInput(this, s.arr);
3319
3361
  const idx = this.emitExpr(s.index);
3320
3362
  if (s.arr.type.kind !== "array")
3321
3363
  throw new InternalCompilerError(`llvm emitter bug: ${s.kind} on non-array`);
@@ -3334,12 +3376,11 @@ export class LlEmitter {
3334
3376
  // IrBytesElem is static, so never rediscover it through the
3335
3377
  // generic runtime switch in a hot loop.
3336
3378
  const arr = this.emitStableReceiver(s.arr, [s.index, s.value]);
3337
- const integerIndex = this.emitIntegerLoopIndex(s.index);
3338
- const idx = integerIndex ?? this.emitExpr(s.index).name;
3379
+ const idx = this.emitExpr(s.index);
3339
3380
  const v = this.emitExpr(s.value);
3340
3381
  if (s.arr.type.kind !== "bytes")
3341
3382
  throw new InternalCompilerError("llvm emitter bug: bytesSet on non-bytes");
3342
- 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));
3343
3384
  break;
3344
3385
  }
3345
3386
  case "fieldSet":
@@ -3612,8 +3653,26 @@ export class LlEmitter {
3612
3653
  // release it — scopeDepth captured after the push, C parity).
3613
3654
  const integerArrayLoop = this.debug === null ? matchIntegerArrayForLoop(s, this.currentLocals, this.integerArrayBindings) : null;
3614
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
+ }
3615
3673
  this.scopes.push([]);
3616
3674
  let integerSlot = null;
3675
+ let countedLimit = null;
3617
3676
  if (integerLoop) {
3618
3677
  integerSlot = B.slot();
3619
3678
  B.entryAllocas.push(`${integerSlot} = alloca ${this.sizeType} ; integer induction ${this.currentLocals.get(integerLoop.localId).name}`);
@@ -3625,7 +3684,18 @@ export class LlEmitter {
3625
3684
  this.integerArrayBindings.add(integerLoop.localId);
3626
3685
  }
3627
3686
  B.line(`store ${this.sizeType} ${start}, ptr ${integerSlot}`);
3628
- this.integerLoopBindings.set(integerLoop.localId, 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);
3629
3699
  }
3630
3700
  else if (s.init) {
3631
3701
  // A multi-declarator head shares the loop's scope. Emitting its
@@ -3680,6 +3750,13 @@ export class LlEmitter {
3680
3750
  B.line(`${inBounds} = icmp ult ${this.sizeType} ${index}, ${len}`);
3681
3751
  B.condBr(inBounds, lb, le);
3682
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
+ }
3683
3760
  else if (s.cond)
3684
3761
  B.condBr(this.emitCondition(s.cond), lb, le);
3685
3762
  else
@@ -3707,6 +3784,13 @@ export class LlEmitter {
3707
3784
  B.line(`${next} = add nuw ${this.sizeType} ${old}, 1`);
3708
3785
  B.line(`store ${this.sizeType} ${next}, ptr ${integerSlot}`);
3709
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
+ }
3710
3794
  else if (s.update)
3711
3795
  this.emitStmt(s.update);
3712
3796
  B.br(lc);
@@ -3715,8 +3799,14 @@ export class LlEmitter {
3715
3799
  this.releaseScope(this.scopes.pop());
3716
3800
  if (integerLoop)
3717
3801
  this.integerLoopBindings.delete(integerLoop.localId);
3802
+ if (countedLoop)
3803
+ this.integerLoopBindings.delete(countedLoop.localId);
3718
3804
  if (integerArrayLoop)
3719
3805
  this.integerArrayBindings.delete(integerArrayLoop.localId);
3806
+ if (countedJoin) {
3807
+ B.br(countedJoin);
3808
+ B.startBlock(countedJoin);
3809
+ }
3720
3810
  break;
3721
3811
  }
3722
3812
  case "forOf": {
@@ -4202,12 +4292,15 @@ export class LlEmitter {
4202
4292
  * branch and moves the result into `slot`: the chosen value's ownership
4203
4293
  * transfers, every other temp the arm allocated releases inside the
4204
4294
  * branch. The shared core of ternary/logical. */
4205
- emitBranchInto(slot, expr) {
4295
+ emitBranchInto(slot, expr, integerSlot) {
4206
4296
  this.frames.push([]);
4207
4297
  const v = this.emitExpr(expr);
4208
4298
  this.moveTemp(v);
4209
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}`);
4210
4302
  this.releaseFrame(this.frames.pop());
4303
+ return v.uint32 !== undefined;
4211
4304
  }
4212
4305
  // ── expressions ─────────────────────────────────────────────────────────
4213
4306
  emitLiteralExpr(e) {
@@ -4484,8 +4577,8 @@ export class LlEmitter {
4484
4577
  emitIntegerLoopIndex(expr) {
4485
4578
  return emitIntegerLoopIndex(this, expr);
4486
4579
  }
4487
- emitBytesIndex(receiver, index, integerIndex = false) {
4488
- return emitBytesIndex(this, receiver, index, integerIndex);
4580
+ emitBytesIndex(receiver, index, expr, inBounds = false) {
4581
+ return emitBytesIndex(this, receiver, index, expr, undefined, inBounds);
4489
4582
  }
4490
4583
  emitBytesData(receiver) {
4491
4584
  return emitBytesData(this, receiver);
@@ -4493,14 +4586,21 @@ export class LlEmitter {
4493
4586
  emitBytesLength(elem, receiver, bytes) {
4494
4587
  return emitBytesLength(this, elem, receiver, bytes);
4495
4588
  }
4496
- emitBytesGet(elem, receiver, index, integerIndex = false) {
4497
- return emitBytesGet(this, elem, receiver, index, integerIndex);
4589
+ emitBytesGet(elem, receiver, index, expr, inBounds = false) {
4590
+ return emitBytesGet(this, elem, receiver, index, expr, inBounds);
4498
4591
  }
4499
- emitToUint32(value, expr) {
4500
- 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}`);
4501
4601
  }
4502
- emitBytesSet(elem, receiver, index, value, integerIndex = false) {
4503
- 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);
4504
4604
  }
4505
4605
  emitBytesIntrinsic(e) {
4506
4606
  return emitBytesIntrinsic(this, e);