@scriptc/compiler 0.0.33 → 0.0.34

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 (102) hide show
  1. package/dist/backend/cc-cache.test.js +122 -0
  2. package/dist/backend/cc-cache.test.js.map +1 -1
  3. package/dist/backend/cc.d.ts +19 -0
  4. package/dist/backend/cc.js +111 -20
  5. package/dist/backend/cc.js.map +1 -1
  6. package/dist/backend/emission/emit-exprs.js +26 -1
  7. package/dist/backend/emission/emit-exprs.js.map +1 -1
  8. package/dist/backend/emission/emit-shapes.js +37 -4
  9. package/dist/backend/emission/emit-shapes.js.map +1 -1
  10. package/dist/backend/emission/emitter.d.ts +11 -2
  11. package/dist/backend/emission/emitter.js +11 -5
  12. package/dist/backend/emission/emitter.js.map +1 -1
  13. package/dist/backend/library-identity.d.ts +22 -0
  14. package/dist/backend/library-identity.js +103 -0
  15. package/dist/backend/library-identity.js.map +1 -0
  16. package/dist/backend/llvm/emitter.d.ts +3 -0
  17. package/dist/backend/llvm/emitter.js +35 -5
  18. package/dist/backend/llvm/emitter.js.map +1 -1
  19. package/dist/backend/llvm/shapes.d.ts +1 -0
  20. package/dist/backend/llvm/shapes.js +25 -1
  21. package/dist/backend/llvm/shapes.js.map +1 -1
  22. package/dist/backend/mangle.d.ts +1 -0
  23. package/dist/backend/mangle.js +3 -0
  24. package/dist/backend/mangle.js.map +1 -1
  25. package/dist/backend/object-localize.d.ts +5 -5
  26. package/dist/backend/object-localize.js +19 -16
  27. package/dist/backend/object-localize.js.map +1 -1
  28. package/dist/backend/object-localize.test.js +37 -13
  29. package/dist/backend/object-localize.test.js.map +1 -1
  30. package/dist/frontend/input-tracker.d.ts +82 -0
  31. package/dist/frontend/input-tracker.js +380 -0
  32. package/dist/frontend/input-tracker.js.map +1 -0
  33. package/dist/frontend/input-tracker.test.d.ts +1 -0
  34. package/dist/frontend/input-tracker.test.js +137 -0
  35. package/dist/frontend/input-tracker.test.js.map +1 -0
  36. package/dist/frontend/lowering/lower-builtins.js +2 -8
  37. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  38. package/dist/frontend/lowering/lower-calls.js +155 -140
  39. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  40. package/dist/frontend/lowering/lower-classes.js +3 -0
  41. package/dist/frontend/lowering/lower-classes.js.map +1 -1
  42. package/dist/frontend/lowering/lower-containers.js +48 -9
  43. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  44. package/dist/frontend/lowering/lower-exprs.js +67 -1
  45. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  46. package/dist/frontend/lowering/lower-inspect.js +45 -26
  47. package/dist/frontend/lowering/lower-inspect.js.map +1 -1
  48. package/dist/frontend/lowering/lower-modules.js +1 -1
  49. package/dist/frontend/lowering/lower-modules.js.map +1 -1
  50. package/dist/frontend/lowering/lower-stmts.js +48 -3
  51. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  52. package/dist/frontend/lowering/lowerer.d.ts +67 -29
  53. package/dist/frontend/lowering/lowerer.js +410 -87
  54. package/dist/frontend/lowering/lowerer.js.map +1 -1
  55. package/dist/frontend/npm-static-rewrite.js +25 -26
  56. package/dist/frontend/npm-static-rewrite.js.map +1 -1
  57. package/dist/frontend/npm-static.js +29 -30
  58. package/dist/frontend/npm-static.js.map +1 -1
  59. package/dist/frontend/npm-static.test.d.ts +1 -0
  60. package/dist/frontend/npm-static.test.js +15 -0
  61. package/dist/frontend/npm-static.test.js.map +1 -0
  62. package/dist/frontend/npm.js +5 -17
  63. package/dist/frontend/npm.js.map +1 -1
  64. package/dist/frontend/program.js +2 -6
  65. package/dist/frontend/program.js.map +1 -1
  66. package/dist/frontend/resolve.js +8 -29
  67. package/dist/frontend/resolve.js.map +1 -1
  68. package/dist/frontend/ts7/program.js +17 -18
  69. package/dist/frontend/ts7/program.js.map +1 -1
  70. package/dist/frontend/types.d.ts +33 -0
  71. package/dist/frontend/types.js +114 -3
  72. package/dist/frontend/types.js.map +1 -1
  73. package/dist/index.d.ts +1 -1
  74. package/dist/index.js +241 -42
  75. package/dist/index.js.map +1 -1
  76. package/dist/ir/nodes.d.ts +21 -4
  77. package/dist/ir/nodes.js.map +1 -1
  78. package/dist/ir/serialize.d.ts +1 -1
  79. package/dist/ir/serialize.js +1 -1
  80. package/dist/ir/validate.js +31 -1
  81. package/dist/ir/validate.js.map +1 -1
  82. package/dist/library/early-cache.d.ts +65 -0
  83. package/dist/library/early-cache.js +380 -0
  84. package/dist/library/early-cache.js.map +1 -0
  85. package/dist/library/early-cache.test.d.ts +1 -0
  86. package/dist/library/early-cache.test.js +528 -0
  87. package/dist/library/early-cache.test.js.map +1 -0
  88. package/dist/library/int-infer.js +14 -0
  89. package/dist/library/int-infer.js.map +1 -1
  90. package/dist/library/int-infer.test.js +21 -4
  91. package/dist/library/int-infer.test.js.map +1 -1
  92. package/dist/library/semantic-source.d.ts +13 -0
  93. package/dist/library/semantic-source.js +257 -0
  94. package/dist/library/semantic-source.js.map +1 -0
  95. package/dist/library/semantic-source.test.d.ts +1 -0
  96. package/dist/library/semantic-source.test.js +113 -0
  97. package/dist/library/semantic-source.test.js.map +1 -0
  98. package/dist/library/sidecar.d.ts +6 -0
  99. package/dist/library/sidecar.js +11 -0
  100. package/dist/library/sidecar.js.map +1 -1
  101. package/package.json +2 -2
  102. package/surface-manifest.json +1 -1
