@asmlift/core 0.9.0 → 0.10.0

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 (101) hide show
  1. package/README.md +25 -24
  2. package/package.json +1 -1
  3. package/src/aggregate.ts +149 -0
  4. package/src/backend/c.ts +1 -0
  5. package/src/backend/cfamily.ts +165 -50
  6. package/src/backend/cpp.ts +2 -1
  7. package/src/backend/pascal.ts +31 -12
  8. package/src/contracts.ts +173 -16
  9. package/src/declare.ts +69 -20
  10. package/src/frontend/asmdata.ts +7 -1
  11. package/src/frontend/call-plan.ts +964 -60
  12. package/src/frontend/dead-reads.ts +628 -0
  13. package/src/frontend/device-pins.ts +23 -4
  14. package/src/frontend/emit.ts +4 -3
  15. package/src/frontend/fpu.ts +224 -17
  16. package/src/frontend/frame-objects.ts +1232 -192
  17. package/src/frontend/frontend.ts +64 -3
  18. package/src/frontend/high-half.ts +53 -4
  19. package/src/frontend/jump-table.ts +530 -0
  20. package/src/frontend/local-object.ts +158 -32
  21. package/src/frontend/mips.ts +2076 -215
  22. package/src/frontend/own-struct-return.ts +500 -0
  23. package/src/frontend/ppc.ts +2711 -232
  24. package/src/frontend/register-struct-call.ts +484 -0
  25. package/src/frontend/reloc-symbol.ts +22 -7
  26. package/src/frontend/ssa.ts +1460 -44
  27. package/src/frontend/stackargs.ts +106 -57
  28. package/src/frontend/thumb.ts +2044 -318
  29. package/src/ir/alias.ts +42 -0
  30. package/src/ir/bits.ts +681 -1
  31. package/src/ir/core.ts +100 -5
  32. package/src/ir/discipline.ts +86 -21
  33. package/src/ir/entry.ts +159 -0
  34. package/src/{structure → ir}/loops.ts +6 -2
  35. package/src/ir/opcodes.ts +109 -9
  36. package/src/ir/types.ts +86 -2
  37. package/src/ir/verify.ts +80 -11
  38. package/src/l3/ast.ts +290 -18
  39. package/src/l3/basecse.ts +33 -5
  40. package/src/l3/constinit.ts +382 -0
  41. package/src/l3/dce.ts +2 -2
  42. package/src/l3/initfirst.ts +42 -11
  43. package/src/l3/intcell.ts +2 -1
  44. package/src/l3/narrowdecl.ts +77 -36
  45. package/src/l3/pollguard.ts +8 -2
  46. package/src/l3/ptrcell.ts +31 -34
  47. package/src/l3/ptrfield.ts +123 -8
  48. package/src/l3/regspell.ts +9 -4
  49. package/src/l3/reindex.ts +283 -96
  50. package/src/l3/storage.ts +98 -1
  51. package/src/l3/symbol-refs.ts +84 -6
  52. package/src/l3/tailmerge.ts +4 -2
  53. package/src/l3/typing.ts +108 -15
  54. package/src/l3/unreduce.ts +6 -5
  55. package/src/l3/volatileptr.ts +17 -54
  56. package/src/mangle.ts +30 -0
  57. package/src/pattern/engine.ts +361 -73
  58. package/src/pipeline.ts +541 -24
  59. package/src/proto-context.ts +905 -45
  60. package/src/proto.ts +555 -40
  61. package/src/raise/aggmembers.ts +290 -0
  62. package/src/raise/arrays.ts +5 -1
  63. package/src/raise/declared-volatile.ts +207 -31
  64. package/src/raise/floathelpers.ts +254 -33
  65. package/src/raise/gvn.ts +3 -3
  66. package/src/raise/latch.ts +162 -6
  67. package/src/raise/memberarrays.ts +10 -4
  68. package/src/raise/offsetnames.ts +10 -26
  69. package/src/raise/pairparams.ts +33 -0
  70. package/src/raise/paramwidth.ts +196 -6
  71. package/src/raise/pre-recovery.ts +102 -19
  72. package/src/raise/pre-structure.ts +54 -0
  73. package/src/raise/recover.ts +12 -5
  74. package/src/raise/reroll.ts +694 -0
  75. package/src/raise/shortcircuit.ts +26 -12
  76. package/src/raise/softdiv.ts +1 -1
  77. package/src/raise/storesign.ts +153 -0
  78. package/src/raise/struct-arrays.ts +30 -11
  79. package/src/raise/structs.ts +396 -33
  80. package/src/raise/truncload.ts +4 -3
  81. package/src/raise/unthread.ts +400 -0
  82. package/src/rank-declare.ts +118 -12
  83. package/src/rank-variations.ts +89 -31
  84. package/src/rank.ts +307 -54
  85. package/src/runtime-helpers.ts +140 -22
  86. package/src/structure/analysis.ts +165 -2
  87. package/src/structure/arith-ops.ts +23 -0
  88. package/src/structure/globalaccess.ts +111 -9
  89. package/src/structure/hazards.ts +16 -64
  90. package/src/structure/local-statics.ts +145 -9
  91. package/src/structure/namecoalesce.ts +1 -1
  92. package/src/structure/pointer-spelling.ts +1106 -0
  93. package/src/structure/redundant-test.ts +1 -1
  94. package/src/structure/structure.ts +2180 -1054
  95. package/src/structure/switch-recover.ts +244 -100
  96. package/src/symbols.ts +6 -0
  97. package/src/target.ts +765 -52
  98. package/src/trace.ts +26 -8
  99. package/src/variation-definitions.ts +126 -14
  100. package/src/variation-gates.ts +2 -0
  101. package/src/variation-tokens.ts +5 -1
