@asmlift/core 0.6.0 → 0.7.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 (74) hide show
  1. package/README.md +2 -2
  2. package/package.json +1 -1
  3. package/src/backend/cfamily.ts +39 -11
  4. package/src/contracts.ts +60 -11
  5. package/src/frontend/ssa.ts +1 -1
  6. package/src/frontend/thumb.ts +2 -2
  7. package/src/ir/alias.ts +24 -0
  8. package/src/ir/core.ts +8 -0
  9. package/src/ir/opcodes.ts +43 -7
  10. package/src/ir/simplify.ts +1 -1
  11. package/src/l3/address.ts +2 -2
  12. package/src/l3/advance.ts +373 -0
  13. package/src/l3/argbase.ts +4 -4
  14. package/src/l3/ast.ts +65 -21
  15. package/src/l3/basecse.ts +48 -28
  16. package/src/l3/coalesce.ts +9 -9
  17. package/src/l3/gates.ts +75 -1
  18. package/src/l3/hoist.ts +1 -1
  19. package/src/l3/homesplit.ts +13 -13
  20. package/src/l3/initfirst.ts +3 -3
  21. package/src/l3/inlinebase.ts +16 -16
  22. package/src/l3/mentions.ts +68 -5
  23. package/src/l3/mulfirst.ts +3 -3
  24. package/src/l3/nearbase.ts +4 -4
  25. package/src/l3/offmember.ts +5 -5
  26. package/src/l3/parkfirst.ts +6 -6
  27. package/src/l3/pollguard.ts +3 -3
  28. package/src/l3/ptrfield.ts +4 -4
  29. package/src/l3/regspell.ts +8 -8
  30. package/src/l3/reindex.ts +22 -17
  31. package/src/l3/scopebase.ts +28 -25
  32. package/src/l3/sinkinit.ts +7 -7
  33. package/src/l3/slotorder.ts +3 -3
  34. package/src/l3/storage.ts +1 -1
  35. package/src/l3/tailmerge.ts +2 -2
  36. package/src/l3/typing.ts +3 -3
  37. package/src/l3/unmerge.ts +483 -59
  38. package/src/l3/unreduce.ts +13 -13
  39. package/src/l3/volatileptr.ts +11 -11
  40. package/src/l3/volatileval.ts +11 -11
  41. package/src/l3/volstore.ts +16 -16
  42. package/src/l3/zerosub.ts +6 -6
  43. package/src/pattern/engine.ts +4 -4
  44. package/src/pipeline.ts +17 -5
  45. package/src/proto.ts +2 -2
  46. package/src/raise/const.ts +203 -3
  47. package/src/raise/divpow2.ts +2 -2
  48. package/src/raise/extscale.ts +342 -0
  49. package/src/raise/globalshape.ts +32 -12
  50. package/src/raise/gvn.ts +2 -2
  51. package/src/raise/magicdiv.ts +2 -2
  52. package/src/raise/memberarrays.ts +4 -4
  53. package/src/raise/narrowlocal.ts +18 -2
  54. package/src/raise/paramwidth.ts +24 -2
  55. package/src/raise/pre-recovery.ts +90 -25
  56. package/src/raise/retsink.ts +381 -15
  57. package/src/raise/shortcircuit.ts +595 -34
  58. package/src/raise/structs.ts +4 -4
  59. package/src/raise/tailsink.ts +126 -0
  60. package/src/rank-declare.ts +4 -4
  61. package/src/{rank-axes.ts → rank-variations.ts} +319 -189
  62. package/src/rank.ts +1148 -803
  63. package/src/structure/analysis.ts +87 -90
  64. package/src/structure/bitfields.ts +130 -30
  65. package/src/structure/globalaccess.ts +30 -4
  66. package/src/structure/namecoalesce.ts +32 -13
  67. package/src/structure/structure.ts +1415 -200
  68. package/src/structure/switch-recover.ts +100 -7
  69. package/src/symbols.ts +127 -6
  70. package/src/target.ts +155 -35
  71. package/src/trace.ts +1 -1
  72. package/src/variation-definitions.ts +1540 -0
  73. package/src/variation-gates.ts +89 -0
  74. package/src/variation-tokens.ts +355 -0
@@ -1,4 +1,4 @@
1
- // L3 re-spelling lever: name a reused global base in a pointer local placed by SCOPE rather than at
1
+ // L3 respell variation: name a reused global base in a pointer local placed by SCOPE rather than at
2
2
  // the function top. Two region rules ship, one pass and one collected index behind them:
