@asmlift/core 0.6.0 → 0.8.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 +48 -24
- package/package.json +1 -1
- package/src/backend/cfamily.ts +39 -11
- package/src/backend/pascal.ts +2 -2
- package/src/codegen-flags.ts +640 -0
- package/src/contracts.ts +60 -11
- package/src/frontend/disasm.ts +141 -11
- package/src/frontend/high-half.ts +149 -0
- package/src/frontend/mips.ts +458 -209
- package/src/frontend/ppc.ts +332 -67
- package/src/frontend/reloc-symbol.ts +109 -0
- package/src/frontend/splat.ts +56 -18
- package/src/frontend/ssa.ts +127 -30
- package/src/frontend/stackargs.ts +420 -0
- package/src/frontend/thumb.ts +209 -232
- package/src/ir/alias.ts +24 -0
- package/src/ir/core.ts +70 -3
- package/src/ir/opcodes.ts +52 -7
- package/src/ir/parse.ts +7 -1
- 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 +6 -6
- package/src/l3/argcopy.ts +269 -0
- package/src/l3/ast.ts +110 -22
- package/src/l3/basecse.ts +50 -30
- package/src/l3/coalesce.ts +118 -61
- 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 +32 -29
- 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/tailret.ts +70 -0
- package/src/l3/typing.ts +3 -3
- package/src/l3/unmerge.ts +483 -59
- package/src/l3/unreduce.ts +15 -14
- 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/mangle.ts +49 -0
- package/src/pattern/engine.ts +132 -17
- package/src/pipeline.ts +39 -16
- 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 +345 -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 +133 -3
- package/src/raise/pre-recovery.ts +100 -25
- package/src/raise/retsink.ts +389 -19
- package/src/raise/shortcircuit.ts +595 -34
- package/src/raise/structs.ts +4 -4
- package/src/raise/tailsink.ts +141 -0
- package/src/rank-declare.ts +21 -13
- package/src/{rank-axes.ts → rank-variations.ts} +319 -189
- package/src/rank.ts +1176 -805
- 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/retspell.ts +95 -0
- package/src/structure/structure.ts +1425 -201
- package/src/structure/switch-recover.ts +101 -8
- package/src/symbols.ts +127 -6
- package/src/target.ts +374 -44
- package/src/trace.ts +28 -19
- package/src/variation-definitions.ts +1590 -0
- package/src/variation-gates.ts +92 -0
- package/src/variation-tokens.ts +356 -0
package/src/raise/paramwidth.ts
CHANGED
|
@@ -21,6 +21,76 @@
|
|
|
21
21
|
// narrow parameter with the `extsb`/`extsh` the frontend lifts to the same op, and the synthetic
|
|
22
22
|
// `sextb`/`tos8` rows keep matching on that toolchain through this pass.
|
|
23
23
|
//
|
|
24
|
+
// BUT THE PROLOGUE TEST IS NOT UNIVERSAL EVIDENCE, AND ON MIPS IT IS NO EVIDENCE AT ALL. Everything
|
|
25
|
+
// above reads the extension's POSITION, which works only where the compiler puts a declaration's
|
|
26
|
+
// extension somewhere a body cast's never goes. IDO 7.1 at -O2 does not: it leads the function with
|
|
27
|
+
// the `sll` for BOTH spellings. Two OTHER facts separate them there, and it takes both — each one
|
|
28
|
+
// alone is refuted by a compiled counterexample, at the row's own flags:
|
|
29
|
+
//
|
|
30
|
+
// home store widened in place
|
|
31
|
+
// int f(s8 x){ return x; } sw a0 sll a0,a0,0x18 the declaration
|
|
32
|
+
// int f(s32 x){ return (s8)x; } — sll v0,a0,0x18 a body cast
|
|
33
|
+
// int f(int x){ x = (signed char)x; return x; } — sll a0,a0,0x18 NOT homed
|
|
34
|
+
// int f(long long x){ return (signed char)x; } sw a1 sll v0,a1,0x18 NOT in place
|
|
35
|
+
//
|
|
36
|
+
// The last two lines are why the conjunction is the claim. A DEAD ABI ARGUMENT HOME STORE on its
|
|
37
|
+
// own is not "declared narrow" — IDO emits one wherever the incoming register value goes unused,
|
|
38
|
+
// whatever that value's type:
|
|
39
|
+
//
|
|
40
|
+
// int unused1(int x){ return 7; } sw a0,0(sp) / jr ra / li v0,7
|
|
41
|
+
// int ptr1(int *p, int y){ return y; } sw a0,0(sp) / jr ra / move v0,a1
|
|
42
|
+
// int ll2i(long long x){ return (int)x; } sw a0,0(sp) / sw a1,4(sp) / jr ra / move v0,a1
|
|
43
|
+
// int used2(int x, int y){ return x+y; } jr ra / addu v0,a0,a1 (no store at all)
|
|
44
|
+
//
|
|
45
|
+
// — so the store says the incoming register was not consumed, not that anything was declared
|
|
46
|
+
// narrow, and on the `long long` line that is a 64-bit parameter whose low half the extension would
|
|
47
|
+
// narrow to `s8`; `widened-elsewhere` is what refuses it. WIDENING IN PLACE on its own is not
|
|
48
|
+
// "declared narrow" either: the third line is a plain `int` the source re-assigns, and only the
|
|
49
|
+
// absent home store tells it from the first.
|
|
50
|
+
//
|
|
51
|
+
// The conjunction is NECESSARY-and-measured, not necessary-and-sufficient, and it errs toward
|
|
52
|
+
// refusing: `int m2(signed char a){ int i,s=0; for(i=0;i<10;i++) s+=arr[i]+a; return s; }` is a
|
|
53
|
+
// narrow declaration IDO widens into a SCRATCH register under register pressure (`sll a1,a0,0x18`),
|
|
54
|
+
// so this pass leaves its parameter wide and re-spells the cast. That is a worse-reading answer,
|
|
55
|
+
// not a wrong one.
|
|
56
|
+
//
|
|
57
|
+
// THE PAIR SAYS A NARROW DECLARATION EXISTS, NOT WHICH ONE. The WIDTH comes from the extension,
|
|
58
|
+
// and where a wider mask sits on that extension's result the declaration could have been the wider
|
|
59
|
+
// type — `int f(u16 x){ x = (signed char)x; return x; }` and `int f(s8 x){ return x & 0xffff; }`
|
|
60
|
+
// are ONE object at the row's own flags (`sw a0,0(sp) / sll a0,a0,0x18 / sra a0,a0,0x18 / andi
|
|
61
|
+
// v0,a0,0xffff`), so the asm decides nothing between them and this pass answers `s8`. Refusing on
|
|
62
|
+
// that disagreement is not the repair, because the spelling a refusal falls back to is a DIFFERENT
|
|
63
|
+
// object: `int f(int a){ return ((a << 24) >> 24) & 0xffff; }` is four words with no home store and
|
|
64
|
+
// both shifts in `v0`, so the refusal would cost the byte match under BOTH readings. Where the
|
|
65
|
+
// caller declares the parameter `proto-width` takes the tiebreak; where nobody does, the two
|
|
66
|
+
// readings recompile alike and only a PROTOTYPED CALL SITE of this function could tell them apart.
|
|
67
|
+
//
|
|
68
|
+
// Ungated, this pass narrows the body cast too and loses `synthetic:tos8:ido7.1`, a MATCH. The two
|
|
69
|
+
// MIPS GCCs are a third case again: their two spellings are one BYTE-IDENTICAL object, so nothing
|
|
70
|
+
// in the asm decides the width and the honest answer is to leave the extension standing.
|
|
71
|
+
//
|
|
72
|
+
// So which fact settles the width is a per-COMPILER question, asked as
|
|
73
|
+
// `compilerBehaviors.narrowParamWitness` and answered by `no-declaration-witness`, `unhomed-param`
|
|
74
|
+
// and `widened-elsewhere` below. Both facts it reads are destroyed at lift and survive as the
|
|
75
|
+
// frontend's `Fn.paramEvidence` (ir/core.ts).
|
|
76
|
+
//
|
|
77
|
+
// `not-prologue` STILL FIRES ON SUCH A TARGET, and it costs rather than protects there. IDO's
|
|
78
|
+
// scheduler interleaves the widenings of several narrow parameters with the arithmetic that
|
|
79
|
+
// consumes the earlier ones, and the scan below stops at the first value-reading op, so only the
|
|
80
|
+
// LEADING parameters are seen as prologue:
|
|
81
|
+
//
|
|
82
|
+
// int d4(s8 a, s8 b, s8 c){ return a+b+c; } sll a1 / sll a0 / sll a2 / sra a0 / sra a1 /
|
|
83
|
+
// addu t6,a0,a1 / sra a2 / addu v0,t6,a2
|
|
84
|
+
// recovered s32 d4(s8 a0, s8 a1, s32 a2) { return a0 + a1 + (s8)a2; }
|
|
85
|
+
// int m3(s16 a, s16 b, s16 c, s16 d){ return a*b+c*d; } all four widenings precede the first
|
|
86
|
+
// recovered s32 m3(s16 a0, s16 a1, s16 a2, s16 a3) multiply — all four narrow
|
|
87
|
+
//
|
|
88
|
+
// So the limit is the SCHEDULE's, not the declaration's, and the gate answers in the refusing
|
|
89
|
+
// direction on the parameters it cuts off. Kept because it is the sound direction and because it is
|
|
90
|
+
// the only gate that keeps a body cast behind real body code from being read as a prologue widening
|
|
91
|
+
// on the position-witness targets; NO row on either tier is known to turn on the IDO half of it —
|
|
92
|
+
// `d4` above is a probe, not a row, and lifting the scan is a change with its own measurement to make.
|
|
93
|
+
//
|
|
24
94
|
// WHAT THE PROLOGUE TEST CANNOT SEE, and why the declaration settles it. The scan steps over the
|
|
25
95
|
// pure materializations agbcc interleaves among the extensions, so a constant the scheduler HOISTED
|
|
26
96
|
// above a mid-body cast leaves `pb` looking like `pa`:
|
|
@@ -43,11 +113,21 @@
|
|
|
43
113
|
// What that refusal protects is a function this pass never compiles: agbcc truncates at every
|
|
44
114
|
// PROTOTYPED CALL SITE of a narrow-declared callee — `lsl/asr` ahead of the `bl`, two Thumb
|
|
45
115
|
// instructions per site — so a wrong width here costs bytes the per-function differ cannot see.
|
|
116
|
+
//
|
|
117
|
+
// FUSED BEHIND A POOL LOAD. The prologue scan steps over a pool-loaded address, and a body cast
|
|
118
|
+
// with nothing else ahead of it — `gB = gW[(u8)a]` is `ldr r2,=gB / ldr r1,=gW / lsl r0,#24 /
|
|
119
|
+
// lsr r0,#22` — reaches it looking like a declaration once raise/extscale.ts has re-split the fused
|
|
120
|
+
// pair. The fold knows where the machine put that `lsl` and records it (`ScaleRecord.behindPool`),
|
|
121
|
+
// and `fused-behind-pool` reads the record: an unsigned declared parameter's `lsl` precedes every
|
|
122
|
+
// pool load in the benchmark's agbcc references (extscale.ts's header has the census). Only the
|
|
123
|
+
// fold's own extensions are judged this way; a plain cast's extension sits at its right shift,
|
|
124
|
+
// where its position says nothing about its `lsl`'s.
|
|
46
125
|
import { type Fn, type Op, type Value, replaceAllUsesWith, successorsOf } from '../ir/core';
|
|
47
126
|
import { CAST_WIDTHS, MATERIALIZING_OPS } from '../ir/opcodes';
|
|
48
127
|
import { T } from '../ir/types';
|
|
49
128
|
import { type Gate, firstRejection } from '../l3/gates';
|
|
50
129
|
import { type FnProto, declaredWidth } from '../proto';
|
|
130
|
+
import type { NarrowParamWitness } from '../target';
|
|
51
131
|
|
|
52
132
|
/** What the gates below judge: one entry parameter and the extension that reads it. */
|
|
53
133
|
export interface NarrowParamCandidate {
|
|
@@ -63,6 +143,15 @@ export interface NarrowParamCandidate {
|
|
|
63
143
|
uses: number;
|
|
64
144
|
/** the width the caller's own prototype declares for this parameter, if it declares one */
|
|
65
145
|
declared: number | undefined;
|
|
146
|
+
/** the extension is one raise/extscale.ts re-split from a fused pair whose `shl` the machine ran
|
|
147
|
+
* behind a pool load — see FUSED BEHIND A POOL LOAD */
|
|
148
|
+
fusedBehindPool: boolean;
|
|
149
|
+
/** what this compiler's object shows for a narrow declaration (target.ts `narrowParamWitness`) */
|
|
150
|
+
witness: NarrowParamWitness;
|
|
151
|
+
/** the machine stored this parameter to a stack slot nothing reads back (`ParamObservation`) */
|
|
152
|
+
homed: boolean;
|
|
153
|
+
/** the machine put the parameter's own widened value back in its argument register (`ParamObservation`) */
|
|
154
|
+
selfRedefined: boolean;
|
|
66
155
|
}
|
|
67
156
|
|
|
68
157
|
export const PARAM_WIDTH_GATES: readonly Gate<NarrowParamCandidate>[] = [
|
|
@@ -103,11 +192,41 @@ export const PARAM_WIDTH_GATES: readonly Gate<NarrowParamCandidate>[] = [
|
|
|
103
192
|
},
|
|
104
193
|
{
|
|
105
194
|
id: 'not-prologue',
|
|
106
|
-
why: 'an extension behind body code is where the SOURCE wrote the cast',
|
|
195
|
+
why: 'an extension behind body code is where the SOURCE wrote the cast — and on a schedule that interleaves them, the refusing answer',
|
|
107
196
|
sound: true,
|
|
108
197
|
guardedBy: 'param-width.test.ts: an extension behind a nullary call is body code',
|
|
109
198
|
rejects: (c) => !c.inPrologue,
|
|
110
199
|
},
|
|
200
|
+
{
|
|
201
|
+
id: 'no-declaration-witness',
|
|
202
|
+
why: 'this compiler spells a narrow declaration and a body cast the same way, so the asm decides nothing',
|
|
203
|
+
sound: true,
|
|
204
|
+
guardedBy:
|
|
205
|
+
'param-width.test.ts: a compiler whose two spellings are one object refuses the narrowing, measured or not',
|
|
206
|
+
rejects: (c) => c.witness === 'none',
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
id: 'unhomed-param',
|
|
210
|
+
why: 'this compiler homes a narrow DECLARED parameter, so the absent home store proves the declaration was wide',
|
|
211
|
+
sound: true,
|
|
212
|
+
guardedBy: 'param-width.test.ts: a homing compiler refuses the parameter it did not home',
|
|
213
|
+
rejects: (c) => c.witness === 'home-store-and-in-place' && !c.homed,
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
id: 'widened-elsewhere',
|
|
217
|
+
why: 'this compiler widens a narrow DECLARED parameter in the argument register itself, so a widening that lands in a scratch register is not a declaration',
|
|
218
|
+
sound: true,
|
|
219
|
+
guardedBy:
|
|
220
|
+
'param-width.test.ts: \u2026and refuses one it homed but widened SOMEWHERE ELSE \u2014 that is a 64-bit half',
|
|
221
|
+
rejects: (c) => c.witness === 'home-store-and-in-place' && !c.selfRedefined,
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
id: 'fused-behind-pool',
|
|
225
|
+
why: 'a fused cast behind a pool load is body code if unsigned, and either width is the same object if signed',
|
|
226
|
+
sound: true,
|
|
227
|
+
guardedBy: 'extscale.test.ts: a body cast behind nothing but a pool load keeps its parameter wide',
|
|
228
|
+
rejects: (c) => c.fusedBehindPool,
|
|
229
|
+
},
|
|
111
230
|
];
|
|
112
231
|
|
|
113
232
|
/** How many times `v` is read anywhere in `fn` — op operands and branch arguments alike. */
|
|
@@ -125,13 +244,20 @@ function useCount(fn: Fn, v: Value): number {
|
|
|
125
244
|
}
|
|
126
245
|
|
|
127
246
|
/** Type an entry parameter at the width its prologue extension proves, and drop the extension.
|
|
128
|
-
* `
|
|
129
|
-
*
|
|
247
|
+
* `witness` is what this compiler's object shows for a narrow declaration (target.ts
|
|
248
|
+
* `narrowParamWitness`); `self` is the prototype the caller supplied for THIS function, if any;
|
|
249
|
+
* `fusedBehindPool` is raise/extscale.ts's record of the extensions it re-split behind a pool load
|
|
250
|
+
* (`ScaleRecord.behindPool`). Returns the number of parameters narrowed. */
|
|
130
251
|
export function narrowEntryParams(
|
|
131
252
|
fn: Fn,
|
|
253
|
+
witness: NarrowParamWitness,
|
|
132
254
|
self?: FnProto,
|
|
133
255
|
gates: readonly Gate<NarrowParamCandidate>[] = PARAM_WIDTH_GATES,
|
|
256
|
+
fusedBehindPool: ReadonlySet<Op> = new Set(),
|
|
134
257
|
): number {
|
|
258
|
+
// ABSENT ⇒ NEITHER OBSERVATION, which is the refusing direction on a target that reads them: a
|
|
259
|
+
// function nobody measured (parsed IR, a hand-built fn) is never narrowed on evidence never taken.
|
|
260
|
+
const evidence = fn.paramEvidence;
|
|
135
261
|
const entry = fn.blocks[0];
|
|
136
262
|
const declared = Array.isArray(self?.params) ? self.params.map(declaredWidth) : [];
|
|
137
263
|
const entryIsJoin = fn.blocks.some((b) => successorsOf(b).includes(entry));
|
|
@@ -166,6 +292,10 @@ export function narrowEntryParams(
|
|
|
166
292
|
inPrologue: prologue.has(op),
|
|
167
293
|
uses: useCount(fn, p),
|
|
168
294
|
declared: declared[entry.params.indexOf(p)],
|
|
295
|
+
fusedBehindPool: fusedBehindPool.has(op),
|
|
296
|
+
witness,
|
|
297
|
+
homed: evidence?.get(p)?.deadHome ?? false,
|
|
298
|
+
selfRedefined: evidence?.get(p)?.selfRedefined ?? false,
|
|
169
299
|
};
|
|
170
300
|
if (firstRejection(gates, c) !== null) {
|
|
171
301
|
continue;
|
|
@@ -18,12 +18,21 @@ import type { TargetDescription } from '../target';
|
|
|
18
18
|
import { recognizeArrays } from './arrays';
|
|
19
19
|
import { recognizeConsts } from './const';
|
|
20
20
|
import { recognizeDivPow2 } from './divpow2';
|
|
21
|
+
import {
|
|
22
|
+
type PoolOrder,
|
|
23
|
+
type ScaleRecord,
|
|
24
|
+
emptyScaleRecord,
|
|
25
|
+
foldScaledExtensions,
|
|
26
|
+
foldsShiftPairCasts,
|
|
27
|
+
poolOrderOf,
|
|
28
|
+
restoreUnclaimedScales,
|
|
29
|
+
} from './extscale';
|
|
21
30
|
import { numberPureValues } from './gvn';
|
|
22
31
|
import { recognizeMagicDivision } from './magicdiv';
|
|
23
32
|
import { recognizeMemberArrays } from './memberarrays';
|
|
24
33
|
import { rerootNarrowReads } from './narrow';
|
|
25
34
|
import { type MergeShape, mergeShapes, narrowBlockLocals } from './narrowlocal';
|
|
26
|
-
import { narrowEntryParams } from './paramwidth';
|
|
35
|
+
import { PARAM_WIDTH_GATES, narrowEntryParams } from './paramwidth';
|
|
27
36
|
import { type BranchShortCircuitOptions, recognizeBranchShortCircuit, recognizeShortCircuit } from './shortcircuit';
|
|
28
37
|
import { recognizeSoftDiv } from './softdiv';
|
|
29
38
|
import { recognizeStructArrays } from './struct-arrays';
|
|
@@ -34,7 +43,7 @@ import { recognizeStructs } from './structs';
|
|
|
34
43
|
* A field here selects between spellings its pass can already produce; none of them relaxes a
|
|
35
44
|
* soundness refusal, and each recognizer documents its own. */
|
|
36
45
|
export interface PreRecoveryOptions {
|
|
37
|
-
/** raise/shortcircuit.ts `recognizeBranchShortCircuit` — the connective-vs-comparison-tree
|
|
46
|
+
/** raise/shortcircuit.ts `recognizeBranchShortCircuit` — the connective-vs-comparison-tree question. */
|
|
38
47
|
shortCircuit?: BranchShortCircuitOptions;
|
|
39
48
|
}
|
|
40
49
|
|
|
@@ -44,9 +53,18 @@ export interface PreRecoveryOptions {
|
|
|
44
53
|
* by then divpow2 has deleted a block, both short-circuit folds have rewritten edges, and the
|
|
45
54
|
* eight `dce: true` passes have changed op counts. So the driver reads it here and hands it down. */
|
|
46
55
|
export interface PreRecoveryFacts {
|
|
47
|
-
/** the join shape of every block, for raise/narrowlocal.ts's `edge-extends
|
|
48
|
-
*
|
|
56
|
+
/** the join shape of every block, for raise/narrowlocal.ts's `edge-extends` and, downstream of
|
|
57
|
+
* pre-recovery entirely, raise/retsink.ts's `pre-diamond`. Blocks a later pass creates are
|
|
58
|
+
* absent, and absent reads as "no diamond" — the refusing direction, in both readers. */
|
|
49
59
|
mergeShapes: Map<Block, MergeShape>;
|
|
60
|
+
/** which entry-block ops the machine ran after its first pool-loaded address, for
|
|
61
|
+
* raise/extscale.ts's behind-a-pool-load record. Read HERE because `addrnum`, the first pass,
|
|
62
|
+
* hoists duplicated addresses to the head of the entry block and that order is gone after it. */
|
|
63
|
+
poolOrder: PoolOrder;
|
|
64
|
+
/** THE ONE FACT A PASS WRITES rather than the lift: what raise/extscale.ts's fold made, for the
|
|
65
|
+
* two passes after it that read it — paramwidth's `fused-behind-pool` gate and
|
|
66
|
+
* `extscale-restore`. Empty until the fold runs. */
|
|
67
|
+
scales: ScaleRecord;
|
|
50
68
|
}
|
|
51
69
|
|
|
52
70
|
export interface PreRecoveryPass {
|
|
@@ -55,7 +73,9 @@ export interface PreRecoveryPass {
|
|
|
55
73
|
/** run the recognizer; returns a truthy value (a change count, or `true`) iff it CHANGED the IR.
|
|
56
74
|
* `self` is the prototype the caller supplied for the function being raised — read only by
|
|
57
75
|
* parameter-width, which checks its inference against a declared width. `lifted` is the
|
|
58
|
-
* pre-pass
|
|
76
|
+
* pre-pass snapshot — read by narrow-local (its CFG) and scaled-extension (its entry order) —
|
|
77
|
+
* plus the fold's record, which scaled-extension writes and parameter-width and
|
|
78
|
+
* scaled-extension-restore read. */
|
|
59
79
|
run: (
|
|
60
80
|
fn: Fn,
|
|
61
81
|
self: FnProto | undefined,
|
|
@@ -70,9 +90,10 @@ export interface PreRecoveryPass {
|
|
|
70
90
|
}
|
|
71
91
|
|
|
72
92
|
/** THE ordered pre-recovery pass list — the single source of truth shared by pipeline / rank / report.
|
|
73
|
-
* address-numbering → const-materialize → magic-division → pow2-division → soft-division →
|
|
74
|
-
*
|
|
75
|
-
* narrow-local → parameter-width
|
|
93
|
+
* address-numbering → const-materialize → magic-division → pow2-division → soft-division →
|
|
94
|
+
* scaled-extension → array-legalize → struct-array → member-array → struct-pointer → short-circuit →
|
|
95
|
+
* branch-short-circuit → narrow-reads → narrow-local → parameter-width → scaled-extension-restore.
|
|
96
|
+
* See each recognizer's file for the rationale. */
|
|
76
97
|
export const PRE_RECOVERY_PASSES: PreRecoveryPass[] = [
|
|
77
98
|
// FIRST: collapsing duplicate address definitions removes block params every later recognizer
|
|
78
99
|
// would otherwise have to reason around, and it can only shrink the value graph.
|
|
@@ -81,10 +102,11 @@ export const PRE_RECOVERY_PASSES: PreRecoveryPass[] = [
|
|
|
81
102
|
// Numbering alone is not enough and not safe to ship alone: collapsing the duplicates leaves a
|
|
82
103
|
// block param whose edges now all carry one value, and the structurer still destroys THAT into
|
|
83
104
|
// a local (it only reuses a name a carrier already has, and an inlined `gaddr` has none).
|
|
84
|
-
// Numbering alone
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
//
|
|
105
|
+
// Numbering alone cost kleod:UpdateHUDCounterDisplay its match (measured on kleod's
|
|
106
|
+
// kl-eod-decomp rows, retired 2026-09-13), so the pair is the atomic unit, expressed as a body
|
|
107
|
+
// rather than a sum of two unrelated counts. It was NOT monotone, which is worth knowing before
|
|
108
|
+
// tuning either half: dropping the cleanup IMPROVED kleod:ConfigureEntityBehavior and
|
|
109
|
+
// kleod:CountCollectedGems, neither of them near matching then.
|
|
88
110
|
run: (fn) => {
|
|
89
111
|
const n = numberPureValues(fn);
|
|
90
112
|
return n + simplifyTrivialPhis(fn);
|
|
@@ -100,6 +122,17 @@ export const PRE_RECOVERY_PASSES: PreRecoveryPass[] = [
|
|
|
100
122
|
// beside magicdiv so that a reader looking for division recovery finds both together.
|
|
101
123
|
{ id: 'divpow2', run: recognizeDivPow2, dce: true },
|
|
102
124
|
{ id: 'softdiv', run: (fn) => recognizeSoftDiv(fn), dce: false, gate: (t) => !t.capabilities.hwDivide },
|
|
125
|
+
// AFTER `const`, which folds a shift pair over a constant to the constant it computes, and BEFORE
|
|
126
|
+
// the three array recognizers, whose input this pass produces: `shl(ext(x), k)` is an element
|
|
127
|
+
// scale they legalize and the fused pair is not. `dce: true` — the `shl` a fold leaves readerless.
|
|
128
|
+
// It reads `lifted.poolOrder`, the entry block's order before `addrnum` hoisted its addresses,
|
|
129
|
+
// and writes `lifted.scales`.
|
|
130
|
+
{
|
|
131
|
+
id: 'extscale',
|
|
132
|
+
run: (fn, _self, _opts, _target, lifted) => foldScaledExtensions(fn, lifted.poolOrder, lifted.scales),
|
|
133
|
+
dce: true,
|
|
134
|
+
gate: foldsShiftPairCasts,
|
|
135
|
+
},
|
|
103
136
|
{ id: 'arrays', run: recognizeArrays, dce: true },
|
|
104
137
|
// struct-arrays AFTER arrays (scalar stride==width shapes are claimed first — see the
|
|
105
138
|
// discriminator note in raise/struct-arrays.ts) and BEFORE structs (an element's field
|
|
@@ -128,28 +161,35 @@ export const PRE_RECOVERY_PASSES: PreRecoveryPass[] = [
|
|
|
128
161
|
// counts 0 fused heads across the 782 rows that lift map-lessly, so the hazard has no producer
|
|
129
162
|
// there. The reverse direction never could: the value form replaces its head's `cond_br` with a
|
|
130
163
|
// `br`, which this pass never matches.
|
|
164
|
+
// The `target` argument is read by ONE conjunct of ONE gate, as at `narrowlocal` below:
|
|
165
|
+
// `read-behind-effect`'s "a local costs a second load", compiled on all five bench toolchains and
|
|
166
|
+
// true on agbcc alone (raise/shortcircuit.ts, target.ts `reloadsLocalReread`).
|
|
131
167
|
{
|
|
132
168
|
id: 'branch-shortcircuit',
|
|
133
|
-
run: (fn, _self, opts) =>
|
|
169
|
+
run: (fn, _self, opts, target) =>
|
|
170
|
+
recognizeBranchShortCircuit(fn, {
|
|
171
|
+
...opts.shortCircuit,
|
|
172
|
+
reloadsLocalReread: target.compilerBehaviors.reloadsLocalReread,
|
|
173
|
+
}),
|
|
134
174
|
dce: true,
|
|
135
175
|
},
|
|
136
176
|
// LAST, and the position IS load-bearing: this pass reads the CFG's edge arguments to find a
|
|
137
177
|
// loop variable's next value, and both short-circuit folds above rewrite the very edges it reads.
|
|
138
178
|
// `dce: false` — the rewrite orphans nothing, since the operand it drops keeps its other use.
|
|
139
179
|
{ id: 'narrow', run: rerootNarrowReads, dce: false },
|
|
140
|
-
// The two WIDTH passes, last and in either order relative to each other: each
|
|
141
|
-
// extension and retypes the parameter that fed it, so every recognizer above sees
|
|
142
|
-
// was written against and neither can match a shape the other creates. They are
|
|
143
|
-
// construction — `narrowlocal` refuses an entry parameter, `paramwidth` reads only
|
|
144
|
-
// parameters — and `narrowlocal` cannot take an extension `narrow` above wants either, since
|
|
145
|
-
// parameter carrying BOTH a `zext` and a `sext` has two readers and is refused.
|
|
180
|
+
// The two WIDTH passes, last of the recognizers and in either order relative to each other: each
|
|
181
|
+
// only DELETES an extension and retypes the parameter that fed it, so every recognizer above sees
|
|
182
|
+
// the shape it was written against and neither can match a shape the other creates. They are
|
|
183
|
+
// disjoint by construction — `narrowlocal` refuses an entry parameter, `paramwidth` reads only
|
|
184
|
+
// entry parameters — and `narrowlocal` cannot take an extension `narrow` above wants either, since
|
|
185
|
+
// a parameter carrying BOTH a `zext` and a `sext` has two readers and is refused.
|
|
146
186
|
// `dce: false` on both — the extension each drops is spliced out in place, and its result has no
|
|
147
187
|
// other reader.
|
|
148
188
|
// The `target` argument is read by ONE conjunct of ONE gate — see raise/narrowlocal.ts's
|
|
149
189
|
// `NarrowLocalOptions`. A whole-pass `gate` would be wrong: the pass's SOUND rules are claims
|
|
150
190
|
// about C and run everywhere; only the join-shape evidence is a claim about gcc 2.x's optimizer.
|
|
151
|
-
// That same conjunct is why
|
|
152
|
-
//
|
|
191
|
+
// That same conjunct is why it reads `lifted.mergeShapes`: every pass above it can rewrite the
|
|
192
|
+
// CFG whose shape it judges.
|
|
153
193
|
{
|
|
154
194
|
id: 'narrowlocal',
|
|
155
195
|
run: (fn, _self, _opts, target, lifted) =>
|
|
@@ -161,20 +201,54 @@ export const PRE_RECOVERY_PASSES: PreRecoveryPass[] = [
|
|
|
161
201
|
),
|
|
162
202
|
dce: false,
|
|
163
203
|
},
|
|
164
|
-
|
|
204
|
+
// The `target` argument is read by TWO of this pass's gates, and unlike `narrowlocal`'s single
|
|
205
|
+
// conjunct it is not a tuning knob: `narrowParamWitness` says WHICH fact in the object settles a
|
|
206
|
+
// parameter's declared width on this compiler, and a target that names none refuses outright.
|
|
207
|
+
{
|
|
208
|
+
id: 'paramwidth',
|
|
209
|
+
run: (fn, self, _opts, target, lifted) =>
|
|
210
|
+
narrowEntryParams(
|
|
211
|
+
fn,
|
|
212
|
+
target.compilerBehaviors.narrowParamWitness ?? 'none',
|
|
213
|
+
self,
|
|
214
|
+
PARAM_WIDTH_GATES,
|
|
215
|
+
lifted.scales.behindPool,
|
|
216
|
+
),
|
|
217
|
+
dce: false,
|
|
218
|
+
},
|
|
219
|
+
// LAST, after every pass that can CLAIM what `extscale` exposed — the two width passes above take
|
|
220
|
+
// an extension, the array recognizers a scale. What none of them took goes back to the pair the
|
|
221
|
+
// frontend lifted (raise/extscale.ts, WHAT NOBODY CLAIMED). `dce: true` — the extension it leaves
|
|
222
|
+
// readerless.
|
|
223
|
+
{
|
|
224
|
+
id: 'extscale-restore',
|
|
225
|
+
run: (fn, _self, _opts, _target, lifted) => restoreUnclaimedScales(fn, lifted.scales),
|
|
226
|
+
dce: true,
|
|
227
|
+
gate: foldsShiftPairCasts,
|
|
228
|
+
},
|
|
165
229
|
];
|
|
166
230
|
|
|
167
231
|
/** Run the pre-recovery passes in order. For each pass whose gate passes and that CHANGES the IR, run
|
|
168
232
|
* `dce` when the pass declares it, then invoke `afterPass(pass, result)` (the caller's verify/trace
|
|
169
|
-
* hook), if given.
|
|
233
|
+
* hook), if given.
|
|
234
|
+
*
|
|
235
|
+
* RETURNS the facts it read, because a pass AFTER pre-recovery needs one of them too:
|
|
236
|
+
* `raise/retsink.ts`'s `pre-diamond` asks whether a return merge was a diamond in the ROM, and
|
|
237
|
+
* by its turn `raise/shortcircuit.ts` has manufactured diamonds that were not. The map is the
|
|
238
|
+
* driver's to compute — it is the only place that sees `fn` before the first pass — so handing it
|
|
239
|
+
* back is cheaper and truer than recomputing something later that cannot be recomputed. */
|
|
170
240
|
export function runPreRecovery(
|
|
171
241
|
fn: Fn,
|
|
172
242
|
target: TargetDescription,
|
|
173
243
|
afterPass?: (pass: PreRecoveryPass, result: number | boolean) => void,
|
|
174
244
|
self?: FnProto,
|
|
175
245
|
opts: PreRecoveryOptions = {},
|
|
176
|
-
):
|
|
177
|
-
const lifted: PreRecoveryFacts = {
|
|
246
|
+
): PreRecoveryFacts {
|
|
247
|
+
const lifted: PreRecoveryFacts = {
|
|
248
|
+
mergeShapes: mergeShapes(fn),
|
|
249
|
+
poolOrder: poolOrderOf(fn),
|
|
250
|
+
scales: emptyScaleRecord(),
|
|
251
|
+
};
|
|
178
252
|
for (const pass of PRE_RECOVERY_PASSES) {
|
|
179
253
|
if (pass.gate && !pass.gate(target)) {
|
|
180
254
|
continue;
|
|
@@ -187,4 +261,5 @@ export function runPreRecovery(
|
|
|
187
261
|
afterPass?.(pass, result);
|
|
188
262
|
}
|
|
189
263
|
}
|
|
264
|
+
return lifted;
|
|
190
265
|
}
|