@@ -27,7 +27,7 @@ import {
27
27
  import type { Gate } from './l3/gates';
28
28
  import type { HoistPlacement } from './l3/hoist';
29
29
  import { initFirstGuards } from './l3/initfirst';
30
- import { narrowDeclarations, narrowReadDeclarations } from './l3/narrowdecl';
30
+ import { narrowDeclarations, narrowLoadDeclarations, narrowReadDeclarations } from './l3/narrowdecl';
31
31
  import { pollGuards, pollReads } from './l3/pollguard';
32
32
  import { unmergeJoins } from './l3/unmerge';
33
33
  // TYPE-ONLY, and deliberately: the structure-variation table types its `options` as
@@ -71,7 +71,7 @@ export interface StructureVariation {
71
71
  | 'derivedHome'
72
72
  | 'mergeHome'
73
73
  | 'escapeHome'
74
- | 'unsCmp'
74
+ | 'signedCmp'
75
75
  | 'freshMerge'
76
76
  | 'copyDefPos'
77
77
  | 'siteSense';
@@ -225,17 +225,22 @@ export const STRUCTURE_VARIATIONS: readonly StructureVariation[] = [
225
225
  perLiftGate: hasEscapingExtension,
226
226
  strip: true,
227
227
  },
228
- // `/uns-cmp` — spell unsigned compares unsigned (structure.ts unsignedCompareSpelling): an
229
- // icmp_u* operand takes a (u32) cast where the rendered operands do not guarantee the
230
- // unsignedness, and a mixed-claimant declaration reconciles to u32 when nothing under the
231
- // name needs signed. Which side the source spelled is genuinely ambiguous: a signed spelling
232
- // that byte-matched was PROVED non-negative by the compiler (only then does it emit the
233
- // unsigned branch from a signed compare), and emission's provable set is smaller than the
234
- // compiler's. Gated on the function having an unsigned compare at all.
228
+ // `/signed-cmp` — spell an unsigned compare SIGNED (structure.ts unsignedCompareSpelling OFF): no
229
+ // `(u32)` cast at an icmp_u* and no u32 reconciliation of a mixed-claimant declaration. The
230
+ // default is the one spelling value-faithful without a proof; this one is a real source where the
231
+ // compiler PROVED both sides non-negative (only then does it emit the unsigned branch from a
232
+ // signed compare), which emission cannot always repeat. Where the proof does not hold it is a
233
+ // different program, so rank.ts judges every emitted tree (`signedOverUnsignedCompare`) and makes
234
+ // a signed rendering `Candidate.matchOnly`, whichever setting or respell spelled it. A tie or a
235
+ // better nonmatch score is no evidence for it (`synthetic:selloop:gcc2.7.2kmc`).
236
+ // KNOWN GAP, in the safe direction: the judgment reads the tree's declarations, and globals are
237
+ // not among them, so a compare that renders unsigned only through a `void *` global the backend
238
+ // declares (ptrcell.test.ts KR1, `g >= gLimit`) is still withheld on a nonmatch.
239
+ // Gated on the function having an unsigned compare at all.
235
240
  {
236
- flag: 'unsCmp',
237
- name: 'uns-cmp',
238
- options: (on) => ({ unsignedCompareSpelling: on }),
241
+ flag: 'signedCmp',
242
+ name: 'signed-cmp',
243
+ options: (on) => ({ unsignedCompareSpelling: !on }),
239
244
  sharedGate: (sharedLift) => sharedLift.blocks.some((b) => b.ops.some((op) => op.opcode.startsWith('icmp_u'))),
240
245
  strip: true,
241
246
  },
@@ -373,24 +378,31 @@ export const STRUCTURE_VARIATIONS: readonly StructureVariation[] = [
373
378
  },
374
379
  ];
375
380
 
376
- /** The STACKED variations (sanctioned in the POLICY note at rank.ts's respell site): each entry is
377
- * a statement-order/shape or declaration-width respell variation orthogonal to every respell
378
- * variation outside this table, derived onto every source. Two members need not commute
379
- * (`/narrow-decl` and `/narrow-read` compete for one cast), and table order then decides which one
380
- * takes it in the all-together candidate. Each fires alone, plus
381
- * all of them together in table order — not the full subset lattice; the pairs question is
382
- * settled by applyStacked' skip-on-decline below, and a row demanding a true EXCLUSION pair —
383
- * every member fires, the match needs a strict subset of them — is what would earn the lattice.
384
- * So a member appended here also changes the all-together candidate wherever it fires beside two
385
- * or more of the others. */
386
- export const STACKED_VARIATIONS: { name: VariationName; apply: (sfn: SFn) => SFn | null }[] = [
381
+ /** The statement-order/shape members. */
382
+ const STACKED_SHAPES: { name: VariationName; apply: (sfn: SFn) => SFn | null }[] = [
387
383
  { name: 'initfirst', apply: initFirstGuards },
388
384
  { name: 'pollguard', apply: pollGuards },
389
385
  { name: 'pollread', apply: pollReads },
386
+ ];
387
+
388
+ /** The declaration-width members: one dimension, the width each kind of local is declared at
389
+ * (l3/narrowdecl.ts). A function holding a cast-written local,
390
+ * load-written locals and call-result locals may need any combination of the three kinds narrow
391
+ * while all three fire (the matching suite compiles all eight), so every subset is offered. */
392
+ const STACKED_WIDTHS: { name: VariationName; apply: (sfn: SFn) => SFn | null }[] = [
390
393
  { name: 'narrow-decl', apply: narrowDeclarations },
394
+ { name: 'narrow-load', apply: narrowLoadDeclarations },
391
395
  { name: 'narrow-read', apply: narrowReadDeclarations },
392
396
  ];
393
397
 
398
+ /** The STACKED variations (sanctioned in the POLICY note at rank.ts's respell site): each entry is
399
+ * a statement-order/shape or declaration-width respell variation orthogonal to every respell
400
+ * variation outside this table, derived onto every source. Two members need not commute
401
+ * (`/narrow-decl` and `/narrow-read` compete for one cast, `/narrow-load` and `/narrow-read` for
402
+ * one local), and table order then decides which one takes it in a candidate holding both. Which
403
+ * of them a candidate applies is STACKED_SUBSETS. */
404
+ export const STACKED_VARIATIONS = [...STACKED_SHAPES, ...STACKED_WIDTHS];
405
+
394
406
  /** The PRE-RESPELL variations (sanctioned in the POLICY note at rank.ts's respell site): a tree
395
407
  * rewrite applied BEFORE the respell set, so the whole set derives from its output instead of
396
408
  * composing onto it. Same record type as STACKED_VARIATIONS above, and deliberately so — the only difference is
@@ -458,17 +470,47 @@ export const STACKED_VARIATIONS: { name: VariationName; apply: (sfn: SFn) => SFn
458
470
  * method are in `apps/benchmark/dataset/synthetic.ts`'s `/unmerge` block. */
459
471
  export const PRE_RESPELL_VARIATIONS: typeof STACKED_VARIATIONS = [{ name: 'unmerge', apply: unmergeJoins }];
460
472
 
461
- export const STACKED_SUBSETS: (typeof STACKED_VARIATIONS)[number][][] = [
462
- ...STACKED_VARIATIONS.map((x) => [x]),
463
- ...(STACKED_VARIATIONS.length > 1 ? [STACKED_VARIATIONS] : []),
464
- ];
473
+ /** Every subset of `xs`, the empty one first, each in `xs`' order. */
474
+ const subsets = <X>(xs: readonly X[]): X[][] =>
475
+ xs.reduce<X[][]>((acc, x) => [...acc, ...acc.map((s) => [...s, x])], [[]]);
476
+
477
+ /** The stacked members a candidate applies: the table's two dimensions crossed, a shape choice
478
+ * (none, each shape alone, or every shape) by a width choice (any subset of the widths, none
479
+ * included), the shapes first. A product, because a function may need a shape AND a strict subset
480
+ * of the widths that fire: `initfirst` beside a narrow cast-written local and wide load-written
481
+ * ones (the matching suite compiles that pair both ways). The shapes are not a lattice of their own:
482
+ * applyStacked's skip-on-decline makes "every shape" each pair of them wherever the third declines,
483
+ * and a row demanding a strict subset of the shapes while all fire is what would earn one. Fewest
484
+ * members first, so where everything else ties the smaller set is enumerated first and wins. */
485
+ export const STACKED_SUBSETS: (typeof STACKED_VARIATIONS)[number][][] = subsets(STACKED_WIDTHS)
486
+ .flatMap((widths) =>
487
+ [[], ...STACKED_SHAPES.map((x) => [x]), ...(STACKED_SHAPES.length > 1 ? [STACKED_SHAPES] : [])].map((shapes) => [
488
+ ...shapes,
489
+ ...widths,
490
+ ]),
491
+ )
492
+ .filter((subset) => subset.length > 0)
493
+ .sort((a, b) => a.length - b.length);
494
+
495
+ /** A stacked member that threw, named by the members that fired before it and then itself: the
496
+ * step that threw, which every subset reaching it shares. Reported under that name rather than
497
+ * the whole subset's, so a consumer deduping on the step prints one broken member once and not
498
+ * once per subset holding it. */
499
+ export class StackedMemberThrew extends Error {
500
+ constructor(
501
+ readonly variations: readonly VariationName[],
502
+ cause: unknown,
503
+ ) {
504
+ super(cause instanceof Error ? cause.message : String(cause), { cause });
505
+ }
506
+ }
465
507
 
466
508
  /** The subset applied in table order, SKIP-ON-DECLINE: a member that declines contributes
467
509
  * nothing rather than killing the combination — the all-shapes candidate is "everything that
468
510
  * fires", so a pair is reachable whenever the third declines. The variations are the members that
469
- * actually FIRED, so they never name a variation that declined; a fired-set that
470
- * duplicates a smaller subset emits identical source and the dedup collapses it. Null when
471
- * nothing fired. */
511
+ * actually FIRED, so they never name a variation that declined; a fired set that
512
+ * duplicates a smaller subset's derives the same tree, which the caller emits once
513
+ * (`firstFired`). Null when nothing fired. A member that throws throws StackedMemberThrew. */
472
514
  export const applyStacked = (
473
515
  subset: readonly (typeof STACKED_VARIATIONS)[number][],
474
516
  from: SFn,
@@ -476,7 +518,12 @@ export const applyStacked = (
476
518
  let cur = from;
477
519
  const fired: VariationName[] = [];
478
520
  for (const sp of subset) {
479
- const r = sp.apply(cur);
521
+ let r: SFn | null;
522
+ try {
523
+ r = sp.apply(cur);
524
+ } catch (e) {
525
+ throw new StackedMemberThrew([...fired, sp.name], e);
526
+ }
480
527
  if (r) {
481
528
  cur = r;
482
529
  fired.push(sp.name);
@@ -485,6 +532,17 @@ export const applyStacked = (
485
532
  return fired.length > 0 ? { out: cur, variations: fired } : null;
486
533
  };
487
534
 
535
+ /** Whether `fired` is a fired set not yet in `seen`, recording it. Two subsets that fire the same
536
+ * members derive the same tree, so a caller emits only the first. */
537
+ export const firstFired = (seen: Set<string>, fired: readonly VariationName[]): boolean => {
538
+ const key = fired.join('/');
539
+ if (seen.has(key)) {
540
+ return false;
541
+ }
542
+ seen.add(key);
543
+ return true;
544
+ };
545
+
488
546
  /** The locals a variation added — a NAME diff rather than a positional slice, so a pass that ever
489
547
  * reorders locals cannot silently empty the set. It is what scopes `/volatile` to the pointers
490
548
  * the variation itself created (volatilePtrLocals' `only`), leaving the tree's own locals alone. */