3
3
  //
4
4
  // `/scopebase` ONE local for a key, at the innermost list holding all of its uses (else the
@@ -9,13 +9,15 @@
9
9
  // and the three-block-scoped one assemble byte-identically. So there is no nested
10
10
  // declaration block here and none is needed — the locals are declared at function
11
11
  // top and only their ASSIGNMENTS are placed per region. That compiler fact is
12
- // PINNED rather than asserted: packages/cli/test/matching/decl-scope-axis.test.ts
12
+ // PINNED rather than asserted: packages/cli/test/matching/decl-scope-variation.test.ts
13
13
  // compiles both spellings through the project's own agbcc and compares the object
14
14
  // bytes, and compiles a count-collapsed third spelling to show the COUNT is not
15
15
  // free either.
16
16
  //
17
- // The lever earns its place: returning `null` from `hoistScopedBases` costs
18
- // kleod:UpdateHUDCounterDisplay its match, so the benchmark's zero-lost gate guards this file.
17
+ // The variation earned its place on kleod's kl-eod-decomp rows: returning `null` from `hoistScopedBases`
18
+ // cost kleod:UpdateHUDCounterDisplay its match. That row was retired on 2026-09-13 with its source,
19
+ // so no current benchmark row is known to guard this file; re-measure by ablation on the current
20
+ // rows before naming one.
19
21
  //
20
22
  // `l3/basecse.ts` already hoists a reused leaf base — at three positions now, of which two are in
21
23
  // the TOP-LEVEL statement list (the function top, or an init's first use where a roster row asks
@@ -28,8 +30,8 @@
28
30
  // narrows that range and does not close it: the init still lands ABOVE the `if`. `l3/hoist.ts`'s
29
31
  // third placement, `scope`, now does close it for the run basecse places, so "into a nested list"
30
32
  // is no longer this file's alone; what stays here is the base this file can SEE (below) and the
31
- // COUNT question (`REGION_RULES`), which no placement answers. That argument is why the lever is
32
- // scope-aware; it is NOT a claim about what the lever achieves, and no committed measurement
33
+ // COUNT question (`REGION_RULES`), which no placement answers. That argument is why the variation is
34
+ // scope-aware; it is NOT a claim about what the variation achieves, and no committed measurement
33
35
  // separates basecse's two flat placements (the one that did edited a reference source by hand and
34
36
  // cannot be re-run). On kleod:UpdateHUDCounterDisplay the primary path declines outright (a later
35
37
  // pass retired the phi it keyed on, so the base's uses span the function body), and the cluster
@@ -47,7 +49,7 @@
47
49
  // shape. The decomp author's alternative is a no-op read-modify-write (`g[0][K] += 0;`) purely to
48
50
  // force that materialization; naming the base is the same codegen without the quirk.
49
51
  //
50
- // A LEVER, not a rewrite: both region rules are emitted as ADDITIONAL candidates (rank.ts
52
+ // A VARIATION, not a committed rewrite: both region rules are emitted as ADDITIONAL candidates (rank.ts
51
53
  // `/scopebase`, `/regionbase`) with the differ refereeing, so the un-hoisted spelling is always
52
54
  // still in the list and neither can cost a match.
53
55
  //
@@ -60,7 +62,7 @@
60
62
  // sees it (they check resolution, deref typing, and whether a local is written ANYWHERE).
61
63
  //
62
64
  // ORDERING: `hoistBaseLocals` (basecse) runs unconditionally in `structureChecked`, BEFORE
63
- // rank's levers see the tree. So this pass's `addr`/`const` input is what basecse's DEFAULT table
65
+ // rank's variations see the tree. So this pass's `addr`/`const` input is what basecse's DEFAULT table
64
66
  // refused — EVERY gate in it, single-use bases as much as loop and repeated-constant-offset ones —
65
67
  // which is why `SCOPEBASE_GATES` re-states basecse's rules rather than assuming those bases never
66
68
  // arrive.
@@ -72,7 +74,7 @@ import { type Gate, ablateHeuristic, firstRejection } from './gates';
72
74
  import { nameAllocator, takenNames } from './hoist';
73
75
  import { addressableGlobals } from './storage';
74
76
 
75
- /** A base this lever may name: a leaf whose value is a fixed address. */
77
+ /** A base this variation may name: a leaf whose value is a fixed address. */
76
78
  type LeafBase = Extract<Expr, { k: 'addr' } | { k: 'const' } | { k: 'var' }>;
