@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/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,38 +15,46 @@
|
|
|
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).
|
|
39
42
|
// This module is browser-pure by contract (no Node APIs, enforced by
|
|
40
43
|
// test/browser-safe.test.ts): the toolchain paths that COMPILE for these targets
|
|
41
44
|
// live in @asmlift/toolchains.
|
|
45
|
+
import { type CodegenProfile, type FlagFamily, parseFlags } from './codegen-flags';
|
|
42
46
|
import type { StructureOptions } from './structure/structure';
|
|
43
47
|
|
|
48
|
+
/** What a compiler's OBJECT shows for a narrow declared parameter — see
|
|
49
|
+
* `compilerBehaviors.narrowParamWitness` for the compiled pair behind each value. */
|
|
50
|
+
export type NarrowParamWitness = 'prologue-extension' | 'home-store-and-in-place' | 'none';
|
|
51
|
+
|
|
44
52
|
export interface TargetDescription {
|
|
45
53
|
id: string; // the ISA — 'armv4t' / 'mips' / 'ppc'. Selects the frontend (registry.ts).
|
|
46
|
-
// The COMPILER is a first-class
|
|
54
|
+
// The COMPILER is a first-class field distinct from the ISA (matching = deoptimize to a specific
|
|
47
55
|
// compiler): two targets can share an ISA (⇒ one frontend) yet differ here — e.g. MIPS_IDO vs
|
|
48
56
|
// MIPS_GCC. Consumed by pattern gating (patternApplies) and the report. (version/flags/language
|
|
49
|
-
// are future
|
|
57
|
+
// are future fields, added when earned.)
|
|
50
58
|
compiler: string; // 'agbcc' / 'ido' / 'gcc' / 'mwcc'
|
|
51
59
|
argRegs: string[];
|
|
52
60
|
returnReg: string;
|
|
@@ -91,7 +99,7 @@ export interface TargetDescription {
|
|
|
91
99
|
// the same SPELLING question — "would a source have written `volatile` here" — and the file
|
|
92
100
|
// header's ledger names them and what each does with the answer. None of them decides for the
|
|
93
101
|
// reader: which cells a source qualified is not derivable from the asm, so both spellings are
|
|
94
|
-
// enumerated and the differ referees. ABSENT ⇒ the
|
|
102
|
+
// enumerated and the differ referees. ABSENT ⇒ the variation declines everywhere and the tie-break
|
|
95
103
|
// has no preference, which is the neutral direction — outside a declared window the qualifier
|
|
96
104
|
// is a claim about ordinary memory that the target does not support.
|
|
97
105
|
//
|
|
@@ -116,7 +124,7 @@ export interface TargetDescription {
|
|
|
116
124
|
// possible memory write — the conservative direction, and what every non-GBA target takes.
|
|
117
125
|
deviceMemoryWriters?: readonly (readonly [number, number])[];
|
|
118
126
|
};
|
|
119
|
-
// COMPILER BEHAVIORS — the specific compiler's canonicalization
|
|
127
|
+
// COMPILER BEHAVIORS — the specific compiler's canonicalization decisions, distinct from
|
|
120
128
|
// hardware `capabilities`. Mostly consumed by the structurer (threaded through StructureOptions);
|
|
121
129
|
// the exceptions are listed at the top of this file and each says so at its own field.
|
|
122
130
|
compilerBehaviors: {
|
|
@@ -141,7 +149,7 @@ export interface TargetDescription {
|
|
|
141
149
|
// where that copy sits, not by where its value was computed. Uniform (true) across all current
|
|
142
150
|
// compilers; absent ⇒ true, and a compiler that opts OUT turns the sort off entirely and emits
|
|
143
151
|
// 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
|
|
152
|
+
// cannot be: the benchmark has rows on both sides inside one compiler (mwcc), so that decision is
|
|
145
153
|
// refereed per row by `/copy-defpos` (rank.ts), never declared per compiler here.
|
|
146
154
|
orderArgCopiesByWriteOrder?: boolean;
|
|
147
155
|
// Regime-A switch recovery: accept an `x != K` test as a case (the EQUAL side is the case
|
|
@@ -150,18 +158,56 @@ export interface TargetDescription {
|
|
|
150
158
|
switchAllowsNeqCase?: boolean;
|
|
151
159
|
// The compiler collapses `if (…) x = a; else x = b;` into `x = b; if (…) x = a;` when both
|
|
152
160
|
// 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`
|
|
161
|
+
// `:471-502`). Absent ⇒ false, and every clause below never admits. `structureOptionsFor`
|
|
157
162
|
// spreads it onto StructureOptions like every other field here, but NO structurer code reads
|
|
158
|
-
// it:
|
|
163
|
+
// it: both readers are raising passes, threaded from their driver's own `target`.
|
|
164
|
+
//
|
|
165
|
+
// TWO READERS, ONE FACT, BOTH READING IT BACKWARDS. One field rather than one per reader,
|
|
166
|
+
// because a second boolean for the same guard lets a round that measures another compiler's
|
|
167
|
+
// `jump_optimize` set one and leave the other false, with both comments reading as
|
|
168
|
+
// authoritative.
|
|
169
|
+
//
|
|
170
|
+
// • raise/narrowlocal.ts's `edge-extends`: a diamond this compiler would have collapsed and
|
|
171
|
+
// did NOT is evidence the source DECLARED the local narrow, because `gcc/thumb.h:344`
|
|
172
|
+
// PROMOTE_MODE expands a narrow-declared assignment past one SET.
|
|
173
|
+
// • raise/retsink.ts's `compiler-hoists-single-set-arm`: a merge-variable select whose arms
|
|
174
|
+
// this guard would have collapsed never comes back as a diamond, so a TARGET holding one
|
|
175
|
+
// was written with early returns and its returns should be sunk.
|
|
159
176
|
//
|
|
160
|
-
// Set on agbcc, where the 2x2 in raise/narrowlocal.ts's header was compiled and scored
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
//
|
|
177
|
+
// Set on agbcc, where the 2x2 in raise/narrowlocal.ts's header was compiled and scored and
|
|
178
|
+
// where retsink's seven-function spelling pair was compiled and committed
|
|
179
|
+
// (`packages/core/test/corpus/agbcc-select-{merge,early}.s`). NOT set on MIPS_GCC despite it
|
|
180
|
+
// being the same compiler family: nothing has measured the pair there, the clause reaches 0 of
|
|
181
|
+
// its benchmark rows on either reader, and `docs/level-tower.md`'s rule for an unmeasured
|
|
182
|
+
// compiler behavior is to claim nothing. The evidence a future round needs is one run of
|
|
183
|
+
// `scripts/regen-select-spelling-probes.ts` retargeted at the compiler in question.
|
|
164
184
|
hoistsSingleSetArm?: boolean;
|
|
185
|
+
// WHAT, IN THIS COMPILER'S OBJECT, WITNESSES A NARROW DECLARED PARAMETER — the fact
|
|
186
|
+
// raise/paramwidth.ts needs before it may retype `s32 a0` to `s8 a0`. Three answers, because
|
|
187
|
+
// the compilers measured give three, and the pass refuses wherever the object is silent. Each
|
|
188
|
+
// target below carries its own measurement, and raise/paramwidth.ts's header carries the
|
|
189
|
+
// compiled pairs all three readings rest on.
|
|
190
|
+
//
|
|
191
|
+
// • `'prologue-extension'` — the extension's POSITION decides it: a narrow-declared
|
|
192
|
+
// parameter widens at the very top of the function, a body cast widens at its use.
|
|
193
|
+
// • `'home-store-and-in-place'` — the position decides NOTHING, both spellings leading the
|
|
194
|
+
// function, and TWO other facts decide it together: the parameter is stored to an argument
|
|
195
|
+
// home nothing reads back AND widened in its own argument register. Each half alone has a
|
|
196
|
+
// compiled counterexample, so only the PAIR separates a declaration from a body cast.
|
|
197
|
+
//
|
|
198
|
+
// THE HOME STORE IS AN `-O2` OBSERVABLE, AND `-g` IS NOT WHAT REMOVES IT. Measured on
|
|
199
|
+
// `int f(s8 x){ return x; }`: present at `-O2` and at `-O2 -g3`, absent at `-O1`, at `-O0`
|
|
200
|
+
// and at `-g` (which implies `-O0`). So the 42 real af rows that build at
|
|
201
|
+
// `-G 0 -non_shared -Wab,-r4300_mul -mips2 -EB -O2 -g3` DO carry it — checked at exactly
|
|
202
|
+
// those flags — and a target built at `-O1`/`-O0` would have to claim `'none'`. The
|
|
203
|
+
// in-place widening survives every one of those levels, but on its own it decides nothing,
|
|
204
|
+
// so the pass refuses there rather than reading half a pair.
|
|
205
|
+
// • `'none'` — the object does not distinguish the two at all, so no reading of it licenses
|
|
206
|
+
// the narrowing. It is a MEASUREMENT rather than a withholding wherever a target claims it.
|
|
207
|
+
//
|
|
208
|
+
// A compiler that sets nothing here also refuses, which is the right default for one nobody has
|
|
209
|
+
// compiled the pair with (docs/level-tower.md: claim nothing about an unmeasured behavior).
|
|
210
|
+
narrowParamWitness?: NarrowParamWitness;
|
|
165
211
|
// A subscript over a DECLARED ARRAY OBJECT expands its base ahead of the index, where every
|
|
166
212
|
// pointer or cast base expands it last — so the instruction order in the target's own assembly
|
|
167
213
|
// says which of the two the source wrote, and `raise/globalshape.ts` may derive an array shape
|
|
@@ -174,7 +220,7 @@ export interface TargetDescription {
|
|
|
174
220
|
// so `u16 *p = (u16 *)&gTbl; p[i]` is base-first in the object while `(p = (u16 *)&gTbl)[i]` is
|
|
175
221
|
// index-first, both through this same fork (compiled; raise/globalshape.ts's header carries the
|
|
176
222
|
// 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
|
|
223
|
+
// ordering needs no fork, only a compiler that does not schedule — so the home variation is denied
|
|
178
224
|
// to ido/kmc/mwcc for a reason that is not its own. Under-reach, unmeasured, and the fix when a
|
|
179
225
|
// row asks for it is a datum of its own rather than a widening of this one.
|
|
180
226
|
//
|
|
@@ -182,7 +228,7 @@ export interface TargetDescription {
|
|
|
182
228
|
// `TREE_CODE (TREE_TYPE (array)) == ARRAY_TYPE && TREE_CODE (array) != INDIRECT_REF` and both
|
|
183
229
|
// spellings were compiled against the same target. NOT set anywhere else: whether ido, kmc or
|
|
184
230
|
// mwcc distinguish them at all is unmeasured, and `docs/level-tower.md`'s rule for an
|
|
185
|
-
// unmeasured
|
|
231
|
+
// unmeasured compiler behavior is to claim nothing. Read off the target by a raising pass
|
|
186
232
|
// (`inferGlobalArrays`), not by the structurer.
|
|
187
233
|
arrayShapeFromStride?: boolean;
|
|
188
234
|
// Which way this compiler hands out FRAME SLOTS against a spilled local's DECLARATION RANK:
|
|
@@ -204,11 +250,30 @@ export interface TargetDescription {
|
|
|
204
250
|
// says so, but nothing in this bag could express it if they did not. Any behavior that can
|
|
205
251
|
// differ between two toolchains sharing one description is mis-keyed by construction.
|
|
206
252
|
spillSlotOrder?: 'ascending' | 'descending' | 'unknown';
|
|
253
|
+
// Arguments past the argument registers are staged into an area this function RESERVES at the
|
|
254
|
+
// BOTTOM of its own frame — `[sp,#0]` upward, one word each — rather than pushed at the call
|
|
255
|
+
// site. It is GCC's ACCUMULATE_OUTGOING_ARGS target macro, and it is what makes an outgoing
|
|
256
|
+
// argument INDISTINGUISHABLE by code alone from a dead local: the words sit inside this
|
|
257
|
+
// frame's reservation and nothing this function does ever reloads one.
|
|
258
|
+
//
|
|
259
|
+
// Absent ⇒ no outgoing area is claimed and the Thumb frontend's stack-argument licence never
|
|
260
|
+
// fires, so a `[sp,#k]` store reaching a call unread declines exactly as it did before that
|
|
261
|
+
// licence existed. Read off the target by the frontend (`frontend/thumb.ts` `declaredCall`),
|
|
262
|
+
// not by the structurer.
|
|
263
|
+
//
|
|
264
|
+
// Set on agbcc, where the layout was read off `gcc/config/arm/thumb.h` and then measured —
|
|
265
|
+
// the corpus's `stkarg` (accepting) and `stkwide` (refusing) rows and kleod's
|
|
266
|
+
// `sub_0804C300` all stage their words at [sp,#0] upward inside the prologue's own
|
|
267
|
+
// reservation. NOT set anywhere else: a push-based caller would stage nothing inside the
|
|
268
|
+
// frame, and `docs/level-tower.md`'s rule for an unmeasured compiler behavior is to claim
|
|
269
|
+
// nothing. No other frontend calls the analysis today, so the field claims a premise rather
|
|
270
|
+
// than changing a verdict — which is the point: a second armv4t compiler must state it.
|
|
271
|
+
stagesOutgoingArgsInFrame?: boolean;
|
|
207
272
|
// Regime-A switch recovery: accept a RELATIONAL test whose BRANCH admits exactly one scrutinee
|
|
208
273
|
// value as that case (`cmp r0, #1 / bcc` is `case 0:` of an unsigned switch) rather than as
|
|
209
274
|
// navigation.
|
|
210
275
|
//
|
|
211
|
-
// A DEFAULT rather than a candidate
|
|
276
|
+
// A DEFAULT rather than a candidate variation because for agbcc the asm determines the source: at
|
|
212
277
|
// -O2 fold-const rewrites a bounded unsigned comparison into an equality before codegen, so
|
|
213
278
|
// `x < 1u` compiles to `cmp r0, #0 / bne` and `x > 0u` to `cmp r0, #0 / beq` — no source-level
|
|
214
279
|
// comparison chain emits a bound test at all. `emit_case_nodes` runs after folding and does:
|
|
@@ -232,13 +297,43 @@ export interface TargetDescription {
|
|
|
232
297
|
// Absent ⇒ ascending case value, where ido/kmc-gcc/mwcc sit: each has a scheduler and none has
|
|
233
298
|
// been put through that evidence. A compiler opts in on its own, never by inheriting.
|
|
234
299
|
switchArmsFollowLayout?: boolean;
|
|
300
|
+
// Switch recovery: DECLINE a comparison tree whose own layout INTERLEAVES a test block with a
|
|
301
|
+
// case body, on the reading that the source wrote an if/else-if LADDER there. True claims the
|
|
302
|
+
// compiler emits a source `switch`'s whole dispatch AHEAD of every arm body — the same
|
|
303
|
+
// `expand_end_case` closing `reorder_insns` `switchArmsFollowLayout` is read off, used for the
|
|
304
|
+
// other half of what it does — while a ladder's tests stay above their own bodies.
|
|
305
|
+
//
|
|
306
|
+
// IT NEEDS HALF OF `switchArmsFollowLayout`'s PREMISE, AND THE IMPLICATION RUNS ONE WAY ONLY.
|
|
307
|
+
// That flag PLACES the arms and so needs the whole no-reordering claim (nothing moved a block
|
|
308
|
+
// at all); this one only asks whether any BODY sits above a test, so a compiler that moves
|
|
309
|
+
// instructions, fills delay slots, or reorders within a block can still declare it. A compiler
|
|
310
|
+
// that declares the PLACING one has therefore already said what this one needs — never the
|
|
311
|
+
// converse. `MIPS_GCC` is the standing counterexample to the converse: it declares this flag on
|
|
312
|
+
// its own pairs (below) and deliberately does NOT declare `switchArmsFollowLayout`, because it
|
|
313
|
+
// has a scheduler. Declaring this one is not evidence for that one.
|
|
314
|
+
//
|
|
315
|
+
// agbcc declares it, and its own pair of objects says the reading is not vacuous: at agbcc's
|
|
316
|
+
// canonical flags the same two-case body is 20 bytes (0x14, ten Thumb instructions)
|
|
317
|
+
// written either way and is a DIFFERENT object — the `switch` emits `cmp #0x1e; beq` then
|
|
318
|
+
// `cmp #0x64; bne` before either body, sorted ascending and so in the reverse of the written
|
|
319
|
+
// order; the ladder emits `cmp #0x64; bne` directly above its own body and reaches `cmp #0x1e`
|
|
320
|
+
// only after it. The pair is committed: `corpus/agbcc-sw{frontload,ladder}.s` from
|
|
321
|
+
// `corpus/probe-agbcc-sw{frontload,ladder}.c`, regenerated by
|
|
322
|
+
// `scripts/regen-switch-spelling-probes.ts`, asserted in switch-arms.test.ts.
|
|
323
|
+
//
|
|
324
|
+
// Absent ⇒ every recoverable tree is still spelled `switch`, which is where ido/mwcc sit: each
|
|
325
|
+
// has a scheduler that may move a body above a test, and neither has been put through the pair.
|
|
326
|
+
// A compiler opts in on its own compiled evidence, never by inheriting — and where one
|
|
327
|
+
// description serves two toolchains (`MIPS_GCC`), each toolchain owes its own pair, because the
|
|
328
|
+
// field cannot distinguish them (the KEYED BY DESCRIPTION note at `spillSlotOrder`).
|
|
329
|
+
switchRequiresFrontLoadedTests?: boolean;
|
|
235
330
|
// Commutative load pairs re-spell in def (evaluation) order (structure.ts lowerDef). Absent
|
|
236
331
|
// ⇒ true — verified byte-exact on agbcc and IDO; a compiler whose scheduler is shown
|
|
237
332
|
// re-ordering independent loads opts OUT here.
|
|
238
333
|
defOrderLoadPairs?: boolean;
|
|
239
|
-
// The single-add-immediate derivation reach for the /nearbase
|
|
334
|
+
// The single-add-immediate derivation reach for the /nearbase variation (l3/nearbase.ts):
|
|
240
335
|
// neighbor absolute addresses within this many bytes may share one base local. Thumb's
|
|
241
|
-
// `add rd, #imm8` reaches 255. Absent ⇒ the
|
|
336
|
+
// `add rd, #imm8` reaches 255. Absent ⇒ the variation stands down for this target.
|
|
242
337
|
nearBaseSpan?: number;
|
|
243
338
|
// Does this compiler CONSTANT-FOLD a constant SUBSCRIPT into the literal address it
|
|
244
339
|
// materializes for an inline constant-address access? agbcc does: `((u8 *)0x3001100)[3]`
|
|
@@ -250,25 +345,56 @@ export interface TargetDescription {
|
|
|
250
345
|
// and PPC lanes put the addend in the instruction by construction (`lui`/`%lo`, `lis`/`ori`),
|
|
251
346
|
// so a surviving offset carries no information there. Absent ⇒ the row is never offered.
|
|
252
347
|
foldsConstAddrOffset?: boolean;
|
|
348
|
+
// Does this compiler fold a pointer local's OWN ADVANCE back into the memory operand —
|
|
349
|
+
// `*p = a; p = p + 1; *p = b;` → `strh [r3, #0]` + `strh [r3, #2]`, no `add` — so the advanced
|
|
350
|
+
// spelling emits the indexed one's stores wherever the pointee is not volatile? agbcc does, on
|
|
351
|
+
// its own compiled evidence: the four corners in test/advance.test.ts's header, each built
|
|
352
|
+
// through the benchmark's agbcc against `kleod:StreamCmd_SetWindowRegs`'s object, and the pair
|
|
353
|
+
// `TARGET_BEHAVIOR_READINGS` compiles to one object in the matching suite. True ⇒
|
|
354
|
+
// the `advance` registry entry's target gate withholds the UN-QUALIFIED variation, whose spelling this compiler cannot
|
|
355
|
+
// distinguish from the indexed one it already offers; `/advance/volatile` still rides, because
|
|
356
|
+
// `volatile` is what bars the fold and that product is the match on this row. Absent ⇒ falsy ⇒
|
|
357
|
+
// the plain variation ships, which is the conservative reading for a compiler whose pair nobody has
|
|
358
|
+
// compiled — a compiler opts in on its own evidence and never by inheriting.
|
|
359
|
+
foldsPointerAdvance?: boolean;
|
|
253
360
|
// Does this compiler EMIT a memory read in the block the source SPELLED it in? One direction
|
|
254
361
|
// only: the def-block placement rule (StructureOptions.readsStayWhereWritten) re-spells a read
|
|
255
362
|
// at the block the asm performed it in, which reproduces the asm iff nothing sinks a spelled
|
|
256
363
|
// read past a branch and nothing lifts one to a dominator. The CONVERSE — the asm's read block
|
|
257
364
|
// is where the source read — is FALSE even here, and no default may be declared as if it held.
|
|
258
365
|
//
|
|
259
|
-
// agbcc (gcc 2.9-arm
|
|
260
|
-
//
|
|
261
|
-
//
|
|
262
|
-
//
|
|
263
|
-
//
|
|
264
|
-
//
|
|
265
|
-
//
|
|
266
|
-
//
|
|
267
|
-
//
|
|
366
|
+
// agbcc (gcc 2.9-arm) declares TRUE from its own sources plus compiled pairs: gcc's Makefile
|
|
367
|
+
// SRCS compiles neither sched.c nor reorg.c and toplev.c never mentions flag_schedule_insns, so
|
|
368
|
+
// there is no scheduler; and `s = *g; if (c) A(s); else B(s);` against `if (c) A(*g); else
|
|
369
|
+
// B(*g);` emits one ldrb + one pool word versus one of each PER ARM, moving neither. gcse.c calls
|
|
370
|
+
// one_code_hoisting_pass only `if (optimize_size)`, which toplev.c sets only for -Os, so at -O2
|
|
371
|
+
// the hoister never runs. At -Os it runs, and on `if (c) A(*gp); else B(*gp);` it moves only the
|
|
372
|
+
// pool ADDRESS load above the branch: each arm keeps its own dereference, so the read still
|
|
373
|
+
// stays in the block that spelled it. That pair is committed: `corpus/agbcc-hoist-{O2,Os}.s` from
|
|
374
|
+
// `corpus/probe-agbcc-hoist.c`, regenerated by `scripts/regen-flag-pair-probes.ts`, asserted in
|
|
375
|
+
// hoist-level-probes.test.ts. The two passes that DO move a read between blocks at -O2 — loop
|
|
376
|
+
// invariant motion, and the PRE that makes the converse false — are refusals the rule owes;
|
|
377
|
+
// structure/analysis.ts carries them.
|
|
268
378
|
//
|
|
269
379
|
// ABSENT ⇒ the rule stands down, where ido/kmc-gcc/mwcc sit: each has a scheduler and none has
|
|
270
380
|
// been put through that pair. A compiler opts in on its own evidence, never by inheriting.
|
|
271
381
|
readsStayWhereWritten?: boolean;
|
|
382
|
+
// Does a LOCAL initialised with a memory read its dominating TEST already performed cost this
|
|
383
|
+
// compiler a SECOND load? The pair is the arm of `if (a && (p[1] & 0x7f) == 0x7f) { … }` spelled
|
|
384
|
+
// `u8 v = p[1]; p[2] = v;` against `p[2] = p[1];`, and again with a store and with a call
|
|
385
|
+
// between the local and its use. agbcc loads `p[1]` TWICE for every local spelling and once
|
|
386
|
+
// for the inline one; ido7.1, gcc2.7.2kmc, gcc2.7.2 and mwcc_242_81 load it ONCE for every
|
|
387
|
+
// local spelling, holding the register across the store and across the call. agbcc is the
|
|
388
|
+
// odd one out of five, so the one agbcc-shaped claim that rested on it — raise/shortcircuit.ts's
|
|
389
|
+
// `read-behind-effect`, "a copy analysis.ts spells as a local costs a load" — reads it here
|
|
390
|
+
// rather than running on every target — on mwcc it costs the probe named at PPC_MWCC's value
|
|
391
|
+
// its byte-match.
|
|
392
|
+
// Read off the target by a raising pass (raise/pre-recovery.ts), not by the structurer.
|
|
393
|
+
//
|
|
394
|
+
// ABSENT ⇒ false: the refusal stands down, and a compiler opts IN on its own compiled pair. The
|
|
395
|
+
// three descriptions that measured false (four compilers) set it anyway, so absent means
|
|
396
|
+
// UNMEASURED rather than "no".
|
|
397
|
+
reloadsLocalReread?: boolean;
|
|
272
398
|
};
|
|
273
399
|
}
|
|
274
400
|
|
|
@@ -324,11 +450,15 @@ export const ARMV4T_AGBCC: TargetDescription = {
|
|
|
324
450
|
orderArgCopiesByWriteOrder: true,
|
|
325
451
|
nearBaseSpan: 255,
|
|
326
452
|
foldsConstAddrOffset: true,
|
|
453
|
+
foldsPointerAdvance: true,
|
|
327
454
|
readsStayWhereWritten: true,
|
|
328
455
|
switchAllowsBoundCase: true,
|
|
329
456
|
switchArmsFollowLayout: true,
|
|
457
|
+
switchRequiresFrontLoadedTests: true,
|
|
330
458
|
hoistsSingleSetArm: true,
|
|
331
459
|
arrayShapeFromStride: true,
|
|
460
|
+
reloadsLocalReread: true,
|
|
461
|
+
narrowParamWitness: 'prologue-extension',
|
|
332
462
|
// agbcc: reload walks pseudos ascending handing each global-alloc loser a fresh slot, a user
|
|
333
463
|
// local's pseudo number is its `expand_decl` position, and the Thumb frame grows UPWARD
|
|
334
464
|
// (FRAME_GROWS_DOWNWARD is commented out in thumb.h). So the earlier-declared spilled local
|
|
@@ -337,6 +467,9 @@ export const ARMV4T_AGBCC: TargetDescription = {
|
|
|
337
467
|
// control `synthetic:spillorder_rev` (the same body in the order asmlift already emits, which
|
|
338
468
|
// must stay a MATCH), plus `synthetic:dma_fill_uninit`, a row this did not author.
|
|
339
469
|
spillSlotOrder: 'ascending',
|
|
470
|
+
// agbcc reserves the outgoing area with the rest of the frame (`add sp, sp, #-N` covers both)
|
|
471
|
+
// and stages arguments 5+ into it at [sp,#0] upward — thumb.h's ACCUMULATE_OUTGOING_ARGS.
|
|
472
|
+
stagesOutgoingArgsInFrame: true,
|
|
340
473
|
},
|
|
341
474
|
};
|
|
342
475
|
|
|
@@ -358,6 +491,13 @@ export const MIPS_IDO: TargetDescription = {
|
|
|
358
491
|
preserveDivergentBranchSense: true,
|
|
359
492
|
orderArgCopiesByWriteOrder: true,
|
|
360
493
|
switchAllowsNeqCase: false,
|
|
494
|
+
// MEASURED — the pair at the field compiles to one load of `p[1]` for every local spelling.
|
|
495
|
+
reloadsLocalReread: false,
|
|
496
|
+
// MEASURED at `-mips2 -O2 -32 -non_shared -G 0`: the `sll` leads the function for BOTH
|
|
497
|
+
// spellings, so the prologue position cannot decide; a narrow DECLARED parameter is the one that
|
|
498
|
+
// is both homed dead AND widened in its own argument register. raise/paramwidth.ts's header has
|
|
499
|
+
// the disassemblies, including the counterexample for each half alone.
|
|
500
|
+
narrowParamWitness: 'home-store-and-in-place',
|
|
361
501
|
// MEASURED `descending` (the earlier-declared spilled local takes the HIGHER offset) and NOT
|
|
362
502
|
// SHIPPED. The probe is COMMITTED — `packages/core/test/corpus/probe-declrank.c` and its
|
|
363
503
|
// reversed-declaration twin, with this compiler's objects beside them — and a test reads the
|
|
@@ -388,7 +528,7 @@ export const MIPS_GCC: TargetDescription = {
|
|
|
388
528
|
// KMC GCC keeps a loop seeded from an argument register IN that register (coalesceLoopInit
|
|
389
529
|
// true, like IDO): test/corpus/gcc-gcd.asm runs its whole loop on a0/a1 with no init copies,
|
|
390
530
|
// and the row it comes from matches only with the parameters as the loop's homes. The other
|
|
391
|
-
// structuring
|
|
531
|
+
// structuring compiler behaviors take the universal default until a KMC fixture says otherwise.
|
|
392
532
|
//
|
|
393
533
|
// THIS IS A COMPILER-WIDE GUESS STANDING IN FOR A PER-FUNCTION OBSERVATION the assembly states
|
|
394
534
|
// outright: whether the compiler kept a loop's induction variable in its argument register. What
|
|
@@ -407,6 +547,48 @@ export const MIPS_GCC: TargetDescription = {
|
|
|
407
547
|
coalesceLoopInit: true,
|
|
408
548
|
preserveDivergentBranchSense: true,
|
|
409
549
|
orderArgCopiesByWriteOrder: true,
|
|
550
|
+
// DECLARED ON A PAIR FROM EACH TOOLCHAIN THIS DESCRIPTION SERVES, never inherited from agbcc's
|
|
551
|
+
// and never from one sibling to the other. This description is keyed per DESCRIPTION while the
|
|
552
|
+
// fact is per TOOLCHAIN (the note at `spillSlotOrder`), and `MIPS_GCC` serves two, so both owe
|
|
553
|
+
// a pair: `corpus/gcc272kmc-sw{frontload,ladder}.asm` from GCC_KMC_TOOLCHAIN at -O2 and
|
|
554
|
+
// `corpus/gcc272-sw{frontload,ladder}.asm` from the Mario Party 3 toolchain at -O1 — one
|
|
555
|
+
// two-case body written each way, regenerated from their committed C bodies by
|
|
556
|
+
// `scripts/regen-switch-spelling-probes.ts`, each carrying a provenance header, and each pair
|
|
557
|
+
// two different objects: the `switch` emits both `beq`s before the first `sw`, while the ladder
|
|
558
|
+
// puts an arm's `sw` above the second test. A test asserts that split off every fixture. The
|
|
559
|
+
// two toolchains come out byte-identical on this body — measured, not assumed, and their
|
|
560
|
+
// declaration-rank probe objects differ, so a sibling pair is not a formality.
|
|
561
|
+
//
|
|
562
|
+
// BOTH TOOLCHAINS EMIT BRANCH-LIKELY on some bodies, and the reading survives it — a property
|
|
563
|
+
// of the pair, not a way the two diverge. `s32 m1(s32 x, s32 *p){ switch (x) { case 6: *p = 1;
|
|
564
|
+
// break; case 7: *p = 2; break; } return 0; }` cross-jumps the two stores into one and compiles
|
|
565
|
+
// instruction for instruction identically at -O1 and at -O2, to `beq` / `beql` with the shared
|
|
566
|
+
// `sw` after both; the same body as an if/else-if ladder puts the first arm's `li v0,1` BETWEEN
|
|
567
|
+
// the two tests. Both spellings get all the way through on both toolchains — the `switch` one
|
|
568
|
+
// recovers a `switch`, the ladder an `if` nest — so what keeps that body out of the fixture set
|
|
569
|
+
// is not a decline. It is that the cross-jumped arms leave no STORE to read the split off: both
|
|
570
|
+
// put their one `sw` after both tests, and the interleaved instruction is the ladder's
|
|
571
|
+
// `li v0,1`. The committed pair keeps its distinct-store arms for that reason. (A THREE-case
|
|
572
|
+
// body says the
|
|
573
|
+
// same more loudly, the balanced tree's `slti` bound test landing ahead of the bodies with the
|
|
574
|
+
// rest, but at three cases neither spelling reaches Regime A on this compiler, so that pair
|
|
575
|
+
// could not also serve as the recovery test.)
|
|
576
|
+
//
|
|
577
|
+
// WHAT IS WEAKER HERE THAN AT agbcc: this compiler HAS a scheduler and fills delay slots, and
|
|
578
|
+
// both fixtures show it — the ladder's `bne` carries the NEXT test's `li` in its slot. What the
|
|
579
|
+
// pair shows is that it moves no BODY above a test, which is the only claim the gate rests on,
|
|
580
|
+
// and the gate's failure direction (switch-recover.ts PRE5) is a lost `switch` spelling, never
|
|
581
|
+
// a wrong answer. A lost spelling is not always a clean ladder: recovery re-runs on the
|
|
582
|
+
// sub-trees a decline leaves, so a NESTED dispatch comes back as an `if` nest around a `switch`
|
|
583
|
+
// over some of its arms.
|
|
584
|
+
switchRequiresFrontLoadedTests: true,
|
|
585
|
+
// MEASURED on BOTH toolchains this description serves (the note above): one load of `p[1]` for
|
|
586
|
+
// every local spelling of the pair at the field, gcc2.7.2kmc at -O2 and gcc2.7.2 at -O1 alike.
|
|
587
|
+
reloadsLocalReread: false,
|
|
588
|
+
// MEASURED on BOTH toolchains this description serves: `int f(s8 x){return x;}` and
|
|
589
|
+
// `int f(s32 x){return (s8)x;}` compile to BYTE-IDENTICAL objects, gcc2.7.2kmc at -O2 and
|
|
590
|
+
// gcc2.7.2 at -O1 alike — so the object carries no witness at all and the pass refuses.
|
|
591
|
+
narrowParamWitness: 'none',
|
|
410
592
|
// MEASURED `ascending` on both toolchains this description serves — 7 of 7 spills each, and
|
|
411
593
|
// rank → offset unchanged under a reversed declaration list — and NOT SHIPPED, for the same
|
|
412
594
|
// reason as ido7.1: no row on either tier lifts with two or more spilled user locals. Both
|
|
@@ -433,13 +615,22 @@ export const PPC_MWCC: TargetDescription = {
|
|
|
433
615
|
argRegs: ['r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9', 'r10'],
|
|
434
616
|
returnReg: 'r3',
|
|
435
617
|
capabilities: { endianness: 'big', hwDivide: true, hwFloat: true, flags: true },
|
|
436
|
-
// CodeWarrior's structuring
|
|
618
|
+
// CodeWarrior's structuring compiler behaviors are UNKNOWN until fixtures reveal them — safe universal
|
|
437
619
|
// defaults; coalesceLoopInit false until a CW loop fixture says otherwise — the second of the
|
|
438
620
|
// two compiler-wide guesses standing in for the per-function observation named at MIPS_GCC.
|
|
439
621
|
compilerBehaviors: {
|
|
440
622
|
coalesceLoopInit: false,
|
|
441
623
|
preserveDivergentBranchSense: true,
|
|
442
624
|
orderArgCopiesByWriteOrder: true,
|
|
625
|
+
// MEASURED — one load of `p[1]` for every local spelling of the pair at the field, the value
|
|
626
|
+
// held in a callee-saved register across the call. And the rule it turns off pays here:
|
|
627
|
+
// `u8 v = p[3]; if ((v & 0x7f) == 0x7f) { fnA(); p[4] = v; return; }` under an `if (a)`
|
|
628
|
+
// matches only once `read-behind-effect` stops refusing it (3/24 → MATCH 0/22).
|
|
629
|
+
reloadsLocalReread: false,
|
|
630
|
+
// The PowerPC prologue widens a declared narrow parameter with `extsb`/`extsh`, which the
|
|
631
|
+
// frontend lifts to the same `sext` op agbcc's shift pair folds to — the position shape, on
|
|
632
|
+
// another ISA. `synthetic:{sextb,tos8}:mwcc_242_81` are its rows, MATCH through that pass.
|
|
633
|
+
narrowParamWitness: 'prologue-extension',
|
|
443
634
|
// NOT MEASURED, and `'unknown'` is therefore the only honest value here rather than a withheld
|
|
444
635
|
// one, as it is at MIPS_IDO and MIPS_GCC. No mwcc row lifts with two or more spilled user
|
|
445
636
|
// locals, and the compiler does not spill the committed declaration-rank probe either: at
|
|
@@ -451,9 +642,146 @@ export const PPC_MWCC: TargetDescription = {
|
|
|
451
642
|
},
|
|
452
643
|
};
|
|
453
644
|
|
|
645
|
+
/** A toolchain: one compiler binary, named as decomp.me names it. Several toolchains may share a
|
|
646
|
+
* description (`MIPS_GCC` serves two), and each keeps its own evidence. */
|
|
647
|
+
export interface ToolchainTarget {
|
|
648
|
+
family: FlagFamily;
|
|
649
|
+
/** The codegen flags every committed probe of this toolchain was compiled with: the flags a
|
|
650
|
+
* synthetic row compiles at, and the flags a decompile with none given assumes. They are in
|
|
651
|
+
* normal form (`storedFlags` keeps every word), so the words only the harness needs (`-c`, the
|
|
652
|
+
* diagnostics) live beside the binary's paths in @asmlift/toolchains.
|
|
653
|
+
*
|
|
654
|
+
* A toolchain with no SYNTHETIC tier has NONE, and inventing one would be the fiction the field
|
|
655
|
+
* exists to avoid: nothing about the compiler picks a set, every row names the flags its own
|
|
656
|
+
* build compiles that unit with, and the probes behind its description are run at those. A
|
|
657
|
+
* decompile that reaches such a toolchain with no flags from anywhere is refused rather than
|
|
658
|
+
* resolved against a set nobody chose (`resolveFlags`). */
|
|
659
|
+
canonicalFlags?: readonly string[];
|
|
660
|
+
/** What this compiler does. Every flag set of the toolchain decompiles against it: a profile of a
|
|
661
|
+
* compiler inherits its declarations until a probe refutes one there. */
|
|
662
|
+
description: TargetDescription;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
export const TOOLCHAIN_TARGETS = {
|
|
666
|
+
agbcc: {
|
|
667
|
+
family: 'agbcc',
|
|
668
|
+
canonicalFlags: ['-mthumb-interwork', '-O2', '-fhex-asm', '-fprologue-bugfix'],
|
|
669
|
+
description: ARMV4T_AGBCC,
|
|
670
|
+
},
|
|
671
|
+
'ido7.1': {
|
|
672
|
+
family: 'ido',
|
|
673
|
+
canonicalFlags: ['-mips2', '-O2', '-32', '-non_shared', '-G', '0'],
|
|
674
|
+
description: MIPS_IDO,
|
|
675
|
+
},
|
|
676
|
+
'gcc2.7.2kmc': {
|
|
677
|
+
family: 'gcc',
|
|
678
|
+
canonicalFlags: [
|
|
679
|
+
'-mabi=32',
|
|
680
|
+
'-mgp32',
|
|
681
|
+
'-mfp32',
|
|
682
|
+
'-mno-abicalls',
|
|
683
|
+
'-fno-PIC',
|
|
684
|
+
'-G',
|
|
685
|
+
'0',
|
|
686
|
+
'-funsigned-char',
|
|
687
|
+
'-mips3',
|
|
688
|
+
'-EB',
|
|
689
|
+
'-O2',
|
|
690
|
+
'-fno-builtin',
|
|
691
|
+
'-fno-asm',
|
|
692
|
+
],
|
|
693
|
+
description: MIPS_GCC,
|
|
694
|
+
},
|
|
695
|
+
'gcc2.7.2': {
|
|
696
|
+
family: 'gcc',
|
|
697
|
+
canonicalFlags: ['-G0', '-mips3', '-mgp32', '-mfp32', '-O1', '-Wa,--vr4300mul-off'],
|
|
698
|
+
description: MIPS_GCC,
|
|
699
|
+
},
|
|
700
|
+
mwcc_242_81: {
|
|
701
|
+
family: 'mwcc',
|
|
702
|
+
canonicalFlags: [
|
|
703
|
+
'-proc',
|
|
704
|
+
'gekko',
|
|
705
|
+
'-O4,p',
|
|
706
|
+
'-enum',
|
|
707
|
+
'int',
|
|
708
|
+
'-inline',
|
|
709
|
+
'auto',
|
|
710
|
+
'-fp',
|
|
711
|
+
'hard',
|
|
712
|
+
'-Cpp_exceptions',
|
|
713
|
+
'off',
|
|
714
|
+
],
|
|
715
|
+
description: PPC_MWCC,
|
|
716
|
+
},
|
|
717
|
+
// The other two CodeWarrior builds the GameCube projects compile with: 2.3.3b163n (Pikmin's whole
|
|
718
|
+
// game tree) and 2.4.7b107 (Mario Party 4's DOL). Both SHARE `PPC_MWCC`, and on their own evidence
|
|
719
|
+
// rather than because they are the same compiler family: `test/matching/ppc-compiler-behaviors.test.ts`
|
|
720
|
+
// re-runs both of its probes on each binary at the flags that build's rows compile at, and each
|
|
721
|
+
// reads as the description says. What moves those readings is the optimisation level — at `-O0,p`
|
|
722
|
+
// the shipped `mwcc_242_81` re-reads the local too — so the difference is one no per-compiler
|
|
723
|
+
// field could carry anyway.
|
|
724
|
+
//
|
|
725
|
+
// NEITHER HAS CANONICAL FLAGS. They have no synthetic tier: every row of theirs is a real one that
|
|
726
|
+
// names its unit's own flags, and Pikmin's `-O4,p -lang=c++` and Mario Party 4's `-O0,p -lang=c`
|
|
727
|
+
// are two different sets, neither of which is "the" one. See `canonicalFlags` above.
|
|
728
|
+
mwcc_233_163n: {
|
|
729
|
+
family: 'mwcc',
|
|
730
|
+
description: PPC_MWCC,
|
|
731
|
+
},
|
|
732
|
+
mwcc_247_107: {
|
|
733
|
+
family: 'mwcc',
|
|
734
|
+
description: PPC_MWCC,
|
|
735
|
+
},
|
|
736
|
+
} as const satisfies Readonly<Record<string, ToolchainTarget>>;
|
|
737
|
+
|
|
738
|
+
export type ToolchainId = keyof typeof TOOLCHAIN_TARGETS;
|
|
739
|
+
|
|
740
|
+
/** A toolchain that HAS canonical flags — every toolchain with a synthetic tier. Computed from the
|
|
741
|
+
* registry, so the set cannot drift from it: a caller that needs a fallback flag set (the
|
|
742
|
+
* playground's target picker, the benchmark's synthetic rows) takes this instead of `ToolchainId`
|
|
743
|
+
* and a real-only toolchain is refused where it is named, not where it is used. */
|
|
744
|
+
export type CanonicalToolchainId = {
|
|
745
|
+
[K in ToolchainId]: (typeof TOOLCHAIN_TARGETS)[K] extends { canonicalFlags: readonly string[] } ? K : never;
|
|
746
|
+
}[ToolchainId];
|
|
747
|
+
|
|
748
|
+
/** A toolchain's canonical flags, or `undefined` where it has none. The ONE place the optional
|
|
749
|
+
* field is read: the registry's literal type says which entries carry it, and every caller that
|
|
750
|
+
* holds a plain `ToolchainId` has to answer for the ones that do not. */
|
|
751
|
+
export function canonicalFlagsOf(id: ToolchainId): readonly string[] | undefined {
|
|
752
|
+
const t: ToolchainTarget = TOOLCHAIN_TARGETS[id];
|
|
753
|
+
return t.canonicalFlags;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
export function isCanonicalToolchainId(id: ToolchainId): id is CanonicalToolchainId {
|
|
757
|
+
return canonicalFlagsOf(id) !== undefined;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
export function isToolchainId(id: string): id is ToolchainId {
|
|
761
|
+
return Object.hasOwn(TOOLCHAIN_TARGETS, id);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
/** A toolchain at one flag set. */
|
|
765
|
+
export interface ResolvedTarget {
|
|
766
|
+
toolchain: ToolchainId;
|
|
767
|
+
/** the flags the function's target and every candidate compile with */
|
|
768
|
+
cflags: readonly string[];
|
|
769
|
+
/** the description asmlift decompiles against: the toolchain's, at every flag set */
|
|
770
|
+
target: TargetDescription;
|
|
771
|
+
/** what the flags make the compiler do */
|
|
772
|
+
profile: CodegenProfile;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/** The target a function compiled by `toolchain` at `cflags` decompiles against, and the profile
|
|
776
|
+
* those flags describe. Throws on a level word the toolchain's family cannot read. */
|
|
777
|
+
export function targetFor(toolchain: ToolchainId, cflags: readonly string[]): ResolvedTarget {
|
|
778
|
+
const t: ToolchainTarget = TOOLCHAIN_TARGETS[toolchain];
|
|
779
|
+
return { toolchain, cflags, target: t.description, profile: parseFlags(t.family, cflags) };
|
|
780
|
+
}
|
|
781
|
+
|
|
454
782
|
/** Build the structurer's options for a target: the function's own `returnsVoid` plus every
|
|
455
|
-
* `compilerBehaviors`
|
|
456
|
-
* target-agnostic structurer — a new behavior
|
|
783
|
+
* `compilerBehaviors` field. The ONE place a target's compiler behaviors flow into the
|
|
784
|
+
* target-agnostic structurer — a new compiler behavior is a field in `compilerBehaviors`, consumed
|
|
457
785
|
* automatically.
|
|
458
786
|
*
|
|
459
787
|
* The spread is over the WHOLE bag, so a behavior whose reader is not the structurer rides along
|
|
@@ -462,8 +790,9 @@ export const PPC_MWCC: TargetDescription = {
|
|
|
462
790
|
* are a SUPERSET of StructureOptions', not a bijection, and nothing may derive one from the other
|
|
463
791
|
* by enumerating keys. */
|
|
464
792
|
export function structureOptionsFor(t: TargetDescription, returnsVoid: boolean): StructureOptions {
|
|
465
|
-
// `littleEndian`
|
|
466
|
-
// recognition is LSB-first
|
|
793
|
+
// `littleEndian` and `deviceRegisters` are the HARDWARE capabilities the structurer consumes
|
|
794
|
+
// (bitfield extract recognition is LSB-first; the dead-read spelling refuses outside the device
|
|
795
|
+
// window); everything else is a compiler behavior.
|
|
467
796
|
//
|
|
468
797
|
// ONE FIELD IS NOT A STRAIGHT SPREAD, and this is where the difference belongs. A frame
|
|
469
798
|
// direction has THREE states here — `ascending`, `descending`, and `'unknown'` meaning measured
|
|
@@ -475,6 +804,7 @@ export function structureOptionsFor(t: TargetDescription, returnsVoid: boolean):
|
|
|
475
804
|
return {
|
|
476
805
|
returnsVoid,
|
|
477
806
|
littleEndian: t.capabilities.endianness === 'little',
|
|
807
|
+
...(t.capabilities.deviceRegisters ? { deviceRegisters: t.capabilities.deviceRegisters } : {}),
|
|
478
808
|
...behaviors,
|
|
479
809
|
...(spillSlotOrder === 'ascending' || spillSlotOrder === 'descending' ? { spillSlotOrder } : {}),
|
|
480
810
|
};
|