@@ -139,26 +139,39 @@ function directExternalTypeSpecifiersByFile(externalTypes) {
139
139
  }
140
140
  return out;
141
141
  }
142
- /** Build lowering runs in two passes over the same ts.Program:
142
+ /** Build lowering runs as a reachability worklist over the ts.Program:
143
143
  *
144
- * 1. DISCOVERY — a worklist computes the set of reachable bodies. Seeds are
144
+ * 1. REACHABLE EMIT — a worklist computes the set of reachable bodies.
145
+ * Seeds are
145
146
  * the per-file init bodies (module top-level statements always run, in
146
147
  * import order); lowering a body yields IR whose call/closure/new/
147
- * virtualCall nodes are the edges that enqueue further bodies. The
148
- * pass's IR, diagnostics, and stats are discarded — it exists only to
149
- * answer "which bodies does the entry reach?".
150
- * 2. EMIT — a fresh Lowerer lowers in the HISTORICAL order (per file:
151
- * function declarations, then class members; then file inits, %main,
152
- * generic instances, lifted lambdas), skipping bodies the discovery
153
- * pass did not mark. Keeping the emit order (and lambda/instance
154
- * numbering) identical to the pre-reachability compiler means a fully
155
- * reachable program emits byte-identical C.
148
+ * virtualCall nodes are the edges that enqueue further bodies. The pass
149
+ * retains that IR. Once the graph closes, those functions are assembled in
150
+ * deterministic declaration order beside the already-lowered init,
151
+ * generic-instance, and lifted bodies. Checker-backed IR construction
152
+ * therefore happens once instead of once for discovery and again for
153
+ * emission.
156
154
  *
157
- * `coverage: true` adds a third pass — the REMAINDER — that lowers only
158
- * the bodies discovery did NOT mark (plus deferred collection diagnostics
159
- * nothing flushed), reported separately: whole-program analysis without
155
+ * `coverage: true` adds a second pass — the REMAINDER — that lowers only
156
+ * the bodies reachable emit did NOT mark (plus deferred collection
157
+ * diagnostics nothing flushed), reported separately: whole-program analysis without
160
158
  * letting unreached code fail builds. */