77
79
 
78
80
  /** THE identity of a base — what makes two accesses "the same address".
@@ -89,7 +91,7 @@ export interface AccessCtx {
89
91
  readonly addressable: ReadonlySet<string>;
90
92
  }
91
93
 
92
- /** Which accesses this lever may re-point. BOTH rules are SOUND: each one, removed, makes the
94
+ /** Which accesses this variation may re-point. BOTH rules are SOUND: each one, removed, makes the
93
95
  * rewrite name DIFFERENT BYTES — C that compiles, type-checks and scores, which is the failure
94
96
  * mode nothing downstream catches.
95
97
  *
@@ -117,14 +119,14 @@ export const SCOPEBASE_ELIGIBILITY: readonly Gate<AccessCtx>[] = [
117
119
  },
118
120
  {
119
121
  id: 'shadowed-or-nonarray-base',
120
- why: '`&name` on a local or a pointer-shaped global names a different object',
122
+ why: 'taking `&name` of a local or a pointer-shaped global names a different object',
121
123
  sound: true,
122
124
  guardedBy: 'addr-placement.test.ts: scopebase declines the shadowed name rather than take its address',
123
125
  rejects: (c) => c.base.k === 'var' && !c.addressable.has(c.base.name),
124
126
  },
125
127
  ];
126
128
 
127
- /** An access this lever may re-point, or null. */
129
+ /** An access this variation may re-point, or null. */
128
130
  function eligible(
129
131
  e: Expr,
130
132
  globals: ReadonlySet<string>,
@@ -320,7 +322,7 @@ function deepestCluster(all: Site[]): { scope: Stmt[]; depth: number; uses: Site
320
322
  return best;
321
323
  }
322
324
 
323
- /** How a key's uses are cut into REGIONS — the one axis this pass varies. Names a `REGION_RULES`
325
+ /** How a key's uses are cut into REGIONS — the one dimension this pass varies. Names a `REGION_RULES`
324
326
  * entry; it is the only field a production caller passes. */
325
327
  export type RegionSelector = 'whole' | 'per-region';
326
328
 
@@ -414,13 +416,13 @@ export interface RegionCtx {
414
416
  const COUNTING_RULES: readonly Gate<RegionCtx>[] = [
415
417
  {
416
418
  id: 'single-use',
417
- why: 'one access re-materializes as cheaply as a named local',
419
+ why: 'a base accessed once is as cheap to load again as to hold in a named local',
418
420
  sound: false,
419
421
  rejects: (c) => c.uses < 2,
420
422
  },
421
423
  {
422
424
  id: 'repeated-const-offset',
423
- why: 'a fixed offset touched twice is a scalar RMW, which the compiler re-materializes',
425
+ why: 'a fixed offset read and then written is one scalar update, and the compiler loads its address again for it',
424
426
  sound: false,
425
427
  rejects: (c) => c.repeatedConstOffset,
426
428
  },
@@ -431,7 +433,7 @@ const COUNTING_RULES: readonly Gate<RegionCtx>[] = [
431
433
  const LOOP_RULES: readonly Gate<RegionCtx>[] = [
432
434
  {
433
435
  id: 'per-iteration-use',
434
- why: 'no scope reachable from the use runs at a loop condition or `for` inc cadence',
436
+ why: 'no scope reachable from the use runs as often as a loop condition or a `for` increment',
435
437
  sound: false,
436
438
  rejects: (c) => c.perIteration,
437
439
  },
@@ -443,7 +445,7 @@ const LOOP_RULES: readonly Gate<RegionCtx>[] = [
443
445
  },
444
446
  ];
445
447
 
446
- /** The admission rules. NONE is sound: a wrong choice here names the same address in a different
448
+ /** The admission rules. NONE is sound: a wrong decision here names the same address in a different
447
449
  * place, so it costs bytes and a match, never meaning — the eligibility table above is where
448
450
  * meaning is at stake, and `rank.ts` keeps the un-hoisted spelling beside every candidate.
449
451
  *
@@ -504,7 +506,7 @@ export const SCOPEBASE_GATES: readonly Gate<RegionCtx>[] = [...COUNTING_RULES, .
504
506
  const perRegionReading = (g: Gate<RegionCtx>): Gate<RegionCtx> => ({
505
507
  ...g,
506
508
  id: `region-${g.id}`,
507
- why: `${g.why} — judged over ONE region's direct uses`,
509
+ why: `counted over one regions own uses: ${g.why}`,
508
510
  });
509
511
 
510
512
  /** `/regionbase`'s admission (rank.ts): the per-region readings of `SCOPEBASE_GATES`, MINUS the one
@@ -531,7 +533,7 @@ const perRegionReading = (g: Gate<RegionCtx>): Gate<RegionCtx> => ({
531
533
  * admits.
532
534
  *
533
535
  * ONE RULE HERE IS PRICED BY A ROW; three are not. Ablating `region-single-use` moves
534
- * `synthetic:dmascope` — the lever's own row — while `region-repeated-const-offset`,
536
+ * `synthetic:dmascope` — the variation's own row — while `region-repeated-const-offset`,
535
537
  * `per-iteration-use` and `regions-degenerate` each leave all five gating rows exactly where they
536
538
  * stand. NO SCORE PAIR IS QUOTED for that move: `dmascope` is MATCH in the committed artifact, so
537
539
  * a pair whose unablated endpoint is a nonmatch score describes a corpus state that no longer
@@ -546,7 +548,7 @@ export const REGIONBASE_GATES: readonly Gate<RegionCtx>[] = [
546
548
  ...ablateHeuristic(LOOP_RULES, 'nested-loop-use'),
547
549
  {
548
550
  id: 'regions-degenerate',
549
- why: 'one region is the function-top hoist basecse and /livebase already offer',
551
+ why: 'a single region is the function-top local that the default hoist and `livebase` already offer',
550
552
  sound: false,
551
553
  rejects: (c) => c.siblingRegions < 2,
552
554
  },
@@ -554,7 +556,7 @@ export const REGIONBASE_GATES: readonly Gate<RegionCtx>[] = [
554
556
 
555
557
  /** THE REGION RULE, as a value. A third rule is one entry here — a partition, a gate table, and
556
558
  * the population its counting rules are judged over — rather than three hand-edited branches in
557
- * three functions, which is the same doctrine `rank.ts` states for its own admissions ("one entry
559
+ * three functions, which is the same doctrine `rank-variations.ts` states for its hoist roster ("one entry
558
560
  * here, one gate table, and that table's line in the gate-contract roster — not nine hand-edited
559
561
  * sites that can drift"). */
560
562
  export interface RegionRule {
@@ -675,9 +677,10 @@ export function planScopedBases(sfn: SFn, opts: ScopeBaseOpts = {}): ScopedBaseP
675
677
  * PER KEY, not per function, and the difference is not hypothetical. Nothing in the L3 contract
676
678
  * forbids the sharing — `l3/pollguard.ts` already emits it (`{ k: 'if', cond: s.cond, then: [s] }`
677
679
  * puts one `cond` object at two tree positions), and it is harmless today only because the shapes
678
- * are derived AFTER this lever in `rank.ts`, an ordering nothing pins. A whole-function decline
680
+ * are derived AFTER this variation in `rank.ts`, an ordering nothing pins. A whole-function decline
679
681
  * would make a future producer that shares one node silently delete every base this pass names —
680
- * including `kleod:UpdateHUDCounterDisplay`'s match, which returning `null` costs. Refusing the
682
+ * including, until that row was retired (2026-09-13), `kleod:UpdateHUDCounterDisplay`'s match,
683
+ * which returning `null` cost. Refusing the
681
684
  * key that actually shares costs that key's spelling and nothing else, and the differ still has
682
685
  * every other spelling in the list.
683
686
  *
@@ -757,14 +760,14 @@ export function planScopedBases(sfn: SFn, opts: ScopeBaseOpts = {}): ScopedBaseP
757
760
 
758
761
  /**
759
762
  * The re-spelling `regions` asks for — `/scopebase` or `/regionbase` — or null when nothing
760
- * qualifies (the caller then adds no candidate rather than a duplicate of the primary).
763
+ * qualifies (the caller then adds no candidate rather than a duplicate of the default).
761
764
  */
762
765
  export const hoistScopedBases = (sfn: SFn, opts: ScopeBaseOpts = {}): SFn | null =>
763
766
  applyScopedBasePlan(sfn, planScopedBases(sfn, opts));
764
767
 
765
768
  /**
766
769
  * `plan` applied to the tree it was planned over — null when it decided nothing (the caller then
767
- * adds no candidate rather than a duplicate of the primary).
770
+ * adds no candidate rather than a duplicate of the default).
768
771
  *
769
772
  * IDENTITY-BOUND to that tree, and not by the type: `scope` is matched against statement LISTS and
770
773
  * `repoint` against access NODES, both by reference. A plan from a DIFFERENT tree therefore splices
@@ -1,18 +1,18 @@
1
- // L3 re-spelling lever: sink each leading pointer-base INIT to the statement that first uses it.
1
+ // L3 respell variation: sink each leading pointer-base INIT to the statement that first uses it.
2
2
  //
3
3
  // `l3/basecse.ts`'s COMMITTED call emits every base hoist at the head of `sfn.body`, so a base
4
4
  // first touched halfway down the function is live across everything above it — a live range the
5
- // original never had, and agbcc pays for it with a callee-saved register. (Its roster admissions
6
- // ask for this placement directly, through the same `l3/hoist.ts` mechanism this lever uses; what
7
- // the lever adds is reaching the run on a tree the roster did not build — one NOTHING re-hoisted,
5
+ // original never had, and agbcc pays for it with a callee-saved register. (Its roster's hoists
6
+ // ask for this placement directly, through the same `l3/hoist.ts` mechanism this variation uses; what
7
+ // the variation adds is reaching the run on a tree the roster did not build — one NOTHING re-hoisted,
8
8
  // or one `l3/nearbase.ts` prepended into, which is the `/nearbase/sinkinit` pairing.)
9
9
  // Compiled pair on `synthetic:basehome`:
10
10
  // assigning at the top adds `push {r4, lr}` / `pop {r4}` / `pop {r0}` / `bx r0` where assigning at
11
- // the first use keeps a plain `bx lr`. The ladder that row records is the argument for the lever
11
+ // the first use keeps a plain `bx lr`. The ladder that row records is the argument for the variation
12
12
  // being a PLACEMENT rather than a wider hoist — top-placed 11, not hoisted at all 9, placed at
13
13
  // first use 0. A hoist at the wrong place is worse than no hoist.
14
14
  //
15
- // A LEVER, NOT THE DEFAULT. Which placement the source used is per-function knowledge the asm does
15
+ // A VARIATION, NOT THE DEFAULT. Which placement the source used is per-function knowledge the asm does
16
16
  // not carry: moving basecse's own head placement to first use moves 8 benchmark rows, 4 better and
17
17
  // 4 worse, two of the losses being matches. The UNSUNK spelling always rides beside this one —
18
18
  // head-placed wherever basecse built the run, prepend-placed under `/nearbase` — and the differ
@@ -25,7 +25,7 @@
25
25
  // immediately before the first TOP-LEVEL statement mentioning its name and never INTO a nested
26
26
  // scope, so it still dominates every use — planning a hoist inside a scope is `l3/scopebase.ts`'s
27
27
  // job and it does the domination work. `placeBaseLocals` carries the refusals; a run where none of
28
- // them moves is this lever declining.
28
+ // them moves is this variation declining.
29
29
  //
30
30
  // SEMANTICS BY CONSTRUCTION: the moved value is a pure address leaf — it reads nothing, writes its
31
31
  // own plain cell and cannot fault — and every statement it crosses mentions the name nowhere, an
@@ -7,13 +7,13 @@
7
7
  // LOWER `[sp,#k]`, and a source whose two spilled locals are declared the other way round compiles
8
8
  // to the same object with those two operands swapped and nothing else moved.
9
9
  //
10
- // A PER-COMPILER DEFAULT, NOT A RANKED AXIS. The asm does not underdetermine the answer: a
10
+ // A COMPILER BEHAVIOR, NOT A RANKED VARIATION. The asm does not underdetermine the answer: a
11
11
  // `[sp,#k]` operand NAMES the slot, and slot → declaration rank is a FUNCTION once the compiler is
12
- // fixed. An axis exists where two source spellings collapse to the same object and only the differ
12
+ // fixed. A variation exists where two source spellings collapse to the same object and only the differ
13
13
  // can choose between them; here the object chooses. The fan cost is zero — this rewrites the one
14
14
  // tree every candidate already carries, adding no candidate, no `structure()` call and no compile.
15
15
  //
16
- // WHY `emit` OWNS IT. Two reasons, and they point the same way. It must run AFTER every L3 lever,
16
+ // WHY `emit` OWNS IT. Two reasons, and they point the same way. It must run AFTER every L3 respell variation,
17
17
  // because each of those rebuilds the declaration list (appends: basecse, scopebase, argbase,
18
18
  // nearbase, regspell, reindex; filters: unmerge, coalesce, dce, inlinebase, pollguard, unreduce),
19
19
  // and `emit` is last by construction. And it must NOT run at a `.emit(` call site: there are seven
package/src/l3/storage.ts CHANGED
@@ -9,7 +9,7 @@
9
9
  // SHADOWING is what the copies disagreed on, and the disagreement is REAL rather than drift: a
10
10
  // name can be declared both a global and a local, because `SFn.globals` records what the code
11
11
  // REFERENCES (structure.ts `noteGlobal`) independently of what it declares. Which answer is right
12
- // depends on how the asking lever will SPELL the name, so the two questions are two functions
12
+ // depends on how the asking pass will SPELL the name, so the two questions are two functions
13
13
  // here instead of one set each caller filters its own way:
14
14
  // • it keeps the reference verbatim (`(u8 *)g`) — the shadow is harmless, the spelling denotes
15
15
  // whatever the original access denoted: `declaredGlobals`;
@@ -25,7 +25,7 @@
25
25
  // above covers only below-vs-in-arms.
26
26
  //
27
27
  // KNOWN INTERACTIONS, both byte-level rather than soundness. This pass is unconditional like
28
- // `dce.ts` and `basecse.ts` rather than a differ-refereed lever, and the argument those files each
28
+ // `dce.ts` and `basecse.ts` rather than a differ-refereed variation, and the argument those files each
29
29
  // state for themselves applies here too and was missing: a wrong merge changes recompiled bytes and
30
30
  // surfaces as a LOST match under the zero-lost gate, never as wrong C.
31
31
  //
@@ -49,7 +49,7 @@
49
49
  // ways in `test/tailmerge.test.ts`.
50
50
  // A ranked row is not stuck with either order: `/copy-defpos` (rank.ts) enumerates the
51
51
  // def-position spelling beside the record's, so the merged form is a candidate the differ can
52
- // pick on bytes — on `CountCollectedGems` its gate admits the axis on the map-ful and map-less
52
+ // pick on bytes — on `CountCollectedGems` its gate admits the variation on the map-ful and map-less
53
53
  // lifts alike. What no arm of the fan spells is the third form, the hidden statement merged
54
54
  // while KEEPING the record's order.
55
55
  //
package/src/l3/typing.ts CHANGED
@@ -21,7 +21,7 @@ import { IrType, T, scalarTypeForAccess } from '../ir/types';
21
21
  import { type Expr, type SFn, exprChildren } from './ast';
22
22
 
23
23
  /** The declared type of a printed variable — the env `exprCType` judges rendered C against.
24
- * THE one copy of the SFn→env derivation — printers, contracts and L3 levers alike: each
24
+ * THE one copy of the SFn→env derivation — printers, contracts and L3 respell variations alike: each
25
25
  * consumer judging against anything but the declarations it emits would let them disagree. */
26
26
  export type VarTypes = (name: string) => IrType | undefined;
27
27
 
@@ -133,7 +133,7 @@ export function arithConversionSignedness(l: Expr, r: Expr, varType: VarTypes):
133
133
  *
134
134
  * THE one rendered-signedness judgment, and it lives beside the declarations it judges against
135
135
  * because every consumer shares it: the C-family backend's operand pin, structure.ts's
136
- * unsigned-compare gate (the /uns-cmp axis) and its signed-compare pin, and — through
136
+ * unsigned-compare gate (the /uns-cmp variation) and its signed-compare pin, and — through
137
137
  * `arithConversionSignedness` above — initfirst's compare-meaning gate. Two of those models
138
138
  * disagreeing about one expression is the drift this placement prevents.
139
139
  *
@@ -149,7 +149,7 @@ export function arithConversionSignedness(l: Expr, r: Expr, varType: VarTypes):
149
149
  * instruction-for-instruction the same where `f` really returns `int`); a missing one is a
150
150
  * miscompile — with `u32 f(void);` in scope `f() / a` calls `__udivsi3` where the machine called
151
151
  * `__divsi3`, and `if (f() >= 0)` compiles to `bl f; mov r0, #0`, comparison and both arms gone.
152
- * The one consumer that reads `undefined` the other way is the /uns-cmp axis's own gate, which is
152
+ * The one consumer that reads `undefined` the other way is the /uns-cmp variation's own gate, which is
153
153
  * asking whether an operand is ALREADY unsigned.
154
154
  *
155
155
  * Anything narrower than 32 bits promotes to `int` and is therefore SIGNED, whatever it was