@asmlift/core 0.8.0 → 0.9.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 +34 -30
- package/package.json +1 -1
- package/src/aggregate.ts +248 -0
- package/src/backend/c.ts +5 -2
- package/src/backend/cfamily.ts +236 -37
- package/src/backend/cpp.ts +114 -9
- package/src/backend/pascal.ts +39 -0
- package/src/cdecl/constant.ts +59 -0
- package/src/cdecl/lex.ts +209 -0
- package/src/cdecl/parse.ts +975 -0
- package/src/cdecl/spell.ts +69 -0
- package/src/codegen-flags.ts +38 -6
- package/src/compiler-diagnostics.ts +194 -0
- package/src/contracts.ts +615 -79
- package/src/declare.ts +75 -5
- package/src/frontend/asmdata.ts +19 -5
- package/src/frontend/call-plan.ts +686 -0
- package/src/frontend/device-pins.ts +277 -0
- package/src/frontend/errors.ts +5 -0
- package/src/frontend/flags-edge.ts +101 -0
- package/src/frontend/fpu.ts +167 -0
- package/src/frontend/frame-objects.ts +2131 -0
- package/src/frontend/high-half.ts +2 -1
- package/src/frontend/local-object.ts +490 -0
- package/src/frontend/mips.ts +363 -40
- package/src/frontend/opaque.ts +97 -18
- package/src/frontend/ppc.ts +520 -129
- package/src/frontend/reloc-symbol.ts +94 -13
- package/src/frontend/splat.ts +108 -5
- package/src/frontend/ssa.ts +669 -76
- package/src/frontend/stackargs.ts +120 -39
- package/src/frontend/thumb.ts +2764 -2018
- package/src/ir/alias.ts +29 -0
- package/src/ir/core.ts +101 -3
- package/src/ir/discipline.ts +248 -0
- package/src/ir/float-bits.ts +47 -0
- package/src/ir/opcodes.ts +84 -112
- package/src/ir/parse.ts +1 -0
- package/src/ir/types.ts +118 -2
- package/src/ir/verify.ts +150 -3
- package/src/l3/address.ts +5 -0
- package/src/l3/argcopy.ts +38 -6
- package/src/l3/ast.ts +186 -14
- package/src/l3/coalesce.ts +21 -13
- package/src/l3/dce.ts +32 -10
- package/src/l3/gates.ts +9 -3
- package/src/l3/hoist.ts +8 -7
- package/src/l3/initfirst.ts +27 -9
- package/src/l3/intcell.ts +97 -0
- package/src/l3/mentions.ts +10 -4
- package/src/l3/narrowdecl.ts +218 -0
- package/src/l3/nearbase.ts +10 -5
- package/src/l3/parkfirst.ts +8 -5
- package/src/l3/pollguard.ts +3 -2
- package/src/l3/ptrcell.ts +357 -0
- package/src/l3/regspell.ts +17 -3
- package/src/l3/reindex.ts +10 -7
- package/src/l3/symbol-refs.ts +104 -23
- package/src/l3/tailmerge.ts +6 -19
- package/src/l3/typing.ts +137 -25
- package/src/l3/unreduce.ts +4 -4
- package/src/l3/volstore.ts +96 -7
- package/src/mangle.ts +11 -1
- package/src/pattern/engine.ts +19 -16
- package/src/pipeline.ts +57 -12
- package/src/proto-context.ts +788 -0
- package/src/proto.ts +689 -50
- package/src/raise/arrays.ts +4 -3
- package/src/raise/declared-volatile.ts +131 -0
- package/src/raise/divpow2.ts +2 -2
- package/src/raise/errors.ts +17 -0
- package/src/raise/floathelpers.ts +243 -0
- package/src/raise/globalshape.ts +11 -5
- package/src/raise/gvn.ts +26 -5
- package/src/raise/narrow.ts +29 -2
- package/src/raise/narrowlocal.ts +11 -6
- package/src/raise/offsetnames.ts +510 -0
- package/src/raise/pairparams.ts +81 -0
- package/src/raise/paramwidth.ts +7 -0
- package/src/raise/pre-recovery.ts +78 -4
- package/src/raise/recover.ts +14 -10
- package/src/raise/retsink.ts +1 -1
- package/src/raise/shortcircuit.ts +41 -19
- package/src/raise/softdiv.ts +21 -23
- package/src/raise/struct-arrays.ts +5 -4
- package/src/raise/structs.ts +311 -98
- package/src/raise/truncload.ts +366 -0
- package/src/raise/widehelpers.ts +106 -0
- package/src/rank-declare.ts +30 -5
- package/src/rank-variations.ts +36 -8
- package/src/rank.ts +276 -80
- package/src/runtime-helpers.ts +287 -0
- package/src/stillborn.ts +289 -0
- package/src/structure/analysis.ts +1285 -448
- package/src/structure/hazards.ts +633 -58
- package/src/structure/local-statics.ts +381 -0
- package/src/structure/namecoalesce.ts +1 -1
- package/src/structure/redundant-test.ts +99 -0
- package/src/structure/structure.ts +1772 -395
- package/src/structure/switch-recover.ts +159 -78
- package/src/symbols.ts +38 -3
- package/src/target.ts +662 -59
- package/src/trace.ts +44 -24
- package/src/variation-definitions.ts +119 -1
- package/src/variation-tokens.ts +20 -6
package/src/target.ts
CHANGED
|
@@ -13,27 +13,47 @@
|
|
|
13
13
|
// • capabilities.endianness → structureOptionsFor (`littleEndian`), gating LSB-first
|
|
14
14
|
// bitfield-extract recognition in the structurer.
|
|
15
15
|
// • capabilities.flags → RESERVED, not yet read by any pass (PPC condition regs will).
|
|
16
|
-
// • capabilities.readOnlyAddressSinks → the
|
|
16
|
+
// • capabilities.readOnlyAddressSinks → the frame-object audit: a frame address stored to
|
|
17
17
|
// one of these reached a device that only reads through it, so it does not retract `undef`.
|
|
18
|
-
// • capabilities.
|
|
18
|
+
// • capabilities.readSourceControl → the same audit: which frame bytes beside the object that
|
|
19
|
+
// device may read, so a slot it cannot reach is not refused.
|
|
20
|
+
// • capabilities.blockTransferCalls → the same audit: a frame address handed to one of these
|
|
21
|
+
// as its SOURCE is read as far as the call's literal control word says, and never written.
|
|
22
|
+
// • capabilities.deviceRegisters → six readers, and they ask ONE question — "would a source
|
|
19
23
|
// have spelled this address `volatile`" — which is a question about SPELLING and may be
|
|
20
24
|
// approximate: the `/vol-store` variation's eligibility (l3/volstore.ts), rank.ts's volatility
|
|
21
25
|
// tie-break between two byte-identical spellings, the first half of `/unreduce`'s
|
|
22
26
|
// disjointness gate (l3/unreduce.ts), the `/homesplit` pairing's refusal to leave a device
|
|
23
|
-
// READ inline where the spelling it replaces would have qualified it (l3/homesplit.ts),
|
|
24
|
-
//
|
|
25
|
-
// reach (structure.ts `volatileQualifiable`, threaded through StructureOptions)
|
|
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
|
|
30
|
+
// device pin (frontend/device-pins.ts), which spells every access in the window `volatile`
|
|
31
|
+
// in a function the frame-object audit keeps as one object, and in one it accepts object by
|
|
32
|
+
// object every device read and the device stores a later store in their block overwrites —
|
|
33
|
+
// under either policy save a read the compiler could not have made of a `volatile`
|
|
34
|
+
// (`volatileReadsExtendInRegister`).
|
|
35
|
+
// That last reader makes the answer a correctness one — agbcc deletes or hoists a plain
|
|
36
|
+
// device access the machine made — so it may be approximate in ONE direction only: the
|
|
37
|
+
// window must cover every register a source reaches, and covering more costs a spelling,
|
|
38
|
+
// never an access.
|
|
26
39
|
// • capabilities.deviceMemoryWriters → the MEMORY-MODEL question, which is a different one and
|
|
27
40
|
// may NOT be approximate: "can a write to this register make the DEVICE write ordinary
|
|
28
41
|
// memory". One reader — `/unreduce`'s second half. Split from `deviceRegisters` because
|
|
29
42
|
// conflating them recorded a false premise (see the field's own comment).
|
|
30
43
|
// • compilerBehaviors.* → mostly consumed by the structurer (threaded via StructureOptions).
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
// `reloadsLocalReread` (raise/pre-recovery.ts),
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
44
|
+
// Others are read off the target directly by a consumer that is not the structurer — among
|
|
45
|
+
// them `nearBaseSpan` and `foldsConstAddrOffset` (rank.ts, L3 respell variations),
|
|
46
|
+
// `reloadsLocalReread`, `narrowParamWitness` and `aggregateBoundary` (raise/pre-recovery.ts),
|
|
47
|
+
// `aggregateReturn`, `largestAlignment`, `enumBytes` and `bitfieldPacking` (aggregate.ts, for
|
|
48
|
+
// frontend/thumb.ts, frontend/ppc.ts and frontend/frame-objects.ts),
|
|
49
|
+
// `hoistsSingleSetArm` (raise/narrowlocal.ts and raise/retsink.ts), `arrayShapeFromStride`
|
|
50
|
+
// (raise/globalshape.ts, run on the LIFTED fn), `eightByteReturnScratch`
|
|
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), `roundTripsDoubleLiterals`
|
|
53
|
+
// (raise/floathelpers.ts) and `volatileReadsExtendInRegister` (frontend/device-pins.ts, and
|
|
54
|
+
// raise/declared-volatile.ts through liftStamped and the offset-name pass). The
|
|
55
|
+
// field names are a SUPERSET of
|
|
56
|
+
// StructureOptions' — see `structureOptionsFor`.
|
|
37
57
|
//
|
|
38
58
|
// `capabilities` (HARDWARE facts) vs `compilerBehaviors` (COMPILER canonicalization decisions) are
|
|
39
59
|
// deliberately separate bags: a new compiler must set its behaviors EXPLICITLY instead of
|
|
@@ -42,22 +62,151 @@
|
|
|
42
62
|
// This module is browser-pure by contract (no Node APIs, enforced by
|
|
43
63
|
// test/browser-safe.test.ts): the toolchain paths that COMPILE for these targets
|
|
44
64
|
// live in @asmlift/toolchains.
|
|
45
|
-
import { type CodegenProfile, type FlagFamily, parseFlags } from './codegen-flags';
|
|
65
|
+
import { type CodegenProfile, type FlagFamily, dialectOf, parseFlags } from './codegen-flags';
|
|
66
|
+
import type { Op } from './ir/core';
|
|
67
|
+
import { PRELUDE_TYPEDEFS, type ParamType, type Prototypes, declaredWidth, spellableType } from './proto';
|
|
68
|
+
import {
|
|
69
|
+
AGBCC_RUNTIME_HELPERS,
|
|
70
|
+
IDO_RUNTIME_HELPERS,
|
|
71
|
+
MIPS_GCC_RUNTIME_HELPERS,
|
|
72
|
+
PPC_MWCC_RUNTIME_HELPERS,
|
|
73
|
+
type RuntimeHelper,
|
|
74
|
+
} from './runtime-helpers';
|
|
75
|
+
import type { StaticLayout } from './structure/local-statics';
|
|
46
76
|
import type { StructureOptions } from './structure/structure';
|
|
77
|
+
import type { SwitchBoundCase } from './structure/switch-recover';
|
|
47
78
|
|
|
48
79
|
/** What a compiler's OBJECT shows for a narrow declared parameter — see
|
|
49
80
|
* `compilerBehaviors.narrowParamWitness` for the compiled pair behind each value. */
|
|
50
81
|
export type NarrowParamWitness = 'prologue-extension' | 'home-store-and-in-place' | 'none';
|
|
51
82
|
|
|
83
|
+
/** What one transfer reads from its SOURCE, as its control bits say: the `unit` it reads in, and
|
|
84
|
+
* which way it `walk`s from the address — `fixed` re-reading one unit, `increment` upward for
|
|
85
|
+
* `bytes` when the control says how many and without end when it does not, `decrement` downward
|
|
86
|
+
* without end. Each transfer source decodes its own bit layout into this (`sourceControlRead`,
|
|
87
|
+
* `blockTransferRead`), and `sourceReach` alone turns it into frame bytes. */
|
|
88
|
+
export interface SourceRead {
|
|
89
|
+
unit: number;
|
|
90
|
+
walk: 'fixed' | 'increment' | 'decrement';
|
|
91
|
+
bytes?: number;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** The bytes a source read reaches from an address `off` bytes into the frame, as `[lo, hi)`
|
|
95
|
+
* relative to that address. The machine may force the address down to a unit boundary — the
|
|
96
|
+
* GBA's DMA and BIOS both do — so a 32-bit read of the halfword at [sp,#2] reads from [sp,#0],
|
|
97
|
+
* and the object below shares its unit. The frame base is at least unit-aligned, so the offset
|
|
98
|
+
* says how far down. */
|
|
99
|
+
export function sourceReach(read: SourceRead, off: number): { lo: number; hi: number } {
|
|
100
|
+
return {
|
|
101
|
+
lo: read.walk === 'decrement' ? -Infinity : 0 - (off % read.unit),
|
|
102
|
+
hi: read.walk === 'increment' ? (read.bytes ?? Infinity) : read.unit,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Could the source have made this read through a `volatile` lvalue, on a compiler that behaves as
|
|
107
|
+
* `behaviors` says? Not a sign-extending narrow load where the compiler extends a qualified narrow
|
|
108
|
+
* read in a register (`volatileReadsExtendInRegister`): the qualified read is another instruction
|
|
109
|
+
* sequence. The device pin (frontend/device-pins.ts) and the `declared` stamp
|
|
110
|
+
* (raise/declared-volatile.ts) both ask it, so the two placements read one load the same way. */
|
|
111
|
+
export function readCouldBeVolatile(
|
|
112
|
+
behaviors: Pick<TargetDescription['compilerBehaviors'], 'volatileReadsExtendInRegister'>,
|
|
113
|
+
op: Op,
|
|
114
|
+
): boolean {
|
|
115
|
+
const width = (op.opcode === 'aload' ? op.attrs.elemSize : op.attrs.width) as number;
|
|
116
|
+
return !(behaviors.volatileReadsExtendInRegister === true && op.attrs.signed === true && width < 4);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** A device channel's source control, read off the halfword at `sink + offset` of a
|
|
120
|
+
* `readOnlyAddressSinks` register: `modes`, indexed by `(control >> modeShift) & (modes.length -
|
|
121
|
+
* 1)`, is the way the source address steps per unit, null for a setting that bounds nothing; a
|
|
122
|
+
* unit is `units[1]` bytes when `wideBit` is set and `units[0]` when not. See `sourceControlRead`. */
|
|
123
|
+
export interface SourceControl {
|
|
124
|
+
offset: number;
|
|
125
|
+
modeShift: number;
|
|
126
|
+
modes: readonly ('increment' | 'decrement' | 'fixed' | null)[];
|
|
127
|
+
wideBit: number;
|
|
128
|
+
units: readonly [number, number];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** The source read one control halfword arms, or null for a mode that bounds nothing. The count
|
|
132
|
+
* lives in another register, so an incrementing read carries no `bytes`. */
|
|
133
|
+
export function sourceControlRead(control: SourceControl, half: number): SourceRead | null {
|
|
134
|
+
const unit = control.units[(half & control.wideBit) !== 0 ? 1 : 0];
|
|
135
|
+
const walk = control.modes[(half >> control.modeShift) & (control.modes.length - 1)];
|
|
136
|
+
return walk === null || walk === undefined ? null : { unit, walk };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** How far a block-transfer call reads through its `source` argument, decoded from the argument
|
|
140
|
+
* at `control`: `control & countMask` units, of `units[1]` bytes when `wideBit` is set and
|
|
141
|
+
* `units[0]` when not, rounded up to a multiple of `countGranule` units — or ONE unit when
|
|
142
|
+
* `fixedBit` is set, since a fill reads its source once. See `blockTransferRead`. */
|
|
143
|
+
export interface BlockTransferCall {
|
|
144
|
+
source: number;
|
|
145
|
+
control: number;
|
|
146
|
+
countMask: number;
|
|
147
|
+
fixedBit: number;
|
|
148
|
+
wideBit: number;
|
|
149
|
+
units: readonly [number, number];
|
|
150
|
+
countGranule: number;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** The source read a block-transfer call makes for a literal control word. */
|
|
154
|
+
export function blockTransferRead(call: BlockTransferCall, control: number): SourceRead {
|
|
155
|
+
const unit = call.units[(control & call.wideBit) !== 0 ? 1 : 0];
|
|
156
|
+
if ((control & call.fixedBit) !== 0) {
|
|
157
|
+
return { unit, walk: 'fixed' };
|
|
158
|
+
}
|
|
159
|
+
const count = (control & call.countMask) >>> 0;
|
|
160
|
+
return { unit, walk: 'increment', bytes: Math.ceil(count / call.countGranule) * call.countGranule * unit };
|
|
161
|
+
}
|
|
162
|
+
|
|
52
163
|
export interface TargetDescription {
|
|
53
164
|
id: string; // the ISA — 'armv4t' / 'mips' / 'ppc'. Selects the frontend (registry.ts).
|
|
54
165
|
// The COMPILER is a first-class field distinct from the ISA (matching = deoptimize to a specific
|
|
55
166
|
// compiler): two targets can share an ISA (⇒ one frontend) yet differ here — e.g. MIPS_IDO vs
|
|
56
|
-
// MIPS_GCC. Consumed by pattern gating (patternApplies) and the report. (version/flags
|
|
57
|
-
//
|
|
167
|
+
// MIPS_GCC. Consumed by pattern gating (patternApplies) and the report. (version/flags are future
|
|
168
|
+
// fields, added when earned.)
|
|
58
169
|
compiler: string; // 'agbcc' / 'ido' / 'gcc' / 'mwcc'
|
|
170
|
+
/** `'c++'` when the build compiles its unit as C++ (mwcc `-lang=c++`/`ec++`), which is what
|
|
171
|
+
* `targetFor` reads off the flags; absent is C. A candidate is C-shaped text either way — only
|
|
172
|
+
* what the C++ front end refuses differs, and `structureOptionsFor` is where that is answered. */
|
|
173
|
+
dialect?: 'c++';
|
|
59
174
|
argRegs: string[];
|
|
60
175
|
returnReg: string;
|
|
176
|
+
/** The floating-point ABI: where a float argument and a float return travel, on a target whose
|
|
177
|
+
* frontend lifts hardware floating point (`docs/floating-point.md`). ABSENT ⇒ none is claimed, and
|
|
178
|
+
* a frontend refuses every read of its FPU file that no instruction of the function wrote.
|
|
179
|
+
*
|
|
180
|
+
* `argRegs` are the float argument registers in argument order, spelled as the frontend keys
|
|
181
|
+
* them; `returnReg` the float return. `slots` is how they count against `argRegs` above, and the
|
|
182
|
+
* two ABIs here differ in kind rather than in degree — both MEASURED by compiling (below):
|
|
183
|
+
* - `'leading'` (MIPS o32): argument k is in `argRegs[k]` only while arguments 0..k are ALL
|
|
184
|
+
* floating, and it still takes integer slot k — `float f(float a, int b)` reads `$f12` and
|
|
185
|
+
* `a1`, while `float f(int a, float b)` receives `b` in `a1` and moves it over with `mtc1`.
|
|
186
|
+
* - `'separate'` (PowerPC EABI): floating arguments count on their own and take no integer
|
|
187
|
+
* slot — `float g(int *p, float b)` and `float g(float b, int *p)` are one object, `r3` and
|
|
188
|
+
* `f1` either way. */
|
|
189
|
+
fpu?: { argRegs: readonly string[]; returnReg: string; slots: 'leading' | 'separate' };
|
|
190
|
+
/** A `double` crosses a call in GENERAL argument words: the two a 64-bit argument takes wherever
|
|
191
|
+
* this target's call placement puts them, HIGH word first, so the sign and exponent are in the
|
|
192
|
+
* first word. Its readers are `proto.ts` `declaredCallArgs` (the call's layout) and
|
|
193
|
+
* `raise/floathelpers.ts` (a literal's two words). ABSENT ⇒ a declared `double` parameter states
|
|
194
|
+
* no layout and the call takes the arg-register guess.
|
|
195
|
+
*
|
|
196
|
+
* IT IS AN ABI FACT BESIDE `fpu`, NOT INSTEAD OF IT. Where the words go is the frontend's 64-bit
|
|
197
|
+
* placement, which a `long long` shares; this states only that a double takes them, and their
|
|
198
|
+
* order, which a `long long` need not share. agbcc: FLOAT_WORDS_BIG_ENDIAN 1
|
|
199
|
+
* (gcc/config/arm/thumb.h:335) on a little-endian target, so the pair is not a `long long`'s
|
|
200
|
+
* naming of the same bits; FUNCTION_ARG (:632) places by word offset, FUNCTION_ARG_PARTIAL_NREGS
|
|
201
|
+
* (:636) splits a pair across r3 and the stack, FUNCTION_ARG_ADVANCE (:647) rounds to a word and
|
|
202
|
+
* PARM_BOUNDARY is 32 (:354).
|
|
203
|
+
*
|
|
204
|
+
* 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 (IDO 7.1). The MIPS targets do not state it
|
|
206
|
+
* because their frontend lays out no 64-bit argument (`CallLowering.pairs`). PowerPC EABI passes
|
|
207
|
+
* a double in a float register that takes no general word (`fpu.slots: 'separate'`), so it has
|
|
208
|
+
* none to state. */
|
|
209
|
+
doubleArgWords?: 'high-first';
|
|
61
210
|
/** Registers this ABI does NOT pass arguments in — half of what makes a def-less live-in read an
|
|
62
211
|
* uninitialised local rather than an argument. The other half is a measurement the FRONTEND
|
|
63
212
|
* owes (did this function save the register), and the rule that combines them is in
|
|
@@ -78,6 +227,35 @@ export interface TargetDescription {
|
|
|
78
227
|
* is classified with no evidence at all, which is the defect the save half exists to close. Every
|
|
79
228
|
* entry must appear in `nonArgRegs`; the frontend refuses a target where one does not. */
|
|
80
229
|
scratchRegs?: readonly string[];
|
|
230
|
+
/** Registers a CALL destroys: after one, they hold whatever the callee left there, and no value
|
|
231
|
+
* this function computed. The frontends hand this to the SSA builder so a read of one past a
|
|
232
|
+
* call refuses instead of resolving to the pre-call definition (frontend/ssa.ts).
|
|
233
|
+
*
|
|
234
|
+
* REQUIRED, unlike its two neighbours above, and the asymmetry is the point. An absent
|
|
235
|
+
* `nonArgRegs` claims no partition, which only makes classification stricter; an absent
|
|
236
|
+
* `scratchRegs` under-states a set whose own comment says under-stating is safe. An absent
|
|
237
|
+
* caller-saved list takes the UNSOUND direction — nothing refuses, so a destroyed register keeps
|
|
238
|
+
* resolving to a value the callee overwrote, silently and with exit 0. A channel whose omission
|
|
239
|
+
* is unsound is a required argument, so that forgetting it is a type error.
|
|
240
|
+
*
|
|
241
|
+
* `argRegs` must be a SUBSET of it — a register the caller passes arguments in is by
|
|
242
|
+
* construction one the callee may destroy — and `clobberedByCall` (frontend/ssa.ts) checks that
|
|
243
|
+
* rather than trusting it, the way `checkedLiveInModel` checks the register partition. */
|
|
244
|
+
callerSaved: readonly string[];
|
|
245
|
+
/** The COMPILER RUNTIME HELPERS this compiler's codegen calls, and what each computes
|
|
246
|
+
* (runtime-helpers.ts). A compiler fact, which is why it lives here and not in `proto.ts`: that
|
|
247
|
+
* file holds signatures fixed by the C STANDARD, and a helper name is fixed by a runtime
|
|
248
|
+
* library — agbcc calls `__muldi3`, CodeWarrior `__div2i`, IDO `__ll_mul`.
|
|
249
|
+
*
|
|
250
|
+
* ABSENT ⇒ no helper is recognised AND none is refused, so every such call stays an ordinary
|
|
251
|
+
* call with its arguments guessed and the backend spells it. That is the UNMEASURED direction,
|
|
252
|
+
* not the safe one: re-emitting a compiler's own runtime call is the one failure that MATCHES,
|
|
253
|
+
* because a compiler handed `__div2i(a, b)` emits the `bl __div2i` the row was lifted from
|
|
254
|
+
* (`raise/widehelpers.ts` states this at the refusal it exists to make). Absence is still the
|
|
255
|
+
* right default, because a name outside the table cannot be told from a project's own
|
|
256
|
+
* `__`-prefixed function by spelling — but it buys nothing on a target whose runtime has simply
|
|
257
|
+
* not been enumerated. */
|
|
258
|
+
runtimeHelpers?: Readonly<Record<string, RuntimeHelper>>;
|
|
81
259
|
// HARDWARE / ISA facts — independent of the compiler.
|
|
82
260
|
capabilities: {
|
|
83
261
|
endianness: 'little' | 'big'; // consumed by structureOptionsFor (bitfield extract recognition is LSB-first)
|
|
@@ -88,18 +266,26 @@ export interface TargetDescription {
|
|
|
88
266
|
// over as a transfer SOURCE, and two facts together are what make that safe to model: the
|
|
89
267
|
// device only ever reads from it, and the register is WRITE-ONLY, so nobody can read the
|
|
90
268
|
// address back out and turn it into a destination. The only code that can name the frame is
|
|
91
|
-
// therefore this function's own, which the
|
|
269
|
+
// therefore this function's own, which the frame-object audit walks.
|
|
92
270
|
//
|
|
93
271
|
// Hardware, so it belongs here — `endianness` above is a board fact rather than an ISA one too
|
|
94
272
|
// (ARMv4T is bi-endian). ABSENT ⇒ every escape is assumed to write, which is the safe
|
|
95
273
|
// direction and what every other target gets.
|
|
96
274
|
readOnlyAddressSinks?: readonly number[];
|
|
275
|
+
// HOW FAR a device reads from the address a `readOnlyAddressSinks` register was handed, read
|
|
276
|
+
// off the control halfword at `sink + offset` (`SourceControl`). A fixed source re-reads one
|
|
277
|
+
// unit — the unit-aligned one holding the address, since the device may drop its low bits — so
|
|
278
|
+
// the frame bytes outside that unit are provably not read.
|
|
279
|
+
// ABSENT ⇒ the read is unbounded in both directions, the safe direction.
|
|
280
|
+
readSourceControl?: SourceControl;
|
|
97
281
|
// The device-register window, `[start, end)`. A cell in it changes under the program's feet,
|
|
98
282
|
// so a source that touched one all but certainly declared it `volatile`. Its readers all ask
|
|
99
283
|
// the same SPELLING question — "would a source have written `volatile` here" — and the file
|
|
100
|
-
// header's ledger names them and what each does with the answer.
|
|
101
|
-
//
|
|
102
|
-
// enumerated and the differ referees.
|
|
284
|
+
// header's ledger names them and what each does with the answer. All but one leave the
|
|
285
|
+
// decision to the differ: which cells a source qualified is not derivable from the asm, so
|
|
286
|
+
// both spellings are enumerated and the differ referees. The device pin decides, because
|
|
287
|
+
// there the plain spelling recompiles to a different program; that is why the window has to
|
|
288
|
+
// cover every register. ABSENT ⇒ the variation declines everywhere and the tie-break
|
|
103
289
|
// has no preference, which is the neutral direction — outside a declared window the qualifier
|
|
104
290
|
// is a claim about ordinary memory that the target does not support.
|
|
105
291
|
//
|
|
@@ -123,6 +309,16 @@ export interface TargetDescription {
|
|
|
123
309
|
// ABSENT ⇒ the target claims nothing, and the one reader treats EVERY device write as a
|
|
124
310
|
// possible memory write — the conservative direction, and what every non-GBA target takes.
|
|
125
311
|
deviceMemoryWriters?: readonly (readonly [number, number])[];
|
|
312
|
+
// CALLS that only READ through one argument, as far as a control argument says — a platform's
|
|
313
|
+
// block-transfer services, bound to a name by the SDK's own stubs. The frame-object audit is
|
|
314
|
+
// the reader: a frame address handed to one as its `source`, with a literal control word,
|
|
315
|
+
// reaches the bytes `blockTransferRead` decodes and is not an address anything writes through.
|
|
316
|
+
// The destination needs no entry, since a frame address handed over anywhere else keeps
|
|
317
|
+
// escaping as one a callee may write through.
|
|
318
|
+
//
|
|
319
|
+
// ABSENT, or a callee it does not name ⇒ an address handed to a callee may be read and written
|
|
320
|
+
// without bound, the safe direction.
|
|
321
|
+
blockTransferCalls?: Readonly<Record<string, BlockTransferCall>>;
|
|
126
322
|
};
|
|
127
323
|
// COMPILER BEHAVIORS — the specific compiler's canonicalization decisions, distinct from
|
|
128
324
|
// hardware `capabilities`. Mostly consumed by the structurer (threaded through StructureOptions);
|
|
@@ -182,6 +378,14 @@ export interface TargetDescription {
|
|
|
182
378
|
// compiler behavior is to claim nothing. The evidence a future round needs is one run of
|
|
183
379
|
// `scripts/regen-select-spelling-probes.ts` retargeted at the compiler in question.
|
|
184
380
|
hoistsSingleSetArm?: boolean;
|
|
381
|
+
// THE REGISTER AN INTERWORKING EPILOGUE POPS THE RETURN ADDRESS INTO WHEN THE RETURN TYPE IS 5
|
|
382
|
+
// TO 8 BYTES, which is then a fact the epilogue states about the function's width. agbcc's
|
|
383
|
+
// `thumb_exit` (gcc/thumb.c) picks that register from the SIZE of the return mode, not from
|
|
384
|
+
// liveness — r0 for `void`, r1 up to 4 bytes, r2 up to 8 — so `pop {r2}; bx r2` ends a function
|
|
385
|
+
// returning a `long long`, a `double`, or an 8-byte aggregate (which DImode covers even when it is
|
|
386
|
+
// returned through memory). Read by `frontend/thumb.ts`'s `refuseWordReturns` directly, not by
|
|
387
|
+
// the structurer. Absent ⇒ the epilogue states nothing.
|
|
388
|
+
eightByteReturnScratch?: string;
|
|
185
389
|
// WHAT, IN THIS COMPILER'S OBJECT, WITNESSES A NARROW DECLARED PARAMETER — the fact
|
|
186
390
|
// raise/paramwidth.ts needs before it may retype `s32 a0` to `s8 a0`. Three answers, because
|
|
187
391
|
// the compilers measured give three, and the pass refuses wherever the object is silent. Each
|
|
@@ -256,35 +460,105 @@ export interface TargetDescription {
|
|
|
256
460
|
// argument INDISTINGUISHABLE by code alone from a dead local: the words sit inside this
|
|
257
461
|
// frame's reservation and nothing this function does ever reloads one.
|
|
258
462
|
//
|
|
259
|
-
//
|
|
260
|
-
//
|
|
261
|
-
//
|
|
262
|
-
//
|
|
263
|
-
//
|
|
264
|
-
// Set on agbcc, where the layout was read off `gcc/config/arm/thumb.h` and then measured —
|
|
265
|
-
//
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
-
//
|
|
269
|
-
//
|
|
270
|
-
// than changing a verdict — which is the point: a second armv4t compiler must state it.
|
|
463
|
+
// It says WHERE the words go and nothing about the rest of the frame. Its one reader is the
|
|
464
|
+
// Thumb frontend's stack-argument licence (`frontend/thumb.ts` `declaredCall`): a declared call
|
|
465
|
+
// consumes the words it stages. Absent ⇒ no outgoing area is claimed, and a `[sp,#k]` store
|
|
466
|
+
// reaching a call unread declines.
|
|
467
|
+
//
|
|
468
|
+
// Set on agbcc, where the layout was read off `gcc/config/arm/thumb.h` and then measured — the
|
|
469
|
+
// corpus's `stkarg` and `stkwide` rows and kleod's `sub_0804C300` all stage their words at
|
|
470
|
+
// [sp,#0] upward inside the prologue's own reservation. NOT set anywhere
|
|
471
|
+
// else: a push-based caller would stage nothing inside the frame, and `docs/level-tower.md`'s
|
|
472
|
+
// rule for an unmeasured compiler behavior is to claim nothing. No other frontend calls the
|
|
473
|
+
// analysis today, so a second armv4t compiler must state this premise rather than inherit it.
|
|
271
474
|
stagesOutgoingArgsInFrame?: boolean;
|
|
272
|
-
//
|
|
273
|
-
//
|
|
274
|
-
//
|
|
275
|
-
//
|
|
276
|
-
//
|
|
277
|
-
//
|
|
278
|
-
//
|
|
279
|
-
//
|
|
280
|
-
//
|
|
281
|
-
// the
|
|
282
|
-
//
|
|
283
|
-
//
|
|
284
|
-
//
|
|
285
|
-
//
|
|
286
|
-
//
|
|
287
|
-
|
|
475
|
+
// The outgoing area is ONE region at the frame bottom, sized for the widest call and shared by
|
|
476
|
+
// every call, with every local and spill above it, and the caller never reads a word of it
|
|
477
|
+
// back after a call — the callee may assign to its stack parameters, so the caller re-stages
|
|
478
|
+
// an argument before every call. A compiler can stage in its frame
|
|
479
|
+
// (`stagesOutgoingArgsInFrame`) and still break this: mwcc may put a local in the words its
|
|
480
|
+
// outgoing parameters use (`frontend/ppc.ts`, "A GUESS THAT FILLS EVERY ARGUMENT REGISTER").
|
|
481
|
+
//
|
|
482
|
+
// It is a UNIVERSAL — for every source, the compiler emits no such read — and its one reader
|
|
483
|
+
// applies the contrapositive (`frontend/stackargs.ts` `survivorBound`): a word loaded after a
|
|
484
|
+
// call before any re-store is not in the area, so it is a local, and so is every word above it,
|
|
485
|
+
// which lets a spill live across a call lift, and a call NO declaration covers
|
|
486
|
+
// (`docs/level-tower.md`, THE CONTRAPOSITIVE OF A UNIVERSAL IS NOT A BACKWARDS READING). It is
|
|
487
|
+
// 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
|
+
// argument silently. The premise is this compiler's, not the ISA's.
|
|
490
|
+
// Absent ⇒ every staged word stays a candidate argument.
|
|
491
|
+
//
|
|
492
|
+
// Set on agbcc: thumb.h puts the locals at sp + outgoing_args_size (ACCUMULATE_OUTGOING_ARGS)
|
|
493
|
+
// and `calls.c` sizes the area as the maximum over all calls; compiled, `spill10` and
|
|
494
|
+
// `spillarg` keep their spills above the area, and `test/corpus/agbcc-restage.s` holds the
|
|
495
|
+
// re-staging and the callee's write. NOT set anywhere else, for the reason above.
|
|
496
|
+
localsAboveOutgoingArea?: boolean;
|
|
497
|
+
// A frame of EXACTLY ONE reserved word whose base escapes this function holds that object and
|
|
498
|
+
// nothing else. It is the layout premise the Thumb frontend's address-taken-local capability
|
|
499
|
+
// rests on (`frontend/thumb.ts`, `capturedObjectIsTheWholeFrame`): a bare `mov rD, sp` names
|
|
500
|
+
// frame offset 0, and the question this answers is whether the rest of that reservation could
|
|
501
|
+
// be somebody else's — an outgoing argument staged at [sp,#0], a by-value struct argument's
|
|
502
|
+
// block-copy destination, or a struct return's hidden pointer.
|
|
503
|
+
//
|
|
504
|
+
// Absent ⇒ no such claim, and every `[sp,#k]` store reaching a call unread keeps the refusal
|
|
505
|
+
// it had before the capability existed. It is a layout fact about ONE compiler and it is not
|
|
506
|
+
// derivable from the architecture, which is why it is a field and not an `arch ==` branch
|
|
507
|
+
// (`docs/level-tower.md`).
|
|
508
|
+
//
|
|
509
|
+
// Set on agbcc, where it is two compiled producer tables rather than a reading — one per
|
|
510
|
+
// escape, both written out at the predicate: at one word agbcc stages no outgoing argument
|
|
511
|
+
// under a captured local (it moves the local above the area and spells its address `add rD,
|
|
512
|
+
// sp, #4`), and it names a block-copy base with a register only from two words up. The one
|
|
513
|
+
// producer a one-word frame does NOT exclude is a <=4-byte non-integer-like struct return,
|
|
514
|
+
// which the post-lift audit settles per call rather than by size.
|
|
515
|
+
oneWordFrameIsTheCapturedObject?: boolean;
|
|
516
|
+
// How this compiler calls through a register: `bl <prefix><reg>` enters a runtime thunk that
|
|
517
|
+
// branches to the address in <reg> with every register as the caller left it, so the call is
|
|
518
|
+
// an indirect call through <reg> and the thunk is no function a source names. `regs` are the
|
|
519
|
+
// thunks whose register can hold an address a source computed; a `<prefix>` callee outside them
|
|
520
|
+
// declines.
|
|
521
|
+
//
|
|
522
|
+
// ABSENT ⇒ a `bl` names a function on this target, whatever it is called.
|
|
523
|
+
callThunks?: { readonly prefix: string; readonly regs: readonly string[] };
|
|
524
|
+
// Regime-A switch recovery: accept a RELATIONAL test as a case where the scrutinee values that
|
|
525
|
+
// can reach it — narrowed by the tests above it — leave exactly one on a side this compiler's
|
|
526
|
+
// dispatch lands a case body on: `'taken'` for its BRANCH only, `'either'` for both sides
|
|
527
|
+
// (structure/switch-recover.ts, the note at `Ranges`).
|
|
528
|
+
//
|
|
529
|
+
// One mechanism on both compilers that declare it: a binary-search dispatch stops testing a
|
|
530
|
+
// value once the tests above have narrowed it to one. agbcc's `emit_case_nodes` jumps straight to
|
|
531
|
+
// `node->left->code_label` on LT once `node_is_bounded (node->left)` holds, so
|
|
532
|
+
// `switch (x) { case 1: … case 2: … case 3: … case 4: … case 5: … case 1000: … case 2000: … }`
|
|
533
|
+
// pins `case 3` with `cmp r1, #2; bgt` under `x < 4`, `x != 2` (`corpus/agbcc-swpathbound.s`),
|
|
534
|
+
// and an unsigned switch's `case 0` with `cmp r0, #1; bcc`, which pins it at the end of the
|
|
535
|
+
// domain whatever came first. mwcc's `switch (x) { case 0: … case 1: … default: … }` is
|
|
536
|
+
// `cmpwi r3,1; beq- case1; bge- default; cmpwi r3,0; bge- case0; b default`, where `x >= 0` is
|
|
537
|
+
// `case 0` only because `x != 1` and `x < 1` came first (`corpus/mwcc-swdispatch.asm`).
|
|
538
|
+
//
|
|
539
|
+
// THE SIDES DIFFER, and each is read off its own compiler. Every jump in `emit_case_nodes` that
|
|
540
|
+
// lands on a case body is its test's BRANCH, while the fall-through always continues into more
|
|
541
|
+
// dispatch, and none of 3176 generated agbcc dispatches lands a body on a fall side — so agbcc
|
|
542
|
+
// is `'taken'`. mwcc lands one on either: `synthetic:sw_ret:mwcc_242_81` pins `case 3` on the
|
|
543
|
+
// FALL side (`cmpwi r3,4; bge- default; b case3` under `x > 2`, `x != 2`), so mwcc is
|
|
544
|
+
// `'either'`.
|
|
545
|
+
//
|
|
546
|
+
// A DEFAULT rather than a candidate variation because each declaring compiler's other producer
|
|
547
|
+
// of the same test is told apart by its LAYOUT. An if/else-if ladder written with relational
|
|
548
|
+
// tests pins values by its path just the same (`if (x >= 4) { if (x < 5) … } else if (x >= 3)
|
|
549
|
+
// …` pins 4 and 3) but puts a body between two tests, where the dispatch puts every test above
|
|
550
|
+
// every body — so this reading needs `switchRequiresFrontLoadedTests` beside it, and both
|
|
551
|
+
// declaring compilers declare that too (`corpus/mwcc-sw{dispatch,ladder,relladder,relnest}.asm`
|
|
552
|
+
// from `corpus/probe-mwcc-sw*.c`, regenerated by `scripts/regen-switch-spelling-probes.ts`). On
|
|
553
|
+
// agbcc the unsigned endpoint has no ladder at all: -O2 fold-const rewrites `x < 1u` into
|
|
554
|
+
// `cmp r0, #0 / bne` before codegen. All three builds PPC_MWCC serves emit the dispatch
|
|
555
|
+
// identically at -O4,p and at -O0,p (`packages/cli/test/matching/ppc-compiler-behaviors.test.ts`).
|
|
556
|
+
//
|
|
557
|
+
// Absent ⇒ every relational test navigates, and inheriting it would be wrong rather than merely
|
|
558
|
+
// unmeasured: on the MIPS lanes `sltiu rd, rs, 1` is the ordinary spelling of `!x`, and it lifts
|
|
559
|
+
// to `icmp_ult rs, 1` with no equality fold anywhere — the identical IR shape, from a producer
|
|
560
|
+
// that is not a dispatch. Each compiler opts in on its own dispatch's evidence.
|
|
561
|
+
switchBoundCase?: SwitchBoundCase;
|
|
288
562
|
// Switch recovery: emit the case arms in the order the ASSEMBLY lays their bodies out, rather
|
|
289
563
|
// than sorted by ascending case value. True claims the compiler emits case bodies as it walks
|
|
290
564
|
// the arms and never MOVES one afterwards — neither reordering basic blocks nor scheduling
|
|
@@ -294,8 +568,14 @@ export interface TargetDescription {
|
|
|
294
568
|
// neither sched.c nor reorg.c. SCOPE — SRCS does compile jump.c, whose cross-jump merges two
|
|
295
569
|
// identical arm bodies into ONE block, so a merged pair's own order is gone from the asm; that
|
|
296
570
|
// surfaces as two case values sharing a body, which switch-recover.ts ties by ascending value.
|
|
297
|
-
// Absent ⇒ ascending case value, where ido/kmc-gcc/mwcc sit
|
|
298
|
-
// been put through that evidence.
|
|
571
|
+
// Absent ⇒ ascending case value, where ido/kmc-gcc/mwcc sit. ido and kmc-gcc have a scheduler
|
|
572
|
+
// and have not been put through that evidence. mwcc_242_81 HAS been, on two compiled pairs, and
|
|
573
|
+
// lays the bodies in source order: `synthetic:sw_defmid` with `default:` between two cases, and
|
|
574
|
+
// a three-case switch whose first case is a `beqlr`. What keeps it from declaring the reading is
|
|
575
|
+
// its FRONTEND: frontend/ppc.ts appends every conditional-return block after all the real ones,
|
|
576
|
+
// so such an arm sorts last whatever its address (switch-recover.ts, at `layoutIndex`). Placing
|
|
577
|
+
// those blocks at their branch's address is the prerequisite, and a declaration after it owes
|
|
578
|
+
// its own pairs. A compiler opts in on its own, never by inheriting.
|
|
299
579
|
switchArmsFollowLayout?: boolean;
|
|
300
580
|
// Switch recovery: DECLINE a comparison tree whose own layout INTERLEAVES a test block with a
|
|
301
581
|
// case body, on the reading that the source wrote an if/else-if LADDER there. True claims the
|
|
@@ -321,8 +601,12 @@ export interface TargetDescription {
|
|
|
321
601
|
// `corpus/probe-agbcc-sw{frontload,ladder}.c`, regenerated by
|
|
322
602
|
// `scripts/regen-switch-spelling-probes.ts`, asserted in switch-arms.test.ts.
|
|
323
603
|
//
|
|
324
|
-
//
|
|
325
|
-
//
|
|
604
|
+
// mwcc declares it on its own objects: its `switch` dispatch puts every test above every body,
|
|
605
|
+
// and a ladder written with relational tests puts a body between two of them
|
|
606
|
+
// (`corpus/mwcc-sw{dispatch,relladder,relnest}.asm`, the note at `switchBoundCase`).
|
|
607
|
+
//
|
|
608
|
+
// Absent ⇒ every recoverable tree is still spelled `switch`, which is where ido sits: it has a
|
|
609
|
+
// scheduler that may move a body above a test, and it has not been put through the pair.
|
|
326
610
|
// A compiler opts in on its own compiled evidence, never by inheriting — and where one
|
|
327
611
|
// description serves two toolchains (`MIPS_GCC`), each toolchain owes its own pair, because the
|
|
328
612
|
// field cannot distinguish them (the KEYED BY DESCRIPTION note at `spillSlotOrder`).
|
|
@@ -357,6 +641,19 @@ export interface TargetDescription {
|
|
|
357
641
|
// the plain variation ships, which is the conservative reading for a compiler whose pair nobody has
|
|
358
642
|
// compiled — a compiler opts in on its own evidence and never by inheriting.
|
|
359
643
|
foldsPointerAdvance?: boolean;
|
|
644
|
+
// Does this compiler build a word global read through a pointer conversion, or stored a pointer
|
|
645
|
+
// it converts implicitly, into the object of the bare integer spelling? agbcc does: under
|
|
646
|
+
// `extern u32 g;` the pointer spelling of a cell, `g = (void *)((u8 *)g + 4); … (gLimit ^ (u32)g)`,
|
|
647
|
+
// and the integer one, `g = g + 4; … (gLimit ^ g)`, are one object (the pair
|
|
648
|
+
// `TARGET_BEHAVIOR_READINGS` compiles), the implicit conversion costing a warning. True ⇒ the
|
|
649
|
+
// `int-cell` registry entry's target gate withholds that variation, which can only tie the
|
|
650
|
+
// default's bytes here. It does not tie the warning: under -Werror the pointer spelling does not
|
|
651
|
+
// build against the integer declaration and the integer one does, which no gate sees, since the
|
|
652
|
+
// witness and the bench compile without -Werror. IDO builds the pair into two objects and
|
|
653
|
+
// CodeWarrior rejects the pointer store, so both are offered. KMC gcc builds one object too and
|
|
654
|
+
// does not declare it: the matching suite compiles each witness on one compiler. Absent ⇒
|
|
655
|
+
// offered.
|
|
656
|
+
pointerIntConversionIsFree?: boolean;
|
|
360
657
|
// Does this compiler EMIT a memory read in the block the source SPELLED it in? One direction
|
|
361
658
|
// only: the def-block placement rule (StructureOptions.readsStayWhereWritten) re-spells a read
|
|
362
659
|
// at the block the asm performed it in, which reproduces the asm iff nothing sinks a spelled
|
|
@@ -395,6 +692,98 @@ export interface TargetDescription {
|
|
|
395
692
|
// three descriptions that measured false (four compilers) set it anyway, so absent means
|
|
396
693
|
// UNMEASURED rather than "no".
|
|
397
694
|
reloadsLocalReread?: boolean;
|
|
695
|
+
// A LAYOUT fact rather than a canonicalization one, kept here because it is the COMPILER's:
|
|
696
|
+
// the size, in bytes, it aligns and rounds EVERY struct and union to, whatever its members.
|
|
697
|
+
// agbcc: 4 — `sizeof(struct { u16 h; })` is 4, and `struct { u8 a; union { u16 h; u8 b[2]; } u; }`
|
|
698
|
+
// seats `u` at 4 (gcc 2.9's arm STRUCTURE_SIZE_BOUNDARY). ido7.1, gcc2.7.2kmc and mwcc_242_81
|
|
699
|
+
// answer 2 and 2: the natural layout, 1. Compiled as `return sizeof …` / `return (int)&((T *)0)->m`
|
|
700
|
+
// at each row's flags; gcc2.7.2 (-O1), mwcc_233_163n and mwcc_247_107 answer the same as their
|
|
701
|
+
// descriptions' measured compilers. Read by raise/structs.ts (through raise/pre-recovery.ts),
|
|
702
|
+
// which sizes a recovered UNION member with it; raise/struct-arrays.ts sizes element structs
|
|
703
|
+
// without it, which on agbcc is a known gap.
|
|
704
|
+
//
|
|
705
|
+
// ABSENT ⇒ unmeasured, and the union recovery declines every union narrower than a word: no
|
|
706
|
+
// value is safe to assume, since one too small mislays the fields after it on agbcc and one too
|
|
707
|
+
// large drops the pad in front of them everywhere else.
|
|
708
|
+
aggregateBoundary?: number;
|
|
709
|
+
// The largest alignment any member of a struct takes, in bytes (BIGGEST_ALIGNMENT): agbcc 4,
|
|
710
|
+
// thumb.h:358, so a `long long` member sits at a word. With `aggregateBoundary` it is what sizes
|
|
711
|
+
// a declared aggregate (`aggregate.ts`). ABSENT ⇒ unmeasured, and no aggregate is sized.
|
|
712
|
+
largestAlignment?: number;
|
|
713
|
+
// HOW A STRUCT OR UNION RETURNED BY VALUE COMES BACK, which decides whether a call to a function
|
|
714
|
+
// 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 frontend/thumb.ts and
|
|
716
|
+
// frontend/ppc.ts).
|
|
717
|
+
// • 'apcs' — in memory when bigger than a word, when a struct has a second member that is not
|
|
718
|
+
// a bitfield, or when a union has a member that would be; in the return register otherwise.
|
|
719
|
+
// agbcc, thumb.c:1423-1493 (compiled: `{u8 a,b,c,d}` through memory; `{u32}`, `{u32 w[1]}`,
|
|
720
|
+
// `{u32 a:8; u32 b:8;}` and `union {u32; u16;}` in r0).
|
|
721
|
+
// • 'svr4' — in r3, or r3:r4, when it is 8 bytes or less, whatever its members (a float's
|
|
722
|
+
// included); in memory otherwise. mwcc_242_81, mwcc_233_163n and mwcc_247_107 alike
|
|
723
|
+
// (compiled: 1 to 8 bytes, `{float}`, `{double}` and a union come back in r3/r3:r4; a 9- and a
|
|
724
|
+
// 12-byte struct are handed `addi r3,…` ahead of the call).
|
|
725
|
+
// ABSENT ⇒ unmeasured, and such a call declines. IDO 7.1 returns every aggregate through memory,
|
|
726
|
+
// a one-word one included (compiled: `struct {u32 x;} mkw(s32)` is called `addiu a0,sp,28 / jal
|
|
727
|
+
// mkw / lw v0,28(sp)`), and states nothing: the MIPS frontend lowers no call to read it.
|
|
728
|
+
aggregateReturn?: 'apcs' | 'svr4';
|
|
729
|
+
// The bytes an enum takes as a struct member (`aggregate.ts`). agbcc: 4 — flag_short_enums is 0
|
|
730
|
+
// unless `-fshort-enums` is given (toplev.c:3552-3554 with no DEFAULT_SHORT_ENUMS), so an enum
|
|
731
|
+
// whose values fit an int is an int (c-decl.c:6123-6135); compiled, `sizeof(enum {K0, K1})`
|
|
732
|
+
// and `enum {B0 = 300}` are both 4. `targetFor` drops it under `-fshort-enums`, and an enum
|
|
733
|
+
// declared with an attribute (`packed`), or with a value an int cannot hold, is not sized
|
|
734
|
+
// (proto-context.ts). ABSENT ⇒ unmeasured,
|
|
735
|
+
// and an aggregate with an enum member is not sized.
|
|
736
|
+
enumBytes?: number;
|
|
737
|
+
// How a struct's bitfields are placed (`aggregate.ts`). 'contiguous' — each at the bit after the
|
|
738
|
+
// one before, straddling a byte or a word of its declared type, with the next member that is not
|
|
739
|
+
// a bitfield at the first byte past it that its alignment allows. agbcc: thumb.h defines no
|
|
740
|
+
// PCC_BITFIELD_TYPE_MATTERS, so stor-layout.c:404-405 lays a bitfield at bit alignment and
|
|
741
|
+
// skips the no-straddle rule of :462-481 (compiled: `{u32 a:20; u32 b:20; u8 c;}` puts `c` at
|
|
742
|
+
// 5, `{u32 a:31; u32 b:31; u32 c:2;}` is 8 bytes, `{u8 a:5; u8 b:5; u8 c;}` puts `c` at 2). A
|
|
743
|
+
// zero-width bitfield, which moves the next member to EMPTY_FIELD_BOUNDARY, is not placed.
|
|
744
|
+
// ABSENT ⇒ unmeasured, and an aggregate with a bitfield is not sized.
|
|
745
|
+
bitfieldPacking?: 'contiguous';
|
|
746
|
+
// Can this compiler CONTRACT a float multiply and the add or subtract that reads it into one
|
|
747
|
+
// fused instruction, which rounds once? mwcc can: `-fp_contract on` (set on some pikmin,
|
|
748
|
+
// marioparty4 and ac-decomp units) turns `a * b + c` into `fmadds` and `-(a * b) + c` into
|
|
749
|
+
// `fnmsubs`, but only within one expression, so the structurer names every such product
|
|
750
|
+
// (StructureOptions.contractsFloatProducts) and the spelling compiles to the unfused pair under
|
|
751
|
+
// either setting. The MIPS targets cannot: MIPS II (ido7.1 `-mips2`) and MIPS III (kmc `-mips3`)
|
|
752
|
+
// have no fused multiply-add, and naming the product there only moves the register allocation
|
|
753
|
+
// off the object (`fpu-lift.test.ts` pins one on ido7.1).
|
|
754
|
+
//
|
|
755
|
+
// ABSENT ⇒ false. A compiler with a fused multiply-add must opt in; the no-FPU targets never
|
|
756
|
+
// compute on a float at all.
|
|
757
|
+
contractsFloatProducts?: boolean;
|
|
758
|
+
// Does this compiler read a double literal's shortest round-trip decimal (`ir/float-bits.ts`
|
|
759
|
+
// `doubleLiteral`) back as the same double? That is how an `fconst` is printed, so its
|
|
760
|
+
// producers run only where this is true. agbcc does: c-lex.c:1308 hands the token to
|
|
761
|
+
// REAL_VALUE_ATOF at DFmode, which is real.c:461 `ereal_atof` → `asctoe53` (:3512) →
|
|
762
|
+
// `asctoeg(s, y, 53)` (:3533), a conversion in extended precision rounded once to 53 bits.
|
|
763
|
+
//
|
|
764
|
+
// ABSENT ⇒ unmeasured, and a literal declines.
|
|
765
|
+
roundTripsDoubleLiterals?: boolean;
|
|
766
|
+
// How the compiler lays out a FUNCTION-SCOPE STATIC: the least alignment of an array, the
|
|
767
|
+
// alignment of a string-literal initializer, and whether a zero scalar keeps its `.data`. The
|
|
768
|
+
// bytes of a static do not say how the source declared it; these, with the alignment and
|
|
769
|
+
// section the target shows, say which declaration lands it there (structure/local-statics.ts).
|
|
770
|
+
// A LAYOUT fact like `aggregateBoundary`, and the compiler's, read by the structurer.
|
|
771
|
+
//
|
|
772
|
+
// ABSENT ⇒ unmeasured, and a function that defines a static declines: a wrong floor mislays
|
|
773
|
+
// every static after the first, which no score sees.
|
|
774
|
+
staticLayout?: StaticLayout;
|
|
775
|
+
// Does this compiler load a `volatile` narrow signed read zero-extended and sign-extend it in a
|
|
776
|
+
// register, never in the load itself? agbcc: yes — `*(volatile s16 *)a` is `ldrh; lsl #16; asr
|
|
777
|
+
// #16` and `volatile s8` is `ldrb; lsl; asr`, where the plain `s16` read is `ldrsh`: the
|
|
778
|
+
// sign-extend expander refuses a volatile MEM while expanding (`general_operand`, recog.c:918,
|
|
779
|
+
// under `init_recog_no_volatile`, function.c:5564) and thumb.md:393-409 then extends in a
|
|
780
|
+
// register; compiled at the canonical flags. So a lifted sign-extending narrow load is evidence
|
|
781
|
+
// the source read was plain (`readCouldBeVolatile`): the device pin (frontend/device-pins.ts)
|
|
782
|
+
// leaves it plain, and the `declared` stamp (raise/declared-volatile.ts) leaves it unplaced.
|
|
783
|
+
//
|
|
784
|
+
// ABSENT ⇒ false: a sign-extending load says nothing about the qualifier, and a device read is
|
|
785
|
+
// pinned, and a read of a declared object placed, whatever its extension.
|
|
786
|
+
volatileReadsExtendInRegister?: boolean;
|
|
398
787
|
};
|
|
399
788
|
}
|
|
400
789
|
|
|
@@ -403,6 +792,7 @@ export const ARMV4T_AGBCC: TargetDescription = {
|
|
|
403
792
|
compiler: 'agbcc',
|
|
404
793
|
argRegs: ['r0', 'r1', 'r2', 'r3'],
|
|
405
794
|
returnReg: 'r0',
|
|
795
|
+
doubleArgWords: 'high-first',
|
|
406
796
|
// AAPCS passes four in r0-r3, so nothing above them can be an argument. The ATPCS aliases are
|
|
407
797
|
// the spellings this ISA's asm actually uses: censused over the vendored ARM asm, `sb`/`sl`/`ip`/
|
|
408
798
|
// `fp` all occur as operands and no `v<n>`/`a<n>` form does. `sp`, `lr` and `pc` are deliberately
|
|
@@ -413,6 +803,12 @@ export const ARMV4T_AGBCC: TargetDescription = {
|
|
|
413
803
|
// `mov ip, r1` in two switch arms, no save anywhere, and a `mov r0, ip` past a third arm that
|
|
414
804
|
// writes nothing — an uninitialised local by construction.
|
|
415
805
|
scratchRegs: ['r12', 'ip'],
|
|
806
|
+
// AAPCS: r0-r3 pass arguments and return, r12 (`ip`) is the intra-procedure-call scratch, and lr
|
|
807
|
+
// holds the return address the `bl` itself overwrites. agbcc's own machine description says the
|
|
808
|
+
// same (`thumb.h` CALL_USED_REGISTERS). Both spellings of r12 for the reason `nonArgRegs` carries
|
|
809
|
+
// both: the ATPCS aliases are what this ISA's asm writes.
|
|
810
|
+
callerSaved: ['r0', 'r1', 'r2', 'r3', 'r12', 'ip', 'lr'],
|
|
811
|
+
runtimeHelpers: AGBCC_RUNTIME_HELPERS,
|
|
416
812
|
// GBA hardware, which this target implies: agbcc is the GBA compiler and this is the only
|
|
417
813
|
// armv4t entry, so `armv4t + agbcc` is the platform. Stated because nothing else states it.
|
|
418
814
|
capabilities: {
|
|
@@ -430,6 +826,15 @@ export const ARMV4T_AGBCC: TargetDescription = {
|
|
|
430
826
|
// The idiom this exists for is their `DMA_FILL`: `vu16 tmp = value;
|
|
431
827
|
// DmaSet(n, &tmp, dest, … DMA_SRC_FIXED …)`, where the frame local is the source.
|
|
432
828
|
readOnlyAddressSinks: [0x040000b0, 0x040000bc, 0x040000c8, 0x040000d4],
|
|
829
|
+
// DMAnCNT_H, 10 bytes above each SAD: Source Address Control is bits 7-8 (setting 3 is
|
|
830
|
+
// prohibited) and bit 10 selects 32-bit units. `DMA_FILL`'s `DMA_SRC_FIXED` is setting 2.
|
|
831
|
+
readSourceControl: {
|
|
832
|
+
offset: 0xa,
|
|
833
|
+
modeShift: 7,
|
|
834
|
+
modes: ['increment', 'decrement', 'fixed', null],
|
|
835
|
+
wideBit: 0x0400,
|
|
836
|
+
units: [2, 4],
|
|
837
|
+
},
|
|
433
838
|
// The GBA I/O register file — one page from 0x04000000, the last live register being
|
|
434
839
|
// 0x04000301 (HALTCNT). Everything a source reaches through `REG_*` is in here, and nothing
|
|
435
840
|
// else is: IWRAM, EWRAM, palette, VRAM and OAM are ordinary memory a source does not qualify.
|
|
@@ -443,21 +848,64 @@ export const ARMV4T_AGBCC: TargetDescription = {
|
|
|
443
848
|
[0x040000d2, 0x040000d4],
|
|
444
849
|
[0x040000de, 0x040000e0],
|
|
445
850
|
],
|
|
851
|
+
// The BIOS block transfers, SWI 0Bh CpuSet and SWI 0Ch CpuFastSet (GBATEK, "BIOS Memory
|
|
852
|
+
// Copy"): r0 the source, r1 the destination, r2 the control — a count in bits 0-20, bit 24 a
|
|
853
|
+
// FIXED source (a fill, reading one unit of r0), and for CpuSet bit 26 selecting 32-bit units
|
|
854
|
+
// over 16-bit ones; CpuFastSet moves words only, its count rounded up to eight. Neither writes
|
|
855
|
+
// through r0. The names are the SDK's: sa3's and pokeemerald's `libagbsyscall.s` bind
|
|
856
|
+
// `CpuSet: svc #11` and `CpuFastSet: svc #12`, kleod's `CpuSet: svc #0xb`, and each declares
|
|
857
|
+
// `void CpuSet(const void *src, void *dest, u32 control)`.
|
|
858
|
+
blockTransferCalls: {
|
|
859
|
+
CpuSet: {
|
|
860
|
+
source: 0,
|
|
861
|
+
control: 2,
|
|
862
|
+
countMask: 0x1fffff,
|
|
863
|
+
fixedBit: 0x01000000,
|
|
864
|
+
wideBit: 0x04000000,
|
|
865
|
+
units: [2, 4],
|
|
866
|
+
countGranule: 1,
|
|
867
|
+
},
|
|
868
|
+
CpuFastSet: {
|
|
869
|
+
source: 0,
|
|
870
|
+
control: 2,
|
|
871
|
+
countMask: 0x1fffff,
|
|
872
|
+
fixedBit: 0x01000000,
|
|
873
|
+
wideBit: 0,
|
|
874
|
+
units: [4, 4],
|
|
875
|
+
countGranule: 8,
|
|
876
|
+
},
|
|
877
|
+
},
|
|
446
878
|
},
|
|
447
879
|
compilerBehaviors: {
|
|
448
880
|
coalesceLoopInit: false,
|
|
449
881
|
preserveDivergentBranchSense: true,
|
|
450
882
|
orderArgCopiesByWriteOrder: true,
|
|
451
883
|
nearBaseSpan: 255,
|
|
884
|
+
volatileReadsExtendInRegister: true,
|
|
452
885
|
foldsConstAddrOffset: true,
|
|
453
886
|
foldsPointerAdvance: true,
|
|
887
|
+
pointerIntConversionIsFree: true,
|
|
454
888
|
readsStayWhereWritten: true,
|
|
455
|
-
|
|
889
|
+
switchBoundCase: 'taken',
|
|
456
890
|
switchArmsFollowLayout: true,
|
|
457
891
|
switchRequiresFrontLoadedTests: true,
|
|
458
892
|
hoistsSingleSetArm: true,
|
|
893
|
+
eightByteReturnScratch: 'r2',
|
|
459
894
|
arrayShapeFromStride: true,
|
|
460
895
|
reloadsLocalReread: true,
|
|
896
|
+
aggregateBoundary: 4,
|
|
897
|
+
largestAlignment: 4,
|
|
898
|
+
aggregateReturn: 'apcs',
|
|
899
|
+
enumBytes: 4,
|
|
900
|
+
bitfieldPacking: 'contiguous',
|
|
901
|
+
roundTripsDoubleLiterals: true,
|
|
902
|
+
// agbcc 2.9 (gcc/varasm.c `assemble_variable`, gcc/thumb.h): an array takes its element's
|
|
903
|
+
// alignment (no DATA_ALIGNMENT); a declaration initialized by a STRING_CST is word-aligned —
|
|
904
|
+
// CONSTANT_ALIGNMENT (thumb.h:361) over DECL_INITIAL (varasm.c:1214-1216), so
|
|
905
|
+
// `static const char s[] = "hi"` is at 4 while `= {'h', 'i', 0}` and an array of strings
|
|
906
|
+
// (whose DECL_INITIAL is a CONSTRUCTOR) are at 1; and `= 0` keeps its `.data`, where only a
|
|
907
|
+
// declaration with no initializer is `.lcomm` (compiled).
|
|
908
|
+
staticLayout: { aggregateAlign: 1, stringAlign: 4, zeroScalar: 'data' },
|
|
461
909
|
narrowParamWitness: 'prologue-extension',
|
|
462
910
|
// agbcc: reload walks pseudos ascending handing each global-alloc loser a fresh slot, a user
|
|
463
911
|
// local's pseudo number is its `expand_decl` position, and the Thumb frame grows UPWARD
|
|
@@ -470,9 +918,27 @@ export const ARMV4T_AGBCC: TargetDescription = {
|
|
|
470
918
|
// agbcc reserves the outgoing area with the rest of the frame (`add sp, sp, #-N` covers both)
|
|
471
919
|
// and stages arguments 5+ into it at [sp,#0] upward — thumb.h's ACCUMULATE_OUTGOING_ARGS.
|
|
472
920
|
stagesOutgoingArgsInFrame: true,
|
|
921
|
+
// …one area for every call, below the locals (thumb.h:573/600-622/628, calls.c:1675), which
|
|
922
|
+
// agbcc re-stages before each call and never reads back after one (agbcc-restage.s).
|
|
923
|
+
localsAboveOutgoingArea: true,
|
|
924
|
+
// the two producer tables behind this are compiled, at agbcc 2.9-arm-000512 and the rows' own
|
|
925
|
+
// flags, and they are written out where the predicate reads it (`frontend/thumb.ts`)
|
|
926
|
+
oneWordFrameIsTheCapturedObject: true,
|
|
927
|
+
// agbcc's `*call_indirect` and `*call_value_indirect` emit `bl _call_via_%0` (gcc/thumb.md:997-1021),
|
|
928
|
+
// and libgcc defines `_call_via_<reg>` as `bx <reg>` for r0-r9, sl, fp, ip, sp and lr
|
|
929
|
+
// (libgcc/lib1thumb.asm:595-633). Neither `sp` (the frame) nor `lr` (which the `bl` itself
|
|
930
|
+
// overwrites) holds a function's address.
|
|
931
|
+
callThunks: {
|
|
932
|
+
prefix: '_call_via_',
|
|
933
|
+
regs: ['r0', 'r1', 'r2', 'r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9', 'sl', 'fp', 'ip'],
|
|
934
|
+
},
|
|
473
935
|
},
|
|
474
936
|
};
|
|
475
937
|
|
|
938
|
+
/** MIPS o32's floating-point homes, shared by both compilers that target it (see `fpu`). The odd
|
|
939
|
+
* halves (`$f13`, `$f15`) carry the other word of a DOUBLE argument, which no frontend lifts. */
|
|
940
|
+
const O32_FPU: TargetDescription['fpu'] = { argRegs: ['$f12', '$f14'], returnReg: '$f0', slots: 'leading' };
|
|
941
|
+
|
|
476
942
|
/** MIPS-II / IDO 7.1 target. IDO is the IRIX C compiler,
|
|
477
943
|
* statically recompiled to run natively (ido-static-recomp). Unlike agbcc
|
|
478
944
|
* it emits no textual asm, so asmlift's input is the DISASSEMBLED object (`mips-linux-gnu-
|
|
@@ -483,16 +949,44 @@ export const MIPS_IDO: TargetDescription = {
|
|
|
483
949
|
compiler: 'ido',
|
|
484
950
|
argRegs: ['a0', 'a1', 'a2', 'a3'],
|
|
485
951
|
returnReg: 'v0',
|
|
952
|
+
// O32: at, v0-v1, a0-a3, t0-t9 and ra are all caller-saved. `frontend/mips.ts` reads the rest, and
|
|
953
|
+
// `ra`, as the registers a frame store SAVES.
|
|
954
|
+
callerSaved: [
|
|
955
|
+
'at',
|
|
956
|
+
'v0',
|
|
957
|
+
'v1',
|
|
958
|
+
'a0',
|
|
959
|
+
'a1',
|
|
960
|
+
'a2',
|
|
961
|
+
'a3',
|
|
962
|
+
't0',
|
|
963
|
+
't1',
|
|
964
|
+
't2',
|
|
965
|
+
't3',
|
|
966
|
+
't4',
|
|
967
|
+
't5',
|
|
968
|
+
't6',
|
|
969
|
+
't7',
|
|
970
|
+
't8',
|
|
971
|
+
't9',
|
|
972
|
+
'ra',
|
|
973
|
+
],
|
|
974
|
+
runtimeHelpers: IDO_RUNTIME_HELPERS,
|
|
975
|
+
// MEASURED with this toolchain's own flags: `float f(float a, float b){ return a + b; }` is
|
|
976
|
+
// `jr ra; add.s $f0,$f12,$f14`, and the `'leading'` rule's two halves are the pair in `fpu`'s note.
|
|
977
|
+
fpu: O32_FPU,
|
|
486
978
|
capabilities: { endianness: 'big', hwDivide: true, hwFloat: true, flags: false },
|
|
487
979
|
// `switchAllowsNeqCase: false` — IDO's switch dispatch uses `==`/`<`, never `!=` cases;
|
|
488
980
|
// leaving it permissive mis-recognises `!=`-rooted if-else chains as switches.
|
|
489
981
|
compilerBehaviors: {
|
|
982
|
+
contractsFloatProducts: false,
|
|
490
983
|
coalesceLoopInit: true,
|
|
491
984
|
preserveDivergentBranchSense: true,
|
|
492
985
|
orderArgCopiesByWriteOrder: true,
|
|
493
986
|
switchAllowsNeqCase: false,
|
|
494
987
|
// MEASURED — the pair at the field compiles to one load of `p[1]` for every local spelling.
|
|
495
988
|
reloadsLocalReread: false,
|
|
989
|
+
aggregateBoundary: 1,
|
|
496
990
|
// MEASURED at `-mips2 -O2 -32 -non_shared -G 0`: the `sll` leads the function for BOTH
|
|
497
991
|
// spellings, so the prologue position cannot decide; a narrow DECLARED parameter is the one that
|
|
498
992
|
// is both homed dead AND widened in its own argument register. raise/paramwidth.ts's header has
|
|
@@ -503,8 +997,7 @@ export const MIPS_IDO: TargetDescription = {
|
|
|
503
997
|
// reversed-declaration twin, with this compiler's objects beside them — and a test reads the
|
|
504
998
|
// correspondence off it: 16 of 16 spills, and rank → offset unchanged when the declaration
|
|
505
999
|
// list is reversed, which is what separates declaration rank from the order of the
|
|
506
|
-
// assignments. No ido7.1 benchmark row lifts with two or more spilled user locals
|
|
507
|
-
// spilling shape in the corpus carries a call, and this frontend declines a call — so no row
|
|
1000
|
+
// assignments. No ido7.1 benchmark row lifts with two or more spilled user locals, so no row
|
|
508
1001
|
// can tell a wrong value from a right one here.
|
|
509
1002
|
//
|
|
510
1003
|
// FLIP CONDITION, and it has TWO parts because the second is easy to miss. (1) The first
|
|
@@ -525,6 +1018,28 @@ export const MIPS_GCC: TargetDescription = {
|
|
|
525
1018
|
compiler: 'gcc',
|
|
526
1019
|
argRegs: ['a0', 'a1', 'a2', 'a3'],
|
|
527
1020
|
returnReg: 'v0',
|
|
1021
|
+
// The same O32 convention MIPS_IDO carries, read the same way.
|
|
1022
|
+
callerSaved: [
|
|
1023
|
+
'at',
|
|
1024
|
+
'v0',
|
|
1025
|
+
'v1',
|
|
1026
|
+
'a0',
|
|
1027
|
+
'a1',
|
|
1028
|
+
'a2',
|
|
1029
|
+
'a3',
|
|
1030
|
+
't0',
|
|
1031
|
+
't1',
|
|
1032
|
+
't2',
|
|
1033
|
+
't3',
|
|
1034
|
+
't4',
|
|
1035
|
+
't5',
|
|
1036
|
+
't6',
|
|
1037
|
+
't7',
|
|
1038
|
+
't8',
|
|
1039
|
+
't9',
|
|
1040
|
+
'ra',
|
|
1041
|
+
],
|
|
1042
|
+
runtimeHelpers: MIPS_GCC_RUNTIME_HELPERS,
|
|
528
1043
|
// KMC GCC keeps a loop seeded from an argument register IN that register (coalesceLoopInit
|
|
529
1044
|
// true, like IDO): test/corpus/gcc-gcd.asm runs its whole loop on a0/a1 with no init copies,
|
|
530
1045
|
// and the row it comes from matches only with the parameters as the loop's homes. The other
|
|
@@ -542,8 +1057,12 @@ export const MIPS_GCC: TargetDescription = {
|
|
|
542
1057
|
// and dmastride on agbcc lost; maxarr and preupdate_exit_call on agbcc gained) — because a pred
|
|
543
1058
|
// that computes the initial value INTO the param's own register wrote the key and still
|
|
544
1059
|
// coalesces.
|
|
1060
|
+
// The same o32 convention, measured on GCC_KMC_TOOLCHAIN at -O2: the fadd pair compiles to the
|
|
1061
|
+
// same two instructions IDO's does, and `float f(int a, float b)` moves `b` over with `mtc1 a1`.
|
|
1062
|
+
fpu: O32_FPU,
|
|
545
1063
|
capabilities: { endianness: 'big', hwDivide: true, hwFloat: true, flags: false },
|
|
546
1064
|
compilerBehaviors: {
|
|
1065
|
+
contractsFloatProducts: false,
|
|
547
1066
|
coalesceLoopInit: true,
|
|
548
1067
|
preserveDivergentBranchSense: true,
|
|
549
1068
|
orderArgCopiesByWriteOrder: true,
|
|
@@ -585,6 +1104,7 @@ export const MIPS_GCC: TargetDescription = {
|
|
|
585
1104
|
// MEASURED on BOTH toolchains this description serves (the note above): one load of `p[1]` for
|
|
586
1105
|
// every local spelling of the pair at the field, gcc2.7.2kmc at -O2 and gcc2.7.2 at -O1 alike.
|
|
587
1106
|
reloadsLocalReread: false,
|
|
1107
|
+
aggregateBoundary: 1,
|
|
588
1108
|
// MEASURED on BOTH toolchains this description serves: `int f(s8 x){return x;}` and
|
|
589
1109
|
// `int f(s32 x){return (s8)x;}` compile to BYTE-IDENTICAL objects, gcc2.7.2kmc at -O2 and
|
|
590
1110
|
// gcc2.7.2 at -O1 alike — so the object carries no witness at all and the pass refuses.
|
|
@@ -614,11 +1134,20 @@ export const PPC_MWCC: TargetDescription = {
|
|
|
614
1134
|
// PPC EABI: r3–r10 pass integer/pointer arguments; r3 also returns.
|
|
615
1135
|
argRegs: ['r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9', 'r10'],
|
|
616
1136
|
returnReg: 'r3',
|
|
1137
|
+
// PPC EABI: r0 and r3-r12 are volatile, and lr carries the return address `bl` overwrites. r11
|
|
1138
|
+
// and r12 are the linker's stub scratch, r13 is the small-data base and r14 upward are
|
|
1139
|
+
// callee-saved.
|
|
1140
|
+
callerSaved: ['r0', 'r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9', 'r10', 'r11', 'r12', 'lr'],
|
|
1141
|
+
runtimeHelpers: PPC_MWCC_RUNTIME_HELPERS,
|
|
1142
|
+
// PPC EABI with `-fp hard`, measured at the synthetic tier's flags: `fadds f1,f1,f2; blr`, a third
|
|
1143
|
+
// float argument in f3, and the GPR/FPR order of mixed parameters absent from the object.
|
|
1144
|
+
fpu: { argRegs: ['f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8'], returnReg: 'f1', slots: 'separate' },
|
|
617
1145
|
capabilities: { endianness: 'big', hwDivide: true, hwFloat: true, flags: true },
|
|
618
1146
|
// CodeWarrior's structuring compiler behaviors are UNKNOWN until fixtures reveal them — safe universal
|
|
619
1147
|
// defaults; coalesceLoopInit false until a CW loop fixture says otherwise — the second of the
|
|
620
1148
|
// two compiler-wide guesses standing in for the per-function observation named at MIPS_GCC.
|
|
621
1149
|
compilerBehaviors: {
|
|
1150
|
+
contractsFloatProducts: true,
|
|
622
1151
|
coalesceLoopInit: false,
|
|
623
1152
|
preserveDivergentBranchSense: true,
|
|
624
1153
|
orderArgCopiesByWriteOrder: true,
|
|
@@ -627,6 +1156,33 @@ export const PPC_MWCC: TargetDescription = {
|
|
|
627
1156
|
// `u8 v = p[3]; if ((v & 0x7f) == 0x7f) { fnA(); p[4] = v; return; }` under an `if (a)`
|
|
628
1157
|
// matches only once `read-behind-effect` stops refusing it (3/24 → MATCH 0/22).
|
|
629
1158
|
reloadsLocalReread: false,
|
|
1159
|
+
aggregateBoundary: 1,
|
|
1160
|
+
// MEASURED on all three builds: `struct {u8 a; double d;}` and its `long long` twin put the
|
|
1161
|
+
// member at 8 and size 16
|
|
1162
|
+
largestAlignment: 8,
|
|
1163
|
+
aggregateReturn: 'svr4',
|
|
1164
|
+
// MEASURED on mwcc_242_81 through the `.comment` alignment record: an array or a struct is at
|
|
1165
|
+
// 4 at least (`u8[1]`, a struct of two bytes), a scalar at its width; a string-literal
|
|
1166
|
+
// initializer gets no more than the array does (`char s[] = "hello!"` and a `u8` list both 4);
|
|
1167
|
+
// and a scalar `= 0` moves to bss, where `int q[1] = {0}` stays in `.data`. In both bss
|
|
1168
|
+
// sections the moved scalars come first in declaration order. The statics with no initializer
|
|
1169
|
+
// follow reversed in `.sbss`; in `.bss` (an object over the `-sdata` threshold: an array over 8
|
|
1170
|
+
// bytes, every scalar at `-sdata 0`) they follow in the order the compiler first uses them
|
|
1171
|
+
// (`a[i]=1; b[i]=2;` puts a first, the swap b), which neither the source's text
|
|
1172
|
+
// (`x = b[i]; a[i] = 1;` puts a first) nor the emitted code (`a[i] = b[i]` reads b first, puts a
|
|
1173
|
+
// first) fixes. The other mwcc builds this description serves are not measured.
|
|
1174
|
+
staticLayout: {
|
|
1175
|
+
aggregateAlign: 4,
|
|
1176
|
+
stringAlign: 4,
|
|
1177
|
+
zeroScalar: 'bss',
|
|
1178
|
+
uninitOrder: { '.sbss': 'reversed', '.bss': 'first-use' },
|
|
1179
|
+
},
|
|
1180
|
+
// MEASURED on all three builds at -O4,p and -O0,p: the note at the field. The two are one
|
|
1181
|
+
// declaration: without the layout gate a relational if-ladder reads as a `switch`. The gate's
|
|
1182
|
+
// reach is the committed probes alone — withdrawn, it moves 0 of the 180 PPC benchmark rows
|
|
1183
|
+
// that lift — so switch-arms.test.ts holds the pairing over every target instead of a row.
|
|
1184
|
+
switchBoundCase: 'either',
|
|
1185
|
+
switchRequiresFrontLoadedTests: true,
|
|
630
1186
|
// The PowerPC prologue widens a declared narrow parameter with `extsb`/`extsh`, which the
|
|
631
1187
|
// frontend lifts to the same `sext` op agbcc's shift pair folds to — the position shape, on
|
|
632
1188
|
// another ISA. `synthetic:{sextb,tos8}:mwcc_242_81` are its rows, MATCH through that pass.
|
|
@@ -766,7 +1322,7 @@ export interface ResolvedTarget {
|
|
|
766
1322
|
toolchain: ToolchainId;
|
|
767
1323
|
/** the flags the function's target and every candidate compile with */
|
|
768
1324
|
cflags: readonly string[];
|
|
769
|
-
/** the description asmlift decompiles against: the toolchain's,
|
|
1325
|
+
/** the description asmlift decompiles against: the toolchain's, less a fact a flag changes */
|
|
770
1326
|
target: TargetDescription;
|
|
771
1327
|
/** what the flags make the compiler do */
|
|
772
1328
|
profile: CodegenProfile;
|
|
@@ -776,7 +1332,16 @@ export interface ResolvedTarget {
|
|
|
776
1332
|
* those flags describe. Throws on a level word the toolchain's family cannot read. */
|
|
777
1333
|
export function targetFor(toolchain: ToolchainId, cflags: readonly string[]): ResolvedTarget {
|
|
778
1334
|
const t: ToolchainTarget = TOOLCHAIN_TARGETS[toolchain];
|
|
779
|
-
|
|
1335
|
+
const profile = parseFlags(t.family, cflags);
|
|
1336
|
+
const cpp = dialectOf(profile.slots.lang) === 'c++';
|
|
1337
|
+
let target: TargetDescription = cpp ? { ...t.description, dialect: 'c++' } : t.description;
|
|
1338
|
+
if (profile.slots['-fshort-enums'] === 'on') {
|
|
1339
|
+
// every enum is the smallest integer its values fit (c-decl.c:6064-6065, :6123; compiled,
|
|
1340
|
+
// `sizeof(struct {enum {K0, K1} k[2];})` is 4 under the flag and 8 without), so none is enumBytes
|
|
1341
|
+
const { enumBytes: _flagged, ...behaviors } = target.compilerBehaviors;
|
|
1342
|
+
target = { ...target, compilerBehaviors: behaviors };
|
|
1343
|
+
}
|
|
1344
|
+
return { toolchain, cflags, target, profile };
|
|
780
1345
|
}
|
|
781
1346
|
|
|
782
1347
|
/** Build the structurer's options for a target: the function's own `returnsVoid` plus every
|
|
@@ -789,7 +1354,11 @@ export function targetFor(toolchain: ToolchainId, cflags: readonly string[]): Re
|
|
|
789
1354
|
* `nearBaseSpan` / `foldsConstAddrOffset` are read off the target by rank.ts. So the field names
|
|
790
1355
|
* are a SUPERSET of StructureOptions', not a bijection, and nothing may derive one from the other
|
|
791
1356
|
* by enumerating keys. */
|
|
792
|
-
export function structureOptionsFor(
|
|
1357
|
+
export function structureOptionsFor(
|
|
1358
|
+
t: TargetDescription,
|
|
1359
|
+
returnsVoid: boolean,
|
|
1360
|
+
prototypes: Prototypes = {},
|
|
1361
|
+
): StructureOptions {
|
|
793
1362
|
// `littleEndian` and `deviceRegisters` are the HARDWARE capabilities the structurer consumes
|
|
794
1363
|
// (bitfield extract recognition is LSB-first; the dead-read spelling refuses outside the device
|
|
795
1364
|
// window); everything else is a compiler behavior.
|
|
@@ -803,6 +1372,9 @@ export function structureOptionsFor(t: TargetDescription, returnsVoid: boolean):
|
|
|
803
1372
|
const { spillSlotOrder, ...behaviors } = t.compilerBehaviors;
|
|
804
1373
|
return {
|
|
805
1374
|
returnsVoid,
|
|
1375
|
+
declaredArgs: declaredArgTypes(prototypes),
|
|
1376
|
+
declaredReturns: declaredReturnTypes(prototypes),
|
|
1377
|
+
...(t.dialect === 'c++' ? { dialect: 'c++' as const } : {}),
|
|
806
1378
|
littleEndian: t.capabilities.endianness === 'little',
|
|
807
1379
|
...(t.capabilities.deviceRegisters ? { deviceRegisters: t.capabilities.deviceRegisters } : {}),
|
|
808
1380
|
...behaviors,
|
|
@@ -810,6 +1382,37 @@ export function structureOptionsFor(t: TargetDescription, returnsVoid: boolean):
|
|
|
810
1382
|
};
|
|
811
1383
|
}
|
|
812
1384
|
|
|
813
|
-
|
|
814
|
-
'
|
|
815
|
-
|
|
1385
|
+
/** The candidate prelude: a `typedef` for every name in the scalar vocabulary. PRINTED from
|
|
1386
|
+
* `proto.ts`'s `PRELUDE_TYPEDEFS` rather than spelled out, because the other reader of that
|
|
1387
|
+
* vocabulary decides which type texts asmlift may PRINT into a candidate, and a second copy of
|
|
1388
|
+
* the list is a copy that can disagree — in the direction no test over two lists catches.
|
|
1389
|
+
*
|
|
1390
|
+
* The decomp checkouts all define the same names, and the harness keeps typedefs per NAME against
|
|
1391
|
+
* the vendored ctx, so a unit that already has them gets no redefinition. */
|
|
1392
|
+
export const C_TYPEDEFS = `${[...PRELUDE_TYPEDEFS].map(([name, base]) => `typedef ${base} ${name};`).join('')}\n`;
|
|
1393
|
+
|
|
1394
|
+
/** Each declared callee's parameter types, where one can be PRINTED as a cast — the argument
|
|
1395
|
+
* conversions C++ refuses to make implicitly (backend/cfamily.ts `argConversion`), and a pointer
|
|
1396
|
+
* cell's integer arguments (l3/ptrcell.ts). An entry this cannot spell is `undefined`, and that
|
|
1397
|
+
* argument is printed uncast. */
|
|
1398
|
+
function declaredArgTypes(prototypes: Prototypes): Record<string, readonly (ParamType | undefined)[]> {
|
|
1399
|
+
const out: Record<string, readonly (ParamType | undefined)[]> = {};
|
|
1400
|
+
for (const [name, p] of Object.entries(prototypes)) {
|
|
1401
|
+
if (Array.isArray(p.params)) {
|
|
1402
|
+
out[name] = p.params.map((t) => (spellableType(t) && declaredWidth(t) !== undefined ? t : undefined));
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
return out;
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
/** Each declared callee's return type as the project spells it, which is never printed: whether a
|
|
1409
|
+
* pointer cell compared with the call meets a pointer (l3/ptrcell.ts). */
|
|
1410
|
+
function declaredReturnTypes(prototypes: Prototypes): Record<string, ParamType> {
|
|
1411
|
+
const out: Record<string, ParamType> = {};
|
|
1412
|
+
for (const [name, p] of Object.entries(prototypes)) {
|
|
1413
|
+
if (p.returns !== undefined) {
|
|
1414
|
+
out[name] = p.returns;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
return out;
|
|
1418
|
+
}
|