@scriptc/compiler 0.2.2 → 0.2.3

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 (178) 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-receivers.d.ts +9 -0
  7. package/dist/backend/llvm/borrowed-receivers.js +58 -0
  8. package/dist/backend/llvm/borrowed-receivers.js.map +1 -0
  9. package/dist/backend/llvm/call-lifetimes.d.ts +20 -0
  10. package/dist/backend/llvm/call-lifetimes.js +179 -0
  11. package/dist/backend/llvm/call-lifetimes.js.map +1 -0
  12. package/dist/backend/llvm/call-lifetimes.test.d.ts +1 -0
  13. package/dist/backend/llvm/call-lifetimes.test.js +205 -0
  14. package/dist/backend/llvm/call-lifetimes.test.js.map +1 -0
  15. package/dist/backend/llvm/dyn.d.ts +2 -2
  16. package/dist/backend/llvm/dyn.js +79 -35
  17. package/dist/backend/llvm/dyn.js.map +1 -1
  18. package/dist/backend/llvm/emitter.d.ts +33 -0
  19. package/dist/backend/llvm/emitter.js +189 -16
  20. package/dist/backend/llvm/emitter.js.map +1 -1
  21. package/dist/backend/llvm/emitter.test.js +20 -1
  22. package/dist/backend/llvm/emitter.test.js.map +1 -1
  23. package/dist/backend/llvm/expr-async.js +1 -1
  24. package/dist/backend/llvm/expr-async.js.map +1 -1
  25. package/dist/backend/llvm/expr-calls.js +42 -5
  26. package/dist/backend/llvm/expr-calls.js.map +1 -1
  27. package/dist/backend/llvm/expr-containers.js +70 -13
  28. package/dist/backend/llvm/expr-containers.js.map +1 -1
  29. package/dist/backend/llvm/expr-dynamic.js +1 -1
  30. package/dist/backend/llvm/expr-dynamic.js.map +1 -1
  31. package/dist/backend/llvm/expr-primitives.js +6 -5
  32. package/dist/backend/llvm/expr-primitives.js.map +1 -1
  33. package/dist/backend/llvm/expr-records.js +1 -1
  34. package/dist/backend/llvm/expr-records.js.map +1 -1
  35. package/dist/backend/llvm/expr-stream-bridges.js +5 -10
  36. package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
  37. package/dist/backend/llvm/lib-shared.js +35 -1
  38. package/dist/backend/llvm/lib-shared.js.map +1 -1
  39. package/dist/backend/llvm/local-array-reads.d.ts +44 -0
  40. package/dist/backend/llvm/local-array-reads.js +313 -0
  41. package/dist/backend/llvm/local-array-reads.js.map +1 -0
  42. package/dist/backend/llvm/local-array-reads.test.d.ts +1 -0
  43. package/dist/backend/llvm/local-array-reads.test.js +446 -0
  44. package/dist/backend/llvm/local-array-reads.test.js.map +1 -0
  45. package/dist/backend/llvm/map-read-lifetimes.d.ts +36 -0
  46. package/dist/backend/llvm/map-read-lifetimes.js +128 -0
  47. package/dist/backend/llvm/map-read-lifetimes.js.map +1 -0
  48. package/dist/backend/llvm/map-read-lifetimes.test.d.ts +1 -0
  49. package/dist/backend/llvm/map-read-lifetimes.test.js +123 -0
  50. package/dist/backend/llvm/map-read-lifetimes.test.js.map +1 -0
  51. package/dist/backend/llvm/shapes.js +1 -0
  52. package/dist/backend/llvm/shapes.js.map +1 -1
  53. package/dist/backend/llvm/split.js +15 -5
  54. package/dist/backend/llvm/split.js.map +1 -1
  55. package/dist/backend/llvm/split.test.js +17 -0
  56. package/dist/backend/llvm/split.test.js.map +1 -1
  57. package/dist/backend/llvm/stack-unions.d.ts +20 -0
  58. package/dist/backend/llvm/stack-unions.js +61 -0
  59. package/dist/backend/llvm/stack-unions.js.map +1 -0
  60. package/dist/backend/llvm/stack-unions.test.d.ts +1 -0
  61. package/dist/backend/llvm/stack-unions.test.js +241 -0
  62. package/dist/backend/llvm/stack-unions.test.js.map +1 -0
  63. package/dist/backend/llvm/string-lifetimes.d.ts +12 -0
  64. package/dist/backend/llvm/string-lifetimes.js +70 -0
  65. package/dist/backend/llvm/string-lifetimes.js.map +1 -0
  66. package/dist/backend/llvm/string-lifetimes.test.d.ts +1 -0
  67. package/dist/backend/llvm/string-lifetimes.test.js +230 -0
  68. package/dist/backend/llvm/string-lifetimes.test.js.map +1 -0
  69. package/dist/backend/mangle.d.ts +2 -0
  70. package/dist/backend/mangle.js +4 -0
  71. package/dist/backend/mangle.js.map +1 -1
  72. package/dist/backend/may-throw.js +67 -29
  73. package/dist/backend/may-throw.js.map +1 -1
  74. package/dist/backend/may-throw.test.js +104 -1
  75. package/dist/backend/may-throw.test.js.map +1 -1
  76. package/dist/backend/native-toolchain.d.ts +2 -2
  77. package/dist/backend/native-toolchain.js +2 -0
  78. package/dist/backend/native-toolchain.js.map +1 -1
  79. package/dist/frontend/input-tracker.js +3 -3
  80. package/dist/frontend/input-tracker.js.map +1 -1
  81. package/dist/frontend/input-tracker.test.js +38 -2
  82. package/dist/frontend/input-tracker.test.js.map +1 -1
  83. package/dist/frontend/lowering/collection-ingestion.d.ts +31 -0
  84. package/dist/frontend/lowering/collection-ingestion.js +215 -0
  85. package/dist/frontend/lowering/collection-ingestion.js.map +1 -0
  86. package/dist/frontend/lowering/containers/bytes.js +2 -0
  87. package/dist/frontend/lowering/containers/bytes.js.map +1 -1
  88. package/dist/frontend/lowering/import-meta.d.ts +10 -0
  89. package/dist/frontend/lowering/import-meta.js +52 -0
  90. package/dist/frontend/lowering/import-meta.js.map +1 -0
  91. package/dist/frontend/lowering/lower-assert.js +105 -41
  92. package/dist/frontend/lowering/lower-assert.js.map +1 -1
  93. package/dist/frontend/lowering/lower-builtin-values.d.ts +2 -0
  94. package/dist/frontend/lowering/lower-builtin-values.js +22 -1
  95. package/dist/frontend/lowering/lower-builtin-values.js.map +1 -1
  96. package/dist/frontend/lowering/lower-builtins.d.ts +3 -13
  97. package/dist/frontend/lowering/lower-builtins.js +150 -46
  98. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  99. package/dist/frontend/lowering/lower-calls.js +6 -3
  100. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  101. package/dist/frontend/lowering/lower-classes.js +4 -4
  102. package/dist/frontend/lowering/lower-classes.js.map +1 -1
  103. package/dist/frontend/lowering/lower-containers.d.ts +5 -1
  104. package/dist/frontend/lowering/lower-containers.js +40 -3
  105. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  106. package/dist/frontend/lowering/lower-exprs.js +35 -41
  107. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  108. package/dist/frontend/lowering/lower-inspect.js +43 -0
  109. package/dist/frontend/lowering/lower-inspect.js.map +1 -1
  110. package/dist/frontend/lowering/lower-stmts.js +72 -68
  111. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  112. package/dist/frontend/lowering/lower-system-errors.d.ts +6 -0
  113. package/dist/frontend/lowering/lower-system-errors.js +29 -0
  114. package/dist/frontend/lowering/lower-system-errors.js.map +1 -0
  115. package/dist/frontend/lowering/lowerer.d.ts +4 -3
  116. package/dist/frontend/lowering/lowerer.js +50 -22
  117. package/dist/frontend/lowering/lowerer.js.map +1 -1
  118. package/dist/frontend/lowering/surfaces.d.ts +2 -2
  119. package/dist/frontend/lowering/surfaces.js +69 -21
  120. package/dist/frontend/lowering/surfaces.js.map +1 -1
  121. package/dist/frontend/module-syntax.d.ts +1 -1
  122. package/dist/frontend/module-syntax.js +8 -1
  123. package/dist/frontend/module-syntax.js.map +1 -1
  124. package/dist/frontend/module-syntax.test.js +10 -0
  125. package/dist/frontend/module-syntax.test.js.map +1 -1
  126. package/dist/frontend/npm.d.ts +4 -0
  127. package/dist/frontend/npm.js +86 -33
  128. package/dist/frontend/npm.js.map +1 -1
  129. package/dist/frontend/npm.test.js +103 -0
  130. package/dist/frontend/npm.test.js.map +1 -1
  131. package/dist/frontend/pipeline-native.js +2 -1
  132. package/dist/frontend/pipeline-native.js.map +1 -1
  133. package/dist/frontend/services-node.js +3 -2
  134. package/dist/frontend/services-node.js.map +1 -1
  135. package/dist/frontend/services.d.ts +5 -1
  136. package/dist/frontend/services.js +9 -1
  137. package/dist/frontend/services.js.map +1 -1
  138. package/dist/frontend/services.test.js +21 -0
  139. package/dist/frontend/services.test.js.map +1 -1
  140. package/dist/frontend/ts7/ast-node.d.ts +2 -0
  141. package/dist/frontend/ts7/ast-node.js +30 -4
  142. package/dist/frontend/ts7/ast-node.js.map +1 -1
  143. package/dist/frontend/ts7/ast-node.test.js +60 -1
  144. package/dist/frontend/ts7/ast-node.test.js.map +1 -1
  145. package/dist/frontend/type-mapper.js +6 -0
  146. package/dist/frontend/type-mapper.js.map +1 -1
  147. package/dist/index.js +3 -2
  148. package/dist/index.js.map +1 -1
  149. package/dist/ir/analysis.js +1 -0
  150. package/dist/ir/analysis.js.map +1 -1
  151. package/dist/ir/integer-loops.d.ts +7 -0
  152. package/dist/ir/integer-loops.js +23 -0
  153. package/dist/ir/integer-loops.js.map +1 -1
  154. package/dist/ir/integer-loops.test.d.ts +1 -0
  155. package/dist/ir/integer-loops.test.js +41 -0
  156. package/dist/ir/integer-loops.test.js.map +1 -0
  157. package/dist/ir/ir.d.ts +21 -17
  158. package/dist/ir/ir.js +43 -9
  159. package/dist/ir/ir.js.map +1 -1
  160. package/dist/ir/ir.test.js +18 -0
  161. package/dist/ir/ir.test.js.map +1 -1
  162. package/dist/ir/validate.js +161 -76
  163. package/dist/ir/validate.js.map +1 -1
  164. package/dist/ir/validate.test.js +227 -0
  165. package/dist/ir/validate.test.js.map +1 -1
  166. package/dist/library/fence-eval.js +9 -2
  167. package/dist/library/fence-eval.js.map +1 -1
  168. package/dist/library/fence-eval.test.d.ts +1 -0
  169. package/dist/library/fence-eval.test.js +21 -0
  170. package/dist/library/fence-eval.test.js.map +1 -0
  171. package/dist/native/runtime-typescript.d.ts +4 -0
  172. package/dist/native/runtime-typescript.js +61 -0
  173. package/dist/native/runtime-typescript.js.map +1 -0
  174. package/dist/native/runtime-typescript.test.d.ts +1 -0
  175. package/dist/native/runtime-typescript.test.js +41 -0
  176. package/dist/native/runtime-typescript.test.js.map +1 -0
  177. package/package.json +20 -20
  178. package/surface-manifest.json +271 -17