161
159
  export function lowerToIr(program, entry, moduleOrder, options = {}) {
160
+ const phaseTiming = process.env["SCRIPTC_TIMING"] === "1";
161
+ const phaseStarted = performance.now();
162
+ let phaseLast = phaseStarted;
163
+ const timing = (phase, detail = {}) => {
164
+ if (!phaseTiming)
165
+ return;
166
+ const now = performance.now();
167
+ process.stderr.write(`scriptc lowering ${JSON.stringify({
168
+ phase,
169
+ phase_ms: Math.round((now - phaseLast) * 10) / 10,
170
+ total_ms: Math.round((now - phaseStarted) * 10) / 10,
171
+ ...detail,
172
+ })}\n`);
173
+ phaseLast = now;
174
+ };
162
175
  const dynamic = options.dynamic ?? false;
163
176
  const targetPlatform = options.targetPlatform ?? process.platform;
164
177
  const startupCrash = options.startupCrash ?? null;
@@ -167,9 +180,8 @@ export function lowerToIr(program, entry, moduleOrder, options = {}) {
167
180
  // pass constructs (nothing calls their %init at startup — the import()
168
181
  // site's namespace builder does, on the engine microtask, Node's
169
182
  // evaluation point for them). Inadmissible static cycles inside the
170
- // added subgraph are minted here and handed to the EMIT pass: the
171
- // discovery pass's diagnostics are discarded by design, and after this
172
- // extension of the shared array no later pass re-walks the subgraph.
183
+ // added subgraph are minted here and handed to reachable emit after this
184
+ // extension of the shared array; no later pass re-walks the subgraph.
173
185
  const dynamicCycleDiags = [];
174
186
  if (dynamic) {
175
187
  appendDynamicImportModules(program, moduleOrder, (cycle, reason) => {
@@ -183,50 +195,71 @@ export function lowerToIr(program, entry, moduleOrder, options = {}) {
183
195
  directExternalTypeSpecifiersByFile(externalTypes);
184
196
  const validation = new Lowerer(program, entry, moduleOrder, dynamic, {
185
197
  targetPlatform,
198
+ startupCrash,
186
199
  ffiImports,
187
200
  libraryCallbacks,
188
201
  externalTypes,
189
202
  externalTypeSpecifiersByFile,
190
203
  });
191
204
  const ffiValidation = validateFfiImports(validation);
192
- // Discovery must use the same exact-symbol ownership as emit. Otherwise a
193
- // local function shadowing a configured ambient name is mistaken for FFI
194
- // while computing reachability, even though emit would correctly lower it
195
- // as ordinary TypeScript. FFI-free builds reuse the validation lowerer
196
- // (validation is an immediate no-op there), retaining the historical
197
- // two-pass construction cost.
198
- const discovery = ffiImports.length === 0
205
+ timing("ffi-validate");
206
+ // Reachability must use the same exact-symbol ownership as FFI validation.
207
+ // Otherwise a local function shadowing a configured ambient name is mistaken for FFI
208
+ // while computing reachability, even though ordinary lowering would
209
+ // correctly handle it as TypeScript. FFI-free builds reuse the validation
210
+ // lowerer because validation is an immediate no-op there.
211
+ const reachableEmit = ffiImports.length === 0
199
212
  ? validation
200
213
  : new Lowerer(program, entry, moduleOrder, dynamic, {
201
214
  targetPlatform,
215
+ startupCrash,
202
216
  ffiImports,
203
217
  libraryCallbacks,
204
218
  externalTypes,
205
219
  externalTypeSpecifiersByFile,
206
220
  ffiBindingSymbols: ffiValidation.symbolsByName,
207
221
  });
208
- const reachable = discovery.discover(options.libRoots);
209
- const emit = new Lowerer(program, entry, moduleOrder, dynamic, {
210
- reachable,
211
- targetPlatform,
212
- startupCrash,
213
- ffiImports,
214
- libraryCallbacks,
215
- ffiBindingSymbols: ffiValidation.symbolsByName,
216
- externalTypes,
217
- externalTypeSpecifiersByFile,
218
- });
219
222
  for (const d of dynamicCycleDiags)
220
- emit.pushDiag(d);
223
+ reachableEmit.pushDiag(d);
221
224
  for (const d of ffiValidation.diagnostics)
222
- emit.pushDiag(d);
223
- const result = emit.run();
225
+ reachableEmit.pushDiag(d);
226
+ const emitted = reachableEmit.emitReachable(options.libRoots);
227
+ const { reachable } = emitted;
228
+ let result = emitted.result;
229
+ let resultLowerer = reachableEmit;
230
+ timing("reachable-emit", { reachable: reachable.size });
231
+ // A generic class-rest support decision can depend on record metadata
232
+ // whose historical owner is discovered only while retained bodies lower.
233
+ // If the settled answer is a fence, rerun the ordinary reachable emit so
234
+ // the PoisonError occurs in its original statement window: later
235
+ // declarators stay unvisited, bindings block, cascades and stats match the
236
+ // historical compiler. This is a rare compatibility fallback; programs
237
+ // without such a settled fence retain checker-backed IR exactly once.
238
+ if (reachableEmit.requiresHistoricalOrderRelower) {
239
+ const emit = new Lowerer(program, entry, moduleOrder, dynamic, {
240
+ reachable,
241
+ targetPlatform,
242
+ startupCrash,
243
+ ffiImports,
244
+ libraryCallbacks,
245
+ ffiBindingSymbols: ffiValidation.symbolsByName,
246
+ externalTypes,
247
+ externalTypeSpecifiersByFile,
248
+ });
249
+ for (const d of dynamicCycleDiags)
250
+ emit.pushDiag(d);
251
+ for (const d of ffiValidation.diagnostics)
252
+ emit.pushDiag(d);
253
+ result = emit.run();
254
+ resultLowerer = emit;
255
+ timing("historical-order-relower");
256
+ }
224
257
  if (options.coverage !== true)
225
258
  return result;
226
259
  const remainder = new Lowerer(program, entry, moduleOrder, dynamic, {
227
260
  reachable,
228
261
  remainder: true,
229
- alreadyFlushed: emit.flushedSymbols,
262
+ alreadyFlushed: resultLowerer.flushedSymbols,
230
263
  targetPlatform,
231
264
  ffiImports,
232
265
  libraryCallbacks,
@@ -602,6 +635,155 @@ export class Lowerer {
602
635
  /** Monomorphization worklist: instances queued by call sites, drained in
603
636
  * run() (processing an instance body can queue more). */
604
637
  instantiationQueue = [];
638
+ /** Historical emit rank of the retained declaration/init body currently
639
+ * lowering, and the earliest such owner that demanded each generic
640
+ * instance. Reachability can encounter a later caller first; the minimum
641
+ * rank recovers the old emitter's source-order monomorphization queue. */
642
+ genericDemandOwner = null;
643
+ genericDemandRoots = [];
644
+ genericFunctionDemandOwner = new Map();
645
+ genericClassDemandOwner = new Map();
646
+ genericDemandPriority = new Map();
647
+ genericClassDemandPriority = new Map();
648
+ withGenericDemandOwner(owner, fn) {
649
+ const previous = this.genericDemandOwner;
650
+ this.genericDemandOwner = owner;
651
+ try {
652
+ return fn();
653
+ }
654
+ finally {
655
+ this.genericDemandOwner = previous;
656
+ }
657
+ }
658
+ noteGenericInstanceDemand(inst) {
659
+ const owner = this.genericDemandOwner;
660
+ owner?.functionDemands.push(inst);
661
+ const ref = this.genericDemandPriority.get(inst) ?? { rank: [4, Number.MAX_SAFE_INTEGER] };
662
+ const currentRank = owner?.priority;
663
+ if (currentRank && (currentRank[0] < ref.rank[0] ||
664
+ (currentRank[0] === ref.rank[0] && currentRank[1] < (ref.rank[1] ?? 0)))) {
665
+ ref.rank = currentRank;
666
+ }
667
+ this.genericDemandPriority.set(inst, ref);
668
+ }
669
+ noteGenericClassInstanceDemand(info) {
670
+ const owner = this.genericDemandOwner;
671
+ owner?.classDemands.push(info);
672
+ const ref = this.genericClassDemandPriority.get(info) ?? { rank: [4, Number.MAX_SAFE_INTEGER] };
673
+ const currentRank = owner?.priority;
674
+ if (currentRank && (currentRank[0] < ref.rank[0] ||
675
+ (currentRank[0] === ref.rank[0] && currentRank[1] < (ref.rank[1] ?? 0)))) {
676
+ ref.rank = currentRank;
677
+ }
678
+ this.genericClassDemandPriority.set(info, ref);
679
+ }
680
+ /** The old emitter lowered all reachable declarations in source order,
681
+ * then every init, before draining generic instances FIFO. Reorder the
682
+ * retained queue from those recorded demands before any generic body
683
+ * lowers, so immediate support decisions see the same shape metadata too. */
684
+ restoreGenericInstanceOrder(from = 0) {
685
+ const tail = this.instantiationQueue.slice(from);
686
+ const discoveryOrder = new Map(tail.map((entry, index) => [entry.inst, index]));
687
+ tail.sort((left, right) => {
688
+ const a = this.genericDemandPriority.get(left.inst)?.rank;
689
+ const b = this.genericDemandPriority.get(right.inst)?.rank;
690
+ if (a !== undefined && b !== undefined) {
691
+ const phase = a[0] - b[0];
692
+ if (phase !== 0)
693
+ return phase;
694
+ const order = a[1] - b[1];
695
+ if (order !== 0)
696
+ return order;
697
+ }
698
+ else if (a !== undefined) {
699
+ return -1;
700
+ }
701
+ else if (b !== undefined) {
702
+ return 1;
703
+ }
704
+ return discoveryOrder.get(left.inst) - discoveryOrder.get(right.inst);
705
+ });
706
+ this.instantiationQueue.splice(from, tail.length, ...tail);
707
+ }
708
+ restoreGenericClassInstanceOrder(from = 0) {
709
+ const tail = this.genericClassInstances.slice(from);
710
+ const discoveryOrder = new Map(tail.map((info, index) => [info, index]));
711
+ tail.sort((left, right) => {
712
+ const a = this.genericClassDemandPriority.get(left)?.rank;
713
+ const b = this.genericClassDemandPriority.get(right)?.rank;
714
+ if (a !== undefined && b !== undefined) {
715
+ const phase = a[0] - b[0];
716
+ if (phase !== 0)
717
+ return phase;
718
+ const order = a[1] - b[1];
719
+ if (order !== 0)
720
+ return order;
721
+ }
722
+ else if (a !== undefined) {
723
+ return -1;
724
+ }
725
+ else if (b !== undefined) {
726
+ return 1;
727
+ }
728
+ return discoveryOrder.get(left) - discoveryOrder.get(right);
729
+ });
730
+ this.genericClassInstances.splice(from, tail.length, ...tail);
731
+ }
732
+ settleGenericDemandPriorities() {
733
+ const functionQueue = [];
734
+ const classQueue = [];
735
+ const seenFunctions = new Set();
736
+ const seenClasses = new Set();
737
+ const enqueue = (owner) => {
738
+ for (const info of owner.classDemands) {
739
+ if (seenClasses.has(info))
740
+ continue;
741
+ seenClasses.add(info);
742
+ classQueue.push(info);
743
+ }
744
+ for (const inst of owner.functionDemands) {
745
+ if (seenFunctions.has(inst))
746
+ continue;
747
+ seenFunctions.add(inst);
748
+ functionQueue.push(inst);
749
+ }
750
+ };
751
+ for (const root of [...this.genericDemandRoots].sort((a, b) => {
752
+ const left = a.priority;
753
+ const right = b.priority;
754
+ return left[0] - right[0] || left[1] - right[1];
755
+ }))
756
+ enqueue(root);
757
+ let classIndex = 0;
758
+ let functionIndex = 0;
759
+ let order = 0;
760
+ while (classIndex < classQueue.length || functionIndex < functionQueue.length) {
761
+ while (classIndex < classQueue.length) {
762
+ const info = classQueue[classIndex++];
763
+ this.genericClassDemandPriority.get(info).rank = [4, order++];
764
+ const owner = this.genericClassDemandOwner.get(info);
765
+ if (owner)
766
+ enqueue(owner);
767
+ }
768
+ while (functionIndex < functionQueue.length) {
769
+ const inst = functionQueue[functionIndex++];
770
+ this.genericDemandPriority.get(inst).rank = [4, order++];
771
+ const owner = this.genericFunctionDemandOwner.get(inst);
772
+ if (owner)
773
+ enqueue(owner);
774
+ }
775
+ }
776
+ for (const info of this.genericClassInstances) {
777
+ const ref = this.genericClassDemandPriority.get(info);
778
+ if (ref && ref.rank[1] === Number.MAX_SAFE_INTEGER)
779
+ ref.rank = [4, order++];
780
+ }
781
+ for (const { inst } of this.instantiationQueue) {
782
+ const ref = this.genericDemandPriority.get(inst);
783
+ if (ref && ref.rank[1] === Number.MAX_SAFE_INTEGER)
784
+ ref.rank = [4, order++];
785
+ }
786
+ }
605
787
  /** Non-null while an instance body lowers: type-parameter symbol →
606
788
  * concrete IR type, consulted inside mapType's recursion. */
607
789
  typeParamBindings = null;
@@ -671,6 +853,16 @@ export class Lowerer {
671
853
  /** Synthetic array-HOF loop functions (map/filter/forEach desugar),
672
854
  * interned per method + element/callback-result type: key → fn name. */
673
855
  arrHofHelpers = new Map();
856
+ /** Derived shape metadata that depends on another shape's declaration
857
+ * order. These settle before helper bodies rebuild from that metadata. */
858
+ shapeOrderMetadataFinalizers = [];
859
+ /** Settled generic class-rest metadata requires the historical emit
860
+ * fallback so its fence can poison the original statement atomically. */
861
+ requiresHistoricalOrderRelower = false;
862
+ /** Helpers that snapshot shape declaration order into their bodies.
863
+ * Reachability lowers inits before the declarations they discover, so
864
+ * these rebuild after the worklist restores historical shape metadata. */
865
+ shapeOrderHelperFinalizers = [];
674
866
  /** Emit-override specializations (`%C.emit:<event>` — lower-emitter.ts's
675
867
  * emit-overrides block): interned names, the drive-loop queue, and the
676
868
  * currently-lowering specialization's context (the super-forward
@@ -900,7 +1092,7 @@ export class Lowerer {
900
1092
  statementsIsland: 0,
901
1093
  functionsSkipped: 0,
902
1094
  };
903
- /** Discovery-pass edge sink (null in the emit pass): every resolution of
1095
+ /** Reachability edge sink: every resolution of
904
1096
  * a reference to a lowerable body reports its name here — recorded even
905
1097
  * when the enclosing statement later poisons. */
906
1098
  onEdge = null;
@@ -944,9 +1136,12 @@ export class Lowerer {
944
1136
  get scopes() {
945
1137
  return this.ctx.scopes;
946
1138
  }
947
- /** Names of bodies the discovery pass reached; null lowers everything
948
- * (the discovery pass itself). */
1139
+ /** Names of bodies a prior reachability pass reached; null lowers everything. */
949
1140
  reachable;
1141
+ /** Reachability computed by this same Lowerer when retained worklist IR
1142
+ * is assembled directly. The configured reachable set remains null so
1143
+ * demand-driven instance lowering keeps its existing gates. */
1144
+ reachableForArtifacts = null;
950
1145
  /** Coverage remainder mode: the reachability gate inverts (see wantBody)
951
1146
  * and no module is built. */
952
1147
  remainder;
@@ -1061,9 +1256,7 @@ export class Lowerer {
1061
1256
  // --dynamic: modules reachable only through dynamic import() joined
1062
1257
  // moduleOrder BEFORE any pass constructed — lowerToIr runs
1063
1258
  // appendDynamicImportModules once on the shared array (a per-pass run
1064
- // here minted cycle refusals into the DISCOVERY pass, whose
1065
- // diagnostics are discarded by design, and the extended order left
1066
- // nothing for the emit pass to re-detect).
1259
+ // here would repeatedly extend the graph and duplicate cycle reports).
1067
1260
  this.moduleOrder.forEach((sf, i) => {
1068
1261
  this.fileTag.set(sf, sf === entry ? "" : `%m${i}.`);
1069
1262
  });
@@ -1094,7 +1287,7 @@ export class Lowerer {
1094
1287
  * namespace path (nsPathPrefix), so `namespace A { export class C }`
1095
1288
  * and a top-level `class C` never collide. Class EXPRESSIONS name by
1096
1289
  * SOURCE POSITION (`%cx<start>.<name>`): deterministic across the
1097
- * discovery and emit passes (no counter can drift between them),
1290
+ * builds (no counter can drift between invocations),
1098
1291
  * program-unique through the file qualifier, and collision-free with
1099
1292
  * user identifiers ('%'). */
1100
1293
  classNamer = (decl) => ts.isClassExpression(decl)
@@ -1776,8 +1969,8 @@ export class Lowerer {
1776
1969
  loc,
1777
1970
  };
1778
1971
  }
1779
- /** True when this body should lower: everything with no reachable set
1780
- * (discovery), the marked bodies in the emit pass, and exactly the
1972
+ /** True when this body should lower: everything with no reachable set,
1973
+ * the marked bodies in an externally-gated pass, and exactly the
1781
1974
  * UNMARKED bodies in the coverage remainder. */
1782
1975
  wantBody(name) {
1783
1976
  if (this.reachable === null)
@@ -1842,7 +2035,7 @@ export class Lowerer {
1842
2035
  // inline require statements call theirs mid-body, and the guards make
1843
2036
  // revisits cache hits — Node's evaluation order over the WHOLE graph
1844
2037
  // falls out of the nesting. The coverage remainder skips them — they
1845
- // are reachable by definition, already counted by the emit pass.
2038
+ // are reachable by definition, already counted by reachable emit.
1846
2039
  if (!this.remainder) {
1847
2040
  for (const fp of parts) {
1848
2041
  functions.push(this.lowerFileInit(fp.sf, fp.topStmts, this.initNameOf.get(fp.sf)));
@@ -1931,6 +2124,11 @@ export class Lowerer {
1931
2124
  ...(this.npmLazyTraps ? { npmLazyTraps: this.npmLazyTraps } : {}),
1932
2125
  };
1933
2126
  }
2127
+ return this.finishModule(functions);
2128
+ }
2129
+ /** Final retention, pruning, and module assembly shared by ordinary emit
2130
+ * and the retained reachability worklist. */
2131
+ finishModule(functions) {
1934
2132
  // Globals typed by a class that never REGISTERED (a JS class whose
1935
2133
  // collection fenced — Symbol-keyed fields, an unsupported base): the
1936
2134
  // declaration statement and every use compiled to runtime fences, but
@@ -2016,7 +2214,7 @@ export class Lowerer {
2016
2214
  const module = this.diags.length > 0
2017
2215
  ? null
2018
2216
  : {
2019
- irVersion: 5,
2217
+ irVersion: 6,
2020
2218
  sourceFile: this.entry.fileName,
2021
2219
  functions,
2022
2220
  classes: artifacts.classes,
@@ -2120,15 +2318,16 @@ export class Lowerer {
2120
2318
  }
2121
2319
  /** Whether run() counts a signature-blocked declaration in
2122
2320
  * stats.functionsSkipped: whole-program passes and the coverage
2123
- * remainder do; the reachability emit pass leaves the counting to the
2321
+ * remainder do; an externally-gated emit pass leaves the counting to the
2124
2322
  * remainder (the declaration was never reached). */
2125
2323
  countsSkips() {
2126
2324
  return this.reachable === null || this.remainder;
2127
2325
  }
2128
2326
  /* ── reachability ─────────────────────────────────────────────────── */
2129
- /** The discovery pass: computes the set of body names the program's entry
2130
- * reaches. Seeds are the per-file init bodies (top-level statements always
2131
- * run); edges fire from RESOLUTION sites while a body lowers (noteEdge /
2327
+ /** Reachable emit: computes the set of body names the program's entry
2328
+ * reaches and retains each body IR as it lowers. Seeds are the per-file
2329
+ * init bodies (top-level statements always run); edges fire from
2330
+ * RESOLUTION sites while a body lowers (noteEdge /
2132
2331
  * noteVirtualEdge) — direct calls, closure creation (a taken closure may
2133
2332
  * be called indirectly), `new`, super calls, accessor invocations, and
2134
2333
  * virtual dispatch. Recording at resolution time (not off the produced
@@ -2138,19 +2337,20 @@ export class Lowerer {
2138
2337
  * demand-driven) and lifted lambdas lower inline with their enclosing
2139
2338
  * body; both fire edges through the same hooks and are not units
2140
2339
  * themselves. */
2141
- discover(extraRoots) {
2340
+ emitReachable(extraRoots) {
2142
2341
  const parts = this.splitFiles();
2143
2342
  this.collectProgram(parts);
2144
2343
  // Decorated classes analyze post-collection here too: the %init seeds
2145
2344
  // lower the decoration calls, whose edges (decorator bodies, construct
2146
- // thunks) the emit pass must see.
2345
+ // thunks) reachable emit must see.
2147
2346
  for (const info of this.classes.values())
2148
2347
  analyzeClassDecoration(this, info);
2149
2348
  this.prepareModuleInits(parts);
2150
2349
  // Every lowerable body, by emitted-function name. The names double as
2151
- // the reachable-set keys the emit pass gates on — deterministic across
2152
- // Lowerer instances by construction (qualified declaration names).
2350
+ // retained-function keys and are deterministic by construction
2351
+ // (qualified declaration names).
2153
2352
  const units = new Map();
2353
+ let unitOrder = 0;
2154
2354
  for (const fp of parts) {
2155
2355
  for (const decl of fp.fnDecls) {
2156
2356
  // Overload signatures share the implementation's symbol (and so
@@ -2163,7 +2363,7 @@ export class Lowerer {
2163
2363
  continue;
2164
2364
  const sig = this.fnSigsBySymbol.get(declSymbol);
2165
2365
  if (sig)
2166
- units.set(sig.name, () => this.lowerFunction(decl));
2366
+ units.set(sig.name, { order: unitOrder++, lower: () => this.lowerFunction(decl) });
2167
2367
  }
2168
2368
  }
2169
2369
  for (const info of this.classes.values()) {
@@ -2178,20 +2378,77 @@ export class Lowerer {
2178
2378
  // A FAMILY has no constructor function and no instance members —
2179
2379
  // only its statics are units.
2180
2380
  if (!info.generic) {
2181
- units.set(`%${cName}.constructor`, () => this.lowerClassCtor(info));
2381
+ units.set(`%${cName}.constructor`, { order: unitOrder++, lower: () => this.lowerClassCtor(info) });
2182
2382
  for (const { mName, member } of this.classMethodMembers(info)) {
2183
- units.set(`%${cName}.${mName}`, () => this.lowerClassMethodMember(info, member));
2383
+ units.set(`%${cName}.${mName}`, { order: unitOrder++, lower: () => this.lowerClassMethodMember(info, member) });
2184
2384
  }
2185
2385
  for (const prop of info.throwingSetters) {
2186
- units.set(`%${cName}.set:${prop}`, () => this.throwingSetterFn(info, prop));
2386
+ units.set(`%${cName}.set:${prop}`, { order: unitOrder++, lower: () => this.throwingSetterFn(info, prop) });
2187
2387
  }
2188
2388
  }
2189
2389
  for (const name of info.staticMethods?.keys() ?? []) {
2190
- units.set(`%${cName}.static:${name}`, () => lowerStaticMethod(this, info, name));
2390
+ units.set(`%${cName}.static:${name}`, { order: unitOrder++, lower: () => lowerStaticMethod(this, info, name) });
2191
2391
  }
2192
2392
  }
2393
+ // The old emit pass visited each file's functions and then its classes,
2394
+ // before every module init. Retained reachability discovers those
2395
+ // bodies from the inits, but first-seen record metadata still has to
2396
+ // follow that old order: Object.keys/JSON/inspect observe a shape's
2397
+ // declaredOrder. Keep output sorting separate — this rank controls only
2398
+ // that observable metadata.
2399
+ const metadataPriority = new Map();
2400
+ let declarationMetadataOrder = 0;
2401
+ const rank = (name) => {
2402
+ if (units.has(name) && !metadataPriority.has(name)) {
2403
+ metadataPriority.set(name, [1, declarationMetadataOrder++]);
2404
+ }
2405
+ };
2406
+ for (const fp of parts) {
2407
+ for (const decl of fp.fnDecls) {
2408
+ if (!decl.body)
2409
+ continue;
2410
+ const symbol = declSymbolOf(this, decl);
2411
+ if (symbol && !this.genericFnsBySymbol.has(symbol)) {
2412
+ const sig = this.fnSigsBySymbol.get(symbol);
2413
+ if (sig)
2414
+ rank(sig.name);
2415
+ }
2416
+ }
2417
+ for (const decl of fp.classDecls) {
2418
+ const info = this.classes.get(this.classNamer(decl));
2419
+ if (!info)
2420
+ continue;
2421
+ const cName = info.def.name;
2422
+ rank(`%${cName}.constructor`);
2423
+ for (const { mName } of this.classMethodMembers(info))
2424
+ rank(`%${cName}.${mName}`);
2425
+ for (const name of info.staticMethods?.keys() ?? [])
2426
+ rank(`%${cName}.static:${name}`);
2427
+ for (const prop of info.throwingSetters)
2428
+ rank(`%${cName}.set:${prop}`);
2429
+ }
2430
+ }
2431
+ // Defensive fallback for declaration-like units registered outside
2432
+ // FileParts; they still precede init bodies in the old emit pass.
2433
+ for (const name of units.keys())
2434
+ rank(name);
2435
+ let expressionMetadataOrder = 0;
2436
+ let instanceMetadataOrder = 0;
2437
+ const demandOwner = (priority) => {
2438
+ const owner = {
2439
+ ...(priority ? { priority } : {}),
2440
+ functionDemands: [],
2441
+ classDemands: [],
2442
+ };
2443
+ if (priority)
2444
+ this.genericDemandRoots.push(owner);
2445
+ return owner;
2446
+ };
2193
2447
  const reachable = new Set();
2194
2448
  const queue = [];
2449
+ const loweredUnits = new Map();
2450
+ const initFunctions = [];
2451
+ const instanceFunctions = [];
2195
2452
  this.onEdge = (name) => {
2196
2453
  if (reachable.has(name))
2197
2454
  return;
@@ -2204,15 +2461,19 @@ export class Lowerer {
2204
2461
  // edge to them can fire (references require the collected class).
2205
2462
  this.onExprClassCollected = (info) => {
2206
2463
  const cName = info.def.name;
2207
- units.set(`%${cName}.constructor`, () => this.lowerClassCtor(info));
2464
+ const register = (name, lower) => {
2465
+ units.set(name, { order: unitOrder++, lower });
2466
+ metadataPriority.set(name, [3, expressionMetadataOrder++]);
2467
+ };
2468
+ register(`%${cName}.constructor`, () => this.lowerClassCtor(info));
2208
2469
  for (const { mName, member } of this.classMethodMembers(info)) {
2209
- units.set(`%${cName}.${mName}`, () => this.lowerClassMethodMember(info, member));
2470
+ register(`%${cName}.${mName}`, () => this.lowerClassMethodMember(info, member));
2210
2471
  }
2211
2472
  for (const name of info.staticMethods?.keys() ?? []) {
2212
- units.set(`%${cName}.static:${name}`, () => lowerStaticMethod(this, info, name));
2473
+ register(`%${cName}.static:${name}`, () => lowerStaticMethod(this, info, name));
2213
2474
  }
2214
2475
  for (const prop of info.throwingSetters) {
2215
- units.set(`%${cName}.set:${prop}`, () => this.throwingSetterFn(info, prop));
2476
+ register(`%${cName}.set:${prop}`, () => this.throwingSetterFn(info, prop));
2216
2477
  }
2217
2478
  };
2218
2479
  // Generic instances queued by the bodies above lower here (an instance
@@ -2226,28 +2487,38 @@ export class Lowerer {
2226
2487
  while (instLowered < this.instantiationQueue.length ||
2227
2488
  clsInstLowered < this.genericClassInstances.length ||
2228
2489
  specLowered < this.emitSpecQueue.length) {
2490
+ while (clsInstLowered < this.genericClassInstances.length) {
2491
+ const info = this.genericClassInstances[clsInstLowered++];
2492
+ const owner = demandOwner();
2493
+ this.genericClassDemandOwner.set(info, owner);
2494
+ const ref = this.genericClassDemandPriority.get(info) ?? { rank: [4, instanceMetadataOrder++] };
2495
+ this.genericClassDemandPriority.set(info, ref);
2496
+ instanceFunctions.push(...this.withGenericDemandOwner(owner, () => this.shapes.withDeclaredOrderPriority(ref, () => this.lowerClassMembers(info))));
2497
+ }
2229
2498
  while (instLowered < this.instantiationQueue.length) {
2230
2499
  const { info, inst } = this.instantiationQueue[instLowered++];
2231
- // Body-level poisons skip the instance here too (the emit pass
2232
- // re-records the diagnostic; discovery only needs the edges the
2233
- // body fired before poisoning).
2500
+ const owner = demandOwner();
2501
+ this.genericFunctionDemandOwner.set(inst, owner);
2502
+ const ref = this.genericDemandPriority.get(inst) ?? { rank: [4, instanceMetadataOrder++] };
2503
+ this.genericDemandPriority.set(inst, ref);
2504
+ // Body-level poisons skip the instance after retaining the
2505
+ // diagnostic and every edge fired before poisoning.
2234
2506
  try {
2235
- this.lowerGenericInstance(info, inst);
2507
+ instanceFunctions.push(this.withGenericDemandOwner(owner, () => this.shapes.withDeclaredOrderPriority(ref, () => this.lowerGenericInstance(info, inst))));
2236
2508
  }
2237
2509
  catch (e) {
2238
2510
  if (!(e instanceof PoisonError))
2239
2511
  throw e;
2240
2512
  }
2241
2513
  }
2242
- while (clsInstLowered < this.genericClassInstances.length) {
2243
- this.lowerClassMembers(this.genericClassInstances[clsInstLowered++]);
2244
- }
2245
2514
  // Emit-override specialization bodies fire edges of their own
2246
2515
  // (the super-forward chain, closures, generic calls) — lower them
2247
- // for discovery exactly like generic instances.
2516
+ // exactly like generic instances.
2248
2517
  while (specLowered < this.emitSpecQueue.length) {
2249
2518
  try {
2250
- lowerEmitOverrideSpec(this, this.emitSpecQueue[specLowered++]);
2519
+ const fn = this.shapes.withDeclaredOrderPriority([4, instanceMetadataOrder++], () => lowerEmitOverrideSpec(this, this.emitSpecQueue[specLowered++]));
2520
+ if (fn)
2521
+ instanceFunctions.push(fn);
2251
2522
  }
2252
2523
  catch (e) {
2253
2524
  if (!(e instanceof PoisonError))
@@ -2256,9 +2527,10 @@ export class Lowerer {
2256
2527
  }
2257
2528
  }
2258
2529
  };
2259
- parts.forEach((fp) => {
2260
- this.lowerFileInit(fp.sf, fp.topStmts, this.initNameOf.get(fp.sf));
2261
- drainInstances();
2530
+ parts.forEach((fp, index) => {
2531
+ const priority = [2, index];
2532
+ const owner = demandOwner(priority);
2533
+ initFunctions.push(this.withGenericDemandOwner(owner, () => this.shapes.withDeclaredOrderPriority(priority, () => this.lowerFileInit(fp.sf, fp.topStmts, this.initNameOf.get(fp.sf)))));
2262
2534
  });
2263
2535
  // LIBRARY mode's extra reachability roots (LowerOptions.libRoots): the
2264
2536
  // profile-mapped exports are called from outside the graph, so they
@@ -2269,21 +2541,72 @@ export class Lowerer {
2269
2541
  while (queue.length > 0) {
2270
2542
  // A body-level poison outside the per-statement catches (a fenced
2271
2543
  // constructor/method parameter default lowered by declareParams):
2272
- // discovery only needs the edges the body fired before poisoning —
2273
- // the emit pass re-records the diagnostic and skips the member.
2544
+ // every edge fired before poisoning remains retained; the diagnostic
2545
+ // stays recorded and the member stays omitted.
2274
2546
  try {
2275
- units.get(queue.shift())();
2547
+ const name = queue.shift();
2548
+ const unit = units.get(name);
2549
+ const priority = metadataPriority.get(name) ?? [3, expressionMetadataOrder++];
2550
+ const owner = demandOwner(priority);
2551
+ const fn = this.withGenericDemandOwner(owner, () => this.shapes.withDeclaredOrderPriority(priority, unit.lower));
2552
+ if (fn)
2553
+ loweredUnits.set(name, fn);
2276
2554
  }
2277
2555
  catch (e) {
2278
2556
  if (!(e instanceof PoisonError))
2279
2557
  throw e;
2280
2558
  }
2281
- drainInstances();
2282
2559
  }
2283
- return reachable;
2284
- }
2285
- /** Discovery hook (see discover): fires when lowering resolves a
2286
- * reference to a lowerable body. Inert in the emit pass. */
2560
+ this.restoreGenericInstanceOrder();
2561
+ this.restoreGenericClassInstanceOrder();
2562
+ // Generic bodies can reach ordinary declarations, whose bodies can in
2563
+ // turn queue more instances. Continue to the joint fixpoint; the initial
2564
+ // queue above is the only portion whose discovery order differed from
2565
+ // historical emit order.
2566
+ for (;;) {
2567
+ drainInstances();
2568
+ if (queue.length === 0)
2569
+ break;
2570
+ while (queue.length > 0) {
2571
+ try {
2572
+ const name = queue.shift();
2573
+ const unit = units.get(name);
2574
+ const priority = metadataPriority.get(name) ?? [3, expressionMetadataOrder++];
2575
+ const owner = demandOwner(priority);
2576
+ const fn = this.withGenericDemandOwner(owner, () => this.shapes.withDeclaredOrderPriority(priority, unit.lower));
2577
+ if (fn)
2578
+ loweredUnits.set(name, fn);
2579
+ }
2580
+ catch (e) {
2581
+ if (!(e instanceof PoisonError))
2582
+ throw e;
2583
+ }
2584
+ }
2585
+ this.restoreGenericInstanceOrder(instLowered);
2586
+ this.restoreGenericClassInstanceOrder(clsInstLowered);
2587
+ }
2588
+ const orderedUnits = [...loweredUnits]
2589
+ .sort(([left], [right]) => units.get(left).order - units.get(right).order)
2590
+ .map(([, fn]) => fn);
2591
+ this.settleGenericDemandPriorities();
2592
+ this.shapes.settleDeclaredOrderPriorities();
2593
+ for (const finalize of this.shapeOrderMetadataFinalizers)
2594
+ finalize();
2595
+ for (const finalize of this.shapeOrderHelperFinalizers)
2596
+ finalize();
2597
+ const functions = [
2598
+ ...orderedUnits,
2599
+ ...initFunctions,
2600
+ this.buildMain(),
2601
+ ...instanceFunctions,
2602
+ ...this.liftedFns,
2603
+ ...this.implicitFns,
2604
+ ];
2605
+ this.reachableForArtifacts = reachable;
2606
+ return { reachable, result: this.finishModule(functions) };
2607
+ }
2608
+ /** Reachability hook (see emitReachable): fires when lowering resolves a
2609
+ * reference to a lowerable body. */
2287
2610
  noteEdge(name) {
2288
2611
  if (this.onEdge)
2289
2612
  this.onEdge(name);