@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/target.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// asmlift — the Target: (isa, compiler) as first-class
|
|
1
|
+
// asmlift — the Target: (isa, compiler) as first-class fields. ABI + capabilities are DATA
|
|
2
2
|
// consumed generically by shared passes — never a target-name branch inside a shared pass
|
|
3
3
|
// (m2c's `arch.arch ==` leakage).
|
|
4
4
|
//
|
|
@@ -15,24 +15,27 @@
|
|
|
15
15
|
// • capabilities.flags → RESERVED, not yet read by any pass (PPC condition regs will).
|
|
16
16
|
// • capabilities.readOnlyAddressSinks → the Thumb frame-object audit: a frame address stored to
|
|
17
17
|
// one of these reached a device that only reads through it, so it does not retract `undef`.
|
|
18
|
-
// • capabilities.deviceRegisters →
|
|
18
|
+
// • capabilities.deviceRegisters → five readers, and they ask ONE question — "would a source
|
|
19
19
|
// have spelled this address `volatile`" — which is a question about SPELLING and may be
|
|
20
|
-
// approximate: the `/vol-store`
|
|
20
|
+
// approximate: the `/vol-store` variation's eligibility (l3/volstore.ts), rank.ts's volatility
|
|
21
21
|
// tie-break between two byte-identical spellings, the first half of `/unreduce`'s
|
|
22
|
-
// disjointness gate (l3/unreduce.ts),
|
|
23
|
-
// READ inline where the spelling it replaces would have qualified it (l3/homesplit.ts)
|
|
22
|
+
// disjointness gate (l3/unreduce.ts), the `/homesplit` pairing's refusal to leave a device
|
|
23
|
+
// READ inline where the spelling it replaces would have qualified it (l3/homesplit.ts), and
|
|
24
|
+
// the structurer's refusal to SPELL a dead memory read whose address no qualifier could ever
|
|
25
|
+
// reach (structure.ts `volatileQualifiable`, threaded through StructureOptions).
|
|
24
26
|
// • capabilities.deviceMemoryWriters → the MEMORY-MODEL question, which is a different one and
|
|
25
27
|
// may NOT be approximate: "can a write to this register make the DEVICE write ordinary
|
|
26
28
|
// memory". One reader — `/unreduce`'s second half. Split from `deviceRegisters` because
|
|
27
29
|
// conflating them recorded a false premise (see the field's own comment).
|
|
28
30
|
// • compilerBehaviors.* → mostly consumed by the structurer (threaded via StructureOptions).
|
|
29
|
-
//
|
|
30
|
-
// structurer: `nearBaseSpan` and `foldsConstAddrOffset` (rank.ts, L3
|
|
31
|
-
// `
|
|
32
|
-
//
|
|
31
|
+
// Five exceptions are read off the target directly, their consumers not being the
|
|
32
|
+
// structurer: `nearBaseSpan` and `foldsConstAddrOffset` (rank.ts, L3 respell variations),
|
|
33
|
+
// `reloadsLocalReread` (raise/pre-recovery.ts), `hoistsSingleSetArm` (two raising passes —
|
|
34
|
+
// raise/narrowlocal.ts and raise/retsink.ts) and
|
|
35
|
+
// `arrayShapeFromStride` (raise/globalshape.ts, run on the LIFTED fn). The field names are a
|
|
33
36
|
// SUPERSET of StructureOptions' — see `structureOptionsFor`.
|
|
34
37
|
//
|
|
35
|
-
// `capabilities` (HARDWARE facts) vs `compilerBehaviors` (COMPILER canonicalization
|
|
38
|
+
// `capabilities` (HARDWARE facts) vs `compilerBehaviors` (COMPILER canonicalization decisions) are
|
|
36
39
|
// deliberately separate bags: a new compiler must set its behaviors EXPLICITLY instead of
|
|
37
40
|
// silently inheriting a universal that is really per-compiler. `coalesceLoopInit` already
|
|
38
41
|
// differs across targets (IDO true, agbcc/GCC false).
|
|
@@ -43,10 +46,10 @@ import type { StructureOptions } from './structure/structure';
|
|
|
43
46
|
|
|
44
47
|
export interface TargetDescription {
|
|
45
48
|
id: string; // the ISA — 'armv4t' / 'mips' / 'ppc'. Selects the frontend (registry.ts).
|
|
46
|
-
// The COMPILER is a first-class
|
|
49
|
+
// The COMPILER is a first-class field distinct from the ISA (matching = deoptimize to a specific
|
|
47
50
|
// compiler): two targets can share an ISA (⇒ one frontend) yet differ here — e.g. MIPS_IDO vs
|
|
48
51
|
// MIPS_GCC. Consumed by pattern gating (patternApplies) and the report. (version/flags/language
|
|
49
|
-
// are future
|
|
52
|
+
// are future fields, added when earned.)
|
|
50
53
|
compiler: string; // 'agbcc' / 'ido' / 'gcc' / 'mwcc'
|
|
51
54
|
argRegs: string[];
|
|
52
55
|
returnReg: string;
|
|
@@ -91,7 +94,7 @@ export interface TargetDescription {
|
|
|
91
94
|
// the same SPELLING question — "would a source have written `volatile` here" — and the file
|
|
92
95
|
// header's ledger names them and what each does with the answer. None of them decides for the
|
|
93
96
|
// reader: which cells a source qualified is not derivable from the asm, so both spellings are
|
|
94
|
-
// enumerated and the differ referees. ABSENT ⇒ the
|
|
97
|
+
// enumerated and the differ referees. ABSENT ⇒ the variation declines everywhere and the tie-break
|
|
95
98
|
// has no preference, which is the neutral direction — outside a declared window the qualifier
|
|
96
99
|
// is a claim about ordinary memory that the target does not support.
|
|
97
100
|
//
|
|
@@ -116,7 +119,7 @@ export interface TargetDescription {
|
|
|
116
119
|
// possible memory write — the conservative direction, and what every non-GBA target takes.
|
|
117
120
|
deviceMemoryWriters?: readonly (readonly [number, number])[];
|
|
118
121
|
};
|
|
119
|
-
// COMPILER BEHAVIORS — the specific compiler's canonicalization
|
|
122
|
+
// COMPILER BEHAVIORS — the specific compiler's canonicalization decisions, distinct from
|
|
120
123
|
// hardware `capabilities`. Mostly consumed by the structurer (threaded through StructureOptions);
|
|
121
124
|
// the exceptions are listed at the top of this file and each says so at its own field.
|
|
122
125
|
compilerBehaviors: {
|
|
@@ -141,7 +144,7 @@ export interface TargetDescription {
|
|
|
141
144
|
// where that copy sits, not by where its value was computed. Uniform (true) across all current
|
|
142
145
|
// compilers; absent ⇒ true, and a compiler that opts OUT turns the sort off entirely and emits
|
|
143
146
|
// in source/param order. WHICH order a measured edge takes is not this flag's question and
|
|
144
|
-
// cannot be: the benchmark has rows on both sides inside one compiler (mwcc), so that
|
|
147
|
+
// cannot be: the benchmark has rows on both sides inside one compiler (mwcc), so that decision is
|
|
145
148
|
// refereed per row by `/copy-defpos` (rank.ts), never declared per compiler here.
|
|
146
149
|
orderArgCopiesByWriteOrder?: boolean;
|
|
147
150
|
// Regime-A switch recovery: accept an `x != K` test as a case (the EQUAL side is the case
|
|
@@ -150,17 +153,29 @@ export interface TargetDescription {
|
|
|
150
153
|
switchAllowsNeqCase?: boolean;
|
|
151
154
|
// The compiler collapses `if (…) x = a; else x = b;` into `x = b; if (…) x = a;` when both
|
|
152
155
|
// arms are ONE speculatable SET — gcc 2.x's `jump_optimize` (`gcc/jump.c:443-445`, guard at
|
|
153
|
-
// `:471-502`).
|
|
154
|
-
// BACKWARDS: a diamond this compiler would have collapsed and did not is evidence the source
|
|
155
|
-
// DECLARED the local narrow, because `gcc/thumb.h:344` PROMOTE_MODE expands a narrow-declared
|
|
156
|
-
// assignment past one SET. Absent ⇒ false, and the clause never admits. `structureOptionsFor`
|
|
156
|
+
// `:471-502`). Absent ⇒ false, and every clause below never admits. `structureOptionsFor`
|
|
157
157
|
// spreads it onto StructureOptions like every other field here, but NO structurer code reads
|
|
158
|
-
// it:
|
|
158
|
+
// it: both readers are raising passes, threaded from their driver's own `target`.
|
|
159
159
|
//
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
//
|
|
160
|
+
// TWO READERS, ONE FACT, BOTH READING IT BACKWARDS. One field rather than one per reader,
|
|
161
|
+
// because a second boolean for the same guard lets a round that measures another compiler's
|
|
162
|
+
// `jump_optimize` set one and leave the other false, with both comments reading as
|
|
163
|
+
// authoritative.
|
|
164
|
+
//
|
|
165
|
+
// • raise/narrowlocal.ts's `edge-extends`: a diamond this compiler would have collapsed and
|
|
166
|
+
// did NOT is evidence the source DECLARED the local narrow, because `gcc/thumb.h:344`
|
|
167
|
+
// PROMOTE_MODE expands a narrow-declared assignment past one SET.
|
|
168
|
+
// • raise/retsink.ts's `compiler-hoists-single-set-arm`: a merge-variable select whose arms
|
|
169
|
+
// this guard would have collapsed never comes back as a diamond, so a TARGET holding one
|
|
170
|
+
// was written with early returns and its returns should be sunk.
|
|
171
|
+
//
|
|
172
|
+
// Set on agbcc, where the 2x2 in raise/narrowlocal.ts's header was compiled and scored and
|
|
173
|
+
// where retsink's seven-function spelling pair was compiled and committed
|
|
174
|
+
// (`packages/core/test/corpus/agbcc-select-{merge,early}.s`). NOT set on MIPS_GCC despite it
|
|
175
|
+
// being the same compiler family: nothing has measured the pair there, the clause reaches 0 of
|
|
176
|
+
// its benchmark rows on either reader, and `docs/level-tower.md`'s rule for an unmeasured
|
|
177
|
+
// compiler behavior is to claim nothing. The evidence a future round needs is one run of
|
|
178
|
+
// `scripts/regen-select-spelling-probes.ts` retargeted at the compiler in question.
|
|
164
179
|
hoistsSingleSetArm?: boolean;
|
|
165
180
|
// A subscript over a DECLARED ARRAY OBJECT expands its base ahead of the index, where every
|
|
166
181
|
// pointer or cast base expands it last — so the instruction order in the target's own assembly
|
|
@@ -174,7 +189,7 @@ export interface TargetDescription {
|
|
|
174
189
|
// so `u16 *p = (u16 *)&gTbl; p[i]` is base-first in the object while `(p = (u16 *)&gTbl)[i]` is
|
|
175
190
|
// index-first, both through this same fork (compiled; raise/globalshape.ts's header carries the
|
|
176
191
|
// four-way table). This flag is therefore NARROWER than that consumer's mechanism — statement
|
|
177
|
-
// ordering needs no fork, only a compiler that does not schedule — so the home
|
|
192
|
+
// ordering needs no fork, only a compiler that does not schedule — so the home variation is denied
|
|
178
193
|
// to ido/kmc/mwcc for a reason that is not its own. Under-reach, unmeasured, and the fix when a
|
|
179
194
|
// row asks for it is a datum of its own rather than a widening of this one.
|
|
180
195
|
//
|
|
@@ -182,7 +197,7 @@ export interface TargetDescription {
|
|
|
182
197
|
// `TREE_CODE (TREE_TYPE (array)) == ARRAY_TYPE && TREE_CODE (array) != INDIRECT_REF` and both
|
|
183
198
|
// spellings were compiled against the same target. NOT set anywhere else: whether ido, kmc or
|
|
184
199
|
// mwcc distinguish them at all is unmeasured, and `docs/level-tower.md`'s rule for an
|
|
185
|
-
// unmeasured
|
|
200
|
+
// unmeasured compiler behavior is to claim nothing. Read off the target by a raising pass
|
|
186
201
|
// (`inferGlobalArrays`), not by the structurer.
|
|
187
202
|
arrayShapeFromStride?: boolean;
|
|
188
203
|
// Which way this compiler hands out FRAME SLOTS against a spilled local's DECLARATION RANK:
|
|
@@ -208,7 +223,7 @@ export interface TargetDescription {
|
|
|
208
223
|
// value as that case (`cmp r0, #1 / bcc` is `case 0:` of an unsigned switch) rather than as
|
|
209
224
|
// navigation.
|
|
210
225
|
//
|
|
211
|
-
// A DEFAULT rather than a candidate
|
|
226
|
+
// A DEFAULT rather than a candidate variation because for agbcc the asm determines the source: at
|
|
212
227
|
// -O2 fold-const rewrites a bounded unsigned comparison into an equality before codegen, so
|
|
213
228
|
// `x < 1u` compiles to `cmp r0, #0 / bne` and `x > 0u` to `cmp r0, #0 / beq` — no source-level
|
|
214
229
|
// comparison chain emits a bound test at all. `emit_case_nodes` runs after folding and does:
|
|
@@ -232,13 +247,43 @@ export interface TargetDescription {
|
|
|
232
247
|
// Absent ⇒ ascending case value, where ido/kmc-gcc/mwcc sit: each has a scheduler and none has
|
|
233
248
|
// been put through that evidence. A compiler opts in on its own, never by inheriting.
|
|
234
249
|
switchArmsFollowLayout?: boolean;
|
|
250
|
+
// Switch recovery: DECLINE a comparison tree whose own layout INTERLEAVES a test block with a
|
|
251
|
+
// case body, on the reading that the source wrote an if/else-if LADDER there. True claims the
|
|
252
|
+
// compiler emits a source `switch`'s whole dispatch AHEAD of every arm body — the same
|
|
253
|
+
// `expand_end_case` closing `reorder_insns` `switchArmsFollowLayout` is read off, used for the
|
|
254
|
+
// other half of what it does — while a ladder's tests stay above their own bodies.
|
|
255
|
+
//
|
|
256
|
+
// IT NEEDS HALF OF `switchArmsFollowLayout`'s PREMISE, AND THE IMPLICATION RUNS ONE WAY ONLY.
|
|
257
|
+
// That flag PLACES the arms and so needs the whole no-reordering claim (nothing moved a block
|
|
258
|
+
// at all); this one only asks whether any BODY sits above a test, so a compiler that moves
|
|
259
|
+
// instructions, fills delay slots, or reorders within a block can still declare it. A compiler
|
|
260
|
+
// that declares the PLACING one has therefore already said what this one needs — never the
|
|
261
|
+
// converse. `MIPS_GCC` is the standing counterexample to the converse: it declares this flag on
|
|
262
|
+
// its own pairs (below) and deliberately does NOT declare `switchArmsFollowLayout`, because it
|
|
263
|
+
// has a scheduler. Declaring this one is not evidence for that one.
|
|
264
|
+
//
|
|
265
|
+
// agbcc declares it, and its own pair of objects says the reading is not vacuous: at
|
|
266
|
+
// TOOLCHAIN.agbccFlags the same two-case body is 20 bytes (0x14, ten Thumb instructions)
|
|
267
|
+
// written either way and is a DIFFERENT object — the `switch` emits `cmp #0x1e; beq` then
|
|
268
|
+
// `cmp #0x64; bne` before either body, sorted ascending and so in the reverse of the written
|
|
269
|
+
// order; the ladder emits `cmp #0x64; bne` directly above its own body and reaches `cmp #0x1e`
|
|
270
|
+
// only after it. The pair is committed: `corpus/agbcc-sw{frontload,ladder}.s` from
|
|
271
|
+
// `corpus/probe-agbcc-sw{frontload,ladder}.c`, regenerated by
|
|
272
|
+
// `scripts/regen-switch-spelling-probes.ts`, asserted in switch-arms.test.ts.
|
|
273
|
+
//
|
|
274
|
+
// Absent ⇒ every recoverable tree is still spelled `switch`, which is where ido/mwcc sit: each
|
|
275
|
+
// has a scheduler that may move a body above a test, and neither has been put through the pair.
|
|
276
|
+
// A compiler opts in on its own compiled evidence, never by inheriting — and where one
|
|
277
|
+
// description serves two toolchains (`MIPS_GCC`), each toolchain owes its own pair, because the
|
|
278
|
+
// field cannot distinguish them (the KEYED BY DESCRIPTION note at `spillSlotOrder`).
|
|
279
|
+
switchRequiresFrontLoadedTests?: boolean;
|
|
235
280
|
// Commutative load pairs re-spell in def (evaluation) order (structure.ts lowerDef). Absent
|
|
236
281
|
// ⇒ true — verified byte-exact on agbcc and IDO; a compiler whose scheduler is shown
|
|
237
282
|
// re-ordering independent loads opts OUT here.
|
|
238
283
|
defOrderLoadPairs?: boolean;
|
|
239
|
-
// The single-add-immediate derivation reach for the /nearbase
|
|
284
|
+
// The single-add-immediate derivation reach for the /nearbase variation (l3/nearbase.ts):
|
|
240
285
|
// neighbor absolute addresses within this many bytes may share one base local. Thumb's
|
|
241
|
-
// `add rd, #imm8` reaches 255. Absent ⇒ the
|
|
286
|
+
// `add rd, #imm8` reaches 255. Absent ⇒ the variation stands down for this target.
|
|
242
287
|
nearBaseSpan?: number;
|
|
243
288
|
// Does this compiler CONSTANT-FOLD a constant SUBSCRIPT into the literal address it
|
|
244
289
|
// materializes for an inline constant-address access? agbcc does: `((u8 *)0x3001100)[3]`
|
|
@@ -250,6 +295,18 @@ export interface TargetDescription {
|
|
|
250
295
|
// and PPC lanes put the addend in the instruction by construction (`lui`/`%lo`, `lis`/`ori`),
|
|
251
296
|
// so a surviving offset carries no information there. Absent ⇒ the row is never offered.
|
|
252
297
|
foldsConstAddrOffset?: boolean;
|
|
298
|
+
// Does this compiler fold a pointer local's OWN ADVANCE back into the memory operand —
|
|
299
|
+
// `*p = a; p = p + 1; *p = b;` → `strh [r3, #0]` + `strh [r3, #2]`, no `add` — so the advanced
|
|
300
|
+
// spelling emits the indexed one's stores wherever the pointee is not volatile? agbcc does, on
|
|
301
|
+
// its own compiled evidence: the four corners in test/advance.test.ts's header, each built
|
|
302
|
+
// through the benchmark's agbcc against `kleod:StreamCmd_SetWindowRegs`'s object, and the pair
|
|
303
|
+
// `TARGET_BEHAVIOR_READINGS` compiles to one object in the matching suite. True ⇒
|
|
304
|
+
// the `advance` registry entry's target gate withholds the UN-QUALIFIED variation, whose spelling this compiler cannot
|
|
305
|
+
// distinguish from the indexed one it already offers; `/advance/volatile` still rides, because
|
|
306
|
+
// `volatile` is what bars the fold and that product is the match on this row. Absent ⇒ falsy ⇒
|
|
307
|
+
// the plain variation ships, which is the conservative reading for a compiler whose pair nobody has
|
|
308
|
+
// compiled — a compiler opts in on its own evidence and never by inheriting.
|
|
309
|
+
foldsPointerAdvance?: boolean;
|
|
253
310
|
// Does this compiler EMIT a memory read in the block the source SPELLED it in? One direction
|
|
254
311
|
// only: the def-block placement rule (StructureOptions.readsStayWhereWritten) re-spells a read
|
|
255
312
|
// at the block the asm performed it in, which reproduces the asm iff nothing sinks a spelled
|
|
@@ -269,6 +326,22 @@ export interface TargetDescription {
|
|
|
269
326
|
// ABSENT ⇒ the rule stands down, where ido/kmc-gcc/mwcc sit: each has a scheduler and none has
|
|
270
327
|
// been put through that pair. A compiler opts in on its own evidence, never by inheriting.
|
|
271
328
|
readsStayWhereWritten?: boolean;
|
|
329
|
+
// Does a LOCAL initialised with a memory read its dominating TEST already performed cost this
|
|
330
|
+
// compiler a SECOND load? The pair is the arm of `if (a && (p[1] & 0x7f) == 0x7f) { … }` spelled
|
|
331
|
+
// `u8 v = p[1]; p[2] = v;` against `p[2] = p[1];`, and again with a store and with a call
|
|
332
|
+
// between the local and its use. agbcc loads `p[1]` TWICE for every local spelling and once
|
|
333
|
+
// for the inline one; ido7.1, gcc2.7.2kmc, gcc2.7.2 and mwcc_242_81 load it ONCE for every
|
|
334
|
+
// local spelling, holding the register across the store and across the call. agbcc is the
|
|
335
|
+
// odd one out of five, so the one agbcc-shaped claim that rested on it — raise/shortcircuit.ts's
|
|
336
|
+
// `read-behind-effect`, "a copy analysis.ts spells as a local costs a load" — reads it here
|
|
337
|
+
// rather than running on every target — on mwcc it costs the probe named at PPC_MWCC's value
|
|
338
|
+
// its byte-match.
|
|
339
|
+
// Read off the target by a raising pass (raise/pre-recovery.ts), not by the structurer.
|
|
340
|
+
//
|
|
341
|
+
// ABSENT ⇒ false: the refusal stands down, and a compiler opts IN on its own compiled pair. The
|
|
342
|
+
// three descriptions that measured false (four compilers) set it anyway, so absent means
|
|
343
|
+
// UNMEASURED rather than "no".
|
|
344
|
+
reloadsLocalReread?: boolean;
|
|
272
345
|
};
|
|
273
346
|
}
|
|
274
347
|
|
|
@@ -324,11 +397,14 @@ export const ARMV4T_AGBCC: TargetDescription = {
|
|
|
324
397
|
orderArgCopiesByWriteOrder: true,
|
|
325
398
|
nearBaseSpan: 255,
|
|
326
399
|
foldsConstAddrOffset: true,
|
|
400
|
+
foldsPointerAdvance: true,
|
|
327
401
|
readsStayWhereWritten: true,
|
|
328
402
|
switchAllowsBoundCase: true,
|
|
329
403
|
switchArmsFollowLayout: true,
|
|
404
|
+
switchRequiresFrontLoadedTests: true,
|
|
330
405
|
hoistsSingleSetArm: true,
|
|
331
406
|
arrayShapeFromStride: true,
|
|
407
|
+
reloadsLocalReread: true,
|
|
332
408
|
// agbcc: reload walks pseudos ascending handing each global-alloc loser a fresh slot, a user
|
|
333
409
|
// local's pseudo number is its `expand_decl` position, and the Thumb frame grows UPWARD
|
|
334
410
|
// (FRAME_GROWS_DOWNWARD is commented out in thumb.h). So the earlier-declared spilled local
|
|
@@ -358,6 +434,8 @@ export const MIPS_IDO: TargetDescription = {
|
|
|
358
434
|
preserveDivergentBranchSense: true,
|
|
359
435
|
orderArgCopiesByWriteOrder: true,
|
|
360
436
|
switchAllowsNeqCase: false,
|
|
437
|
+
// MEASURED — the pair at the field compiles to one load of `p[1]` for every local spelling.
|
|
438
|
+
reloadsLocalReread: false,
|
|
361
439
|
// MEASURED `descending` (the earlier-declared spilled local takes the HIGHER offset) and NOT
|
|
362
440
|
// SHIPPED. The probe is COMMITTED — `packages/core/test/corpus/probe-declrank.c` and its
|
|
363
441
|
// reversed-declaration twin, with this compiler's objects beside them — and a test reads the
|
|
@@ -388,7 +466,7 @@ export const MIPS_GCC: TargetDescription = {
|
|
|
388
466
|
// KMC GCC keeps a loop seeded from an argument register IN that register (coalesceLoopInit
|
|
389
467
|
// true, like IDO): test/corpus/gcc-gcd.asm runs its whole loop on a0/a1 with no init copies,
|
|
390
468
|
// and the row it comes from matches only with the parameters as the loop's homes. The other
|
|
391
|
-
// structuring
|
|
469
|
+
// structuring compiler behaviors take the universal default until a KMC fixture says otherwise.
|
|
392
470
|
//
|
|
393
471
|
// THIS IS A COMPILER-WIDE GUESS STANDING IN FOR A PER-FUNCTION OBSERVATION the assembly states
|
|
394
472
|
// outright: whether the compiler kept a loop's induction variable in its argument register. What
|
|
@@ -407,6 +485,41 @@ export const MIPS_GCC: TargetDescription = {
|
|
|
407
485
|
coalesceLoopInit: true,
|
|
408
486
|
preserveDivergentBranchSense: true,
|
|
409
487
|
orderArgCopiesByWriteOrder: true,
|
|
488
|
+
// DECLARED ON A PAIR FROM EACH TOOLCHAIN THIS DESCRIPTION SERVES, never inherited from agbcc's
|
|
489
|
+
// and never from one sibling to the other. This description is keyed per DESCRIPTION while the
|
|
490
|
+
// fact is per TOOLCHAIN (the note at `spillSlotOrder`), and `MIPS_GCC` serves two, so both owe
|
|
491
|
+
// a pair: `corpus/gcc272kmc-sw{frontload,ladder}.asm` from GCC_KMC_TOOLCHAIN at -O2 and
|
|
492
|
+
// `corpus/gcc272-sw{frontload,ladder}.asm` from the Mario Party 3 toolchain at -O1 — one
|
|
493
|
+
// two-case body written each way, regenerated from their committed C bodies by
|
|
494
|
+
// `scripts/regen-switch-spelling-probes.ts`, each carrying a provenance header, and each pair
|
|
495
|
+
// two different objects: the `switch` emits both `beq`s before the first `sw`, while the ladder
|
|
496
|
+
// puts an arm's `sw` above the second test. A test asserts that split off every fixture. The
|
|
497
|
+
// two toolchains come out byte-identical on this body — measured, not assumed, and their
|
|
498
|
+
// declaration-rank probe objects differ, so a sibling pair is not a formality.
|
|
499
|
+
//
|
|
500
|
+
// BOTH TOOLCHAINS EMIT BRANCH-LIKELY on some bodies, and the reading survives it — a property
|
|
501
|
+
// of the pair, not a way the two diverge. `s32 m1(s32 x, s32 *p){ switch (x) { case 6: *p = 1;
|
|
502
|
+
// break; case 7: *p = 2; break; } return 0; }` cross-jumps the two stores into one and compiles
|
|
503
|
+
// instruction for instruction identically at -O1 and at -O2, to `beq` / `beql` with the shared
|
|
504
|
+
// `sw` after both; the same body as an if/else-if ladder puts the first arm's `li v0,1` BETWEEN
|
|
505
|
+
// the two tests. Same split — not committed as a fixture only because `beql` is an unmodelled
|
|
506
|
+
// control transfer, so the row declines before PRE5 and the cross-jumped arms leave no store to
|
|
507
|
+
// read the split off. Re-measure it the day branch-likely lands. (A THREE-case body says the
|
|
508
|
+
// same more loudly, the balanced tree's `slti` bound test landing ahead of the bodies with the
|
|
509
|
+
// rest, but at three cases neither spelling reaches Regime A on this compiler, so that pair
|
|
510
|
+
// could not also serve as the recovery test.)
|
|
511
|
+
//
|
|
512
|
+
// WHAT IS WEAKER HERE THAN AT agbcc: this compiler HAS a scheduler and fills delay slots, and
|
|
513
|
+
// both fixtures show it — the ladder's `bne` carries the NEXT test's `li` in its slot. What the
|
|
514
|
+
// pair shows is that it moves no BODY above a test, which is the only claim the gate rests on,
|
|
515
|
+
// and the gate's failure direction (switch-recover.ts PRE5) is a lost `switch` spelling, never
|
|
516
|
+
// a wrong answer. A lost spelling is not always a clean ladder: recovery re-runs on the
|
|
517
|
+
// sub-trees a decline leaves, so a NESTED dispatch comes back as an `if` nest around a `switch`
|
|
518
|
+
// over some of its arms.
|
|
519
|
+
switchRequiresFrontLoadedTests: true,
|
|
520
|
+
// MEASURED on BOTH toolchains this description serves (the note above): one load of `p[1]` for
|
|
521
|
+
// every local spelling of the pair at the field, gcc2.7.2kmc at -O2 and gcc2.7.2 at -O1 alike.
|
|
522
|
+
reloadsLocalReread: false,
|
|
410
523
|
// MEASURED `ascending` on both toolchains this description serves — 7 of 7 spills each, and
|
|
411
524
|
// rank → offset unchanged under a reversed declaration list — and NOT SHIPPED, for the same
|
|
412
525
|
// reason as ido7.1: no row on either tier lifts with two or more spilled user locals. Both
|
|
@@ -433,13 +546,18 @@ export const PPC_MWCC: TargetDescription = {
|
|
|
433
546
|
argRegs: ['r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9', 'r10'],
|
|
434
547
|
returnReg: 'r3',
|
|
435
548
|
capabilities: { endianness: 'big', hwDivide: true, hwFloat: true, flags: true },
|
|
436
|
-
// CodeWarrior's structuring
|
|
549
|
+
// CodeWarrior's structuring compiler behaviors are UNKNOWN until fixtures reveal them — safe universal
|
|
437
550
|
// defaults; coalesceLoopInit false until a CW loop fixture says otherwise — the second of the
|
|
438
551
|
// two compiler-wide guesses standing in for the per-function observation named at MIPS_GCC.
|
|
439
552
|
compilerBehaviors: {
|
|
440
553
|
coalesceLoopInit: false,
|
|
441
554
|
preserveDivergentBranchSense: true,
|
|
442
555
|
orderArgCopiesByWriteOrder: true,
|
|
556
|
+
// MEASURED — one load of `p[1]` for every local spelling of the pair at the field, the value
|
|
557
|
+
// held in a callee-saved register across the call. And the rule it turns off pays here:
|
|
558
|
+
// `u8 v = p[3]; if ((v & 0x7f) == 0x7f) { fnA(); p[4] = v; return; }` under an `if (a)`
|
|
559
|
+
// matches only once `read-behind-effect` stops refusing it (3/24 → MATCH 0/22).
|
|
560
|
+
reloadsLocalReread: false,
|
|
443
561
|
// NOT MEASURED, and `'unknown'` is therefore the only honest value here rather than a withheld
|
|
444
562
|
// one, as it is at MIPS_IDO and MIPS_GCC. No mwcc row lifts with two or more spilled user
|
|
445
563
|
// locals, and the compiler does not spill the committed declaration-rank probe either: at
|
|
@@ -452,8 +570,8 @@ export const PPC_MWCC: TargetDescription = {
|
|
|
452
570
|
};
|
|
453
571
|
|
|
454
572
|
/** Build the structurer's options for a target: the function's own `returnsVoid` plus every
|
|
455
|
-
* `compilerBehaviors`
|
|
456
|
-
* target-agnostic structurer — a new behavior
|
|
573
|
+
* `compilerBehaviors` field. The ONE place a target's compiler behaviors flow into the
|
|
574
|
+
* target-agnostic structurer — a new compiler behavior is a field in `compilerBehaviors`, consumed
|
|
457
575
|
* automatically.
|
|
458
576
|
*
|
|
459
577
|
* The spread is over the WHOLE bag, so a behavior whose reader is not the structurer rides along
|
|
@@ -462,8 +580,9 @@ export const PPC_MWCC: TargetDescription = {
|
|
|
462
580
|
* are a SUPERSET of StructureOptions', not a bijection, and nothing may derive one from the other
|
|
463
581
|
* by enumerating keys. */
|
|
464
582
|
export function structureOptionsFor(t: TargetDescription, returnsVoid: boolean): StructureOptions {
|
|
465
|
-
// `littleEndian`
|
|
466
|
-
// recognition is LSB-first
|
|
583
|
+
// `littleEndian` and `deviceRegisters` are the HARDWARE capabilities the structurer consumes
|
|
584
|
+
// (bitfield extract recognition is LSB-first; the dead-read spelling refuses outside the device
|
|
585
|
+
// window); everything else is a compiler behavior.
|
|
467
586
|
//
|
|
468
587
|
// ONE FIELD IS NOT A STRAIGHT SPREAD, and this is where the difference belongs. A frame
|
|
469
588
|
// direction has THREE states here — `ascending`, `descending`, and `'unknown'` meaning measured
|
|
@@ -475,6 +594,7 @@ export function structureOptionsFor(t: TargetDescription, returnsVoid: boolean):
|
|
|
475
594
|
return {
|
|
476
595
|
returnsVoid,
|
|
477
596
|
littleEndian: t.capabilities.endianness === 'little',
|
|
597
|
+
...(t.capabilities.deviceRegisters ? { deviceRegisters: t.capabilities.deviceRegisters } : {}),
|
|
478
598
|
...behaviors,
|
|
479
599
|
...(spillSlotOrder === 'ascending' || spillSlotOrder === 'descending' ? { spillSlotOrder } : {}),
|
|
480
600
|
};
|
package/src/trace.ts
CHANGED
|
@@ -280,7 +280,7 @@ function traceTower(
|
|
|
280
280
|
);
|
|
281
281
|
|
|
282
282
|
// (4) structure → neutral AST; boundary contract: no unresolved value leaked (strict) or
|
|
283
|
-
// spelled as a loud ASMLIFT_ERROR marker (annotate) — same onGap
|
|
283
|
+
// spelled as a loud ASMLIFT_ERROR marker (annotate) — same onGap option as decompile()
|
|
284
284
|
const mapSymbols = opts.symbols ? symbolsByName(opts.symbols) : undefined;
|
|
285
285
|
const sfn = structureChecked(fn, {
|
|
286
286
|
...structureOptionsFor(target, returnsVoid),
|