@@ -2,7 +2,10 @@ import type { IrBytesElem, IrExpr, IrFfiImport, IrFunction, IrLocal, IrModule, I
2
2
  import { type IntegerRanges } from "../../ir/integer-ranges.js";
3
3
  import { type ConstantNumericTable } from "../../ir/constant-tables.js";
4
4
  import { type FfiCallbackAdapter } from "../ffi-callbacks.js";
5
+ import { type CallLifetimes } from "./call-lifetimes.js";
5
6
  import { BlockBuilder } from "./blocks.js";
7
+ import { type LocalArrayRead } from "./local-array-reads.js";
8
+ import { type MapReadLifetimes } from "./map-read-lifetimes.js";
6
9
  import { type LlClassMeta } from "./classes.js";
7
10
  import { LlDyn, type DynHost } from "./dyn.js";
8
11
  import { LlWalkers } from "./walkers.js";
@@ -29,6 +32,19 @@ export declare function emitLlvmModule(mod: IrModule, options?: LlvmTargetOption
29
32
  export declare function emitLlvmModuleSource(mod: IrModule, options?: LlvmTargetOptions): string | readonly string[];
30
33
  export declare class LlEmitter {
31
34
  readonly mod: IrModule;
35
+ localArrayReads: Map<string, LocalArrayRead>;
36
+ private localStackUnions;
37
+ integerArrayBindings: Set<string>;
38
+ private readonly fieldAliasTags;
39
+ private readonly fieldPointerTags;
40
+ /** Native class fields with different property names cannot occupy the
41
+ * same slot of one object. Inherited views of the same property share a
42
+ * tag. Array storage has its own disjoint categories; untagged runtime
43
+ * operations and record views conservatively alias all of them. */
44
+ private markFieldPointer;
45
+ markMemoryPointer(ptr: string, field: string): void;
46
+ fieldAliasAttachment(ptr: string): string;
47
+ private fieldAliasMetadata;
32
48
  private readonly debug;
33
49
  private debugScope;
34
50
  readonly sizeType: "i32" | "i64";
@@ -84,6 +100,12 @@ export declare class LlEmitter {
84
100
  needsBadKey: boolean;
85
101
  private needsRetainBox;
86
102
  readonly fnByName: Map<string, IrFunction>;
103
+ private readonly arrayPreservingFunctions;
104
+ private readonly optionalArrayReads;
105
+ callArrayReads: Map<IrExpr, LocalArrayRead>;
106
+ mapReadLifetimes: MapReadLifetimes;
107
+ readonly callLifetimes: CallLifetimes;
108
+ private borrowedParameters;
87
109
  /** Manifest-bound native imports, used by ffiCall emission. */
88
110
  readonly ffiByName: Map<string, IrFfiImport>;
89
111
  /** C-ABI callback trampolines and (for raw/no-userdata callbacks) their
@@ -412,6 +434,16 @@ export declare class LlEmitter {
412
434
  /** Move a clean async return value into the current fiber's promise. */
413
435
  private emitWasiFulfill;
414
436
  private emitFunction;
437
+ /** Keep the ordinary owned ABI for closures, virtual dispatch, generated
438
+ * runtime adapters and library exports. Only direct IR calls select the
439
+ * borrowing body. Ownership of other parameters moves into that body;
440
+ * borrowed parameters are released here on normal and exceptional exits.
441
+ * The pending exception remains for the adapter's caller to handle. */
442
+ private emitOwnedCallAdapter;
443
+ /** A local immutable binding keeps its value alive while later arguments
444
+ * and the callee execute. Global, captured, TDZ and mutable bindings need
445
+ * an owned snapshot, even when the callee only inspects the value. */
446
+ canBorrowCallArgument(value: IrExpr): boolean;
415
447
  private emitStmts;
416
448
  /** Emits a block in its own lexical scope (refcounted locals released at
417
449
  * end). `setup` runs after the
@@ -487,6 +519,7 @@ export declare class LlEmitter {
487
519
  * their ordinary statement-frame ownership. The caller must retain any
488
520
  * reference result before evaluating another expression. */
489
521
  emitReadReceiver(e: IrExpr): LlValue;
522
+ canBorrowReceiver(e: IrExpr): boolean;
490
523
  emitExpr(e: IrExpr): LlValue;
491
524
  emitJsMarshal(e: IrExpr & {
492
525
  kind: "jsMarshal";
@@ -22,7 +22,7 @@ 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 } from "../../ir/integer-loops.js";
26
26
  import { scalarizeNumericRecords } from "../../ir/scalar-records.js";
27
27
  import { everyStmtList } from "../../ir/traverse.js";
28
28
  import { analyzeIntegerRanges } from "../../ir/integer-ranges.js";
@@ -30,8 +30,13 @@ import { findConstantNumericTables } from "../../ir/constant-tables.js";
30
30
  import { allocateFfiCallbackAdapters, hasForeignFfiCallback, hasRetainedFfiCallback } from "../ffi-callbacks.js";
31
31
  import { RUNTIME_ABI_MARKER } from "../runtime-abi.js";
32
32
  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";
33
+ import { mangleArgPack, mangleAsyncSpawn, mangleBorrowedFunction, mangleClassObj, mangleClassStruct, mangleFnClosure, mangleFunction, mangleGenDrop, mangleGenSpawn, mangleGlobal, mangleLocal, mangleRecordStruct, mangleTrampoline, mangleWrapper, mangleVtInstance } from "../mangle.js";
34
+ import { analyzeCallLifetimes } from "./call-lifetimes.js";
35
+ import { emitStackUnion, findLocalStackUnions } from "./stack-unions.js";
34
36
  import { BlockBuilder } from "./blocks.js";
37
+ import { emitLocalArrayRead, findArrayPreservingFunctions, findLocalArrayReads, findCallArrayReads, OptionalArrayReads } from "./local-array-reads.js";
38
+ import { emitStackMapRead, findMapReadLifetimes } from "./map-read-lifetimes.js";
39
+ import { emitBorrowedFieldSequence } from "./borrowed-receivers.js";
35
40
  import { LlvmDebugInfo } from "./debug-info.js";
36
41
  import { f64Lit, ffiNativeTypeLl, ffiNativeParamLl, ffiNativeReturnLl, llvmCommentText } from "./common.js";
37
42
  import { ffiExtendsNarrowIntegers } from "../targets.js";
@@ -101,6 +106,41 @@ function llStrBytes(text) {
101
106
  }
102
107
  export class LlEmitter {
103
108
  mod;
109
+ localArrayReads = new Map();
110
+ localStackUnions = new Map();
111
+ integerArrayBindings = new Set();
112
+ fieldAliasTags = new Map();
113
+ fieldPointerTags = new Map();
114
+ /** Native class fields with different property names cannot occupy the
115
+ * same slot of one object. Inherited views of the same property share a
116
+ * tag. Array storage has its own disjoint categories; untagged runtime
117
+ * operations and record views conservatively alias all of them. */
118
+ markFieldPointer(ptr, field) {
119
+ this.markMemoryPointer(ptr, `field:${field}`);
120
+ }
121
+ markMemoryPointer(ptr, field) {
122
+ if (this.debug !== null)
123
+ return;
124
+ let tag = this.fieldAliasTags.get(field);
125
+ if (tag === undefined) {
126
+ tag = 2 + this.fieldAliasTags.size * 2;
127
+ this.fieldAliasTags.set(field, tag);
128
+ }
129
+ this.fieldPointerTags.set(ptr, tag);
130
+ }
131
+ fieldAliasAttachment(ptr) {
132
+ const tag = this.fieldPointerTags.get(ptr);
133
+ return tag === undefined ? "" : `, !tbaa !${tag}`;
134
+ }
135
+ fieldAliasMetadata() {
136
+ if (this.fieldAliasTags.size === 0)
137
+ return [];
138
+ const lines = ['!0 = !{!"scriptc native fields"}'];
139
+ for (const tag of this.fieldAliasTags.values()) {
140
+ lines.push(`!${tag - 1} = !{!"field.${tag}", !0, i64 0}`, `!${tag} = !{!${tag - 1}, !${tag - 1}, i64 0}`);
141
+ }
142
+ return lines;
143
+ }
104
144
  debug;
105
145
  debugScope = null;
106
146
  sizeType;
@@ -149,6 +189,12 @@ export class LlEmitter {
149
189
  needsBadKey = false;
150
190
  needsRetainBox = false;
151
191
  fnByName = new Map();
192
+ arrayPreservingFunctions;
193
+ optionalArrayReads;
194
+ callArrayReads = new Map();
195
+ mapReadLifetimes = { locals: new Map(), arguments: new Map() };
196
+ callLifetimes;
197
+ borrowedParameters = new Set();
152
198
  /** Manifest-bound native imports, used by ffiCall emission. */
153
199
  ffiByName = new Map();
154
200
  /** C-ABI callback trampolines and (for raw/no-userdata callbacks) their
@@ -298,6 +344,9 @@ export class LlEmitter {
298
344
  }
299
345
  for (const u of mod.unions ?? [])
300
346
  this.unionsById.set(u.id, u);
347
+ this.optionalArrayReads = new OptionalArrayReads(this.fnByName, this.unionsById);
348
+ this.arrayPreservingFunctions = findArrayPreservingFunctions(this.fnByName, this.unionsById, this.optionalArrayReads);
349
+ this.callLifetimes = analyzeCallLifetimes(this.fnByName);
301
350
  for (const r of mod.records ?? [])
302
351
  this.recordsById.set(r.id, r);
303
352
  const traced = computeTraced(mod);
@@ -849,6 +898,7 @@ export class LlEmitter {
849
898
  this.declare(`declare void @scr_net_island_install()`);
850
899
  if (usesIsland) {
851
900
  this.declare(`declare void @scr_island_modules(ptr, ${this.sizeType}, ptr, ${this.sizeType})`);
901
+ this.declare(`declare void @scr_island_entry_module(ptr)`);
852
902
  this.declare(`declare i32 @scr_island_exit_code()`);
853
903
  if (moduleEmbedsCompressedNpm(this.mod)) {
854
904
  this.declare(`declare void @scr_island_set_inflate(ptr)`);
@@ -1116,6 +1166,7 @@ export class LlEmitter {
1116
1166
  }
1117
1167
  if (this.templateStringsInstances.size > 0)
1118
1168
  out.push(``);
1169
+ const islandEntryPath = usesIsland ? this.cstr(this.mod.sourceFile) : null;
1119
1170
  for (const [text, c] of this.cstrs) {
1120
1171
  // NUL-terminated byte-array constants for scr_jb_puts and indentation.
1121
1172
  out.push(`@${c.sym} = internal constant [${c.len + 1} x i8] c"${llStrBytes(text)}"`);
@@ -1247,6 +1298,7 @@ export class LlEmitter {
1247
1298
  if (hasNoInlineRecordClone)
1248
1299
  out.push(`attributes #2 = { noinline sanitize_address }`);
1249
1300
  out.push(``);
1301
+ out.push(...this.fieldAliasMetadata());
1250
1302
  if (this.debug !== null)
1251
1303
  out.push(this.debug.render());
1252
1304
  return out;
@@ -1260,6 +1312,7 @@ export class LlEmitter {
1260
1312
  // scr_watch.c links only when this line is emitted.
1261
1313
  ...(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
1314
  ? [
1315
+ ` call void @scr_island_entry_module(ptr ${islandEntryPath})`,
1263
1316
  ...(moduleEmbedsCompressedNpm(this.mod)
1264
1317
  ? [` call void @scr_island_set_inflate(ptr @scr_zlib_inflate_exact)`]
1265
1318
  : []),
@@ -1359,6 +1412,7 @@ export class LlEmitter {
1359
1412
  // lane's -fsanitize=address link activates instrumentation over the
1360
1413
  // emitted functions too (the runtime TUs get theirs from clang).
1361
1414
  `attributes #0 = { sanitize_address }`, ...(this.wasi ? [`attributes #1 = { sanitize_address presplitcoroutine }`] : []), ...(hasNoInlineRecordClone ? [`attributes #2 = { noinline sanitize_address }`] : []), ``);
1415
+ out.push(...this.fieldAliasMetadata());
1362
1416
  if (this.debug !== null)
1363
1417
  out.push(this.debug.render());
1364
1418
  return out;
@@ -2418,7 +2472,7 @@ export class LlEmitter {
2418
2472
  * a retained peek. */
2419
2473
  unionExtract(uName, arm) {
2420
2474
  const B = this.B;
2421
- if (arm.kind === "f64") {
2475
+ if (arm.kind === "f64" || arm.kind === "procStream") {
2422
2476
  const t = B.tmp();
2423
2477
  this.declare(`declare double @scr_union_get_f64(ptr)`);
2424
2478
  B.line(`${t} = call double @scr_union_get_f64(ptr ${uName})`);
@@ -2438,7 +2492,7 @@ export class LlEmitter {
2438
2492
  unionNewOwned(tag, v) {
2439
2493
  const B = this.B;
2440
2494
  const t = B.tmp();
2441
- if (v.type.kind === "f64") {
2495
+ if (v.type.kind === "f64" || v.type.kind === "procStream") {
2442
2496
  this.declare(`declare ptr @scr_union_new_f64(i32, double)`);
2443
2497
  B.line(`${t} = call ptr @scr_union_new_f64(i32 ${tag}, double ${v.name})`);
2444
2498
  return t;
@@ -2469,6 +2523,7 @@ export class LlEmitter {
2469
2523
  const { index, type } = classFieldIndex(meta, field);
2470
2524
  const p = this.B.tmp();
2471
2525
  this.B.line(`${p} = getelementptr inbounds %${classStructSym(className)}, ptr ${objName}, i64 0, i32 ${index}`);
2526
+ this.markFieldPointer(p, field);
2472
2527
  return { ptr: p, type };
2473
2528
  }
2474
2529
  /** `o->vt->pre` — the dynamic class's preorder number (borrowed object;
@@ -2559,7 +2614,7 @@ export class LlEmitter {
2559
2614
  const B = this.B;
2560
2615
  const fieldTy = llFieldType(t);
2561
2616
  const raw = B.tmp();
2562
- B.line(`${raw} = load ${fieldTy}, ptr ${ptr}`);
2617
+ B.line(`${raw} = load ${fieldTy}, ptr ${ptr}${this.fieldAliasAttachment(ptr)}`);
2563
2618
  if (fieldTy !== "i8")
2564
2619
  return raw;
2565
2620
  const b = B.tmp();
@@ -2571,12 +2626,12 @@ export class LlEmitter {
2571
2626
  const B = this.B;
2572
2627
  const fieldTy = llFieldType(t);
2573
2628
  if (fieldTy !== "i8") {
2574
- B.line(`store ${fieldTy} ${value}, ptr ${ptr}`);
2629
+ B.line(`store ${fieldTy} ${value}, ptr ${ptr}${this.fieldAliasAttachment(ptr)}`);
2575
2630
  return;
2576
2631
  }
2577
2632
  const z = B.tmp();
2578
2633
  B.line(`${z} = zext i1 ${value} to i8`);
2579
- B.line(`store i8 ${z}, ptr ${ptr}`);
2634
+ B.line(`store i8 ${z}, ptr ${ptr}${this.fieldAliasAttachment(ptr)}`);
2580
2635
  }
2581
2636
  // ── bindings ────────────────────────────────────────────────────────────
2582
2637
  /** A binding's storage: a module global, a plain local slot, or a boxed
@@ -2856,8 +2911,20 @@ export class LlEmitter {
2856
2911
  this.unwindCleanups.clear();
2857
2912
  this.jumpTargets = [];
2858
2913
  this.currentLocals = new Map(fn.locals.map((l) => [l.id, l]));
2914
+ this.borrowedParameters.clear();
2915
+ const borrowedParameterIndexes = this.callLifetimes.parameters.get(fn.name);
2916
+ if (borrowedParameterIndexes) {
2917
+ for (const index of borrowedParameterIndexes)
2918
+ this.borrowedParameters.add(fn.params[index].localId);
2919
+ }
2859
2920
  this.captureIds = new Set([...(fn.captures ?? []), ...(fn.classCaptures ?? [])].map((c) => c.localId));
2860
2921
  this.integerLoopBindings.clear();
2922
+ this.fieldPointerTags.clear();
2923
+ 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);
2926
+ this.mapReadLifetimes = findMapReadLifetimes(fn, this.unionsById, this.callLifetimes);
2927
+ this.localStackUnions = findLocalStackUnions(fn, this.callLifetimes, this.unionsById);
2861
2928
  this.integerRanges = analyzeIntegerRanges(fn);
2862
2929
  this.chainSlots.clear();
2863
2930
  this.finallyStack = [];
@@ -2949,7 +3016,8 @@ export class LlEmitter {
2949
3016
  // params (callees own their params — callers passed +1). Boxed params
2950
3017
  // allocate the shared binding and move the raw value in.
2951
3018
  const fnScope = [];
2952
- for (const p of fn.params) {
3019
+ const borrowed = this.callLifetimes.parameters.get(fn.name);
3020
+ for (const [index, p] of fn.params.entries()) {
2953
3021
  const local = this.currentLocals.get(p.localId);
2954
3022
  const slot = `%${mangleLocal(p.localId)}`;
2955
3023
  if (local.boxed) {
@@ -2961,7 +3029,7 @@ export class LlEmitter {
2961
3029
  continue;
2962
3030
  }
2963
3031
  B.line(`store ${this.llType(p.type)} %p_${mangleLocal(p.localId)}, ptr ${slot}`);
2964
- if (isRefCounted(p.type))
3032
+ if (isRefCounted(p.type) && !borrowed?.has(index))
2965
3033
  fnScope.push({ slot, type: p.type });
2966
3034
  }
2967
3035
  this.scopes.push(fnScope);
@@ -3043,7 +3111,38 @@ export class LlEmitter {
3043
3111
  const ret = this.llType(fn.returnType);
3044
3112
  const attrs = coro !== null ? "#1" : FN_ATTRS;
3045
3113
  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}`;
3114
+ const symbol = borrowed ? mangleBorrowedFunction(fn.name) : mangleFunction(fn.name);
3115
+ const body = `define internal ${ret} @${symbol}(${params.join(", ")}) ${attrs}${debug} { ; ${fn.name}\n${B.render()}\n}`;
3116
+ return borrowed ? body + "\n" + this.emitOwnedCallAdapter(fn, borrowed) : body;
3117
+ }
3118
+ /** Keep the ordinary owned ABI for closures, virtual dispatch, generated
3119
+ * runtime adapters and library exports. Only direct IR calls select the
3120
+ * borrowing body. Ownership of other parameters moves into that body;
3121
+ * borrowed parameters are released here on normal and exceptional exits.
3122
+ * The pending exception remains for the adapter's caller to handle. */
3123
+ emitOwnedCallAdapter(fn, borrowed) {
3124
+ const B = new BlockBuilder();
3125
+ this.B = B;
3126
+ const params = fn.params.map((param, index) => `${this.llType(param.type)} %p${index}`);
3127
+ const ret = this.llType(fn.returnType);
3128
+ const call = `call ${ret} @${mangleBorrowedFunction(fn.name)}(${params.join(", ")})`;
3129
+ B.line(ret === "void" ? call : `%result = ${call}`);
3130
+ for (const index of borrowed)
3131
+ this.releaseValue(`%p${index}`, fn.params[index].type);
3132
+ B.terminate(ret === "void" ? "ret void" : `ret ${ret} %result`);
3133
+ return `define internal ${ret} @${mangleFunction(fn.name)}(${params.join(", ")}) ${FN_ATTRS} {\n${B.render()}\n}`;
3134
+ }
3135
+ /** A local immutable binding keeps its value alive while later arguments
3136
+ * and the callee execute. Global, captured, TDZ and mutable bindings need
3137
+ * an owned snapshot, even when the callee only inspects the value. */
3138
+ canBorrowCallArgument(value) {
3139
+ if (value.kind === "strLit")
3140
+ return true;
3141
+ if (value.kind !== "varRef")
3142
+ return false;
3143
+ const binding = this.binding(value.localId);
3144
+ return binding.kind === "local" && binding.local !== undefined &&
3145
+ (!binding.local.mutable || this.borrowedParameters.has(value.localId)) && !binding.local.boxed && !binding.local.tdz;
3047
3146
  }
3048
3147
  // ── statements ──────────────────────────────────────────────────────────
3049
3148
  emitStmts(stmts) {
@@ -3087,6 +3186,31 @@ export class LlEmitter {
3087
3186
  switch (s.kind) {
3088
3187
  case "varDecl": {
3089
3188
  const b = this.binding(s.localId);
3189
+ const localUnion = this.localStackUnions.get(s.localId);
3190
+ if (localUnion) {
3191
+ const union = emitStackUnion(this, localUnion);
3192
+ B.line(`store ptr ${union.value.name}, ptr ${b.slot}`);
3193
+ if (union.payload) {
3194
+ this.moveTemp(union.payload);
3195
+ this.scopes[this.scopes.length - 1].push({ slot: union.payloadSlot, type: union.payload.type });
3196
+ }
3197
+ break;
3198
+ }
3199
+ const localRead = this.localArrayReads.get(s.localId);
3200
+ if (localRead) {
3201
+ const owner = emitLocalArrayRead(this, localRead, b.slot);
3202
+ if (owner)
3203
+ this.scopes[this.scopes.length - 1].push(owner);
3204
+ break;
3205
+ }
3206
+ const mapRead = this.mapReadLifetimes.locals.get(s.localId);
3207
+ if (mapRead) {
3208
+ const result = emitStackMapRead(this, mapRead);
3209
+ B.line(`store ptr ${result.value.name}, ptr ${b.slot}`);
3210
+ if (result.owner)
3211
+ this.scopes[this.scopes.length - 1].push(result.owner);
3212
+ break;
3213
+ }
3090
3214
  if (b.kind === "boxed") {
3091
3215
  // Box FIRST, then evaluate the initializer: a named function
3092
3216
  // expression's closure captures this box during init evaluation.
@@ -3225,7 +3349,7 @@ export class LlEmitter {
3225
3349
  // release — a release can trigger a cycle collection, which must
3226
3350
  // never see a heap edge whose count was already given up).
3227
3351
  // Classes and records share the struct layout, so one emission.
3228
- const obj = this.emitExpr(s.obj);
3352
+ const obj = isRefCounted(s.value.type) ? this.emitExpr(s.obj) : this.emitStableReceiver(s.obj, [s.value]);
3229
3353
  const v = this.emitExpr(s.value);
3230
3354
  const { ptr, type } = s.kind === "fieldSet"
3231
3355
  ? this.classFieldPtr(obj.name, s.className, s.field)
@@ -3486,13 +3610,21 @@ export class LlEmitter {
3486
3610
  case "for": {
3487
3611
  // The init's scope wraps the whole loop (break/continue must NOT
3488
3612
  // release it — scopeDepth captured after the push, C parity).
3489
- const integerLoop = this.debug === null ? matchIntegerBytesForLoop(s, this.currentLocals) : null;
3613
+ const integerArrayLoop = this.debug === null ? matchIntegerArrayForLoop(s, this.currentLocals, this.integerArrayBindings) : null;
3614
+ const integerLoop = integerArrayLoop ?? (this.debug === null ? matchIntegerBytesForLoop(s, this.currentLocals) : null);
3490
3615
  this.scopes.push([]);
3491
3616
  let integerSlot = null;
3492
3617
  if (integerLoop) {
3493
3618
  integerSlot = B.slot();
3494
3619
  B.entryAllocas.push(`${integerSlot} = alloca ${this.sizeType} ; integer induction ${this.currentLocals.get(integerLoop.localId).name}`);
3495
- B.line(`store ${this.sizeType} 0, ptr ${integerSlot}`);
3620
+ let start = "0";
3621
+ if (integerArrayLoop && s.init?.kind === "varDecl" && s.init.init) {
3622
+ const value = this.emitExpr(s.init.init);
3623
+ start = B.tmp();
3624
+ B.line(`${start} = fptoui double ${value.name} to ${this.sizeType}`);
3625
+ this.integerArrayBindings.add(integerLoop.localId);
3626
+ }
3627
+ B.line(`store ${this.sizeType} ${start}, ptr ${integerSlot}`);
3496
3628
  this.integerLoopBindings.set(integerLoop.localId, integerSlot);
3497
3629
  }
3498
3630
  else if (s.init) {
@@ -3540,8 +3672,10 @@ export class LlEmitter {
3540
3672
  const len = B.tmp();
3541
3673
  const index = B.tmp();
3542
3674
  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}`);
3675
+ B.line(`${lenPtr} = getelementptr inbounds %${integerArrayLoop ? "ScrArr" : "ScrBytes"}, ptr ${receiver.name}, i64 0, i32 1`);
3676
+ if (integerArrayLoop)
3677
+ this.markMemoryPointer(lenPtr, "array:header");
3678
+ B.line(`${len} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}`);
3545
3679
  B.line(`${index} = load ${this.sizeType}, ptr ${integerSlot}`);
3546
3680
  B.line(`${inBounds} = icmp ult ${this.sizeType} ${index}, ${len}`);
3547
3681
  B.condBr(inBounds, lb, le);
@@ -3581,6 +3715,8 @@ export class LlEmitter {
3581
3715
  this.releaseScope(this.scopes.pop());
3582
3716
  if (integerLoop)
3583
3717
  this.integerLoopBindings.delete(integerLoop.localId);
3718
+ if (integerArrayLoop)
3719
+ this.integerArrayBindings.delete(integerArrayLoop.localId);
3584
3720
  break;
3585
3721
  }
3586
3722
  case "forOf": {
@@ -4083,7 +4219,7 @@ export class LlEmitter {
4083
4219
  // to this expression's frame so they survive those reads and still
4084
4220
  // die on the path that created them, including inside lazy branches.
4085
4221
  this.scopes.push([]);
4086
- const result = emitOperatorExpr(this, e);
4222
+ const result = emitBorrowedFieldSequence(this, e) ?? emitOperatorExpr(this, e);
4087
4223
  for (const local of this.scopes.pop()) {
4088
4224
  this.currentFrame().push({ name: local.slot, type: local.type, slot: true, ...(local.boxed ? { boxed: true } : {}) });
4089
4225
  }
@@ -4170,6 +4306,8 @@ export class LlEmitter {
4170
4306
  emitReadReceiver(e) {
4171
4307
  if (!isRefCounted(e.type))
4172
4308
  return this.emitExpr(e);
4309
+ if (e.kind === "strLit")
4310
+ return { name: this.internLiteral(e.value), type: e.type };
4173
4311
  if (e.kind === "varRef") {
4174
4312
  const binding = this.binding(e.localId);
4175
4313
  // Capture boxes can carry TDZ and caught-value conversion semantics.
@@ -4192,8 +4330,43 @@ export class LlEmitter {
4192
4330
  : this.classFieldPtr(receiver.name, e.className, e.field);
4193
4331
  return { name: this.loadField(ptr, type), type: e.type };
4194
4332
  }
4333
+ if (e.kind === "ternary" && this.canBorrowReceiver(e.then) && this.canBorrowReceiver(e.else_)) {
4334
+ // Checked projections lower to a ternary with an always-throwing
4335
+ // arm. Keep that check at the use, but borrow the successful arm.
4336
+ const B = this.B;
4337
+ const cond = this.emitExpr(e.cond);
4338
+ const slot = B.slot();
4339
+ B.entryAllocas.push(`${slot} = alloca ptr`);
4340
+ const yes = B.newLabel("read.then"), no = B.newLabel("read.else"), join = B.newLabel("read.join");
4341
+ B.condBr(cond.name, yes, no);
4342
+ for (const [label, value] of [[yes, e.then], [no, e.else_]]) {
4343
+ B.startBlock(label);
4344
+ this.frames.push([]);
4345
+ const result = this.emitReadReceiver(value);
4346
+ B.line(`store ptr ${result.name}, ptr ${slot}`);
4347
+ this.releaseFrame(this.frames.pop());
4348
+ B.br(join);
4349
+ }
4350
+ B.startBlock(join);
4351
+ const value = B.tmp();
4352
+ B.line(`${value} = load ptr, ptr ${slot}`);
4353
+ return { name: value, type: e.type };
4354
+ }
4195
4355
  return this.emitExpr(e);
4196
4356
  }
4357
+ canBorrowReceiver(e) {
4358
+ switch (e.kind) {
4359
+ case "varRef": return this.binding(e.localId).kind !== "boxed";
4360
+ case "unionNarrow": return this.canBorrowReceiver(e.value);
4361
+ case "fieldGet":
4362
+ case "recordGet": return this.canBorrowReceiver(e.obj);
4363
+ case "ternary": return this.canBorrowReceiver(e.then) && this.canBorrowReceiver(e.else_);
4364
+ // The runtime always sets a pending exception; its unreachable
4365
+ // typed dummy is null and owns no receiver.
4366
+ case "libCall": return e.fn === "error.nodeThrow";
4367
+ default: return false;
4368
+ }
4369
+ }
4197
4370
  emitExpr(e) {
4198
4371
  return emitExpr(this, e);
4199
4372
  }