@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.
- package/README.md +2 -2
- package/package.json +1 -1
- package/src/backend/cfamily.ts +39 -11
- package/src/contracts.ts +60 -11
- package/src/frontend/ssa.ts +1 -1
- package/src/frontend/thumb.ts +2 -2
- package/src/ir/alias.ts +24 -0
- package/src/ir/core.ts +8 -0
- package/src/ir/opcodes.ts +43 -7
- package/src/ir/simplify.ts +1 -1
- package/src/l3/address.ts +2 -2
- package/src/l3/advance.ts +373 -0
- package/src/l3/argbase.ts +4 -4
- package/src/l3/ast.ts +65 -21
- package/src/l3/basecse.ts +48 -28
- package/src/l3/coalesce.ts +9 -9
- package/src/l3/gates.ts +75 -1
- package/src/l3/hoist.ts +1 -1
- package/src/l3/homesplit.ts +13 -13
- package/src/l3/initfirst.ts +3 -3
- package/src/l3/inlinebase.ts +16 -16
- package/src/l3/mentions.ts +68 -5
- package/src/l3/mulfirst.ts +3 -3
- package/src/l3/nearbase.ts +4 -4
- package/src/l3/offmember.ts +5 -5
- package/src/l3/parkfirst.ts +6 -6
- package/src/l3/pollguard.ts +3 -3
- package/src/l3/ptrfield.ts +4 -4
- package/src/l3/regspell.ts +8 -8
- package/src/l3/reindex.ts +22 -17
- package/src/l3/scopebase.ts +28 -25
- package/src/l3/sinkinit.ts +7 -7
- package/src/l3/slotorder.ts +3 -3
- package/src/l3/storage.ts +1 -1
- package/src/l3/tailmerge.ts +2 -2
- package/src/l3/typing.ts +3 -3
- package/src/l3/unmerge.ts +483 -59
- package/src/l3/unreduce.ts +13 -13
- package/src/l3/volatileptr.ts +11 -11
- package/src/l3/volatileval.ts +11 -11
- package/src/l3/volstore.ts +16 -16
- package/src/l3/zerosub.ts +6 -6
- package/src/pattern/engine.ts +4 -4
- package/src/pipeline.ts +17 -5
- package/src/proto.ts +2 -2
- package/src/raise/const.ts +203 -3
- package/src/raise/divpow2.ts +2 -2
- package/src/raise/extscale.ts +342 -0
- package/src/raise/globalshape.ts +32 -12
- package/src/raise/gvn.ts +2 -2
- package/src/raise/magicdiv.ts +2 -2
- package/src/raise/memberarrays.ts +4 -4
- package/src/raise/narrowlocal.ts +18 -2
- package/src/raise/paramwidth.ts +24 -2
- package/src/raise/pre-recovery.ts +90 -25
- package/src/raise/retsink.ts +381 -15
- package/src/raise/shortcircuit.ts +595 -34
- package/src/raise/structs.ts +4 -4
- package/src/raise/tailsink.ts +126 -0
- package/src/rank-declare.ts +4 -4
- package/src/{rank-axes.ts → rank-variations.ts} +319 -189
- package/src/rank.ts +1148 -803
- package/src/structure/analysis.ts +87 -90
- package/src/structure/bitfields.ts +130 -30
- package/src/structure/globalaccess.ts +30 -4
- package/src/structure/namecoalesce.ts +32 -13
- package/src/structure/structure.ts +1415 -200
- package/src/structure/switch-recover.ts +100 -7
- package/src/symbols.ts +127 -6
- package/src/target.ts +155 -35
- package/src/trace.ts +1 -1
- package/src/variation-definitions.ts +1540 -0
- package/src/variation-gates.ts +89 -0
- package/src/variation-tokens.ts +355 -0
package/src/l3/scopebase.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// L3
|
|
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-
|
|
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
|
|
18
|
-
// kleod:UpdateHUDCounterDisplay its match
|
|
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
|
|
32
|
-
// scope-aware; it is NOT a claim about what the
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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`
|
|
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
|
|
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
|
|
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: '
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
509
|
+
why: `counted over one region’s 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
|
|
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: '
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
package/src/l3/sinkinit.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
// L3
|
|
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
|
|
6
|
-
// ask for this placement directly, through the same `l3/hoist.ts` mechanism this
|
|
7
|
-
// the
|
|
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
|
|
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
|
|
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
|
|
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
|
package/src/l3/slotorder.ts
CHANGED
|
@@ -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
|
|
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.
|
|
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
|
|
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
|
|
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`;
|
package/src/l3/tailmerge.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|