@asmlift/core 0.8.1 → 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 +36 -31
- package/package.json +1 -1
- package/src/aggregate.ts +397 -0
- package/src/backend/c.ts +6 -2
- package/src/backend/cfamily.ts +370 -56
- package/src/backend/cpp.ts +116 -10
- package/src/backend/pascal.ts +69 -11
- 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 +777 -84
- package/src/declare.ts +132 -13
- package/src/frontend/asmdata.ts +26 -6
- package/src/frontend/call-plan.ts +1590 -0
- package/src/frontend/dead-reads.ts +628 -0
- package/src/frontend/device-pins.ts +296 -0
- package/src/frontend/emit.ts +4 -3
- package/src/frontend/errors.ts +5 -0
- package/src/frontend/flags-edge.ts +101 -0
- package/src/frontend/fpu.ts +374 -0
- package/src/frontend/frame-objects.ts +3171 -0
- package/src/frontend/frontend.ts +64 -3
- package/src/frontend/high-half.ts +55 -5
- package/src/frontend/jump-table.ts +530 -0
- package/src/frontend/local-object.ts +616 -0
- package/src/frontend/mips.ts +2392 -208
- package/src/frontend/opaque.ts +97 -18
- package/src/frontend/own-struct-return.ts +500 -0
- package/src/frontend/ppc.ts +3137 -267
- package/src/frontend/register-struct-call.ts +484 -0
- package/src/frontend/reloc-symbol.ts +109 -13
- package/src/frontend/splat.ts +108 -5
- package/src/frontend/ssa.ts +2098 -89
- package/src/frontend/stackargs.ts +172 -42
- package/src/frontend/thumb.ts +4563 -2091
- package/src/ir/alias.ts +71 -0
- package/src/ir/bits.ts +681 -1
- package/src/ir/core.ts +199 -6
- package/src/ir/discipline.ts +313 -0
- package/src/ir/entry.ts +159 -0
- package/src/ir/float-bits.ts +47 -0
- package/src/{structure → ir}/loops.ts +6 -2
- package/src/ir/opcodes.ts +193 -121
- package/src/ir/parse.ts +1 -0
- package/src/ir/types.ts +202 -2
- package/src/ir/verify.ts +219 -3
- package/src/l3/address.ts +5 -0
- package/src/l3/argcopy.ts +38 -6
- package/src/l3/ast.ts +467 -23
- package/src/l3/basecse.ts +33 -5
- package/src/l3/coalesce.ts +21 -13
- package/src/l3/constinit.ts +382 -0
- package/src/l3/dce.ts +34 -12
- package/src/l3/gates.ts +9 -3
- package/src/l3/hoist.ts +8 -7
- package/src/l3/initfirst.ts +68 -19
- package/src/l3/intcell.ts +98 -0
- package/src/l3/mentions.ts +10 -4
- package/src/l3/narrowdecl.ts +259 -0
- package/src/l3/nearbase.ts +10 -5
- package/src/l3/parkfirst.ts +8 -5
- package/src/l3/pollguard.ts +11 -4
- package/src/l3/ptrcell.ts +354 -0
- package/src/l3/ptrfield.ts +123 -8
- package/src/l3/regspell.ts +26 -7
- package/src/l3/reindex.ts +293 -103
- package/src/l3/storage.ts +98 -1
- package/src/l3/symbol-refs.ts +182 -23
- package/src/l3/tailmerge.ts +9 -20
- package/src/l3/typing.ts +237 -32
- package/src/l3/unreduce.ts +10 -9
- package/src/l3/volatileptr.ts +17 -54
- package/src/l3/volstore.ts +96 -7
- package/src/mangle.ts +41 -1
- package/src/pattern/engine.ts +373 -82
- package/src/pipeline.ts +589 -27
- package/src/proto-context.ts +1648 -0
- package/src/proto.ts +1203 -49
- package/src/raise/aggmembers.ts +290 -0
- package/src/raise/arrays.ts +8 -3
- package/src/raise/declared-volatile.ts +307 -0
- package/src/raise/divpow2.ts +2 -2
- package/src/raise/errors.ts +17 -0
- package/src/raise/floathelpers.ts +464 -0
- package/src/raise/globalshape.ts +11 -5
- package/src/raise/gvn.ts +29 -8
- package/src/raise/latch.ts +162 -6
- package/src/raise/memberarrays.ts +10 -4
- package/src/raise/narrow.ts +29 -2
- package/src/raise/narrowlocal.ts +11 -6
- package/src/raise/offsetnames.ts +494 -0
- package/src/raise/pairparams.ts +114 -0
- package/src/raise/paramwidth.ts +203 -6
- package/src/raise/pre-recovery.ts +174 -17
- package/src/raise/pre-structure.ts +54 -0
- package/src/raise/recover.ts +26 -15
- package/src/raise/reroll.ts +694 -0
- package/src/raise/retsink.ts +1 -1
- package/src/raise/shortcircuit.ts +66 -30
- package/src/raise/softdiv.ts +21 -23
- package/src/raise/storesign.ts +153 -0
- package/src/raise/struct-arrays.ts +34 -14
- package/src/raise/structs.ts +674 -98
- package/src/raise/truncload.ts +367 -0
- package/src/raise/unthread.ts +400 -0
- package/src/raise/widehelpers.ts +106 -0
- package/src/rank-declare.ts +143 -12
- package/src/rank-variations.ts +117 -31
- package/src/rank.ts +563 -122
- package/src/runtime-helpers.ts +405 -0
- package/src/stillborn.ts +289 -0
- package/src/structure/analysis.ts +1449 -449
- package/src/structure/arith-ops.ts +23 -0
- package/src/structure/globalaccess.ts +111 -9
- package/src/structure/hazards.ts +641 -114
- package/src/structure/local-statics.ts +517 -0
- package/src/structure/namecoalesce.ts +2 -2
- package/src/structure/pointer-spelling.ts +1106 -0
- package/src/structure/redundant-test.ts +99 -0
- package/src/structure/structure.ts +3478 -975
- package/src/structure/switch-recover.ts +403 -178
- package/src/symbols.ts +44 -3
- package/src/target.ts +1387 -71
- package/src/trace.ts +66 -28
- package/src/variation-definitions.ts +243 -13
- package/src/variation-gates.ts +2 -0
- package/src/variation-tokens.ts +25 -7
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
// asmlift — the argument ABI at a function's boundary, both register files, read once for the two
|
|
2
|
+
// frontends that lift hardware floating point (frontend/mips.ts, frontend/ppc.ts). The homes are
|
|
3
|
+
// target data (`TargetDescription.fpu`); this is where a lift is held to them.
|
|
4
|
+
//
|
|
5
|
+
// WHY A FRONTEND NEEDS THIS AT ALL, and why decoding the arithmetic alone is not a smaller version
|
|
6
|
+
// of it: a register of the FPU's file read before any write mints an entry parameter like any other
|
|
7
|
+
// key (frontend/ssa.ts), and a value left in the float return register is not the integer one. With
|
|
8
|
+
// the decode and neither of these, `float fadd(float a, float b){ return a + b; }` lifts on every
|
|
9
|
+
// FPU target as `void fadd(s32 a0, s32 a1) { return; }` — two phantom integer parameters and a dead
|
|
10
|
+
// add. `docs/floating-point.md` §2 measures that.
|
|
11
|
+
import type { Fn, Op, Value } from '../ir/core';
|
|
12
|
+
import { FLOAT_OPS, FLOAT_TO_INT, INT_TO_FLOAT, NEGATED_FCMP } from '../ir/opcodes';
|
|
13
|
+
import { pruneDeadParams } from '../ir/simplify';
|
|
14
|
+
import { type IrType, T } from '../ir/types';
|
|
15
|
+
import { declaresAggregateReturn, declaresVoidReturn, floatingSpelling, isFloatingSpelling } from '../proto';
|
|
16
|
+
import type { TargetDescription } from '../target';
|
|
17
|
+
import { computedOnlyToReturn } from './dead-reads';
|
|
18
|
+
import { FrontendUnsupportedError } from './errors';
|
|
19
|
+
import type { OwnDeclaration } from './frontend';
|
|
20
|
+
import type { ArgSlots, SsaBuilder } from './ssa';
|
|
21
|
+
|
|
22
|
+
export type Fpu = NonNullable<TargetDescription['fpu']>;
|
|
23
|
+
|
|
24
|
+
/** The one float type a function's FPU registers hold: double where it decodes a double op, single
|
|
25
|
+
* where it decodes a single one, of no stated precision (`T.fUnstated`) where every op it decodes
|
|
26
|
+
* carries none, and null where it decodes both. `widthOf` is the frontend's reading of a mnemonic
|
|
27
|
+
* (null for one that carries no precision, undefined for one it does not decode). */
|
|
28
|
+
export function fpPrecision(
|
|
29
|
+
instrs: readonly { mnemonic: string }[],
|
|
30
|
+
widthOf: (mnemonic: string) => 32 | 64 | null | undefined,
|
|
31
|
+
): IrType | null {
|
|
32
|
+
const widths = new Set(instrs.map((ins) => widthOf(ins.mnemonic)).filter((w) => w !== null && w !== undefined));
|
|
33
|
+
return widths.size > 1 ? null : widths.has(64) ? T.f64() : widths.has(32) ? T.f32() : T.fUnstated();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** The precision a function's OWN DECLARATION states for the floats it computes on, where none of
|
|
37
|
+
* its instructions states one (`T.fUnstated`): `double` where every floating parameter and a
|
|
38
|
+
* floating return are spelled `double`. PowerPC's `fcmpo`, `fctiwz`, `fmr` and `fneg` are one
|
|
39
|
+
* object over a float and a double, so `int d2i(double x){ return (int)x; }` is the bytes of the
|
|
40
|
+
* `float` function too, and only a declaration that may prove (`OwnDeclaration.proves`) tells them
|
|
41
|
+
* apart. A declaration spelling both is a function in both precisions, null, which refuses where a
|
|
42
|
+
* float instruction is decoded (`refuseBothPrecisions`). Any other leaves the precision unstated,
|
|
43
|
+
* which C spells `float`: a `float` parameter of an old-style definition arrives as a double. */
|
|
44
|
+
export function declaredPrecision(lifted: IrType | null, own: OwnDeclaration | undefined): IrType | null {
|
|
45
|
+
if (lifted?.kind !== 'float' || lifted.width !== null || !own?.proves) {
|
|
46
|
+
return lifted;
|
|
47
|
+
}
|
|
48
|
+
const { params, returns } = own.proto;
|
|
49
|
+
const spelled = [...(Array.isArray(params) ? params : []), ...(returns === undefined ? [] : [returns])]
|
|
50
|
+
.map(floatingSpelling)
|
|
51
|
+
.filter((f) => f !== undefined);
|
|
52
|
+
if (spelled.length === 0 || spelled.some((f) => f === 'long double')) {
|
|
53
|
+
return lifted;
|
|
54
|
+
}
|
|
55
|
+
return spelled.every((f) => f === 'double') ? T.f64() : spelled.every((f) => f === 'float') ? lifted : null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Refuse a function computing in both precisions, where the frontend DECODES a float instruction of
|
|
59
|
+
* it, so a refusal the stream reaches first keeps its own reason. The SSA builder types a register
|
|
60
|
+
* per key, not per value, and the rounding between the precisions (a conversion, PowerPC's `frsp`,
|
|
61
|
+
* or a single op over a double operand) is not modelled. It throws here, so its throw site is the
|
|
62
|
+
* one `declines.test.ts` harvests. */
|
|
63
|
+
export function refuseBothPrecisions(name: string, mnemonic: string): never {
|
|
64
|
+
throw new FrontendUnsupportedError(
|
|
65
|
+
`cannot lift '${name}': '${mnemonic}' is in a function that computes in ` +
|
|
66
|
+
'both single and double precision, and the rounding between them is not modelled',
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** THE ARGUMENT SLOTS OF BOTH REGISTER FILES, as the one `ArgSlots` the MIPS and PowerPC frontends
|
|
71
|
+
* hand to `mintArgSlotHoles` and `abiSortEntryParams` (frontend/ssa.ts). Naming is positional
|
|
72
|
+
* (`a0`, `a1`, … in sort order), so the ORDER `slotOf` gives and the HOLES `holes` fills are two
|
|
73
|
+
* uses of one reading — kept in one place because under `'leading'` the files share a slot
|
|
74
|
+
* sequence, and a hole filled from the wrong file is a signature that compiles and binds its
|
|
75
|
+
* caller's arguments one slot off.
|
|
76
|
+
*
|
|
77
|
+
* `slotOf` is the slot, with one exception: under `'separate'` a float argument ranks after every
|
|
78
|
+
* integer one. THAT ORDER IS A SPELLING, NOT A READING. Under the PowerPC EABI the two files count
|
|
79
|
+
* independently, so `float g(int *p, float b)` and `float g(float b, int *p)` compile to one object
|
|
80
|
+
* and nothing in it says which the source wrote. Either spelling reproduces the bytes in a unit
|
|
81
|
+
* that does not declare the function; this one is fixed so the output is deterministic. KNOWN GAP:
|
|
82
|
+
* a unit that does declare it — a project context — decides the order, and only the C++ backend
|
|
83
|
+
* reads that declaration (`bindSpecParams`). In C the int-first spelling of
|
|
84
|
+
* `float mix(float a, int n);` is a redeclaration mwcc refuses: a noncompile, not a wrong program.
|
|
85
|
+
* Ranking by the function's own declared parameters here would close it, once `proto.ts` can
|
|
86
|
+
* size a float. A key in neither file is no slot (null), and sorts after every argument.
|
|
87
|
+
*
|
|
88
|
+
* `holes` applies FOUR RULES to the keys the entry reads, each a refusal or a parameter the ABI
|
|
89
|
+
* proves:
|
|
90
|
+
* 1. A register of the FPU file that arrives at the entry is a float ARGUMENT, or the lift is
|
|
91
|
+
* refused. Nothing else in that file carries a caller's value: a read of `$f4` before any write
|
|
92
|
+
* is uninitialised storage or code this frontend has not followed, and minting a parameter for
|
|
93
|
+
* it would fabricate an argument.
|
|
94
|
+
* 2. Every slot below the highest one read is a parameter too, read or not. Arguments take their
|
|
95
|
+
* registers in order, so `r5` is the third argument whatever became of the first two, and a
|
|
96
|
+
* signature holding only the registers read would bind every later argument one slot low.
|
|
97
|
+
* 3. …and it is minted from the file the ABI puts that slot in. Under `'separate'` each file fills
|
|
98
|
+
* its own holes. Under `'leading'` (MIPS o32) a float argument is in `argRegs[k]` only while
|
|
99
|
+
* arguments 0..k are all floating, so a hole below the highest float read is a float, and one
|
|
100
|
+
* above it an integer — `float f(float a, float b, int c, int d){ return d ? a : b; }` reads
|
|
101
|
+
* `$f12`, `$f14` and `a3`, and its third argument is `a2`. Which file a hole comes from thus
|
|
102
|
+
* depends on what the function reads, which is why `ArgSlots` asks for the holes of a read
|
|
103
|
+
* set rather than for the key of a slot: `int ib(int a, int b){ return b; }` reads only `a1`,
|
|
104
|
+
* and its first argument is `a0`, not `$f12`.
|
|
105
|
+
* 4. Under `'leading'` a float argument still takes the INTEGER slots it shadows — one for a
|
|
106
|
+
* single, two for a double (`floatType`) — so an integer argument register read in one of those
|
|
107
|
+
* slots is a contradiction the ABI does not produce, and is refused rather than ranked. */
|
|
108
|
+
export function fpuArgSlots(
|
|
109
|
+
name: string,
|
|
110
|
+
fpu: Fpu | undefined,
|
|
111
|
+
argRegs: readonly string[],
|
|
112
|
+
isFpKey: (key: string) => boolean,
|
|
113
|
+
floatType: IrType | null = T.f32(),
|
|
114
|
+
): ArgSlots {
|
|
115
|
+
// How many integer slots a float argument takes under `'leading'`: IDO reads the integer after a
|
|
116
|
+
// double two slots on (`double di(double a, int b)` reads b from a2).
|
|
117
|
+
const words = floatType?.kind === 'float' && floatType.width === 64 ? 2 : 1;
|
|
118
|
+
const slotOf = (key: string): { slot: number; float: boolean } | null => {
|
|
119
|
+
const gpr = argRegs.indexOf(key);
|
|
120
|
+
if (gpr >= 0) {
|
|
121
|
+
return { slot: gpr, float: false };
|
|
122
|
+
}
|
|
123
|
+
const fp = fpu?.argRegs.indexOf(key) ?? -1;
|
|
124
|
+
return fp >= 0 ? { slot: fpu?.slots === 'leading' ? fp * words : fp, float: true } : null;
|
|
125
|
+
};
|
|
126
|
+
return {
|
|
127
|
+
slotOf: (key) => {
|
|
128
|
+
const s = slotOf(key);
|
|
129
|
+
return s === null ? null : s.float && fpu?.slots === 'separate' ? argRegs.length + s.slot : s.slot;
|
|
130
|
+
},
|
|
131
|
+
holes: (readKeys) => {
|
|
132
|
+
for (const k of readKeys.filter(isFpKey)) {
|
|
133
|
+
if (!fpu?.argRegs.includes(k)) {
|
|
134
|
+
throw new FrontendUnsupportedError(
|
|
135
|
+
`cannot lift '${name}': ${k} is read before this function writes it, and no floating-point argument ` +
|
|
136
|
+
`arrives there — not modelled`,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const read = readKeys.map(slotOf).filter((s) => s !== null);
|
|
141
|
+
const top = (float: boolean) => Math.max(-1, ...read.filter((s) => s.float === float).map((s) => s.slot));
|
|
142
|
+
const floatTop = top(true);
|
|
143
|
+
const intTop = top(false);
|
|
144
|
+
if (fpu?.slots === 'leading') {
|
|
145
|
+
// The last integer slot the floats read shadow; a double shadows two.
|
|
146
|
+
const floatEnd = floatTop < 0 ? -1 : floatTop + words - 1;
|
|
147
|
+
const shadowed = read.find((s) => !s.float && s.slot <= floatEnd);
|
|
148
|
+
if (shadowed) {
|
|
149
|
+
const k = shadowed.slot;
|
|
150
|
+
throw new FrontendUnsupportedError(
|
|
151
|
+
`cannot lift '${name}': ${argRegs[k]} and ${fpu.argRegs[Math.floor(k / words)]} both carry argument ` +
|
|
152
|
+
`slot ${k} — a floating-point argument takes the integer slots it shadows, so the ABI does not ` +
|
|
153
|
+
'produce this — not modelled',
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
return [
|
|
157
|
+
...fpu.argRegs.slice(0, Math.max(0, floatTop / words)),
|
|
158
|
+
...argRegs.slice(floatEnd + 1, Math.max(floatEnd + 1, intTop)),
|
|
159
|
+
];
|
|
160
|
+
}
|
|
161
|
+
return [...(fpu?.argRegs.slice(0, Math.max(0, floatTop)) ?? []), ...argRegs.slice(0, Math.max(0, intTop))];
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Whether a function RETURNS a float, where no float reaches the integer file: some instruction the
|
|
167
|
+
* frontend decodes writes the float return register, in a block the entry REACHES. Decided over the
|
|
168
|
+
* whole function, before any `ret` is emitted, because a function has one return type on every
|
|
169
|
+
* path — a path that leaves the register untouched returns the float argument that arrived there
|
|
170
|
+
* (PowerPC's `f1` is both), or refuses by `fpuArgSlots`' rule 1. Reachable blocks only: `addi
|
|
171
|
+
* r3,r3,1; blr; fmr f1,f2` returns `r3`, and the `fmr` past the `blr` is on no path of it.
|
|
172
|
+
*
|
|
173
|
+
* And then the INTEGER return register is scratch, even where the function writes it. Both halves
|
|
174
|
+
* rest on one fact: in such a function a float reaches nothing but another float op or the return
|
|
175
|
+
* — every move to memory is refused. A write to the float return register that did not feed the
|
|
176
|
+
* return would therefore be dead in the source, and the compiler does not emit dead arithmetic.
|
|
177
|
+
* IDO's unrolled `float pw(float a, int n)` counts in `v0`.
|
|
178
|
+
*
|
|
179
|
+
* THE FACT FAILS ONCE A FLOAT REACHES THE INTEGER FILE through a compare or a conversion: a float
|
|
180
|
+
* temporary in the home then feeds the compare, not the return (`void vb(float a, float b, int *p)
|
|
181
|
+
* { float c = a * b; if (c < a) *p = 1; }` multiplies into `$f0` on KMC). Such a function is
|
|
182
|
+
* settled once lifted (`settleReturnHome`), never by this. A layer that lets a float reach MEMORY
|
|
183
|
+
* removes the fact for every function, and must settle them all that way (`fpu-lift.test.ts` pins
|
|
184
|
+
* the function that breaks it, `docs/floating-point.md` §6). */
|
|
185
|
+
export function writesFloatReturn(
|
|
186
|
+
instrs: readonly { mnemonic: string; ops: string[] }[],
|
|
187
|
+
decodes: ReadonlySet<string>,
|
|
188
|
+
destKey: (tok: string) => string | null,
|
|
189
|
+
fpu: Fpu | undefined,
|
|
190
|
+
): boolean {
|
|
191
|
+
return (
|
|
192
|
+
fpu !== undefined &&
|
|
193
|
+
instrs.some((ins) => decodes.has(ins.mnemonic) && ins.ops[0] !== undefined && destKey(ins.ops[0]) === fpu.returnReg)
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Which home a function returns in, as far as can be said BEFORE its blocks are filled: the float
|
|
198
|
+
* home, the integer one (which covers a `void` function), or `undecided`, which `settleReturnHome`
|
|
199
|
+
* answers once the lift is done. The function's OWN DECLARATION decides first, where it may prove
|
|
200
|
+
* (`OwnDeclaration.proves`): a floating return type is the float home, any other return the integer
|
|
201
|
+
* one. Without it, a function where no float reaches the integer file (`crosses` false) is
|
|
202
|
+
* decided by `writesFloatReturn`. One where a float does is undecided: a write to the float home
|
|
203
|
+
* no longer says it is the result, and a value in the integer home may be one computed from a
|
|
204
|
+
* float. */
|
|
205
|
+
export interface ReturnHomeRule {
|
|
206
|
+
readonly home: 'float' | 'integer' | 'undecided';
|
|
207
|
+
/** whether the own declaration decided it */
|
|
208
|
+
readonly declared: boolean;
|
|
209
|
+
}
|
|
210
|
+
export function returnHomeRule(
|
|
211
|
+
own: OwnDeclaration | undefined,
|
|
212
|
+
crosses: boolean,
|
|
213
|
+
writesFloat: () => boolean,
|
|
214
|
+
): ReturnHomeRule {
|
|
215
|
+
const p = own?.proves ? own.proto : undefined;
|
|
216
|
+
if (p !== undefined && (declaresVoidReturn(p) || declaresAggregateReturn(p) || p.returns !== undefined)) {
|
|
217
|
+
return { home: p.returns !== undefined && isFloatingSpelling(p.returns) ? 'float' : 'integer', declared: true };
|
|
218
|
+
}
|
|
219
|
+
return { home: crosses ? 'undecided' : writesFloat() ? 'float' : 'integer', declared: false };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** What the float return home holds at a return, read WITHOUT a read: every definition that reaches
|
|
223
|
+
* it, and whether one does on every path (a path that brings none brings the caller's register).
|
|
224
|
+
* A read would leave a definition behind, and where the entry reaches it an entry parameter. Asked
|
|
225
|
+
* once every block is filled. */
|
|
226
|
+
export interface HomeAtReturn {
|
|
227
|
+
readonly every: boolean;
|
|
228
|
+
readonly defs: readonly Value[];
|
|
229
|
+
}
|
|
230
|
+
export function floatHomeAt(ssa: Pick<SsaBuilder, 'everyReachingDef'>, home: string, bi: number): HomeAtReturn {
|
|
231
|
+
const defs: Value[] = [];
|
|
232
|
+
const every = ssa.everyReachingDef(home, bi, (v) => {
|
|
233
|
+
defs.push(v);
|
|
234
|
+
return true;
|
|
235
|
+
});
|
|
236
|
+
return { every, defs };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** Whether an undecided function's float home holds a FLOAT on every path into every return, so
|
|
240
|
+
* that `settleReturnHome` may weigh it: then the frontend reads it at each return before the SSA is
|
|
241
|
+
* finished, and hands it on as the `ret`'s LAST operand, where the SSA's own clean-up keeps it
|
|
242
|
+
* current (a join it resolves away is replaced there, and one nothing read would be pruned). */
|
|
243
|
+
export function carriesFloatHome(rule: ReturnHomeRule, homes: readonly HomeAtReturn[]): boolean {
|
|
244
|
+
return rule.home === 'undecided' && homes.every((h) => h.every && h.defs.every((v) => v.type.kind === 'float'));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** SETTLE THE RETURN HOME of a lifted function, and refuse where the bytes do not decide it.
|
|
248
|
+
* `sites` are its returns, each reading what the INTEGER home rule gave it (the operand a `ret`
|
|
249
|
+
* reads where the function returns a word), plus, where `carriesFloat`, the float home's value
|
|
250
|
+
* last; `homes` is what the float home holds at each (`floatHomeAt`); `movedIn` the words a move
|
|
251
|
+
* from the integer file put in an FPU register. Run after the SSA is finished.
|
|
252
|
+
*
|
|
253
|
+
* An INTEGER WORD in the float home at a return refuses, unless the integer home proves itself
|
|
254
|
+
* (below) or the own declaration says the return is no float: `mtc1 $zero,$f0` is how IDO, KMC and
|
|
255
|
+
* GCC 2.7.2 return `0.0f`, and `mtc1 a1,$f0` how they return a float that arrived in a GPR, and
|
|
256
|
+
* either is the word's bits as a float, which is not modelled. KMC's `trunc.w.s $f0,$f12; mfc1
|
|
257
|
+
* v0,$f0` leaves a word there too, and returns it in `v0`. A word an `mtc1` moved in refuses even
|
|
258
|
+
* where the integer home proves itself, since that proof reads the same value. A float return whose
|
|
259
|
+
* `ret` reads a word refuses as well.
|
|
260
|
+
*
|
|
261
|
+
* An UNDECIDED function returns the home whose value at every return was computed only to be
|
|
262
|
+
* returned (frontend/dead-reads.ts `computedOnlyToReturn`): an optimizing compiler emits no write
|
|
263
|
+
* whose value nothing reads, so a value that survives with only a return to read it is the
|
|
264
|
+
* result. `int vi2(float a, float b){ float c = a + b; if (c < a) return 3; … }` adds into `$f0`,
|
|
265
|
+
* which the compares read, and returns the `li` in `v0`. Where the float home holds no float at
|
|
266
|
+
* some return, the function returns none — unless the home is an argument register (`f1`), whose
|
|
267
|
+
* float may be the caller's, and then the integer home must prove itself. Where neither home
|
|
268
|
+
* proves, or both do, the lift refuses: `void vb(float a, float b, int *p){ float c = a * b; if (c
|
|
269
|
+
* < a) *p = 1; }` and `float vb(…){ …; return c; }` are one object, and so are `float mx(float a,
|
|
270
|
+
* float b, int *p){ if (a < b) *p = 1; return a; }` and the same function returning `p`. The own
|
|
271
|
+
* declaration decides those (`returnHomeRule`). */
|
|
272
|
+
export function settleReturnHome(
|
|
273
|
+
name: string,
|
|
274
|
+
fn: Fn,
|
|
275
|
+
rule: ReturnHomeRule,
|
|
276
|
+
sites: readonly { op: Op }[],
|
|
277
|
+
homes: readonly HomeAtReturn[],
|
|
278
|
+
carriesFloat: boolean,
|
|
279
|
+
fpu: Fpu,
|
|
280
|
+
intReg: string,
|
|
281
|
+
movedIn: ReadonlySet<Value> = new Set(),
|
|
282
|
+
): void {
|
|
283
|
+
const floatAt = carriesFloat ? sites.map(({ op }) => op.operands.pop()!) : undefined;
|
|
284
|
+
const isFloat = (v: Value) => v.type.kind === 'float';
|
|
285
|
+
if (rule.home === 'float') {
|
|
286
|
+
if (sites.some(({ op }) => !op.operands.every(isFloat))) {
|
|
287
|
+
throw new FrontendUnsupportedError(
|
|
288
|
+
`cannot lift '${name}': a return reads an integer word from ${fpu.returnReg}, the float return register`,
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
// A word MOVED IN from the integer file (`mtc1 v0,$f0`) is one value with its source in the IR, so
|
|
294
|
+
// the integer home would seem to prove itself: `float u(float a, float b){ U u; u.i = a < b ? 1 :
|
|
295
|
+
// 2; return u.f; }` computes into `v0` and returns it in `$f0`. KMC's `f2i` leaves the word by
|
|
296
|
+
// `trunc.w` and moves it OUT, which is the other direction.
|
|
297
|
+
if (!rule.declared && homes.some((h) => h.defs.some((v) => movedIn.has(v)))) {
|
|
298
|
+
throw new FrontendUnsupportedError(
|
|
299
|
+
`cannot lift '${name}': ${fpu.returnReg}, the float return register, holds an integer word at a return ` +
|
|
300
|
+
"that an 'mtc1' moved in from the integer file — the word's bits as a float are not modelled",
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
const computed = computedOnlyToReturn(fn.blocks);
|
|
304
|
+
const intProven = sites.every(({ op }) => op.operands.length === 1 && computed(op.operands[0]));
|
|
305
|
+
if (!rule.declared && !intProven && homes.some((h) => !h.defs.every(isFloat))) {
|
|
306
|
+
throw new FrontendUnsupportedError(
|
|
307
|
+
`cannot lift '${name}': ${fpu.returnReg}, the float return register, holds an integer word at a return, ` +
|
|
308
|
+
`and ${intReg} holds no value computed only to be returned — the word's bits as a float are not modelled`,
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
if (rule.home === 'integer' || (floatAt === undefined && !fpu.argRegs.includes(fpu.returnReg))) {
|
|
312
|
+
if (floatAt !== undefined) {
|
|
313
|
+
pruneDeadParams(fn);
|
|
314
|
+
}
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
const floatProven = floatAt !== undefined && floatAt.every(computed);
|
|
318
|
+
if (floatProven !== intProven) {
|
|
319
|
+
if (floatProven) {
|
|
320
|
+
sites.forEach(({ op }, i) => {
|
|
321
|
+
op.operands = [floatAt![i]];
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
// the home not returned may have left a join nothing reads now
|
|
325
|
+
pruneDeadParams(fn);
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
throw new FrontendUnsupportedError(
|
|
329
|
+
`cannot lift '${name}': a float reaches the integer file (a compare or a conversion), and then whether it ` +
|
|
330
|
+
`returns ${fpu.returnReg}, ${intReg} or nothing is not decided by the bytes — ` +
|
|
331
|
+
(floatProven
|
|
332
|
+
? 'each holds a value computed only to be returned'
|
|
333
|
+
: 'neither holds a value computed only to be returned') +
|
|
334
|
+
', and no declaration of its own says',
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** Refuse a function where an FPU register's INTEGER WORD meets a float, or its float meets an
|
|
339
|
+
* integer, across a block boundary. Within a block each read is checked as it is decoded; a read
|
|
340
|
+
* that reaches an earlier block comes back as a block parameter, which the builder types as the
|
|
341
|
+
* file's float before it knows what flows in (`makeSsaBuilder`'s `keyType`). So the check runs
|
|
342
|
+
* again over the finished function: a float op, a compare or a conversion out of a float over
|
|
343
|
+
* anything but a float, a conversion into one over a float, and an edge passing a word to a float
|
|
344
|
+
* parameter or the reverse. `float g(float a, float b, float c)` on O32 hands `c` over in `a2`,
|
|
345
|
+
* which `mtc1` moves in as a word: computing on it as a float is a bit cast this does not model. */
|
|
346
|
+
export function refuseFpWordCrossings(name: string, fn: Fn): void {
|
|
347
|
+
const fail = (what: string): never => {
|
|
348
|
+
throw new FrontendUnsupportedError(
|
|
349
|
+
`cannot lift '${name}': ${what} — an FPU register holds a float or an integer word, and this one meets the other`,
|
|
350
|
+
);
|
|
351
|
+
};
|
|
352
|
+
const isFloat = (v: Value) => v.type.kind === 'float';
|
|
353
|
+
for (const b of fn.blocks) {
|
|
354
|
+
for (const op of b.ops) {
|
|
355
|
+
const takesFloats =
|
|
356
|
+
(FLOAT_OPS.has(op.opcode) && op.opcode !== 'fconst') ||
|
|
357
|
+
FLOAT_TO_INT.has(op.opcode) ||
|
|
358
|
+
NEGATED_FCMP[op.opcode] !== undefined;
|
|
359
|
+
if (takesFloats && !op.operands.every(isFloat)) {
|
|
360
|
+
fail(`'${op.opcode}' is handed an integer word`);
|
|
361
|
+
}
|
|
362
|
+
if (INT_TO_FLOAT.has(op.opcode) && op.operands.some(isFloat)) {
|
|
363
|
+
fail(`'${op.opcode}' is handed a float`);
|
|
364
|
+
}
|
|
365
|
+
for (const s of op.successors) {
|
|
366
|
+
s.args.forEach((a, i) => {
|
|
367
|
+
if (isFloat(a) !== (s.block.params[i]?.type.kind === 'float')) {
|
|
368
|
+
fail('a block boundary passes one where the other arrives');
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|