@asmlift/core 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -24
- package/package.json +1 -1
- package/src/aggregate.ts +149 -0
- package/src/backend/c.ts +1 -0
- package/src/backend/cfamily.ts +165 -50
- package/src/backend/cpp.ts +2 -1
- package/src/backend/pascal.ts +31 -12
- package/src/contracts.ts +173 -16
- package/src/declare.ts +69 -20
- package/src/frontend/asmdata.ts +7 -1
- package/src/frontend/call-plan.ts +964 -60
- package/src/frontend/dead-reads.ts +628 -0
- package/src/frontend/device-pins.ts +23 -4
- package/src/frontend/emit.ts +4 -3
- package/src/frontend/fpu.ts +224 -17
- package/src/frontend/frame-objects.ts +1232 -192
- package/src/frontend/frontend.ts +64 -3
- package/src/frontend/high-half.ts +53 -4
- package/src/frontend/jump-table.ts +530 -0
- package/src/frontend/local-object.ts +158 -32
- package/src/frontend/mips.ts +2076 -215
- package/src/frontend/own-struct-return.ts +500 -0
- package/src/frontend/ppc.ts +2711 -232
- package/src/frontend/register-struct-call.ts +484 -0
- package/src/frontend/reloc-symbol.ts +22 -7
- package/src/frontend/ssa.ts +1460 -44
- package/src/frontend/stackargs.ts +106 -57
- package/src/frontend/thumb.ts +2044 -318
- package/src/ir/alias.ts +42 -0
- package/src/ir/bits.ts +681 -1
- package/src/ir/core.ts +100 -5
- package/src/ir/discipline.ts +86 -21
- package/src/ir/entry.ts +159 -0
- package/src/{structure → ir}/loops.ts +6 -2
- package/src/ir/opcodes.ts +109 -9
- package/src/ir/types.ts +86 -2
- package/src/ir/verify.ts +80 -11
- package/src/l3/ast.ts +290 -18
- package/src/l3/basecse.ts +33 -5
- package/src/l3/constinit.ts +382 -0
- package/src/l3/dce.ts +2 -2
- package/src/l3/initfirst.ts +42 -11
- package/src/l3/intcell.ts +2 -1
- package/src/l3/narrowdecl.ts +77 -36
- package/src/l3/pollguard.ts +8 -2
- package/src/l3/ptrcell.ts +31 -34
- package/src/l3/ptrfield.ts +123 -8
- package/src/l3/regspell.ts +9 -4
- package/src/l3/reindex.ts +283 -96
- package/src/l3/storage.ts +98 -1
- package/src/l3/symbol-refs.ts +84 -6
- package/src/l3/tailmerge.ts +4 -2
- package/src/l3/typing.ts +108 -15
- package/src/l3/unreduce.ts +6 -5
- package/src/l3/volatileptr.ts +17 -54
- package/src/mangle.ts +30 -0
- package/src/pattern/engine.ts +361 -73
- package/src/pipeline.ts +541 -24
- package/src/proto-context.ts +905 -45
- package/src/proto.ts +555 -40
- package/src/raise/aggmembers.ts +290 -0
- package/src/raise/arrays.ts +5 -1
- package/src/raise/declared-volatile.ts +207 -31
- package/src/raise/floathelpers.ts +254 -33
- package/src/raise/gvn.ts +3 -3
- package/src/raise/latch.ts +162 -6
- package/src/raise/memberarrays.ts +10 -4
- package/src/raise/offsetnames.ts +10 -26
- package/src/raise/pairparams.ts +33 -0
- package/src/raise/paramwidth.ts +196 -6
- package/src/raise/pre-recovery.ts +102 -19
- package/src/raise/pre-structure.ts +54 -0
- package/src/raise/recover.ts +12 -5
- package/src/raise/reroll.ts +694 -0
- package/src/raise/shortcircuit.ts +26 -12
- package/src/raise/softdiv.ts +1 -1
- package/src/raise/storesign.ts +153 -0
- package/src/raise/struct-arrays.ts +30 -11
- package/src/raise/structs.ts +396 -33
- package/src/raise/truncload.ts +4 -3
- package/src/raise/unthread.ts +400 -0
- package/src/rank-declare.ts +118 -12
- package/src/rank-variations.ts +89 -31
- package/src/rank.ts +307 -54
- package/src/runtime-helpers.ts +140 -22
- package/src/structure/analysis.ts +165 -2
- package/src/structure/arith-ops.ts +23 -0
- package/src/structure/globalaccess.ts +111 -9
- package/src/structure/hazards.ts +16 -64
- package/src/structure/local-statics.ts +145 -9
- package/src/structure/namecoalesce.ts +1 -1
- package/src/structure/pointer-spelling.ts +1106 -0
- package/src/structure/redundant-test.ts +1 -1
- package/src/structure/structure.ts +2180 -1054
- package/src/structure/switch-recover.ts +244 -100
- package/src/symbols.ts +6 -0
- package/src/target.ts +765 -52
- package/src/trace.ts +26 -8
- package/src/variation-definitions.ts +126 -14
- package/src/variation-gates.ts +2 -0
- package/src/variation-tokens.ts +5 -1
|
@@ -33,9 +33,10 @@
|
|
|
33
33
|
*
|
|
34
34
|
* A FUNCTION ACCEPTED OBJECT BY OBJECT pins every device read, and of its device stores only those
|
|
35
35
|
* of the first kind (`overwritten`). A read is pinned because a plain one is lost either way:
|
|
36
|
-
* nothing uses the `dmaRegs[2];` that ends a DMA macro, so the lift
|
|
37
|
-
*
|
|
38
|
-
*
|
|
36
|
+
* nothing uses the `dmaRegs[2];` that ends a DMA macro, so the lift would drop it outright (the
|
|
37
|
+
* dead-read pin, frontend/dead-reads.ts, keeps such a read in every function), and a poll is
|
|
38
|
+
* hoisted as above, which only this pin prevents. Its other device stores stay plain, their
|
|
39
|
+
* qualified spelling left to `/vol-store`'s candidate (l3/volstore.ts): pinned in the structured tree they are pinned in every
|
|
39
40
|
* variation, and the ones that home the base or un-reduce a loop refuse a qualified base, which
|
|
40
41
|
* costs `synthetic:dmastride` and `synthetic:dmaptrsrc` their matches. That is also why the two
|
|
41
42
|
* policies stay two rows: the one-object row's every-store pin is what keeps `threeFills`' nine
|
|
@@ -53,6 +54,7 @@
|
|
|
53
54
|
* (target.ts `readCouldBeVolatile`): on a compiler that sign-extends a qualified narrow read in a
|
|
54
55
|
* register, a sign-extending load is a plain read in the source, and qualified it recompiles to
|
|
55
56
|
* another instruction sequence. */
|
|
57
|
+
import { type RangeCtx, type StridedRange, incomingArgs, stridedRange } from '../ir/bits';
|
|
56
58
|
import type { Block, Op, Value } from '../ir/core';
|
|
57
59
|
import type { SymbolMap } from '../symbols';
|
|
58
60
|
import { type TargetDescription, readCouldBeVolatile } from '../target';
|
|
@@ -64,6 +66,9 @@ export interface LiteralAddresses {
|
|
|
64
66
|
constOf(v: Value): number | undefined;
|
|
65
67
|
/** the literal address `v` denotes, or undefined when this cannot say */
|
|
66
68
|
literalAddrOf(v: Value): number | undefined;
|
|
69
|
+
/** every address `v` may denote, as a strided unsigned interval, or undefined when this cannot
|
|
70
|
+
* bound it */
|
|
71
|
+
addressRangeOf(v: Value): StridedRange | undefined;
|
|
67
72
|
}
|
|
68
73
|
|
|
69
74
|
/** `literalAddrOf`: `const` is the bare pool word, `gaddr` is the same word after the symbol map
|
|
@@ -128,7 +133,21 @@ export function literalAddresses(irBlocks: readonly Block[], symbols: SymbolMap
|
|
|
128
133
|
}
|
|
129
134
|
return undefined;
|
|
130
135
|
};
|
|
131
|
-
|
|
136
|
+
// `addressRangeOf`: where `literalAddrOf` names one address, this names every address a value
|
|
137
|
+
// may hold (ir/bits.ts `stridedRange`), a placed name being the one leaf only this module reads.
|
|
138
|
+
const rangeCtx: RangeCtx = {
|
|
139
|
+
defs: defOf,
|
|
140
|
+
constOf,
|
|
141
|
+
incoming: incomingArgs(irBlocks),
|
|
142
|
+
entry: new Set(irBlocks[0]?.params ?? []),
|
|
143
|
+
pointOf: (v) => (defOf.get(v)?.opcode === 'gaddr' ? literalAddrOf(v) : undefined),
|
|
144
|
+
};
|
|
145
|
+
return {
|
|
146
|
+
defOf,
|
|
147
|
+
constOf,
|
|
148
|
+
literalAddrOf: (v) => literalAddrOf(v),
|
|
149
|
+
addressRangeOf: (v) => stridedRange(rangeCtx, v),
|
|
150
|
+
};
|
|
132
151
|
}
|
|
133
152
|
|
|
134
153
|
/** Which device stores a policy pins, beside every device read. */
|
package/src/frontend/emit.ts
CHANGED
|
@@ -45,13 +45,14 @@ export function mkEmitKit(ops: Op[], write: (reg: string, v: Value) => void): Em
|
|
|
45
45
|
|
|
46
46
|
/** Emit a recovered jump-table dispatch: N case successors followed by the default (LAST), with
|
|
47
47
|
* the dense 0..N-1 `cases` list derived here — the single home of the case/successor
|
|
48
|
-
* alignment invariant.
|
|
49
|
-
|
|
48
|
+
* alignment invariant. `exhaustive`: the scrutinee is already known to be in 0..N-1 here (see
|
|
49
|
+
* the opcode's attr). */
|
|
50
|
+
export function pushSwitchBr(ops: Op[], scrut: Value, successors: Successor[], exhaustive = false): void {
|
|
50
51
|
ops.push(
|
|
51
52
|
mkOp('switch_br', {
|
|
52
53
|
operands: [scrut],
|
|
53
54
|
successors,
|
|
54
|
-
attrs: { cases: successors.slice(0, -1).map((_, k) => k) },
|
|
55
|
+
attrs: { cases: successors.slice(0, -1).map((_, k) => k), ...(exhaustive ? { exhaustive: true } : {}) },
|
|
55
56
|
}),
|
|
56
57
|
);
|
|
57
58
|
}
|
package/src/frontend/fpu.ts
CHANGED
|
@@ -8,10 +8,16 @@
|
|
|
8
8
|
// the decode and neither of these, `float fadd(float a, float b){ return a + b; }` lifts on every
|
|
9
9
|
// FPU target as `void fadd(s32 a0, s32 a1) { return; }` — two phantom integer parameters and a dead
|
|
10
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';
|
|
11
14
|
import { type IrType, T } from '../ir/types';
|
|
15
|
+
import { declaresAggregateReturn, declaresVoidReturn, floatingSpelling, isFloatingSpelling } from '../proto';
|
|
12
16
|
import type { TargetDescription } from '../target';
|
|
17
|
+
import { computedOnlyToReturn } from './dead-reads';
|
|
13
18
|
import { FrontendUnsupportedError } from './errors';
|
|
14
|
-
import type {
|
|
19
|
+
import type { OwnDeclaration } from './frontend';
|
|
20
|
+
import type { ArgSlots, SsaBuilder } from './ssa';
|
|
15
21
|
|
|
16
22
|
export type Fpu = NonNullable<TargetDescription['fpu']>;
|
|
17
23
|
|
|
@@ -27,6 +33,28 @@ export function fpPrecision(
|
|
|
27
33
|
return widths.size > 1 ? null : widths.has(64) ? T.f64() : widths.has(32) ? T.f32() : T.fUnstated();
|
|
28
34
|
}
|
|
29
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
|
+
|
|
30
58
|
/** Refuse a function computing in both precisions, where the frontend DECODES a float instruction of
|
|
31
59
|
* it, so a refusal the stream reaches first keeps its own reason. The SSA builder types a register
|
|
32
60
|
* per key, not per value, and the rounding between the precisions (a conversion, PowerPC's `frsp`,
|
|
@@ -135,25 +163,25 @@ export function fpuArgSlots(
|
|
|
135
163
|
};
|
|
136
164
|
}
|
|
137
165
|
|
|
138
|
-
/** Whether a function RETURNS a float
|
|
139
|
-
* register, in a block the entry REACHES. Decided over the
|
|
140
|
-
* emitted, because a function has one return type on every
|
|
141
|
-
* untouched returns the float argument that arrived there
|
|
142
|
-
* `fpuArgSlots`' rule 1. Reachable blocks only: `addi
|
|
143
|
-
* the `fmr` past the `blr` is on no path of it.
|
|
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.
|
|
144
172
|
*
|
|
145
173
|
* And then the INTEGER return register is scratch, even where the function writes it. Both halves
|
|
146
|
-
* rest on one fact:
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
* result, meanwhile, could only have come from a float by a conversion, which refuses. IDO's
|
|
151
|
-
* unrolled `float pw(float a, int n)` is the witness for the second half: it counts in `v0`.
|
|
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`.
|
|
152
178
|
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
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). */
|
|
157
185
|
export function writesFloatReturn(
|
|
158
186
|
instrs: readonly { mnemonic: string; ops: string[] }[],
|
|
159
187
|
decodes: ReadonlySet<string>,
|
|
@@ -165,3 +193,182 @@ export function writesFloatReturn(
|
|
|
165
193
|
instrs.some((ins) => decodes.has(ins.mnemonic) && ins.ops[0] !== undefined && destKey(ins.ops[0]) === fpu.returnReg)
|
|
166
194
|
);
|
|
167
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
|
+
}
|