@asmlift/core 0.9.0 → 0.10.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 +25 -24
- package/package.json +1 -1
- package/src/aggregate.ts +149 -0
- package/src/backend/c.ts +1 -0
- package/src/backend/cfamily.ts +165 -50
- package/src/backend/cpp.ts +2 -1
- package/src/backend/pascal.ts +31 -12
- package/src/contracts.ts +173 -16
- package/src/declare.ts +69 -20
- package/src/frontend/asmdata.ts +7 -1
- package/src/frontend/call-plan.ts +964 -60
- package/src/frontend/dead-reads.ts +628 -0
- package/src/frontend/device-pins.ts +23 -4
- package/src/frontend/emit.ts +4 -3
- package/src/frontend/fpu.ts +224 -17
- package/src/frontend/frame-objects.ts +1232 -192
- package/src/frontend/frontend.ts +64 -3
- package/src/frontend/high-half.ts +53 -4
- package/src/frontend/jump-table.ts +530 -0
- package/src/frontend/local-object.ts +158 -32
- package/src/frontend/mips.ts +2076 -215
- package/src/frontend/own-struct-return.ts +500 -0
- package/src/frontend/ppc.ts +2711 -232
- package/src/frontend/register-struct-call.ts +484 -0
- package/src/frontend/reloc-symbol.ts +22 -7
- package/src/frontend/ssa.ts +1460 -44
- package/src/frontend/stackargs.ts +106 -57
- package/src/frontend/thumb.ts +2044 -318
- package/src/ir/alias.ts +42 -0
- package/src/ir/bits.ts +681 -1
- package/src/ir/core.ts +100 -5
- package/src/ir/discipline.ts +86 -21
- package/src/ir/entry.ts +159 -0
- package/src/{structure → ir}/loops.ts +6 -2
- package/src/ir/opcodes.ts +109 -9
- package/src/ir/types.ts +86 -2
- package/src/ir/verify.ts +80 -11
- package/src/l3/ast.ts +290 -18
- package/src/l3/basecse.ts +33 -5
- package/src/l3/constinit.ts +382 -0
- package/src/l3/dce.ts +2 -2
- package/src/l3/initfirst.ts +42 -11
- package/src/l3/intcell.ts +2 -1
- package/src/l3/narrowdecl.ts +77 -36
- package/src/l3/pollguard.ts +8 -2
- package/src/l3/ptrcell.ts +31 -34
- package/src/l3/ptrfield.ts +123 -8
- package/src/l3/regspell.ts +9 -4
- package/src/l3/reindex.ts +283 -96
- package/src/l3/storage.ts +98 -1
- package/src/l3/symbol-refs.ts +84 -6
- package/src/l3/tailmerge.ts +4 -2
- package/src/l3/typing.ts +108 -15
- package/src/l3/unreduce.ts +6 -5
- package/src/l3/volatileptr.ts +17 -54
- package/src/mangle.ts +30 -0
- package/src/pattern/engine.ts +361 -73
- package/src/pipeline.ts +541 -24
- package/src/proto-context.ts +905 -45
- package/src/proto.ts +555 -40
- package/src/raise/aggmembers.ts +290 -0
- package/src/raise/arrays.ts +5 -1
- package/src/raise/declared-volatile.ts +207 -31
- package/src/raise/floathelpers.ts +254 -33
- package/src/raise/gvn.ts +3 -3
- package/src/raise/latch.ts +162 -6
- package/src/raise/memberarrays.ts +10 -4
- package/src/raise/offsetnames.ts +10 -26
- package/src/raise/pairparams.ts +33 -0
- package/src/raise/paramwidth.ts +196 -6
- package/src/raise/pre-recovery.ts +102 -19
- package/src/raise/pre-structure.ts +54 -0
- package/src/raise/recover.ts +12 -5
- package/src/raise/reroll.ts +694 -0
- package/src/raise/shortcircuit.ts +26 -12
- package/src/raise/softdiv.ts +1 -1
- package/src/raise/storesign.ts +153 -0
- package/src/raise/struct-arrays.ts +30 -11
- package/src/raise/structs.ts +396 -33
- package/src/raise/truncload.ts +4 -3
- package/src/raise/unthread.ts +400 -0
- package/src/rank-declare.ts +118 -12
- package/src/rank-variations.ts +89 -31
- package/src/rank.ts +307 -54
- package/src/runtime-helpers.ts +140 -22
- package/src/structure/analysis.ts +165 -2
- package/src/structure/arith-ops.ts +23 -0
- package/src/structure/globalaccess.ts +111 -9
- package/src/structure/hazards.ts +16 -64
- package/src/structure/local-statics.ts +145 -9
- package/src/structure/namecoalesce.ts +1 -1
- package/src/structure/pointer-spelling.ts +1106 -0
- package/src/structure/redundant-test.ts +1 -1
- package/src/structure/structure.ts +2180 -1054
- package/src/structure/switch-recover.ts +244 -100
- package/src/symbols.ts +6 -0
- package/src/target.ts +765 -52
- package/src/trace.ts +26 -8
- package/src/variation-definitions.ts +126 -14
- package/src/variation-gates.ts +2 -0
- package/src/variation-tokens.ts +5 -1
package/src/target.ts
CHANGED
|
@@ -19,14 +19,12 @@
|
|
|
19
19
|
// device may read, so a slot it cannot reach is not refused.
|
|
20
20
|
// • capabilities.blockTransferCalls → the same audit: a frame address handed to one of these
|
|
21
21
|
// as its SOURCE is read as far as the call's literal control word says, and never written.
|
|
22
|
-
// • capabilities.deviceRegisters →
|
|
22
|
+
// • capabilities.deviceRegisters → five readers, and they ask ONE question — "would a source
|
|
23
23
|
// have spelled this address `volatile`" — which is a question about SPELLING and may be
|
|
24
24
|
// approximate: the `/vol-store` variation's eligibility (l3/volstore.ts), rank.ts's volatility
|
|
25
25
|
// tie-break between two byte-identical spellings, the first half of `/unreduce`'s
|
|
26
26
|
// disjointness gate (l3/unreduce.ts), the `/homesplit` pairing's refusal to leave a device
|
|
27
|
-
// READ inline where the spelling it replaces would have qualified it (l3/homesplit.ts), the
|
|
28
|
-
// structurer's refusal to SPELL a dead memory read whose address no qualifier could ever
|
|
29
|
-
// reach (structure.ts `volatileQualifiable`, threaded through StructureOptions), and the
|
|
27
|
+
// READ inline where the spelling it replaces would have qualified it (l3/homesplit.ts), and the
|
|
30
28
|
// device pin (frontend/device-pins.ts), which spells every access in the window `volatile`
|
|
31
29
|
// in a function the frame-object audit keeps as one object, and in one it accepts object by
|
|
32
30
|
// object every device read and the device stores a later store in their block overwrites —
|
|
@@ -44,14 +42,19 @@
|
|
|
44
42
|
// Others are read off the target directly by a consumer that is not the structurer — among
|
|
45
43
|
// them `nearBaseSpan` and `foldsConstAddrOffset` (rank.ts, L3 respell variations),
|
|
46
44
|
// `reloadsLocalReread`, `narrowParamWitness` and `aggregateBoundary` (raise/pre-recovery.ts),
|
|
47
|
-
// `aggregateReturn`, `largestAlignment`, `enumBytes
|
|
48
|
-
//
|
|
45
|
+
// `aggregateReturn`, `largestAlignment`, `enumBytes`, `bitfieldPacking` and
|
|
46
|
+
// `byValueAggregateArgWords` (aggregate.ts, for frontend/call-plan.ts — which the Thumb, MIPS
|
|
47
|
+
// and PowerPC lowerings share — frontend/thumb.ts, frontend/mips.ts, frontend/ppc.ts and
|
|
48
|
+
// frontend/frame-objects.ts),
|
|
49
49
|
// `hoistsSingleSetArm` (raise/narrowlocal.ts and raise/retsink.ts), `arrayShapeFromStride`
|
|
50
50
|
// (raise/globalshape.ts, run on the LIFTED fn), `eightByteReturnScratch`
|
|
51
51
|
// (frontend/thumb.ts, which reads the epilogue), `callThunks` (frontend/thumb.ts, which
|
|
52
|
-
// lowers a `bl` to one as a call through its register), `
|
|
53
|
-
//
|
|
54
|
-
// raise/
|
|
52
|
+
// lowers a `bl` to one as a call through its register), `farJumpRelay` (frontend/thumb.ts,
|
|
53
|
+
// which lowers a marked `bl` to the branch it stands for), `roundTripsDoubleLiterals`
|
|
54
|
+
// (raise/floathelpers.ts), `volatileReadsExtendInRegister` (frontend/device-pins.ts, and
|
|
55
|
+
// raise/declared-volatile.ts through liftStamped and the offset-name pass), and
|
|
56
|
+
// `keepsUnusedReads` and `discardsUnusedReadsInto` (frontend/dead-reads.ts, the second
|
|
57
|
+
// gathered by frontend/ppc.ts and frontend/mips.ts as they lower a load). The
|
|
55
58
|
// field names are a SUPERSET of
|
|
56
59
|
// StructureOptions' — see `structureOptionsFor`.
|
|
57
60
|
//
|
|
@@ -70,6 +73,8 @@ import {
|
|
|
70
73
|
IDO_RUNTIME_HELPERS,
|
|
71
74
|
MIPS_GCC_RUNTIME_HELPERS,
|
|
72
75
|
PPC_MWCC_RUNTIME_HELPERS,
|
|
76
|
+
PPC_MWCC_SAVE_ROUTINES,
|
|
77
|
+
type RegisterSaveRoutines,
|
|
73
78
|
type RuntimeHelper,
|
|
74
79
|
} from './runtime-helpers';
|
|
75
80
|
import type { StaticLayout } from './structure/local-statics';
|
|
@@ -91,6 +96,29 @@ export interface SourceRead {
|
|
|
91
96
|
bytes?: number;
|
|
92
97
|
}
|
|
93
98
|
|
|
99
|
+
/** WHERE THIS ABI CAN LEAVE A GENERAL ARGUMENT REGISTER EMPTY BELOW ONE A CALL FILLS — the two ways
|
|
100
|
+
* argument words stop filling in order, read off the facts that already place them:
|
|
101
|
+
* - `skippable`: the registers an argument aligned to 8 can skip, every odd argument word where
|
|
102
|
+
* `evenWordAlignedArgs` holds (O32 `a1`/`a3`, PowerPC `r4`/`r6`/`r8`/`r10`). Compiled on
|
|
103
|
+
* 2026-10-18: `g(1, b, 3LL)` and `g(1, 3LL)` are one object on gcc2.7.2kmc, gcc2.7.2 and the
|
|
104
|
+
* three mwcc builds, `a1`/`r4` untouched under a set pair. agbcc fills every word in order
|
|
105
|
+
* (`g(1, 3LL)` is r0, r1:r2), so it has none.
|
|
106
|
+
* - `leadingFloatShadow`: a leading floating argument travels in the FPU's registers and leaves
|
|
107
|
+
* the general words it counts against holding nothing (`fpu.slots: 'leading'`, O32): with `gp`
|
|
108
|
+
* unprototyped, `gp(x, 5)` of a `float x` parameter and of an `int x` one compile alike on
|
|
109
|
+
* ido7.1, gcc2.7.2kmc and gcc2.7.2 (`li a1,5`, `a0` untouched). PowerPC's floats take no general
|
|
110
|
+
* word (`'separate'`), so they leave no hole there.
|
|
111
|
+
* Its reader is the guessed-arity gap (frontend/call-plan.ts `decideGap`). */
|
|
112
|
+
export function argRegisterHoles(target: Pick<TargetDescription, 'argRegs' | 'evenWordAlignedArgs' | 'fpu'>): {
|
|
113
|
+
skippable: ReadonlySet<string>;
|
|
114
|
+
leadingFloatShadow: boolean;
|
|
115
|
+
} {
|
|
116
|
+
return {
|
|
117
|
+
skippable: new Set(target.evenWordAlignedArgs ? target.argRegs.filter((_, k) => k % 2 === 1) : []),
|
|
118
|
+
leadingFloatShadow: target.fpu?.slots === 'leading',
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
94
122
|
/** The bytes a source read reaches from an address `off` bytes into the frame, as `[lo, hi)`
|
|
95
123
|
* relative to that address. The machine may force the address down to a unit boundary — the
|
|
96
124
|
* GBA's DMA and BIOS both do — so a 32-bit read of the halfword at [sp,#2] reads from [sp,#0],
|
|
@@ -202,16 +230,27 @@ export interface TargetDescription {
|
|
|
202
230
|
* PARM_BOUNDARY is 32 (:354).
|
|
203
231
|
*
|
|
204
232
|
* o32 MIPS has an FPU and a double after an integer argument still takes two general words,
|
|
205
|
-
* high first, at an even word as its `long long` does (
|
|
206
|
-
* because their frontend lays out no 64-bit argument (`CallLowering.pairs`).
|
|
207
|
-
* a double in a float register that takes no general word
|
|
208
|
-
* none to state. */
|
|
233
|
+
* high first, at an even word as its `long long` does (`evenWordAlignedArgs`). The MIPS targets
|
|
234
|
+
* do not state it because their frontend lays out no 64-bit argument (`CallLowering.pairs`).
|
|
235
|
+
* PowerPC EABI passes a double in a float register that takes no general word
|
|
236
|
+
* (`fpu.slots: 'separate'`), so it has none to state. */
|
|
209
237
|
doubleArgWords?: 'high-first';
|
|
238
|
+
/** AN ARGUMENT ALIGNED TO 8 STARTS AT AN EVEN ARGUMENT WORD, and the word it skips is left empty:
|
|
239
|
+
* a `long long`, a `double` in general words, and a struct passed by value whose alignment is 8,
|
|
240
|
+
* alike. `proto.ts` `wordsOf` counts the skipped word, and `aggregate.ts` `byValueCallArgs` reads
|
|
241
|
+
* the same count to place a struct. O32, compiled on ido7.1, gcc2.7.2kmc and gcc2.7.2:
|
|
242
|
+
* `takeLL(1, 0x200000003LL)` and `takeD(1, 2.5)` set `a0` and `a2:a3` with `a1` unset, and so does
|
|
243
|
+
* `takeL(s32 k, struct L { long long v; } l)`; at word 0 each is `a0:a1`. mwcc's PowerPC EABI aligns
|
|
244
|
+
* a `long long` the same way (its doubles take no general word, and a struct passed by value is
|
|
245
|
+
* passed by address). ABSENT ⇒ every argument takes the next free word: agbcc (the note at
|
|
246
|
+
* `wordsOf`). */
|
|
247
|
+
evenWordAlignedArgs?: true;
|
|
210
248
|
/** Registers this ABI does NOT pass arguments in — half of what makes a def-less live-in read an
|
|
211
249
|
* uninitialised local rather than an argument. The other half is a measurement the FRONTEND
|
|
212
250
|
* owes (did this function save the register), and the rule that combines them is in
|
|
213
|
-
* frontend/ssa.ts (LiveInModel.uninitRegs).
|
|
214
|
-
*
|
|
251
|
+
* frontend/ssa.ts (LiveInModel.uninitRegs). Thumb and PPC state it and measure the save half
|
|
252
|
+
* from their prologues (Thumb `savedRegs`, PPC and MIPS `prologueSavedRegs`). ABSENT ⇒ no register
|
|
253
|
+
* partition is claimed.
|
|
215
254
|
*
|
|
216
255
|
* It must be DISJOINT from `argRegs`, and the frontend hands both to the builder so that is
|
|
217
256
|
* checked rather than trusted (`checkedLiveInModel`): a spelling that lands in both lists used
|
|
@@ -219,14 +258,43 @@ export interface TargetDescription {
|
|
|
219
258
|
nonArgRegs?: readonly string[];
|
|
220
259
|
/** Of `nonArgRegs`, the ones this ABI does NOT require a callee to preserve — so the compiler may
|
|
221
260
|
* home a local in one with no prologue save at all, and the save half of the rule above does not
|
|
222
|
-
* apply to it. AAPCS's `ip` is the whole set
|
|
261
|
+
* apply to it. AAPCS's `ip` is the whole set on Thumb, and agbcc really does use it that way;
|
|
262
|
+
* on PPC it is `r0`, `r11` and `r12`, where mwcc homes a leaf's uninitialised locals (PPC_MWCC).
|
|
223
263
|
*
|
|
224
264
|
* UNDER-stating this list only makes the classification stricter: an unlisted register whose save
|
|
225
|
-
* the frontend cannot find
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
265
|
+
* the frontend cannot find is read as one the callee must preserve, and a read of it before any
|
|
266
|
+
* write refuses (frontend/ssa.ts `LiveInModel.unsavedCalleeSavedRegs`). OVER-stating it is the
|
|
267
|
+
* unsound direction — a callee-saved register listed here is classified with no evidence at all,
|
|
268
|
+
* which is the defect the save half exists to close. Every entry must appear in `nonArgRegs`;
|
|
269
|
+
* the frontend refuses a target where one does not. */
|
|
229
270
|
scratchRegs?: readonly string[];
|
|
271
|
+
/** Of `scratchRegs`, the ones something outside the C calling convention hands an input in. On
|
|
272
|
+
* PPC that is mwcc's RUNTIME: its register save routines take their frame address in r11
|
|
273
|
+
* (`addi r11,r1,N; bl _savegpr_NN`, and `_savegpr_31` is `stw r31,-4(r11); blr`), and
|
|
274
|
+
* `__ptmf_scall` takes its member-function pointer in r12 (`lwz r0,0(r12)`). On O32 it is
|
|
275
|
+
* hand-written asm, and every scratch register (`O32_SCRATCH_REGS`). Asm that reads one of these
|
|
276
|
+
* first is being handed a value, not reading an uninitialised local.
|
|
277
|
+
*
|
|
278
|
+
* What separates the two is MEASURED on 2.4.2 and 2.3.3: the uninitialised local the compiler
|
|
279
|
+
* homes in r11 or r12 is assigned on SOME path (`uninit_r12`: `if (s) { x = a*b; ... }`), so its
|
|
280
|
+
* entry value meets that definition at a join. A def-less read of one of these registers whose
|
|
281
|
+
* entry value no join meets with a definition of its own refuses (frontend/ssa.ts
|
|
282
|
+
* `LiveInModel.hiddenInputRegs`). r0 is not
|
|
283
|
+
* listed: the same compilers read a never-assigned local straight out of r0 (`int x; return x +
|
|
284
|
+
* a;` is `add r3,r0,r3`), and the runtime passes nothing there.
|
|
285
|
+
*
|
|
286
|
+
* Every entry must appear in `scratchRegs`; the frontend refuses a target where one does not. */
|
|
287
|
+
hiddenInputRegs?: readonly string[];
|
|
288
|
+
/** FILE-SCOPE GLOBAL REGISTER VARIABLES this compiler accepts (`register T g __asm__("r8");`):
|
|
289
|
+
* each callee-saved register a translation unit may bind a global to, keyed by its disassembly
|
|
290
|
+
* spelling and valued by the name `__asm__` takes. gcc's `globalize_reg` (agbcc regclass.c
|
|
291
|
+
* :587-611) makes such a register fixed and call-used, so no prologue saves it: a callee-saved
|
|
292
|
+
* register a function reads before writing, or writes, WITHOUT saving it is one of these, and
|
|
293
|
+
* the lift reads and writes it as that global (frontend/ssa.ts `LiveInModel.globalRegs`).
|
|
294
|
+
* `contiguous` is the order a compiler that declares them only as a contiguous run requires
|
|
295
|
+
* them in: declaring one declares every register before it. ABSENT ⇒ the compiler has no
|
|
296
|
+
* spelling for one, and such a read or write refuses (`unsavedCalleeSavedRegs`). */
|
|
297
|
+
globalRegisterVariables?: { regs: Readonly<Record<string, string>>; contiguous?: readonly string[] };
|
|
230
298
|
/** Registers a CALL destroys: after one, they hold whatever the callee left there, and no value
|
|
231
299
|
* this function computed. The frontends hand this to the SSA builder so a read of one past a
|
|
232
300
|
* call refuses instead of resolving to the pre-call definition (frontend/ssa.ts).
|
|
@@ -242,6 +310,12 @@ export interface TargetDescription {
|
|
|
242
310
|
* construction one the callee may destroy — and `clobberedByCall` (frontend/ssa.ts) checks that
|
|
243
311
|
* rather than trusting it, the way `checkedLiveInModel` checks the register partition. */
|
|
244
312
|
callerSaved: readonly string[];
|
|
313
|
+
/** Registers the ABI reserves to the kernel, which an exception handler may overwrite between ANY
|
|
314
|
+
* two instructions (O32's `k0`/`k1`), so no value written to one is proved to be there when it is
|
|
315
|
+
* read. A read of one refuses, and the return proof never counts one as holding the return
|
|
316
|
+
* address (frontend/mips.ts). Only hand-written asm names them. Absent on an ABI that reserves
|
|
317
|
+
* none. */
|
|
318
|
+
kernelRegs?: readonly string[];
|
|
245
319
|
/** The COMPILER RUNTIME HELPERS this compiler's codegen calls, and what each computes
|
|
246
320
|
* (runtime-helpers.ts). A compiler fact, which is why it lives here and not in `proto.ts`: that
|
|
247
321
|
* file holds signatures fixed by the C STANDARD, and a helper name is fixed by a runtime
|
|
@@ -256,6 +330,10 @@ export interface TargetDescription {
|
|
|
256
330
|
* `__`-prefixed function by spelling — but it buys nothing on a target whose runtime has simply
|
|
257
331
|
* not been enumerated. */
|
|
258
332
|
runtimeHelpers?: Readonly<Record<string, RuntimeHelper>>;
|
|
333
|
+
/** The runtime's OUT-OF-LINE REGISTER SAVE ROUTINES (runtime-helpers.ts `RegisterSaveRoutines`).
|
|
334
|
+
* ABSENT ⇒ none is recognised: a `bl` to one is an ordinary call, and the frame address it would
|
|
335
|
+
* have consumed is the stack pointer read as data, which refuses. */
|
|
336
|
+
registerSaveRoutines?: RegisterSaveRoutines;
|
|
259
337
|
// HARDWARE / ISA facts — independent of the compiler.
|
|
260
338
|
capabilities: {
|
|
261
339
|
endianness: 'little' | 'big'; // consumed by structureOptionsFor (bitfield extract recognition is LSB-first)
|
|
@@ -454,6 +532,51 @@ export interface TargetDescription {
|
|
|
454
532
|
// says so, but nothing in this bag could express it if they did not. Any behavior that can
|
|
455
533
|
// differ between two toolchains sharing one description is mis-keyed by construction.
|
|
456
534
|
spillSlotOrder?: 'ascending' | 'descending' | 'unknown';
|
|
535
|
+
// Which way this compiler places the ADDRESS-TAKEN local arrays of one frame against their
|
|
536
|
+
// DECLARATION order: `ascending` = the earlier-declared array takes the lower address. Read
|
|
537
|
+
// where the frame audit declares the one object as several (frame-objects.ts
|
|
538
|
+
// `keepAsOneObject`, which declares the parts `provenParts` finds in this order), so the
|
|
539
|
+
// recompile puts each part back at its own offset. Absent REFUSES that split: the one object
|
|
540
|
+
// stays one array.
|
|
541
|
+
//
|
|
542
|
+
// MEASURED on `u8 a[64]; u8 b[32]; u8 c[48];`, each filled by a `memcpy`, at canonical flags:
|
|
543
|
+
// agbcc a@0 b@64 c@96; gcc2.7.2 and gcc2.7.2kmc a@16 b@80 c@112; ido7.1 a@104 b@72 c@24 (the
|
|
544
|
+
// reverse); mwcc_242_81 a@88 b@8 c@40, neither order — so absent there.
|
|
545
|
+
addressTakenArrayOrder?: 'ascending' | 'descending';
|
|
546
|
+
// The frame-offset multiple this compiler starts each address-taken `u8` array of a frame at,
|
|
547
|
+
// whatever its size. Read by the same split (frame-objects.ts `provenParts`): a boundary off
|
|
548
|
+
// this multiple is never where two of the source's arrays met, and a part declared there is
|
|
549
|
+
// placed at the next multiple by the recompile, moving it and growing the frame. Absent
|
|
550
|
+
// REFUSES the split, as `addressTakenArrayOrder` does.
|
|
551
|
+
//
|
|
552
|
+
// MEASURED on `u8 a[na]; u8 b[nb];`, each filled by a `memcpy` and both handed to a callee, at
|
|
553
|
+
// canonical flags, for na x nb = 4x12, 12x12, 6x10, 8x8, 20x16, 2x14: agbcc puts b at sp+4,
|
|
554
|
+
// 12, 8, 8, 20, 4 (4); gcc2.7.2 and gcc2.7.2kmc a@16 and b@24, 32, 24, 24, 40, 24 (8:
|
|
555
|
+
// BIGGEST_ALIGNMENT on MIPS, for a BLKmode local); ido7.1 b@24 and a@36, 36, 40, 32, 44, 44 (4).
|
|
556
|
+
addressTakenArrayAlignment?: 4 | 8;
|
|
557
|
+
// Does this compiler's C front end take a DESIGNATED array element in an initializer —
|
|
558
|
+
// `u8 a[16] = { 1, [8] = 2 };`? A fact about the dialect the compiler parses, not about C or
|
|
559
|
+
// C++ as languages: GNU C had it long before C99, and agbcc and gcc 2.7.2 (KMC's build and the
|
|
560
|
+
// plain one) compile the line above, while IDO 7.1's cfe ("Syntax Error") and every mwcc build
|
|
561
|
+
// the benchmark ships, 2.3.3, 2.4.2 and 2.4.7 ("expression syntax error"), refuse it — each
|
|
562
|
+
// compiled. Read by the C-family printer (`SFn.designators`) where a constant image
|
|
563
|
+
// (l3/constinit.ts) leaves elements out: it spells the element after a run of them by index,
|
|
564
|
+
// and without this spells the run as zeros, which a compiler that cannot parse the index takes.
|
|
565
|
+
// Absent ⇒ zeros, the spelling every C compiler parses.
|
|
566
|
+
designatedInitializers?: boolean;
|
|
567
|
+
// Does this compiler's C front end REFUSE the three pointer conversions C makes implicitly — an
|
|
568
|
+
// integer to a pointer, a pointer to an integer, and an object pointer to one of another
|
|
569
|
+
// pointee — as C++ does? Every mwcc build the benchmark ships refuses all three in C, compiled
|
|
570
|
+
// with `-lang=c`: 2.4.2 and 2.4.7 ("illegal implicit conversion" of an `unsigned int` to an
|
|
571
|
+
// `int *`) and 2.3.3 ("cannot convert"). agbcc, IDO 7.1 and both gcc 2.7.2 builds compile the same three
|
|
572
|
+
// calls at their canonical flags. Read by the C-family printer (`SFn.strictConversions`), which
|
|
573
|
+
// then casts an argument to its callee's declared parameter and a pointer of another pointee
|
|
574
|
+
// into a pointer slot exactly as it does for C++ (backend/cfamily.ts `argConversion`,
|
|
575
|
+
// `legalizePointerWrites`), and by the store of an integer into a declared pointer cell
|
|
576
|
+
// (structure/pointer-spelling.ts `intoPtrCell`, which declines that store under C++, where only a
|
|
577
|
+
// cast to the cell's own type converts). Each cast is the conversion the compiler would
|
|
578
|
+
// otherwise have made, so it changes no instruction. Absent ⇒ C's implicit conversions.
|
|
579
|
+
strictPointerConversions?: boolean;
|
|
457
580
|
// Arguments past the argument registers are staged into an area this function RESERVES at the
|
|
458
581
|
// BOTTOM of its own frame — `[sp,#0]` upward, one word each — rather than pushed at the call
|
|
459
582
|
// site. It is GCC's ACCUMULATE_OUTGOING_ARGS target macro, and it is what makes an outgoing
|
|
@@ -476,24 +599,87 @@ export interface TargetDescription {
|
|
|
476
599
|
// every call, with every local and spill above it, and the caller never reads a word of it
|
|
477
600
|
// back after a call — the callee may assign to its stack parameters, so the caller re-stages
|
|
478
601
|
// an argument before every call. A compiler can stage in its frame
|
|
479
|
-
// (`stagesOutgoingArgsInFrame`) and still break this
|
|
480
|
-
//
|
|
602
|
+
// (`stagesOutgoingArgsInFrame`) and still break this. mwcc does not, though it puts a local in
|
|
603
|
+
// the words a call COULD take as stack arguments where no call takes any — which is why a
|
|
604
|
+
// guess that cannot say whether one does refuses (`frontend/ppc.ts`, "A GUESS THAT FILLS EVERY
|
|
605
|
+
// ARGUMENT REGISTER") — and not where one does: the area starts past them.
|
|
481
606
|
//
|
|
482
|
-
// It is a UNIVERSAL — for every source, the compiler emits no such read — and its
|
|
483
|
-
//
|
|
484
|
-
//
|
|
485
|
-
//
|
|
486
|
-
//
|
|
607
|
+
// It is a UNIVERSAL — for every source, the compiler emits no such read — and its readers apply
|
|
608
|
+
// the contrapositive (`docs/level-tower.md`, THE CONTRAPOSITIVE OF A UNIVERSAL IS NOT A
|
|
609
|
+
// BACKWARDS READING): a word loaded after a call is not in the area, so it is a local, and so is
|
|
610
|
+
// every word above it (`frontend/frame-objects.ts` `storedNeverRead`). An own-frame word no store
|
|
611
|
+
// reaches on any path is a local too, but only the Thumb frontend acts on that
|
|
612
|
+
// (`frontend/thumb.ts`'s own-frame load arm, which reads it as uninitialised), and it was probed
|
|
613
|
+
// on agbcc alone; a MIPS or PPC reader needs its own probe first. A callee that assigned the
|
|
614
|
+
// word as its stack parameter ran before the load, so the load is after a call, and with no
|
|
615
|
+
// call reaching the load there is no such callee. It is
|
|
487
616
|
// exactly as sound as the universal, so its residue is every producer the universal does not
|
|
488
|
-
// cover: hand-written asm that reads its outgoing argument back after a call loses that
|
|
489
|
-
//
|
|
617
|
+
// cover: hand-written asm that reads its outgoing argument back after a call loses that argument
|
|
618
|
+
// silently. The premise is this compiler's, not the ISA's.
|
|
490
619
|
// Absent ⇒ every staged word stays a candidate argument.
|
|
491
620
|
//
|
|
621
|
+
// It promises the AFTER-CALL form and no more: a compiler may still load a staged word BEFORE a
|
|
622
|
+
// call — o32 gcc defines REG_PARM_STACK_SPACE, so calls.c's save_fixed_argument_area loads
|
|
623
|
+
// staged reg-parm words around a nested call. The stronger form, no load of the area at all,
|
|
624
|
+
// is its own field (`outgoingAreaNeverLoadedFrom`), stated per compiler and never read off this.
|
|
625
|
+
//
|
|
492
626
|
// Set on agbcc: thumb.h puts the locals at sp + outgoing_args_size (ACCUMULATE_OUTGOING_ARGS)
|
|
493
627
|
// and `calls.c` sizes the area as the maximum over all calls; compiled, `spill10` and
|
|
494
628
|
// `spillarg` keep their spills above the area, and `test/corpus/agbcc-restage.s` holds the
|
|
495
|
-
// re-staging
|
|
629
|
+
// re-staging, the callee's write, and no load of the area around calls inside arguments. Set
|
|
630
|
+
// on the three MIPS toolchains off their own probe (`test/corpus/probe-o32-frame.c`), where
|
|
631
|
+
// the frame-object audit is the only reader. Set on
|
|
632
|
+
// mwcc off `test/corpus/probe-mwcc-frame.c` on all three builds (`mwcc-frame-probes.test.ts`):
|
|
633
|
+
// nothing but outgoing words lies below the local area, no word there is loaded, and `twice9`
|
|
634
|
+
// stores its ninth argument again before its second call. The frame-object audit is the only
|
|
635
|
+
// reader there too, and on mwcc it is LOAD-BEARING: it is what refuses an undeclared call whose
|
|
636
|
+
// ninth argument the arity guess cannot see (frontend/ppc.ts `measurePpcFrame`).
|
|
496
637
|
localsAboveOutgoingArea?: boolean;
|
|
638
|
+
// The STRONG form of `localsAboveOutgoingArea`, which it requires: from this offset up, the
|
|
639
|
+
// caller never loads a word of its outgoing area at all — not after a call, and not before one
|
|
640
|
+
// either. The offset counts from the area's start, the frame bottom: `[sp,#0]` on Thumb, `0(sp)`
|
|
641
|
+
// on O32 (whose area opens with the a0..a3 home), `0(r1)` past the push on PowerPC (whose
|
|
642
|
+
// parameter area opens at 8, past the callee's back chain and link-register words). From it up
|
|
643
|
+
// the area holds only what the caller staged for a callee; every argument that contains a call
|
|
644
|
+
// is computed before any word is staged, and nothing is saved there around a nested call. So
|
|
645
|
+
// ANY frame word this function loads at or above the offset is a local, and so is every word
|
|
646
|
+
// above it, since the area is one region at the frame bottom. Its readers apply that
|
|
647
|
+
// contrapositive, at exactly the soundness of the universal. Each frontend measures the lowest
|
|
648
|
+
// such word once (Thumb through `frontend/stackargs.ts` `loadedWordBound`, MIPS in `liftOnce`,
|
|
649
|
+
// PPC `ppcLoadedWordBound`), and every reader takes that number: Thumb's outgoing-argument
|
|
650
|
+
// analysis, `frontend/frame-objects.ts` `storedNeverRead` for its pending stores, the MIPS and
|
|
651
|
+
// PPC undeclared-call guesses (`frontend/mips.ts` `lowerJal`, `frontend/ppc.ts` `bl`), and PPC's
|
|
652
|
+
// unread-slot decline, each weighing only the words beneath it. On MIPS it also caps how far an
|
|
653
|
+
// undeclared call's area can reach (`o32OutgoingArea`'s `widest`), which the local area's
|
|
654
|
+
// partition (`measureO32Frame`) and the owned range of an uninitialised read
|
|
655
|
+
// (`measureOwnedFrame`) both read. A load BELOW the offset proves nothing, and no reader counts
|
|
656
|
+
// it. A word no store reaches on any path needs only the after-call form (see
|
|
657
|
+
// `localsAboveOutgoingArea`).
|
|
658
|
+
// Absent ⇒ only the after-call form is claimed (when `localsAboveOutgoingArea` is set).
|
|
659
|
+
//
|
|
660
|
+
// Its residue is the universal's: hand-written asm that loads a word it staged, then stages it
|
|
661
|
+
// for a call, loses that argument. Each value is MEASURED, and the compiled probes are committed
|
|
662
|
+
// (`packages/core/test/outgoing-area-probes.test.ts` reads every one):
|
|
663
|
+
// • agbcc, 0, off its source: thumb.h defines no REG_PARM_STACK_SPACE, so calls.c:642's
|
|
664
|
+
// save_fixed_argument_area — which loads staged words around a nested call — is not
|
|
665
|
+
// compiled; calls.c:1599-1610 precomputes every argument containing a call; and
|
|
666
|
+
// store_one_arg (calls.c:3420-3470) saves an argument word only when a nested call overlaps
|
|
667
|
+
// it, which the precompute rules out. Compiled, `test/corpus/agbcc-restage.s` loads no word
|
|
668
|
+
// of the area around calls in stack arguments.
|
|
669
|
+
// • ido7.1, gcc2.7.2kmc and gcc2.7.2, 16: `test/corpus/*-outarea.txt` at the rows' levels
|
|
670
|
+
// (ido -O1, -O2 and -O2 -g3; gcc 2.7.2 -O1 and -O2) and at -O0. NOT 0 — ido stages a
|
|
671
|
+
// by-value aggregate's register words in the a1..a3 home and loads them back before the
|
|
672
|
+
// call (`big1`: `lw a1,4(sp)`), and kmc and gcc 2.7.2 do it for a char-array struct (`d3`,
|
|
673
|
+
// `d5`): o32 gcc defines REG_PARM_STACK_SPACE, and GCC 2.9's save_fixed_argument_area
|
|
674
|
+
// (calls.c:648-711) covers only `[0, reg_parm_stack_space)`. No probe loads a word from
|
|
675
|
+
// 16(sp) up the area.
|
|
676
|
+
// • mwcc, 8, on all three builds at the rows' flags and at -O0,p, and on mwcc_242_81 at -O4,s
|
|
677
|
+
// too: no probe loads a word of the parameter area; a struct-return temp or a by-value copy
|
|
678
|
+
// sits at 8(r1) only where no call takes a stack word, and above the area beside one that
|
|
679
|
+
// does.
|
|
680
|
+
// Like `stagesOutgoingArgsInFrame`, a second compiler of the same ISA must state this premise
|
|
681
|
+
// rather than inherit it.
|
|
682
|
+
outgoingAreaNeverLoadedFrom?: number;
|
|
497
683
|
// A frame of EXACTLY ONE reserved word whose base escapes this function holds that object and
|
|
498
684
|
// nothing else. It is the layout premise the Thumb frontend's address-taken-local capability
|
|
499
685
|
// rests on (`frontend/thumb.ts`, `capturedObjectIsTheWholeFrame`): a bare `mov rD, sp` names
|
|
@@ -513,6 +699,72 @@ export interface TargetDescription {
|
|
|
513
699
|
// producer a one-word frame does NOT exclude is a <=4-byte non-integer-like struct return,
|
|
514
700
|
// which the post-lift audit settles per call rather than by size.
|
|
515
701
|
oneWordFrameIsTheCapturedObject?: boolean;
|
|
702
|
+
// A frame address this function hands out — in an argument register at a call, or stored to
|
|
703
|
+
// memory — is the address of a LOCAL, so its receiver may read the frame from there up and no
|
|
704
|
+
// word from there up is a call's outgoing argument. Its one reader is the outgoing-argument
|
|
705
|
+
// analysis (`frontend/stackargs.ts`, the `escape` event): a store never reloaded HERE is read
|
|
706
|
+
// THERE. Absent ⇒ a handed-out frame address explains nothing.
|
|
707
|
+
//
|
|
708
|
+
// Set on agbcc, compiled at 2.9-arm-000512 and its canonical flags, and re-measurable from the
|
|
709
|
+
// committed probe (`scripts/regen-outgoing-area-probes.ts` → `test/corpus/agbcc-escape.s`, read
|
|
710
|
+
// by `frame-escape-probes.test.ts`). An address-taken local sits ABOVE the outgoing area
|
|
711
|
+
// (`f1`: `add r0, sp, #4` beside `h5(1,2,3,4,x)`), and at [sp,#0] only when no call in the
|
|
712
|
+
// function takes a stack word (`f0`).
|
|
713
|
+
//
|
|
714
|
+
// Its RESIDUE is a by-value struct argument's block copy (`mov r0, sp` / `add r0, sp, #4`, then
|
|
715
|
+
// `bl memcpy`), whose base IS the outgoing area. agbcc stages a call's outgoing words in
|
|
716
|
+
// ASCENDING offset order, the copy included (`qh5`, `a2`, `a3`): a word staged before the copy
|
|
717
|
+
// lies below its base, and one staged after it is still pending at the call. That is why the
|
|
718
|
+
// event ends pending words PER PROGRAM POINT and only from its offset up — read
|
|
719
|
+
// flow-insensitively, it would set aside `qh5`'s fifth `h5` word and lift `h5(1, 2, 3, 4)`. The
|
|
720
|
+
// cost is false alarms that decline loud (`if (x) gt(&s); else h(y);`); where the analysis also
|
|
721
|
+
// reads the event whole-function, it only refuses or excuses a word no call takes
|
|
722
|
+
// (stackargs.ts). `oneWordFrameIsTheCapturedObject` is the flow-insensitive licence, sound only
|
|
723
|
+
// where no block copy fits.
|
|
724
|
+
escapedFrameAddressNamesALocal?: boolean;
|
|
725
|
+
// WHERE AN O32 FUNCTION KEEPS ITS LOCALS in the one frame `addiu sp,sp,-N` reserves — the area
|
|
726
|
+
// the MIPS frontend hands the frame-object audit as `ownedLocals`/`declaredLocals`. Below it
|
|
727
|
+
// lies the OUTGOING area, the 16-byte home of a0..a3 every calling function reserves and then
|
|
728
|
+
// the stack words its widest call takes (none in a function that makes no call); the saved
|
|
729
|
+
// registers and the locals share the rest, and which way round is the compiler's:
|
|
730
|
+
// 'above-outgoing-area' — from the outgoing area's end, rounded up to the 8-byte stack
|
|
731
|
+
// alignment, to the lowest saved register;
|
|
732
|
+
// 'above-saved-registers' — from just above the highest saved register (or the outgoing
|
|
733
|
+
// area's end, where that is higher) to the frame top N.
|
|
734
|
+
// Spill temps live in the same area on both, so it is the locals' and the spills' together.
|
|
735
|
+
//
|
|
736
|
+
// MEASURED off `corpus/probe-o32-frame.c` (`scripts/regen-o32-frame-probes.ts`, read by
|
|
737
|
+
// `o32-frame-probes.test.ts`): a leaf's array at sp+0 on all three MIPS toolchains; with a call
|
|
738
|
+
// taking no stack word, gcc 2.7.2 (both builds) at sp+16 under the saves and IDO at the frame top
|
|
739
|
+
// above `ra`; with one stack word, gcc at sp+24 (20 rounded) and IDO still at the top; and IDO's
|
|
740
|
+
// spill of a value live across a call between `ra` and the array.
|
|
741
|
+
//
|
|
742
|
+
// Absent ⇒ no partition is claimed, and a captured frame address keeps its refusal.
|
|
743
|
+
o32LocalArea?: 'above-outgoing-area' | 'above-saved-registers';
|
|
744
|
+
// WHERE A FUNCTION THAT PUSHES ONE FRAME KEEPS ITS LOCALS, on a target whose frame starts with a
|
|
745
|
+
// fixed LINKAGE area — the back chain and the callee's link-register save word, which the ABI
|
|
746
|
+
// fixes and the frontend states (`frontend/ppc.ts` `PARAM_AREA_AT`, where the caller's parameter
|
|
747
|
+
// area starts). Above it the OUTGOING area holds the stack words of the widest call (none in a
|
|
748
|
+
// function whose calls take none), and the locals and spill temps share what is left below the
|
|
749
|
+
// lowest saved register:
|
|
750
|
+
// [linkage + 4 * (stack words of the widest call), lowest saved register)
|
|
751
|
+
// in post-push offsets — O32's `above-outgoing-area` rule (`o32LocalArea`) past a linkage area
|
|
752
|
+
// instead of the a0..a3 home, without the 8-byte rounding (`areaAboveOutgoing`). The PPC
|
|
753
|
+
// frontend hands it to the frame-object audit as `ownedLocals`/`declaredLocals`; a call it
|
|
754
|
+
// lowers takes no stack word, so it refuses one whose declaration may and draws the area from
|
|
755
|
+
// the linkage's end. No object start inside it is fixed: the compiler chooses where in the area
|
|
756
|
+
// each local goes.
|
|
757
|
+
//
|
|
758
|
+
// MEASURED on mwcc 2.3.3, 2.4.2 (at -O4,p and at Mario Party 4's -O0,p) and 2.4.7 off
|
|
759
|
+
// `corpus/probe-mwcc-frame.c` (`scripts/regen-mwcc-frame-probes.ts`, read by
|
|
760
|
+
// `mwcc-frame-probes.test.ts`): every local and every home or spill lies in the area, a leaf
|
|
761
|
+
// with a local still pushes a frame whose area starts at 8, one or two stack words of a call push
|
|
762
|
+
// the locals past them, and the widest call sizes the area for every call. The first local's
|
|
763
|
+
// offset inside it differs per build and per function (8, 12 or 16 on 2.3.3; a homed parameter
|
|
764
|
+
// takes 8 at -O0,p), which is why this is an area and not a start.
|
|
765
|
+
//
|
|
766
|
+
// Absent ⇒ no partition is claimed, and a captured frame address keeps its refusal.
|
|
767
|
+
linkageLocalArea?: 'above-outgoing-area';
|
|
516
768
|
// How this compiler calls through a register: `bl <prefix><reg>` enters a runtime thunk that
|
|
517
769
|
// branches to the address in <reg> with every register as the caller left it, so the call is
|
|
518
770
|
// an indirect call through <reg> and the thunk is no function a source names. `regs` are the
|
|
@@ -521,6 +773,16 @@ export interface TargetDescription {
|
|
|
521
773
|
//
|
|
522
774
|
// ABSENT ⇒ a `bl` names a function on this target, whatever it is called.
|
|
523
775
|
callThunks?: { readonly prefix: string; readonly regs: readonly string[] };
|
|
776
|
+
// How this compiler marks a BRANCH it had to spell as a call: a `bl <label>` whose line carries
|
|
777
|
+
// a comment matching `comment` (tested against the text from its `@` to the end of the line) is
|
|
778
|
+
// a relay to that label of the same function, not a call. Two of its facts are this compiler's,
|
|
779
|
+
// and the Thumb frontend's lowering rests on them (`lowerFarJumpRelays`): what the mark means,
|
|
780
|
+
// and that the compiler pushes lr in every function with one, so the lr the relay writes is
|
|
781
|
+
// never the one the function returns through.
|
|
782
|
+
//
|
|
783
|
+
// ABSENT ⇒ no comment makes a `bl` anything but a call: a `bl` to a label of the function keeps
|
|
784
|
+
// its decline, however its line is commented.
|
|
785
|
+
farJumpRelay?: { readonly comment: RegExp };
|
|
524
786
|
// Regime-A switch recovery: accept a RELATIONAL test as a case where the scrutinee values that
|
|
525
787
|
// can reach it — narrowed by the tests above it — leave exactly one on a side this compiler's
|
|
526
788
|
// dispatch lands a case body on: `'taken'` for its BRANCH only, `'either'` for both sides
|
|
@@ -629,6 +891,34 @@ export interface TargetDescription {
|
|
|
629
891
|
// and PPC lanes put the addend in the instruction by construction (`lui`/`%lo`, `lis`/`ori`),
|
|
630
892
|
// so a surviving offset carries no information there. Absent ⇒ the row is never offered.
|
|
631
893
|
foldsConstAddrOffset?: boolean;
|
|
894
|
+
// Does a STORE to a struct member past the ISA's displacement range compile differently from the
|
|
895
|
+
// same store through a cast of the base (`p->f38 = v` against `*((u8 *)p + 38) = v`)? agbcc's
|
|
896
|
+
// does: expand_assignment (gcc/expr.c) expands only the base of a COMPONENT_REF target (:2535)
|
|
897
|
+
// and evaluates the right-hand side first in store_field (:2624), and it expands a cast target
|
|
898
|
+
// whole (:2689) before the right-hand side (:2747). Measured on agbcc for a loaded, a constant, a
|
|
899
|
+
// computed and a parameter right-hand side: every pair builds two objects, and a hoisted
|
|
900
|
+
// `u8 *d = &p->f38; *d = v;` builds the cast's. True reads a moved store whose right-hand side
|
|
901
|
+
// is computed before its address as struct evidence (raise/structs.ts `memberStoreOrder`).
|
|
902
|
+
// ido, KMC, GCC 2.7.2 and the three mwcc builds put the two spellings in one object, a loaded
|
|
903
|
+
// right-hand side included, so they leave it absent. `/far-cast`, the lift that keeps such a
|
|
904
|
+
// store on its materialised address, is offered on every target and does not read this.
|
|
905
|
+
farMemberStoreOrder?: boolean;
|
|
906
|
+
// Does a member past the displacement range get its OWN materialised base at every access, so
|
|
907
|
+
// that one base read or written at two distinct offsets is a pointer the source held? Stated
|
|
908
|
+
// as the displacement `past` which that holds: a base materialised with a displacement whose
|
|
909
|
+
// magnitude exceeds it was built for one access. agbcc's is 0, every base: `p->f38 + p->f39`
|
|
910
|
+
// compiles to `add r0, #0x26; ldrb [r0]` and `add r1, #0x27; ldrb [r1]`, while
|
|
911
|
+
// `u8 *d = &p->f38; d[0] + d[1]` shares one `add r1, #0x26` between `ldrb [r1]` and
|
|
912
|
+
// `ldrb [r1, #1]`, and only the pointer spelling rebuilds those bytes. ido's is 32767, the
|
|
913
|
+
// `addiu` reach: inside it ido shares one `addiu at, a0, 32767` across adjacent members
|
|
914
|
+
// (`sb a1, 7232(at); sb a2, 7233(at)`), and past it the assembler expands every member access
|
|
915
|
+
// to its own `lui at, 3; addu at, at, a0` while a held `u8 *d = &p->b` (b at 200000) shares one
|
|
916
|
+
// `lui at, 3; addu v0, a0, at` between `sb t6, 3392(v0)` and `sw zero, 3396(v0)`. True refuses
|
|
917
|
+
// raise/structs.ts's displaced-access fold for such a base (`held-pointer-offsets`). The three
|
|
918
|
+
// mwcc builds share one `addis` base across members and a held pointer alike, and KMC and
|
|
919
|
+
// GCC 2.7.2 lift the held pair as members at the same bytes, so they leave it absent. Absent ⇒
|
|
920
|
+
// the fold reads a shared base as members.
|
|
921
|
+
farMemberBasePerAccess?: { past: number };
|
|
632
922
|
// Does this compiler fold a pointer local's OWN ADVANCE back into the memory operand —
|
|
633
923
|
// `*p = a; p = p + 1; *p = b;` → `strh [r3, #0]` + `strh [r3, #2]`, no `add` — so the advanced
|
|
634
924
|
// spelling emits the indexed one's stores wherever the pointee is not volatile? agbcc does, on
|
|
@@ -654,6 +944,25 @@ export interface TargetDescription {
|
|
|
654
944
|
// does not declare it: the matching suite compiles each witness on one compiler. Absent ⇒
|
|
655
945
|
// offered.
|
|
656
946
|
pointerIntConversionIsFree?: boolean;
|
|
947
|
+
// Does this compiler keep the constant addend of a POINTER sum's offset where the source put it,
|
|
948
|
+
// and move an INTEGER sum's out to the base? agbcc does: `(u8 *)((u32)g + ((a << 4) + 772))`
|
|
949
|
+
// computes `(g + 772) + (a << 4)` (fold-const.c:5033-5058, `A + (V + C)` → `(A + C) + V`), while
|
|
950
|
+
// `(u8 *)g + ((a << 4) + 772)` computes `g + ((a << 4) + 772)` (c-typeck.c:2663-2692 distributes
|
|
951
|
+
// the constant, fold-const.c:4997-5011 folds it back), which is what the project's own
|
|
952
|
+
// `g->arr[a]` compiles to. Compiled at each toolchain's canonical flags, with `g` a struct
|
|
953
|
+
// pointer, a `void *` and a `u32`, as a call argument and under a dereference: agbcc,
|
|
954
|
+
// gcc2.7.2kmc and gcc2.7.2 build the two spellings into two objects wherever the offset has a
|
|
955
|
+
// constant addend and into one where it has none; ido7.1 builds one object every time, and
|
|
956
|
+
// mwcc_242_81 is CodeWarrior's index-first order (structure/pointer-spelling.ts, THE INTEGER
|
|
957
|
+
// SUM). Two rules read it. True ⇒ the arithmetic table's value row (ARITH_ROWS) spells an
|
|
958
|
+
// undeclared pointer global plus an integer with a constant term as the pointer sum where the
|
|
959
|
+
// sum is a value rather than an access's address; and the structurer declares `u8 *` a temp
|
|
960
|
+
// holding a pointer global's value that is the base of such a sum (`declaresBytePointer`),
|
|
961
|
+
// which an integer temp would compile as `(t + K) + x`. Through a temp ido7.1 does build two
|
|
962
|
+
// objects, the byte pointer's index first where the integer temp and the struct source keep the
|
|
963
|
+
// base first, and mwcc_242_81 builds one (the cli matching suite's pointer-spelling.test.ts).
|
|
964
|
+
// Absent ⇒ the integer sum and the integer temp.
|
|
965
|
+
keepsPointerSumAddend?: boolean;
|
|
657
966
|
// Does this compiler EMIT a memory read in the block the source SPELLED it in? One direction
|
|
658
967
|
// only: the def-block placement rule (StructureOptions.readsStayWhereWritten) re-spells a read
|
|
659
968
|
// at the block the asm performed it in, which reproduces the asm iff nothing sinks a spelled
|
|
@@ -692,6 +1001,16 @@ export interface TargetDescription {
|
|
|
692
1001
|
// three descriptions that measured false (four compilers) set it anyway, so absent means
|
|
693
1002
|
// UNMEASURED rather than "no".
|
|
694
1003
|
reloadsLocalReread?: boolean;
|
|
1004
|
+
// Does this compiler convert a constant to a NARROW lvalue's type before it materializes it
|
|
1005
|
+
// for the store? Then the stored register's upper bits say the lvalue's signedness whenever
|
|
1006
|
+
// the narrow sign bit is set: `u16 *p; p[6] = -1;` loads 0xffff and `s16 *p; p[6] = 0xFFFF;`
|
|
1007
|
+
// loads -1. Measured on every bench compiler at its rows' flags by lifting the probe in
|
|
1008
|
+
// packages/cli/test/matching/narrow-store-constant.test.ts and reading the const each store
|
|
1009
|
+
// takes. Read by raise/storesign.ts (through raise/pre-recovery.ts).
|
|
1010
|
+
//
|
|
1011
|
+
// ABSENT ⇒ the seed stands down and a narrow store keeps the unsigned view; a compiler opts IN
|
|
1012
|
+
// on its own probe.
|
|
1013
|
+
narrowStoreConstantTakesLvalueType?: boolean;
|
|
695
1014
|
// A LAYOUT fact rather than a canonicalization one, kept here because it is the COMPILER's:
|
|
696
1015
|
// the size, in bytes, it aligns and rounds EVERY struct and union to, whatever its members.
|
|
697
1016
|
// agbcc: 4 — `sizeof(struct { u16 h; })` is 4, and `struct { u8 a; union { u16 h; u8 b[2]; } u; }`
|
|
@@ -699,12 +1018,13 @@ export interface TargetDescription {
|
|
|
699
1018
|
// answer 2 and 2: the natural layout, 1. Compiled as `return sizeof …` / `return (int)&((T *)0)->m`
|
|
700
1019
|
// at each row's flags; gcc2.7.2 (-O1), mwcc_233_163n and mwcc_247_107 answer the same as their
|
|
701
1020
|
// descriptions' measured compilers. Read by raise/structs.ts (through raise/pre-recovery.ts),
|
|
702
|
-
// which sizes a recovered UNION member with it
|
|
703
|
-
//
|
|
1021
|
+
// which sizes a recovered UNION member with it, and by raise/struct-arrays.ts, which declines
|
|
1022
|
+
// an element struct whose stride it would round (agbcc: a stride-6 `u16 [3]` row is 8 bytes).
|
|
704
1023
|
//
|
|
705
|
-
// ABSENT ⇒ unmeasured
|
|
706
|
-
// value is safe to assume, since one
|
|
707
|
-
//
|
|
1024
|
+
// ABSENT ⇒ unmeasured: the union recovery declines every union narrower than a word, and the
|
|
1025
|
+
// struct-array recovery declines every element struct. No value is safe to assume, since one
|
|
1026
|
+
// too small mislays the fields after it on agbcc (and mis-strides an element struct) and one
|
|
1027
|
+
// too large drops the pad in front of them everywhere else.
|
|
708
1028
|
aggregateBoundary?: number;
|
|
709
1029
|
// The largest alignment any member of a struct takes, in bytes (BIGGEST_ALIGNMENT): agbcc 4,
|
|
710
1030
|
// thumb.h:358, so a `long long` member sits at a word. With `aggregateBoundary` it is what sizes
|
|
@@ -712,8 +1032,8 @@ export interface TargetDescription {
|
|
|
712
1032
|
largestAlignment?: number;
|
|
713
1033
|
// HOW A STRUCT OR UNION RETURNED BY VALUE COMES BACK, which decides whether a call to a function
|
|
714
1034
|
// declared to return one hands it a hidden pointer as argument 0 and moves every declared
|
|
715
|
-
// argument one register up (`aggregate.ts` `returnsInMemory`, read by
|
|
716
|
-
// frontend/
|
|
1035
|
+
// argument one register up (`aggregate.ts` `returnsInMemory`, read by the call lowering every
|
|
1036
|
+
// frontend shares, frontend/call-plan.ts).
|
|
717
1037
|
// • 'apcs' — in memory when bigger than a word, when a struct has a second member that is not
|
|
718
1038
|
// a bitfield, or when a union has a member that would be; in the return register otherwise.
|
|
719
1039
|
// agbcc, thumb.c:1423-1493 (compiled: `{u8 a,b,c,d}` through memory; `{u32}`, `{u32 w[1]}`,
|
|
@@ -722,10 +1042,55 @@ export interface TargetDescription {
|
|
|
722
1042
|
// included); in memory otherwise. mwcc_242_81, mwcc_233_163n and mwcc_247_107 alike
|
|
723
1043
|
// (compiled: 1 to 8 bytes, `{float}`, `{double}` and a union come back in r3/r3:r4; a 9- and a
|
|
724
1044
|
// 12-byte struct are handed `addi r3,…` ahead of the call).
|
|
725
|
-
//
|
|
726
|
-
//
|
|
727
|
-
//
|
|
728
|
-
|
|
1045
|
+
// • 'memory' — always in memory, whatever its size or members. ido7.1, gcc2.7.2kmc and gcc2.7.2
|
|
1046
|
+
// alike (compiled: `{u8}`, `{u16}`, `{u32}`, `{float}`, `{double}`, 4-, 8- and 12-byte
|
|
1047
|
+
// structs, `union {u32; u16;}` and `{u32 a:8; u32 b:8;}` are each handed `addiu a0,sp,N`
|
|
1048
|
+
// ahead of the `jal`, with the declared arguments from a1 on, and the callee returns that
|
|
1049
|
+
// pointer, `move v0,a0`).
|
|
1050
|
+
// Under every rule the caller's half is lowered (frontend/call-plan.ts), and a function that
|
|
1051
|
+
// itself returns a struct through memory lifts as that struct where its declaration says so and
|
|
1052
|
+
// the lift proves the hidden pointer is only its storage (frontend/own-struct-return.ts); it is
|
|
1053
|
+
// read as the pointer function it compiles to where nothing declares it, or where a context's
|
|
1054
|
+
// declaration does and the proof fails (pipeline.ts `returnOwnStruct`).
|
|
1055
|
+
// ABSENT ⇒ unmeasured, and such a call declines.
|
|
1056
|
+
aggregateReturn?: 'apcs' | 'svr4' | 'memory';
|
|
1057
|
+
// WHETHER A FUNCTION THAT RETURNS A STRUCT THROUGH MEMORY HANDS THE HIDDEN POINTER BACK in the
|
|
1058
|
+
// return register — a COMPILER fact, which no rule above implies. `true` for ido7.1, gcc2.7.2kmc
|
|
1059
|
+
// and gcc2.7.2 (`move v0,a0`, the O32 rule) and for agbcc (`add r0, r6, #0` after a call, r0
|
|
1060
|
+
// left alone in a leaf: compiled `struct S12 f(s32 x)` with and without a call to `fill(t)`).
|
|
1061
|
+
// `false` for mwcc_242_81, mwcc_233_163n and mwcc_247_107, whose callee clobbers r3 before the
|
|
1062
|
+
// `blr`: the same `struct S12 b4(s32 x)` saves the pointer in r30/r31, stores through it, and
|
|
1063
|
+
// returns with r3 = a reload of a frame word. It decides what the proof that a function's own
|
|
1064
|
+
// struct return goes through that pointer asks of each `ret` (frontend/own-struct-return.ts):
|
|
1065
|
+
// where it is `true` every `ret` must hand back the pointer, and where it is `false` what the
|
|
1066
|
+
// return register holds is no part of the function, save a value computed for the return alone
|
|
1067
|
+
// (`returnedAlone`). ABSENT ⇒ unmeasured, and read as `true`, which only refuses more.
|
|
1068
|
+
structReturnHandsBackPointer?: boolean;
|
|
1069
|
+
// WHETHER A CALLER HANDS ITS OWN DESTINATION POINTER STRAIGHT ON as a callee's hidden
|
|
1070
|
+
// struct-return pointer, leaving the first argument register at its incoming value below the
|
|
1071
|
+
// arguments it sets up. `true` for ido7.1: `int b3(struct S *p){ *p = sg(1); return 0; }` is
|
|
1072
|
+
// `jal sg; li a1,1` with a0 never touched, the bytes of `sg(p, 1)` with its result unused.
|
|
1073
|
+
// `false` for gcc2.7.2kmc, gcc2.7.2, agbcc and mwcc_242_81, mwcc_233_163n and mwcc_247_107,
|
|
1074
|
+
// which copy through a frame temporary (a0/r0/r3 set to a frame address ahead of the call).
|
|
1075
|
+
// Compiled at the canonical flags, `b3` and `void b6(struct S *p, int k){ *p = sg(k, 5); }`.
|
|
1076
|
+
// Read by the guessed-arity gap (frontend/call-plan.ts `decideGap`): where it holds, a gap at
|
|
1077
|
+
// the first argument register is decided only where the call's result is read, which no
|
|
1078
|
+
// forwarded struct return's is (IDO reloads `p` from its home after the call in all five shapes
|
|
1079
|
+
// compiled: `return (*p = sg(1)).b`, `return p` and three more). ABSENT ⇒ unmeasured, and read
|
|
1080
|
+
// as `true`, which only refuses more.
|
|
1081
|
+
forwardsStructReturnDestination?: boolean;
|
|
1082
|
+
// A STRUCT PASSED BY VALUE TRAVELS IN CONSECUTIVE ARGUMENT WORDS, the first at the next free
|
|
1083
|
+
// argument word, its size rounded up to words (`aggregate.ts` `byValueCallArgs`). agbcc,
|
|
1084
|
+
// thumb.h:632-650 (FUNCTION_ARG places by word offset, FUNCTION_ARG_PARTIAL_NREGS splits a
|
|
1085
|
+
// BLKmode argument across r3 and the stack, FUNCTION_ARG_ADVANCE rounds its size to a word, and
|
|
1086
|
+
// no FUNCTION_ARG_PASS_BY_REFERENCE is defined); compiled, `struct A {u16 a,b,c,d; s8 e,f;}`
|
|
1087
|
+
// is `ldr r0..r2` off the frame copy ahead of `bl take2`. O32 too, compiled on ido7.1,
|
|
1088
|
+
// gcc2.7.2kmc and gcc2.7.2: `takeQ(s32 k, struct Q {u32 a, b;})` is passed `a1:a2` and a
|
|
1089
|
+
// three-word struct `a0..a2` (an 8-aligned one starts at an even word:
|
|
1090
|
+
// `evenWordAlignedArgs`); IDO also stores each word into its home slot, which the
|
|
1091
|
+
// call owns. ABSENT ⇒ the target passes one some other way or states nothing: mwcc passes the
|
|
1092
|
+
// address of a copy (frontend/ppc.ts refuses it).
|
|
1093
|
+
byValueAggregateArgWords?: boolean;
|
|
729
1094
|
// The bytes an enum takes as a struct member (`aggregate.ts`). agbcc: 4 — flag_short_enums is 0
|
|
730
1095
|
// unless `-fshort-enums` is given (toplev.c:3552-3554 with no DEFAULT_SHORT_ENUMS), so an enum
|
|
731
1096
|
// whose values fit an int is an int (c-decl.c:6123-6135); compiled, `sizeof(enum {K0, K1})`
|
|
@@ -784,9 +1149,76 @@ export interface TargetDescription {
|
|
|
784
1149
|
// ABSENT ⇒ false: a sign-extending load says nothing about the qualifier, and a device read is
|
|
785
1150
|
// pinned, and a read of a declared object placed, whatever its extension.
|
|
786
1151
|
volatileReadsExtendInRegister?: boolean;
|
|
1152
|
+
// Does this compiler, AT THESE FLAGS, keep a plain read whose value nothing uses? A FLAG fact,
|
|
1153
|
+
// never written in a description: `targetFor` sets it from the optimisation level
|
|
1154
|
+
// (`keepsUnusedReadsAt`), so a target at other flags has it absent. Where it holds, a read
|
|
1155
|
+
// the lift finds unused is no evidence of `volatile`, and the dead-read pin
|
|
1156
|
+
// (frontend/dead-reads.ts) stamps only one the machine loaded into the register the compiler
|
|
1157
|
+
// discards a volatile read into (`discardsUnusedReadsInto`).
|
|
1158
|
+
//
|
|
1159
|
+
// ABSENT ⇒ false: the compiler deletes every plain read nobody uses, so one still in the target
|
|
1160
|
+
// was qualified.
|
|
1161
|
+
keepsUnusedReads?: boolean;
|
|
1162
|
+
// WHERE DOES THIS COMPILER PUT A READ NOBODY USES, at a level that keeps a plain one
|
|
1163
|
+
// (`keepsUnusedReads`)? The register a `volatile` read with no consumer is loaded into, and no
|
|
1164
|
+
// plain local is ever homed in. COMPILED at the keeping levels, one statement per line:
|
|
1165
|
+
// `*p;`, `s->b;`, `gV;` and `(*p, 1)` through a volatile lvalue load into `r0` on mwcc 2.4.2,
|
|
1166
|
+
// 2.4.7 and 2.3.3 at `-O0,p`, and into `$zero` on IDO 7.1 at `-O1` and `-O0`; the plain
|
|
1167
|
+
// `x = p[5]; x = 0;` takes a home there instead (`lwz r31`, `lw t6` and a frame store), and so
|
|
1168
|
+
// does a `register` local (`lwz r31`, `lw a2`). Over the 409 complete Mario Party 4 units built
|
|
1169
|
+
// at `-O0,p`, 924 non-frame loads nothing reads land in r17-r31 and f18-f31, and exactly one in
|
|
1170
|
+
// r0: WipeExecAlways's `wipe->type;`, a `volatile u8` member. So a dead load into this
|
|
1171
|
+
// register is a qualified read, and the dead-read pin (frontend/dead-reads.ts) stamps it at a
|
|
1172
|
+
// keeping level too. mwcc keeps one plain read in r0 as well, a read under an arithmetic or a
|
|
1173
|
+
// cast nothing uses (`p[5] + 1;`, `(char)p[5];`), which no real unit holds: that is the pin's
|
|
1174
|
+
// residue. A load this register took and an op then wrote to a home (`lwz r0` + `extsb r31,r0`,
|
|
1175
|
+
// a plain `s8` local) is not discarded (frontend/dead-reads.ts `leftInDiscardRegister`). One
|
|
1176
|
+
// register cannot name a pair: IDO discards a doubleword into zero:at, `lw zero,0(a0)` +
|
|
1177
|
+
// `lw at,4(a0)`, which frontend/mips.ts `emitLoad` reads as the pair this register heads.
|
|
1178
|
+
//
|
|
1179
|
+
// ABSENT ⇒ the compiler has no such register, or it is unmeasured. agbcc, gcc 2.7.2 and KMC
|
|
1180
|
+
// gcc 2.7.2 at `-O0` have none: a volatile `*p;` is an `ldr r0`/`lw v0` nothing stores, the
|
|
1181
|
+
// same instructions as a `register` local assigned and overwritten, so the pin cannot tell the
|
|
1182
|
+
// two apart there and refuses rather than drop the read.
|
|
1183
|
+
discardsUnusedReadsInto?: string;
|
|
1184
|
+
// MAY A DISCARDED READ LAND IN AN ARGUMENT REGISTER, at flags that delete a plain read nobody
|
|
1185
|
+
// uses? Where it may, a value a load left in an argument register is no proof that the next
|
|
1186
|
+
// call takes it. The discard register is the allocator's first free one, not a fixed register,
|
|
1187
|
+
// so a single read on its own says nothing: the probe that decides is one where the usual
|
|
1188
|
+
// discard register is busy. `true` for agbcc, which loads a discarded `volatile` read into the
|
|
1189
|
+
// lowest free argument register (`*(volatile int *)K; g(s)` is `ldr r1,[r1]; bl g`, the bytes of
|
|
1190
|
+
// `g(s, *(int *)K)`). `true` for gcc2.7.2kmc and gcc2.7.2, which discard into v0/v1 until those
|
|
1191
|
+
// are busy and then into a1: `int a = ga * 3; *(volatile int *)&gx; *(volatile int *)&gx;
|
|
1192
|
+
// ga = a + 1; return logv(s) + 1;` is `lw a1,0(v0); lw v0,0(v0); ... jal logv` (`lw a1,0(v1)` on
|
|
1193
|
+
// gcc2.7.2), the bytes of the twin that passes the first read. `true` for mwcc_242_81,
|
|
1194
|
+
// mwcc_233_163n and mwcc_247_107, which discard into r0 until it is busy and then into r4: the
|
|
1195
|
+
// same function's first read is `lwz r4,gx` on all three, and with three reads mwcc_233_163n's
|
|
1196
|
+
// last one is `lwz r4,gx` right before `bl logv`, byte-identical to the twin that passes it.
|
|
1197
|
+
// `false` for ido7.1 alone: `lw zero`, `lbu zero`, `lh zero`, and `lw zero` + `lw at` for a
|
|
1198
|
+
// doubleword, with four discarded reads stacked under the same pressure too. Compiled at the
|
|
1199
|
+
// canonical flags. Read by the guessed-arity gap (frontend/call-plan.ts `decideGap`), which a
|
|
1200
|
+
// load decides only where this is `false`. ABSENT ⇒ unmeasured, and read as `true`, which only
|
|
1201
|
+
// refuses more.
|
|
1202
|
+
discardedReadMayFillArgRegister?: boolean;
|
|
787
1203
|
};
|
|
788
1204
|
}
|
|
789
1205
|
|
|
1206
|
+
/** THE SPELLINGS ONE REGISTER HAS in an ISA's asm, as `[number, alias]` pairs — the one place they
|
|
1207
|
+
* are stated. A frontend keys registers by spelling, so a function that names one register two
|
|
1208
|
+
* ways is rewritten to one of them first (frontend/ssa.ts `unifyRegisterSpellings`, which both
|
|
1209
|
+
* frontends hand these tables); and every register list a target states names both spellings of a
|
|
1210
|
+
* pair or neither, which `target-register-aliases.test.ts` checks against these tables. A pair
|
|
1211
|
+
* missing here splits one register into two partition keys: saved as `sl` and written as `r10`, its
|
|
1212
|
+
* writes read as a global's the epilogue undoes. */
|
|
1213
|
+
export const ARM_REGISTER_ALIASES: readonly (readonly [string, string])[] = [
|
|
1214
|
+
['r9', 'sb'],
|
|
1215
|
+
['r10', 'sl'],
|
|
1216
|
+
['r11', 'fp'],
|
|
1217
|
+
['r12', 'ip'],
|
|
1218
|
+
];
|
|
1219
|
+
/** O32's `$30`, which objdump prints `s8` and hand-written asm also writes `fp` (`ARM_REGISTER_ALIASES`). */
|
|
1220
|
+
export const O32_REGISTER_ALIASES: readonly (readonly [string, string])[] = [['s8', 'fp']];
|
|
1221
|
+
|
|
790
1222
|
export const ARMV4T_AGBCC: TargetDescription = {
|
|
791
1223
|
id: 'armv4t',
|
|
792
1224
|
compiler: 'agbcc',
|
|
@@ -803,6 +1235,17 @@ export const ARMV4T_AGBCC: TargetDescription = {
|
|
|
803
1235
|
// `mov ip, r1` in two switch arms, no save anywhere, and a `mov r0, ip` past a third arm that
|
|
804
1236
|
// writes nothing — an uninitialised local by construction.
|
|
805
1237
|
scratchRegs: ['r12', 'ip'],
|
|
1238
|
+
// MEASURED at the canonical flags, `register int g __asm__("rN")` for each of r4-r11: `f(){ return
|
|
1239
|
+
// g; }` is `add r0, r4, #0` / `mov r0, r8` / `mov r0, sl`, and `s(a){ g = a; }` the reverse, with no
|
|
1240
|
+
// save. agbcc prints r9 as `r9`, r10 as `sl` and r11 as `fp`; hand-written asm also writes `sb`.
|
|
1241
|
+
globalRegisterVariables: {
|
|
1242
|
+
regs: {
|
|
1243
|
+
...Object.fromEntries(['r4', 'r5', 'r6', 'r7', 'r8', 'r9', 'r10', 'r11'].map((r) => [r, r])),
|
|
1244
|
+
sb: 'r9',
|
|
1245
|
+
sl: 'r10',
|
|
1246
|
+
fp: 'r11',
|
|
1247
|
+
},
|
|
1248
|
+
},
|
|
806
1249
|
// AAPCS: r0-r3 pass arguments and return, r12 (`ip`) is the intra-procedure-call scratch, and lr
|
|
807
1250
|
// holds the return address the `bl` itself overwrites. agbcc's own machine description says the
|
|
808
1251
|
// same (`thumb.h` CALL_USED_REGISTERS). Both spellings of r12 for the reason `nonArgRegs` carries
|
|
@@ -883,8 +1326,11 @@ export const ARMV4T_AGBCC: TargetDescription = {
|
|
|
883
1326
|
nearBaseSpan: 255,
|
|
884
1327
|
volatileReadsExtendInRegister: true,
|
|
885
1328
|
foldsConstAddrOffset: true,
|
|
1329
|
+
farMemberStoreOrder: true,
|
|
1330
|
+
farMemberBasePerAccess: { past: 0 },
|
|
886
1331
|
foldsPointerAdvance: true,
|
|
887
1332
|
pointerIntConversionIsFree: true,
|
|
1333
|
+
keepsPointerSumAddend: true,
|
|
888
1334
|
readsStayWhereWritten: true,
|
|
889
1335
|
switchBoundCase: 'taken',
|
|
890
1336
|
switchArmsFollowLayout: true,
|
|
@@ -893,9 +1339,21 @@ export const ARMV4T_AGBCC: TargetDescription = {
|
|
|
893
1339
|
eightByteReturnScratch: 'r2',
|
|
894
1340
|
arrayShapeFromStride: true,
|
|
895
1341
|
reloadsLocalReread: true,
|
|
1342
|
+
// MEASURED: the probe at the field. A signed constant often lifts as `mov #1; neg` or
|
|
1343
|
+
// `mov #0x80; lsl #8`, a `neg`/`shl` the seed does not read. agbcc also materializes one
|
|
1344
|
+
// constant ONCE for several narrow stores through the same base, whatever their signedness:
|
|
1345
|
+
// `struct { s32 a; s16 b; u16 c; } *p; p->b = -300; p->c = -300;` is one `ldr r1,=0xfed4` for
|
|
1346
|
+
// both `strh`. A stamp here then names one store's type, not necessarily each's (the `s16`
|
|
1347
|
+
// store is stamped unsigned, which is also the convention).
|
|
1348
|
+
narrowStoreConstantTakesLvalueType: true,
|
|
896
1349
|
aggregateBoundary: 4,
|
|
897
1350
|
largestAlignment: 4,
|
|
898
1351
|
aggregateReturn: 'apcs',
|
|
1352
|
+
structReturnHandsBackPointer: true,
|
|
1353
|
+
// MEASURED: the probes at the fields.
|
|
1354
|
+
forwardsStructReturnDestination: false,
|
|
1355
|
+
discardedReadMayFillArgRegister: true,
|
|
1356
|
+
byValueAggregateArgWords: true,
|
|
899
1357
|
enumBytes: 4,
|
|
900
1358
|
bitfieldPacking: 'contiguous',
|
|
901
1359
|
roundTripsDoubleLiterals: true,
|
|
@@ -914,16 +1372,23 @@ export const ARMV4T_AGBCC: TargetDescription = {
|
|
|
914
1372
|
// operand rows and nothing else, from two locals declared the other way round) and its
|
|
915
1373
|
// control `synthetic:spillorder_rev` (the same body in the order asmlift already emits, which
|
|
916
1374
|
// must stay a MATCH), plus `synthetic:dma_fill_uninit`, a row this did not author.
|
|
1375
|
+
designatedInitializers: true, // agbcc compiles `{ 1, [8] = 2 }` (see the field)
|
|
917
1376
|
spillSlotOrder: 'ascending',
|
|
1377
|
+
addressTakenArrayOrder: 'ascending', // MEASURED, the probe at the field
|
|
1378
|
+
addressTakenArrayAlignment: 4, // MEASURED, the probe at the field
|
|
918
1379
|
// agbcc reserves the outgoing area with the rest of the frame (`add sp, sp, #-N` covers both)
|
|
919
1380
|
// and stages arguments 5+ into it at [sp,#0] upward — thumb.h's ACCUMULATE_OUTGOING_ARGS.
|
|
920
1381
|
stagesOutgoingArgsInFrame: true,
|
|
921
1382
|
// …one area for every call, below the locals (thumb.h:573/600-622/628, calls.c:1675), which
|
|
922
1383
|
// agbcc re-stages before each call and never reads back after one (agbcc-restage.s).
|
|
923
1384
|
localsAboveOutgoingArea: true,
|
|
1385
|
+
// …nor loads at all, before a call or after: no REG_PARM_STACK_SPACE (see the field).
|
|
1386
|
+
outgoingAreaNeverLoadedFrom: 0,
|
|
924
1387
|
// the two producer tables behind this are compiled, at agbcc 2.9-arm-000512 and the rows' own
|
|
925
1388
|
// flags, and they are written out where the predicate reads it (`frontend/thumb.ts`)
|
|
926
1389
|
oneWordFrameIsTheCapturedObject: true,
|
|
1390
|
+
// compiled: the committed probe `test/corpus/agbcc-escape.s` (`scripts/regen-outgoing-area-probes.ts`)
|
|
1391
|
+
escapedFrameAddressNamesALocal: true,
|
|
927
1392
|
// agbcc's `*call_indirect` and `*call_value_indirect` emit `bl _call_via_%0` (gcc/thumb.md:997-1021),
|
|
928
1393
|
// and libgcc defines `_call_via_<reg>` as `bx <reg>` for r0-r9, sl, fp, ip, sp and lr
|
|
929
1394
|
// (libgcc/lib1thumb.asm:595-633). Neither `sp` (the frame) nor `lr` (which the `bl` itself
|
|
@@ -932,6 +1397,13 @@ export const ARMV4T_AGBCC: TargetDescription = {
|
|
|
932
1397
|
prefix: '_call_via_',
|
|
933
1398
|
regs: ['r0', 'r1', 'r2', 'r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9', 'sl', 'fp', 'ip'],
|
|
934
1399
|
},
|
|
1400
|
+
// agbcc spells a branch past the ±2 KB reach of Thumb `b` as `bl <label>` with this comment,
|
|
1401
|
+
// from three patterns: `jump` (`bl %l0\t@ far jump`, gcc/thumb.md:837), and the relays of
|
|
1402
|
+
// `*cond_branch` (`.LCB`, :930) and `*cond_branch_reversed` (`.LCBR`, :957), both
|
|
1403
|
+
// `bl %l0\t@far jump`. thumb.c `far_jump_used_p` (:715, read at :785 and :1065) makes it push lr
|
|
1404
|
+
// in any function that has one. Only the WHOLE comment is the mark: a longer comment that
|
|
1405
|
+
// mentions a far jump is not the compiler's.
|
|
1406
|
+
farJumpRelay: { comment: /^@\s*far jump\s*$/ },
|
|
935
1407
|
},
|
|
936
1408
|
};
|
|
937
1409
|
|
|
@@ -939,6 +1411,58 @@ export const ARMV4T_AGBCC: TargetDescription = {
|
|
|
939
1411
|
* halves (`$f13`, `$f15`) carry the other word of a DOUBLE argument, which no frontend lifts. */
|
|
940
1412
|
const O32_FPU: TargetDescription['fpu'] = { argRegs: ['$f12', '$f14'], returnReg: '$f0', slots: 'leading' };
|
|
941
1413
|
|
|
1414
|
+
/** O32's register partition, shared by every compiler that targets it (`nonArgRegs` and
|
|
1415
|
+
* `scratchRegs`). Every general register but the four argument registers and the ones that are
|
|
1416
|
+
* never a local's home (`zero`, `k0`/`k1`, `gp`, `sp`, `ra`). `s8` and `fp` are the two spellings
|
|
1417
|
+
* of $30, listed both as `ARMV4T_AGBCC` lists `r12` and `ip`: objdump prints `s8`, and a spelling
|
|
1418
|
+
* nobody listed keeps its existing treatment. */
|
|
1419
|
+
const O32_NON_ARG_REGS = [
|
|
1420
|
+
'at',
|
|
1421
|
+
'v0',
|
|
1422
|
+
'v1',
|
|
1423
|
+
...Array.from({ length: 10 }, (_, i) => `t${i}`),
|
|
1424
|
+
...Array.from({ length: 8 }, (_, i) => `s${i}`),
|
|
1425
|
+
's8',
|
|
1426
|
+
'fp',
|
|
1427
|
+
] as const;
|
|
1428
|
+
/** Of those, the ones O32 lets a callee use with no save — `at`, `v0`/`v1` and `t0`..`t9`, the
|
|
1429
|
+
* caller-saved registers that pass no argument. MEASURED on GCC_KMC_TOOLCHAIN and the stock gcc
|
|
1430
|
+
* 2.7.2 toolchain at their synthetic flags: a leaf homes an uninitialised local in v0
|
|
1431
|
+
* (`uninit_join`: `bgtzl a0,…; sll v0,a0,1; jr ra; ori v0,v0,1`) and in v1 (`uninit_sw`).
|
|
1432
|
+
*
|
|
1433
|
+
* Every one of them is also a `hiddenInputRegs` entry, because O32's hand-written asm hands values
|
|
1434
|
+
* over in them: af's famicom_emu routines are handed a value in t0 or v1 (`sw t0,6348(s8)` is
|
|
1435
|
+
* its first instruction), and a label inside a function starts with its registers live.
|
|
1436
|
+
* MEASURED over every function and label symbol of the vendored snowboardkids2 (KMC),
|
|
1437
|
+
* marioparty3 (gcc 2.7.2) and af (IDO) objects, 29,399 in all, the refusal below fires on 17 —
|
|
1438
|
+
* twelve labels, three hand-written routines (two of famicom_emu's and libkmc's `div64_64_1`) and
|
|
1439
|
+
* two data symbols disassembled as code — and on no compiled function. A
|
|
1440
|
+
* compiled local is assigned on some path, so its entry value meets that assignment at a join;
|
|
1441
|
+
* `uninitfirst` also reads one before the switch that assigns it, which a join allows. The join
|
|
1442
|
+
* must take a definition the entry value does not feed, because an input carried round a loop
|
|
1443
|
+
* (`addiu t0,t0,-1; bnez t0,top`) meets only its own next value at the loop header. famicom_emu's
|
|
1444
|
+
* hand-written Yaz0 reader `func_8082E4E0_jp` is NOT that shape:
|
|
1445
|
+
* its t0 is a bit buffer loaded whenever the count runs out (and the count starts at 0), then
|
|
1446
|
+
* shifted each trip, and it lifts with t0 an uninitialised local no path reads before the load.
|
|
1447
|
+
* The cost is a local NO path assigns
|
|
1448
|
+
* (`int r; return r + a;` is `jr ra; addu v0,v0,a0` on gcc, and `int x, y; if (c) x = y;` joins
|
|
1449
|
+
* v0 with v1), which refuses: none of the 29,399 is one.
|
|
1450
|
+
*
|
|
1451
|
+
* THE RULE'S BLIND SIDE is a count a hand-written routine is handed and RELOADS on an outer arm
|
|
1452
|
+
* (`… addiu t0,t0,-1; bnez t0,inner; …; bnezl a1,inner; li t0,8`): the outer arm's constant is a
|
|
1453
|
+
* definition of its own at the inner loop's header, so the entry value counts as met and the first
|
|
1454
|
+
* pass reads an undeclared local — the input is dropped. Nothing tells it from compiled
|
|
1455
|
+
* `int t; do { do { … } while (--t); t = 8; } while (--n);`, and none of the 29,399 is either.
|
|
1456
|
+
* The same side holds an input RELOADED FROM MEMORY on one arm and then transformed: hand
|
|
1457
|
+
* func_8082E4E0_jp its count in a2 as well (drop its `move a2,zero`), and the arm that runs when
|
|
1458
|
+
* the count is not 0 reads the caller's bit buffer in t0, but the loop header's join meets the
|
|
1459
|
+
* shift `sll t0,t0,1`, which reaches t0's entry value only through the join the load `lw t0,0(a0)`
|
|
1460
|
+
* also feeds. So the shift is a definition of its own, the routine lifts with `int code;` read on
|
|
1461
|
+
* that first pass, and the caller's t0 is dropped. Nothing tells it from compiled `int code;`
|
|
1462
|
+
* with the bit count a parameter, and none of the 29,399 is that shape: the real routine zeroes
|
|
1463
|
+
* a2 before the loop, so its load runs first. */
|
|
1464
|
+
const O32_SCRATCH_REGS = ['at', 'v0', 'v1', ...Array.from({ length: 10 }, (_, i) => `t${i}`)] as const;
|
|
1465
|
+
|
|
942
1466
|
/** MIPS-II / IDO 7.1 target. IDO is the IRIX C compiler,
|
|
943
1467
|
* statically recompiled to run natively (ido-static-recomp). Unlike agbcc
|
|
944
1468
|
* it emits no textual asm, so asmlift's input is the DISASSEMBLED object (`mips-linux-gnu-
|
|
@@ -949,6 +1473,8 @@ export const MIPS_IDO: TargetDescription = {
|
|
|
949
1473
|
compiler: 'ido',
|
|
950
1474
|
argRegs: ['a0', 'a1', 'a2', 'a3'],
|
|
951
1475
|
returnReg: 'v0',
|
|
1476
|
+
// MEASURED on ido7.1, gcc2.7.2kmc and gcc2.7.2: the probes at the field.
|
|
1477
|
+
evenWordAlignedArgs: true,
|
|
952
1478
|
// O32: at, v0-v1, a0-a3, t0-t9 and ra are all caller-saved. `frontend/mips.ts` reads the rest, and
|
|
953
1479
|
// `ra`, as the registers a frame store SAVES.
|
|
954
1480
|
callerSaved: [
|
|
@@ -971,6 +1497,13 @@ export const MIPS_IDO: TargetDescription = {
|
|
|
971
1497
|
't9',
|
|
972
1498
|
'ra',
|
|
973
1499
|
],
|
|
1500
|
+
// O32's partition (`O32_NON_ARG_REGS`). The frontend narrows it to the scratch half and the
|
|
1501
|
+
// callee-saved registers its prologue saves (frontend/mips.ts `prologueSavedRegs`).
|
|
1502
|
+
nonArgRegs: O32_NON_ARG_REGS,
|
|
1503
|
+
scratchRegs: O32_SCRATCH_REGS,
|
|
1504
|
+
// Every scratch register, for the measurement at `O32_SCRATCH_REGS`.
|
|
1505
|
+
hiddenInputRegs: O32_SCRATCH_REGS,
|
|
1506
|
+
kernelRegs: ['k0', 'k1'],
|
|
974
1507
|
runtimeHelpers: IDO_RUNTIME_HELPERS,
|
|
975
1508
|
// MEASURED with this toolchain's own flags: `float f(float a, float b){ return a + b; }` is
|
|
976
1509
|
// `jr ra; add.s $f0,$f12,$f14`, and the `'leading'` rule's two halves are the pair in `fpu`'s note.
|
|
@@ -986,7 +1519,17 @@ export const MIPS_IDO: TargetDescription = {
|
|
|
986
1519
|
switchAllowsNeqCase: false,
|
|
987
1520
|
// MEASURED — the pair at the field compiles to one load of `p[1]` for every local spelling.
|
|
988
1521
|
reloadsLocalReread: false,
|
|
1522
|
+
// MEASURED: the probe at the field.
|
|
1523
|
+
narrowStoreConstantTakesLvalueType: true,
|
|
989
1524
|
aggregateBoundary: 1,
|
|
1525
|
+
// MEASURED on all three O32 toolchains (ido7.1, gcc2.7.2kmc, gcc2.7.2): `struct {char c; double d;}`
|
|
1526
|
+
// and its `long long` twin put the member at 8 and size 16 (`sizeof` and the member's offset
|
|
1527
|
+
// compiled into `.data`).
|
|
1528
|
+
largestAlignment: 8,
|
|
1529
|
+
// MEASURED on all three O32 toolchains: the probes at the field.
|
|
1530
|
+
aggregateReturn: 'memory',
|
|
1531
|
+
structReturnHandsBackPointer: true,
|
|
1532
|
+
byValueAggregateArgWords: true,
|
|
990
1533
|
// MEASURED at `-mips2 -O2 -32 -non_shared -G 0`: the `sll` leads the function for BOTH
|
|
991
1534
|
// spellings, so the prologue position cannot decide; a narrow DECLARED parameter is the one that
|
|
992
1535
|
// is both homed dead AND widened in its own argument register. raise/paramwidth.ts's header has
|
|
@@ -1007,6 +1550,26 @@ export const MIPS_IDO: TargetDescription = {
|
|
|
1007
1550
|
// O32's caller-owned home area `[0,16)` would be read as this function's first four
|
|
1008
1551
|
// declaration ranks. Shipping a direction before the partition orders by argument index.
|
|
1009
1552
|
spillSlotOrder: 'unknown',
|
|
1553
|
+
addressTakenArrayOrder: 'descending', // MEASURED, the probe at the field
|
|
1554
|
+
addressTakenArrayAlignment: 4, // MEASURED, the probe at the field
|
|
1555
|
+
// MEASURED, the probe at the field: the local ends at the frame top, above `ra` and the spills.
|
|
1556
|
+
o32LocalArea: 'above-saved-registers',
|
|
1557
|
+
// MEASURED off the same probe (`twice`, `owns`): O32's outgoing area is the frame bottom, an
|
|
1558
|
+
// unchanged argument is staged again before each call, and a callee assigns to its stack
|
|
1559
|
+
// parameter in place. Read by the frame-object audit alone here (`storedNeverRead`).
|
|
1560
|
+
localsAboveOutgoingArea: true,
|
|
1561
|
+
// MEASURED at -O1, -O2, -O2 -g3 and -O0 (`test/corpus/ido71*-outarea.txt`): no load from 16(sp)
|
|
1562
|
+
// up, but the a1..a3 home words a by-value aggregate is staged in are loaded back (see the field).
|
|
1563
|
+
outgoingAreaNeverLoadedFrom: 16,
|
|
1564
|
+
// MEASURED at -O1 and -O0: the note at the field — `lw zero` for a volatile read, a `t6` load
|
|
1565
|
+
// and a frame store for a plain dead local, `lw a2` for a `register` one.
|
|
1566
|
+
discardsUnusedReadsInto: 'zero',
|
|
1567
|
+
// MEASURED: the probes at the fields.
|
|
1568
|
+
forwardsStructReturnDestination: true,
|
|
1569
|
+
discardedReadMayFillArgRegister: false,
|
|
1570
|
+
// MEASURED, the probes at the field: past the `addiu` reach every member access gets its own
|
|
1571
|
+
// `lui`/`addu`, and a held pointer shares one.
|
|
1572
|
+
farMemberBasePerAccess: { past: 32767 },
|
|
1010
1573
|
},
|
|
1011
1574
|
};
|
|
1012
1575
|
|
|
@@ -1018,6 +1581,8 @@ export const MIPS_GCC: TargetDescription = {
|
|
|
1018
1581
|
compiler: 'gcc',
|
|
1019
1582
|
argRegs: ['a0', 'a1', 'a2', 'a3'],
|
|
1020
1583
|
returnReg: 'v0',
|
|
1584
|
+
// MEASURED on ido7.1, gcc2.7.2kmc and gcc2.7.2: the probes at the field.
|
|
1585
|
+
evenWordAlignedArgs: true,
|
|
1021
1586
|
// The same O32 convention MIPS_IDO carries, read the same way.
|
|
1022
1587
|
callerSaved: [
|
|
1023
1588
|
'at',
|
|
@@ -1039,6 +1604,22 @@ export const MIPS_GCC: TargetDescription = {
|
|
|
1039
1604
|
't9',
|
|
1040
1605
|
'ra',
|
|
1041
1606
|
],
|
|
1607
|
+
// O32's partition, as at MIPS_IDO.
|
|
1608
|
+
nonArgRegs: O32_NON_ARG_REGS,
|
|
1609
|
+
scratchRegs: O32_SCRATCH_REGS,
|
|
1610
|
+
hiddenInputRegs: O32_SCRATCH_REGS,
|
|
1611
|
+
// MEASURED on gcc2.7.2kmc and gcc2.7.2 at their canonical flags: `register int g __asm__("$16");
|
|
1612
|
+
// int f(void){ return g; }` is `jr ra; move v0,s0`, and `$23`/`$30` give s7/s8. KMC's `-fno-asm`
|
|
1613
|
+
// rejects the bare `asm(` spelling, which is why every target spells `__asm__`. IDO 7.1 has no
|
|
1614
|
+
// such declaration (its cfe rejects both spellings), so MIPS_IDO states none.
|
|
1615
|
+
globalRegisterVariables: {
|
|
1616
|
+
regs: {
|
|
1617
|
+
...Object.fromEntries(Array.from({ length: 8 }, (_, i) => [`s${i}`, `$${16 + i}`])),
|
|
1618
|
+
s8: '$30',
|
|
1619
|
+
fp: '$30',
|
|
1620
|
+
},
|
|
1621
|
+
},
|
|
1622
|
+
kernelRegs: ['k0', 'k1'],
|
|
1042
1623
|
runtimeHelpers: MIPS_GCC_RUNTIME_HELPERS,
|
|
1043
1624
|
// KMC GCC keeps a loop seeded from an argument register IN that register (coalesceLoopInit
|
|
1044
1625
|
// true, like IDO): test/corpus/gcc-gcd.asm runs its whole loop on a0/a1 with no init copies,
|
|
@@ -1104,7 +1685,17 @@ export const MIPS_GCC: TargetDescription = {
|
|
|
1104
1685
|
// MEASURED on BOTH toolchains this description serves (the note above): one load of `p[1]` for
|
|
1105
1686
|
// every local spelling of the pair at the field, gcc2.7.2kmc at -O2 and gcc2.7.2 at -O1 alike.
|
|
1106
1687
|
reloadsLocalReread: false,
|
|
1688
|
+
// MEASURED on BOTH toolchains this description serves, the probe at the field.
|
|
1689
|
+
narrowStoreConstantTakesLvalueType: true,
|
|
1690
|
+
// MEASURED on BOTH toolchains this description serves, the pairs at the field.
|
|
1691
|
+
keepsPointerSumAddend: true,
|
|
1107
1692
|
aggregateBoundary: 1,
|
|
1693
|
+
// MEASURED on BOTH toolchains this description serves, and on ido7.1: the probe at MIPS_IDO's.
|
|
1694
|
+
largestAlignment: 8,
|
|
1695
|
+
// MEASURED on BOTH toolchains this description serves: the probes at the field.
|
|
1696
|
+
aggregateReturn: 'memory',
|
|
1697
|
+
structReturnHandsBackPointer: true,
|
|
1698
|
+
byValueAggregateArgWords: true,
|
|
1108
1699
|
// MEASURED on BOTH toolchains this description serves: `int f(s8 x){return x;}` and
|
|
1109
1700
|
// `int f(s32 x){return (s8)x;}` compile to BYTE-IDENTICAL objects, gcc2.7.2kmc at -O2 and
|
|
1110
1701
|
// gcc2.7.2 at -O1 alike — so the object carries no witness at all and the pass refuses.
|
|
@@ -1118,7 +1709,21 @@ export const MIPS_GCC: TargetDescription = {
|
|
|
1118
1709
|
// The two agreeing is not a formality. The value is per DESCRIPTION and TWO toolchains map
|
|
1119
1710
|
// here, so a toolchain whose direction differed from its description's would need a
|
|
1120
1711
|
// per-toolchain override this bag cannot express — see the note at `compilerBehaviors`.
|
|
1712
|
+
designatedInitializers: true, // both gcc 2.7.2 builds compile `{ 1, [8] = 2 }` (see the field)
|
|
1121
1713
|
spillSlotOrder: 'unknown',
|
|
1714
|
+
addressTakenArrayOrder: 'ascending', // MEASURED on both toolchains, the probe at the field
|
|
1715
|
+
addressTakenArrayAlignment: 8, // MEASURED on both toolchains, the probe at the field
|
|
1716
|
+
// MEASURED on BOTH toolchains this description serves, the probe at the field: identical
|
|
1717
|
+
// layouts, the local right above the 8-aligned outgoing area and under the saves.
|
|
1718
|
+
o32LocalArea: 'above-outgoing-area',
|
|
1719
|
+
// MEASURED on BOTH toolchains this description serves, the probe at `o32LocalArea` (`twice`).
|
|
1720
|
+
localsAboveOutgoingArea: true,
|
|
1721
|
+
// MEASURED on BOTH toolchains at their rows' levels and at -O0 (`test/corpus/gcc272*-outarea.txt`):
|
|
1722
|
+
// no load from 16(sp) up the area; a char-array struct's home words are loaded (see the field).
|
|
1723
|
+
outgoingAreaNeverLoadedFrom: 16,
|
|
1724
|
+
// MEASURED on BOTH toolchains this description serves: the probes at the fields.
|
|
1725
|
+
forwardsStructReturnDestination: false,
|
|
1726
|
+
discardedReadMayFillArgRegister: true,
|
|
1122
1727
|
},
|
|
1123
1728
|
};
|
|
1124
1729
|
|
|
@@ -1134,11 +1739,37 @@ export const PPC_MWCC: TargetDescription = {
|
|
|
1134
1739
|
// PPC EABI: r3–r10 pass integer/pointer arguments; r3 also returns.
|
|
1135
1740
|
argRegs: ['r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9', 'r10'],
|
|
1136
1741
|
returnReg: 'r3',
|
|
1742
|
+
// MEASURED on 2.4.2 at the synthetic tier's flags and on 2.3.3 and 2.4.7 at -O4,p:
|
|
1743
|
+
// `takeLL(1, gL)` sets r3 and r5:r6 with r4 unset.
|
|
1744
|
+
evenWordAlignedArgs: true,
|
|
1137
1745
|
// PPC EABI: r0 and r3-r12 are volatile, and lr carries the return address `bl` overwrites. r11
|
|
1138
1746
|
// and r12 are the linker's stub scratch, r13 is the small-data base and r14 upward are
|
|
1139
1747
|
// callee-saved.
|
|
1140
1748
|
callerSaved: ['r0', 'r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9', 'r10', 'r11', 'r12', 'lr'],
|
|
1749
|
+
// Every GPR but r1 (the stack pointer, never an SSA register here) and the eight argument
|
|
1750
|
+
// registers. The frontend narrows it to `scratchRegs` and the ones the prologue saved
|
|
1751
|
+
// (`prologueSavedRegs`). r2 and r13, the small-data bases, never reach that rule: a read of either
|
|
1752
|
+
// as a value refuses first (`SMALL_DATA_BASES` in frontend/ppc.ts).
|
|
1753
|
+
nonArgRegs: ['r0', 'r2', ...Array.from({ length: 21 }, (_, i) => `r${i + 11}`)],
|
|
1754
|
+
// The volatile registers no argument travels in, which a callee may use with no save. MEASURED on
|
|
1755
|
+
// 2.4.2 at the synthetic tier's flags and on 2.3.3 at Pikmin's -O4,p (in C): a leaf homes an
|
|
1756
|
+
// uninitialised local in each of them, unsaved — `int x; if (s) x = a*3; return x + a;` is
|
|
1757
|
+
// `mulli r0,r4,3; add r3,r0,r4`, and with all eight argument registers live two such locals land
|
|
1758
|
+
// in r11 and r12. 2.4.7 at MP4's flags puts them in saved registers instead.
|
|
1759
|
+
scratchRegs: ['r0', 'r11', 'r12'],
|
|
1760
|
+
// r11 is the save routines' frame address and r12 `__ptmf_scall`'s member-function pointer: a
|
|
1761
|
+
// read of either that no path's definition joins refuses instead of minting `uninit_r11`.
|
|
1762
|
+
hiddenInputRegs: ['r11', 'r12'],
|
|
1763
|
+
// MEASURED on 2.4.2 and 2.4.7 at -O4,p: `register int g __asm__("r14"); int f(void){ return g; }`
|
|
1764
|
+
// is `mr r3,r14`. They accept r14..r28 only ("supports 15 registers beginning with r14") and no
|
|
1765
|
+
// gap among them ("nothing at 'r19' not permitted"), in any declaration order. 2.3.3 refuses the
|
|
1766
|
+
// storage class, so `mwcc_233_163n` drops this field.
|
|
1767
|
+
globalRegisterVariables: {
|
|
1768
|
+
regs: Object.fromEntries(Array.from({ length: 15 }, (_, i) => [`r${14 + i}`, `r${14 + i}`])),
|
|
1769
|
+
contiguous: Array.from({ length: 15 }, (_, i) => `r${14 + i}`),
|
|
1770
|
+
},
|
|
1141
1771
|
runtimeHelpers: PPC_MWCC_RUNTIME_HELPERS,
|
|
1772
|
+
registerSaveRoutines: PPC_MWCC_SAVE_ROUTINES,
|
|
1142
1773
|
// PPC EABI with `-fp hard`, measured at the synthetic tier's flags: `fadds f1,f1,f2; blr`, a third
|
|
1143
1774
|
// float argument in f3, and the GPR/FPR order of mixed parameters absent from the object.
|
|
1144
1775
|
fpu: { argRegs: ['f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8'], returnReg: 'f1', slots: 'separate' },
|
|
@@ -1148,6 +1779,9 @@ export const PPC_MWCC: TargetDescription = {
|
|
|
1148
1779
|
// two compiler-wide guesses standing in for the per-function observation named at MIPS_GCC.
|
|
1149
1780
|
compilerBehaviors: {
|
|
1150
1781
|
contractsFloatProducts: true,
|
|
1782
|
+
// MEASURED on all three builds in C: an integer, a pointer and a `u16 *` passed to an `s32 *`,
|
|
1783
|
+
// `u32` and `s32 *` parameter each fail to compile, and each compiles once cast (see the field).
|
|
1784
|
+
strictPointerConversions: true,
|
|
1151
1785
|
coalesceLoopInit: false,
|
|
1152
1786
|
preserveDivergentBranchSense: true,
|
|
1153
1787
|
orderArgCopiesByWriteOrder: true,
|
|
@@ -1156,11 +1790,16 @@ export const PPC_MWCC: TargetDescription = {
|
|
|
1156
1790
|
// `u8 v = p[3]; if ((v & 0x7f) == 0x7f) { fnA(); p[4] = v; return; }` under an `if (a)`
|
|
1157
1791
|
// matches only once `read-behind-effect` stops refusing it (3/24 → MATCH 0/22).
|
|
1158
1792
|
reloadsLocalReread: false,
|
|
1793
|
+
// MEASURED on all three builds at -O4,p, and on mwcc_247_107 at -O0,p: the probe at the field.
|
|
1794
|
+
narrowStoreConstantTakesLvalueType: true,
|
|
1159
1795
|
aggregateBoundary: 1,
|
|
1160
1796
|
// MEASURED on all three builds: `struct {u8 a; double d;}` and its `long long` twin put the
|
|
1161
1797
|
// member at 8 and size 16
|
|
1162
1798
|
largestAlignment: 8,
|
|
1163
1799
|
aggregateReturn: 'svr4',
|
|
1800
|
+
// MEASURED on all three builds: r3 holds a reload of a frame word at the `blr` of a struct
|
|
1801
|
+
// returner that calls something
|
|
1802
|
+
structReturnHandsBackPointer: false,
|
|
1164
1803
|
// MEASURED on mwcc_242_81 through the `.comment` alignment record: an array or a struct is at
|
|
1165
1804
|
// 4 at least (`u8[1]`, a struct of two bytes), a scalar at its width; a string-literal
|
|
1166
1805
|
// initializer gets no more than the array does (`char s[] = "hello!"` and a `u8` list both 4);
|
|
@@ -1187,6 +1826,15 @@ export const PPC_MWCC: TargetDescription = {
|
|
|
1187
1826
|
// frontend lifts to the same `sext` op agbcc's shift pair folds to — the position shape, on
|
|
1188
1827
|
// another ISA. `synthetic:{sextb,tos8}:mwcc_242_81` are its rows, MATCH through that pass.
|
|
1189
1828
|
narrowParamWitness: 'prologue-extension',
|
|
1829
|
+
// MEASURED on all three builds: the note at the field.
|
|
1830
|
+
linkageLocalArea: 'above-outgoing-area',
|
|
1831
|
+
// MEASURED off the same probes, on all three builds and at -O0,p (`twice9`, and every outgoing
|
|
1832
|
+
// word below the area unread): Mario Party 4's -O0,p homes a parameter in the area and reloads
|
|
1833
|
+
// it after a call, which proves the word a local's (`ee`, `synthetic:homecall:mwcc_242_81`).
|
|
1834
|
+
localsAboveOutgoingArea: true,
|
|
1835
|
+
// MEASURED on all three builds at the rows' flags and -O0,p, and on mwcc_242_81 at -O4,s
|
|
1836
|
+
// (`test/corpus/mwcc*-outarea.txt`): no load of the parameter area, which starts at 8(r1).
|
|
1837
|
+
outgoingAreaNeverLoadedFrom: 8,
|
|
1190
1838
|
// NOT MEASURED, and `'unknown'` is therefore the only honest value here rather than a withheld
|
|
1191
1839
|
// one, as it is at MIPS_IDO and MIPS_GCC. No mwcc row lifts with two or more spilled user
|
|
1192
1840
|
// locals, and the compiler does not spill the committed declaration-rank probe either: at
|
|
@@ -1195,9 +1843,19 @@ export const PPC_MWCC: TargetDescription = {
|
|
|
1195
1843
|
// first: `frontend/ppc.ts` claims no `LiveInModel.declaredLocals`, so the shared stamp records
|
|
1196
1844
|
// no slot home on this target at all and a direction here would order nothing until it does.
|
|
1197
1845
|
spillSlotOrder: 'unknown',
|
|
1846
|
+
// MEASURED on all three builds at -O0,p: the note at the field — `lwz r0` for a volatile read,
|
|
1847
|
+
// an `r31` home for a plain or `register` dead local.
|
|
1848
|
+
discardsUnusedReadsInto: 'r0',
|
|
1849
|
+
// MEASURED on all three builds: the probes at the fields.
|
|
1850
|
+
forwardsStructReturnDestination: false,
|
|
1851
|
+
discardedReadMayFillArgRegister: true,
|
|
1198
1852
|
},
|
|
1199
1853
|
};
|
|
1200
1854
|
|
|
1855
|
+
/** CodeWarrior 2.3.3b163n (Pikmin): `PPC_MWCC` but for the one declaration a probe refuted there —
|
|
1856
|
+
* it rejects a global register variable ("illegal storage class"), so it has no spelling for one. */
|
|
1857
|
+
export const PPC_MWCC_233: TargetDescription = { ...PPC_MWCC, globalRegisterVariables: undefined };
|
|
1858
|
+
|
|
1201
1859
|
/** A toolchain: one compiler binary, named as decomp.me names it. Several toolchains may share a
|
|
1202
1860
|
* description (`MIPS_GCC` serves two), and each keeps its own evidence. */
|
|
1203
1861
|
export interface ToolchainTarget {
|
|
@@ -1271,7 +1929,8 @@ export const TOOLCHAIN_TARGETS = {
|
|
|
1271
1929
|
description: PPC_MWCC,
|
|
1272
1930
|
},
|
|
1273
1931
|
// The other two CodeWarrior builds the GameCube projects compile with: 2.3.3b163n (Pikmin's whole
|
|
1274
|
-
// game tree) and 2.4.7b107 (Mario Party 4's DOL). Both SHARE `PPC_MWCC
|
|
1932
|
+
// game tree) and 2.4.7b107 (Mario Party 4's DOL). Both SHARE `PPC_MWCC` (2.3.3 less the global
|
|
1933
|
+
// register variables it refuses, `PPC_MWCC_233`), and on their own evidence
|
|
1275
1934
|
// rather than because they are the same compiler family: `test/matching/ppc-compiler-behaviors.test.ts`
|
|
1276
1935
|
// re-runs both of its probes on each binary at the flags that build's rows compile at, and each
|
|
1277
1936
|
// reads as the description says. What moves those readings is the optimisation level — at `-O0,p`
|
|
@@ -1283,7 +1942,7 @@ export const TOOLCHAIN_TARGETS = {
|
|
|
1283
1942
|
// are two different sets, neither of which is "the" one. See `canonicalFlags` above.
|
|
1284
1943
|
mwcc_233_163n: {
|
|
1285
1944
|
family: 'mwcc',
|
|
1286
|
-
description:
|
|
1945
|
+
description: PPC_MWCC_233,
|
|
1287
1946
|
},
|
|
1288
1947
|
mwcc_247_107: {
|
|
1289
1948
|
family: 'mwcc',
|
|
@@ -1328,6 +1987,41 @@ export interface ResolvedTarget {
|
|
|
1328
1987
|
profile: CodegenProfile;
|
|
1329
1988
|
}
|
|
1330
1989
|
|
|
1990
|
+
/** Does a compiler of `family`, at optimisation level `level` (the profile's `O` slot), keep a plain
|
|
1991
|
+
* read whose value nothing uses (`compilerBehaviors.keepsUnusedReads`)? COMPILED, `int x; x = p[5];
|
|
1992
|
+
* x = 0; *q = x;` and `int x = p[1];`: mwcc 2.3.3 and 2.4.2 at `-O0` and 2.4.7 at `-O0,p` keep
|
|
1993
|
+
* `lwz r31, 20(r3)` in a register nothing reads; IDO 7.1 at `-O0` and at `-O1` (its default) keeps
|
|
1994
|
+
* `lw t6, 20(a0)`; agbcc and KMC gcc 2.7.2 at `-O0` keep the read and store it to the frame. None
|
|
1995
|
+
* of them does at agbcc/KMC `-O1`, `-O2` or `-O3`, IDO `-O2` or `-O3`, or mwcc 2.4.2 `-O1` through
|
|
1996
|
+
* `-O4,p` and `-O4,s`. A plain expression statement's read (`*p;`) is gone at every level on every
|
|
1997
|
+
* one of them; a volatile one stays, at a keeping level in the register `discardsUnusedReadsInto`
|
|
1998
|
+
* names where the compiler has one.
|
|
1999
|
+
*
|
|
2000
|
+
* A LEVEL NO FLAG NAMES (`undefined`) KEEPS. Every family's unflagged compile was measured keeping:
|
|
2001
|
+
* agbcc's and IDO's are their `-O0` and `-O1` (codegen-flags.ts `EFFECTIVE` names them, so they
|
|
2002
|
+
* never reach here unnamed), KMC gcc 2.7.2 with no `-O` emits `-O0`'s object byte for byte, and
|
|
2003
|
+
* mwcc 2.4.2 with no level, with `-opt off` or with `-opt level=0` keeps `x = p[5]; x = 0;`'s
|
|
2004
|
+
* `lwz r31, 20(r3)` as at `-O0`. And the cost runs one way: answering "keeps" where the compiler
|
|
2005
|
+
* deletes withholds a `volatile` outside the discard register (`discardsUnusedReadsInto`): the
|
|
2006
|
+
* read is dropped as plain on mwcc and IDO, and the lift refuses on a compiler with none. Answering
|
|
2007
|
+
* "deletes" where it keeps publishes one the source never wrote, a value this stamp alone decides,
|
|
2008
|
+
* with no candidate to referee it (docs/level-tower.md, A PROFILE OF A COMPILER INHERITS). */
|
|
2009
|
+
export function keepsUnusedReadsAt(family: FlagFamily, level: string | undefined): boolean {
|
|
2010
|
+
if (level === undefined) {
|
|
2011
|
+
return true;
|
|
2012
|
+
}
|
|
2013
|
+
const n = /^\d/.exec(level)?.[0];
|
|
2014
|
+
return n === '0' || (family === 'ido' && n === '1');
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
/** The level `keepsUnusedReadsAt` judges a profile at: its `O` slot, or none where a word the table
|
|
2018
|
+
* does not read may set it too — mwcc's `-opt off` and `-opt level=0` compile at `-O0`, and the
|
|
2019
|
+
* table reads neither, so a profile that spells one has a level nothing here knows. */
|
|
2020
|
+
function unusedReadLevel(profile: CodegenProfile): string | undefined {
|
|
2021
|
+
const optWord = profile.family === 'mwcc' && profile.unclassified.some((w) => w.startsWith('-opt'));
|
|
2022
|
+
return optWord ? undefined : profile.slots.O;
|
|
2023
|
+
}
|
|
2024
|
+
|
|
1331
2025
|
/** The target a function compiled by `toolchain` at `cflags` decompiles against, and the profile
|
|
1332
2026
|
* those flags describe. Throws on a level word the toolchain's family cannot read. */
|
|
1333
2027
|
export function targetFor(toolchain: ToolchainId, cflags: readonly string[]): ResolvedTarget {
|
|
@@ -1335,6 +2029,9 @@ export function targetFor(toolchain: ToolchainId, cflags: readonly string[]): Re
|
|
|
1335
2029
|
const profile = parseFlags(t.family, cflags);
|
|
1336
2030
|
const cpp = dialectOf(profile.slots.lang) === 'c++';
|
|
1337
2031
|
let target: TargetDescription = cpp ? { ...t.description, dialect: 'c++' } : t.description;
|
|
2032
|
+
if (keepsUnusedReadsAt(t.family, unusedReadLevel(profile))) {
|
|
2033
|
+
target = { ...target, compilerBehaviors: { ...target.compilerBehaviors, keepsUnusedReads: true } };
|
|
2034
|
+
}
|
|
1338
2035
|
if (profile.slots['-fshort-enums'] === 'on') {
|
|
1339
2036
|
// every enum is the smallest integer its values fit (c-decl.c:6064-6065, :6123; compiled,
|
|
1340
2037
|
// `sizeof(struct {enum {K0, K1} k[2];})` is 4 under the flag and 8 without), so none is enumBytes
|
|
@@ -1359,9 +2056,8 @@ export function structureOptionsFor(
|
|
|
1359
2056
|
returnsVoid: boolean,
|
|
1360
2057
|
prototypes: Prototypes = {},
|
|
1361
2058
|
): StructureOptions {
|
|
1362
|
-
// `littleEndian`
|
|
1363
|
-
//
|
|
1364
|
-
// window); everything else is a compiler behavior.
|
|
2059
|
+
// `littleEndian` is the HARDWARE capability the structurer consumes (bitfield extract recognition
|
|
2060
|
+
// is LSB-first); everything else is a compiler behavior.
|
|
1365
2061
|
//
|
|
1366
2062
|
// ONE FIELD IS NOT A STRAIGHT SPREAD, and this is where the difference belongs. A frame
|
|
1367
2063
|
// direction has THREE states here — `ascending`, `descending`, and `'unknown'` meaning measured
|
|
@@ -1372,16 +2068,19 @@ export function structureOptionsFor(
|
|
|
1372
2068
|
const { spillSlotOrder, ...behaviors } = t.compilerBehaviors;
|
|
1373
2069
|
return {
|
|
1374
2070
|
returnsVoid,
|
|
1375
|
-
declaredArgs: declaredArgTypes(prototypes),
|
|
2071
|
+
declaredArgs: declaredArgTypes(prototypes, t.dialect !== 'c++'),
|
|
1376
2072
|
declaredReturns: declaredReturnTypes(prototypes),
|
|
1377
2073
|
...(t.dialect === 'c++' ? { dialect: 'c++' as const } : {}),
|
|
1378
2074
|
littleEndian: t.capabilities.endianness === 'little',
|
|
1379
|
-
...(t.capabilities.deviceRegisters ? { deviceRegisters: t.capabilities.deviceRegisters } : {}),
|
|
1380
2075
|
...behaviors,
|
|
1381
2076
|
...(spillSlotOrder === 'ascending' || spillSlotOrder === 'descending' ? { spillSlotOrder } : {}),
|
|
1382
2077
|
};
|
|
1383
2078
|
}
|
|
1384
2079
|
|
|
2080
|
+
/** The global a register `globalRegisterVariables` names is lifted as (`asmName` its `__asm__`
|
|
2081
|
+
* name: `greg_r8`, and `greg_r16` for MIPS `$16`). */
|
|
2082
|
+
export const globalRegisterSymbol = (asmName: string): string => `greg_${asmName.replace(/^\$/, 'r')}`;
|
|
2083
|
+
|
|
1385
2084
|
/** The candidate prelude: a `typedef` for every name in the scalar vocabulary. PRINTED from
|
|
1386
2085
|
* `proto.ts`'s `PRELUDE_TYPEDEFS` rather than spelled out, because the other reader of that
|
|
1387
2086
|
* vocabulary decides which type texts asmlift may PRINT into a candidate, and a second copy of
|
|
@@ -1393,18 +2092,32 @@ export const C_TYPEDEFS = `${[...PRELUDE_TYPEDEFS].map(([name, base]) => `typede
|
|
|
1393
2092
|
|
|
1394
2093
|
/** Each declared callee's parameter types, where one can be PRINTED as a cast — the argument
|
|
1395
2094
|
* conversions C++ refuses to make implicitly (backend/cfamily.ts `argConversion`), and a pointer
|
|
1396
|
-
* cell's integer arguments (l3/ptrcell.ts).
|
|
1397
|
-
*
|
|
1398
|
-
|
|
2095
|
+
* cell's integer arguments (l3/ptrcell.ts). In C (`opaquePointers`), an object pointer to a type
|
|
2096
|
+
* this cannot spell — `struct S *` — is kept too: the printer casts its argument through `void *`,
|
|
2097
|
+
* which C converts to any object pointer implicitly, where a strict front end (mwcc) refuses
|
|
2098
|
+
* `use2(s)` for a `u32 s[2]` the static's bytes were typed as. Any other entry this cannot spell is
|
|
2099
|
+
* `undefined`, and that argument is printed uncast. */
|
|
2100
|
+
function declaredArgTypes(
|
|
2101
|
+
prototypes: Prototypes,
|
|
2102
|
+
opaquePointers: boolean,
|
|
2103
|
+
): Record<string, readonly (ParamType | undefined)[]> {
|
|
1399
2104
|
const out: Record<string, readonly (ParamType | undefined)[]> = {};
|
|
1400
2105
|
for (const [name, p] of Object.entries(prototypes)) {
|
|
1401
2106
|
if (Array.isArray(p.params)) {
|
|
1402
|
-
out[name] = p.params.map((t) =>
|
|
2107
|
+
out[name] = p.params.map((t) =>
|
|
2108
|
+
(spellableType(t) && declaredWidth(t) !== undefined) || (opaquePointers && objectPointerType(t))
|
|
2109
|
+
? t
|
|
2110
|
+
: undefined,
|
|
2111
|
+
);
|
|
1403
2112
|
}
|
|
1404
2113
|
}
|
|
1405
2114
|
return out;
|
|
1406
2115
|
}
|
|
1407
2116
|
|
|
2117
|
+
/** A pointer to an object, a pointer to a function excluded: `void *` converts to it implicitly in
|
|
2118
|
+
* C, which it does not to a function pointer. */
|
|
2119
|
+
const objectPointerType = (t: ParamType): boolean => /\*\s*(?:const|volatile|\s)*$/.test(t) && !t.includes('(');
|
|
2120
|
+
|
|
1408
2121
|
/** Each declared callee's return type as the project spells it, which is never printed: whether a
|
|
1409
2122
|
* pointer cell compared with the call meets a pointer (l3/ptrcell.ts). */
|
|
1410
2123
|
function declaredReturnTypes(prototypes: Prototypes): Record<string, ParamType> {
|