@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,464 @@
|
|
|
1
|
+
// asmlift — the soft-float boundary (L1 recognition): where integer words become floats.
|
|
2
|
+
//
|
|
3
|
+
// Four things cross it. A runtime helper whose op computes on floats (`isFloatHelper`) — the peer of
|
|
4
|
+
// `raise/widehelpers.ts`, gated by the target's runtime table rather than a hardware capability.
|
|
5
|
+
// A parameter an ordinary callee declares `double` (the frontend's `doubles` on the call). The
|
|
6
|
+
// result of an ordinary callee declared to return a floating type (its `floatReturn`). And a
|
|
7
|
+
// double literal, two constant words, where the target states their order
|
|
8
|
+
// (`TargetDescription.doubleArgWords`) and its compiler reads a literal back
|
|
9
|
+
// (`compilerBehaviors.roundTripsDoubleLiterals`). What sets all four apart from the integer
|
|
10
|
+
// helpers is the value's KIND: the words are bits in integer registers, and the pass re-types them
|
|
11
|
+
// as floats, which only data flow may justify — see the function below.
|
|
12
|
+
import { constWord } from '../ir/bits';
|
|
13
|
+
import { Fn, Op, Value, mkOp, mkValue, replaceAllUsesWith } from '../ir/core';
|
|
14
|
+
import { doubleBits, doubleOf } from '../ir/float-bits';
|
|
15
|
+
import { type Opcode, fcmpOfTruth } from '../ir/opcodes';
|
|
16
|
+
import { T, intWidth } from '../ir/types';
|
|
17
|
+
import { arrivesAsDeclared, floatSidesOf, helperOp, isFloatHelper, lookupHelper } from '../runtime-helpers';
|
|
18
|
+
import type { TargetDescription } from '../target';
|
|
19
|
+
import { RaiseUnsupportedError } from './errors';
|
|
20
|
+
import { isArgumentPair } from './pairparams';
|
|
21
|
+
|
|
22
|
+
/** Rewrite the float-arithmetic helper calls to the float ops, in place, each value at the width its
|
|
23
|
+
* helper's signature states, and hand a parameter the callee declares `double` (the frontend's
|
|
24
|
+
* `doubles` on the call) a double. Returns whether anything changed.
|
|
25
|
+
*
|
|
26
|
+
* A SOFT-FLOAT VALUE IS BITS IN INTEGER REGISTERS, AND ONLY DATA FLOW MAY READ THEM AS A FLOAT. A
|
|
27
|
+
* single travels in one register; a double in the pair a long long travels in, which the frontend
|
|
28
|
+
* builds the way it builds a long long's, `concat(lo = r0, hi = r1)` — but agbcc stores a double
|
|
29
|
+
* high word first (thumb.h:335 `FLOAT_WORDS_BIG_ENDIAN`): r0 holds the sign and exponent. Moving
|
|
30
|
+
* the value whole keeps it right, so a float may come only from where the ABI hands one over
|
|
31
|
+
* whole — this function's argument slot, or two consecutive ones for a double, or another of these
|
|
32
|
+
* helpers' results — and may go only into another of them, the return, or a parameter a callee
|
|
33
|
+
* declares `double`. Anything else refuses, and the call stays for `refuseUnmodelledHelpers` to
|
|
34
|
+
* gap:
|
|
35
|
+
* - an operand built from a load or two unrelated words, whose halves the int64 naming would put
|
|
36
|
+
* in the wrong order. Two CONSTANT words are the exception: they are a literal, read in the
|
|
37
|
+
* target's word order into an `fconst` (`a + 1.5` stages `0x3ff80000` in r2, its high word);
|
|
38
|
+
* - an argument the function also reads on its own, as a word or as one half of a pair;
|
|
39
|
+
* - a result read as a word or a half (`*(int *)&c` is a double's HIGH word, in r0), passed to
|
|
40
|
+
* any other call, or carried across an edge;
|
|
41
|
+
* - a result NOTHING reads. Folded, it would be a dead float op, and `dce` would delete the
|
|
42
|
+
* arithmetic. Optimising (-O1, -O2), agbcc deletes a helper call whose result is dead
|
|
43
|
+
* (`s = a * b; g(); return 0;` calls no `__mulsf3`), so the result's one reader was a later call
|
|
44
|
+
* that took it in r0, and a guessed arity does not count a register a call wrote (frontend/ssa.ts
|
|
45
|
+
* `fallbackArgc`, `trimClobberedCallArgs`): `g1(a * b)` with `g1` undeclared reads as
|
|
46
|
+
* `__mulsf3(); return g1();`, and folded would print `return g1();`. A double's pair refuses
|
|
47
|
+
* that shape at the frontend, naming the half the next call reads; a single is one word, so it
|
|
48
|
+
* reaches here. At -O0 the rule is not asked: agbcc stores the result to the frame, which reads
|
|
49
|
+
* it, and nothing folds there, its arguments being read from frame homes. A frontend that reads
|
|
50
|
+
* those homes must re-measure this. A result some other op ALSO reads is not refused:
|
|
51
|
+
* `s = a * b; g(s); return s;` and `s = a * b; g(); return s;` are the same bytes on agbcc, and
|
|
52
|
+
* a declaration of `g` closes the trim's `g()` reading (`trimClobberedCallArgs`).
|
|
53
|
+
* A call refused takes every call its result feeds or is fed by with it, so what folds is a closed
|
|
54
|
+
* set of floats and verify's float rule holds by construction.
|
|
55
|
+
*
|
|
56
|
+
* A CONVERSION HAS AN INTEGER ON ONE SIDE (`floatSidesOf`), and only its float side is held to the
|
|
57
|
+
* rule above: `(float)i` converts whatever integer `i` is, a parameter, a load or a sum, and
|
|
58
|
+
* `(int)x` hands an integer to anything. Its result must still be read, by the dead-result rule,
|
|
59
|
+
* and a float the function also reads as a word is no float source for it either. */
|
|
60
|
+
export function recognizeFloatHelpers(fn: Fn, target: TargetDescription): boolean {
|
|
61
|
+
const table = target.runtimeHelpers ?? {};
|
|
62
|
+
let sites: Op[] = [];
|
|
63
|
+
const consumers = new Map<Op, readonly number[]>();
|
|
64
|
+
const def = new Map<Value, Op>();
|
|
65
|
+
const users = new Map<Value, Array<Op | null>>(); // null: an edge argument
|
|
66
|
+
const returners = new Map<Op, string>(); // ordinary calls declared to return a floating type
|
|
67
|
+
const use = (v: Value, by: Op | null) => users.set(v, [...(users.get(v) ?? []), by]);
|
|
68
|
+
const helperOf = (op: Op) => (op.opcode === 'call' ? lookupHelper(table, String(op.attrs.target)) : undefined);
|
|
69
|
+
const sidesOf = (op: Op) => floatSidesOf(helperOf(op)!);
|
|
70
|
+
for (const block of fn.blocks) {
|
|
71
|
+
for (const op of block.ops) {
|
|
72
|
+
op.results.forEach((r) => def.set(r, op));
|
|
73
|
+
op.operands.forEach((o) => use(o, op));
|
|
74
|
+
op.successors.forEach((s) => s.args.forEach((a) => use(a, null)));
|
|
75
|
+
const helper = helperOf(op);
|
|
76
|
+
if (
|
|
77
|
+
helper &&
|
|
78
|
+
isFloatHelper(helper) &&
|
|
79
|
+
arrivesAsDeclared(
|
|
80
|
+
helper,
|
|
81
|
+
op.operands.map((o) => o.type),
|
|
82
|
+
op.results.map((r) => r.type),
|
|
83
|
+
)
|
|
84
|
+
) {
|
|
85
|
+
sites.push(op);
|
|
86
|
+
} else if (!helper && op.opcode === 'call' && Array.isArray(op.attrs.doubles)) {
|
|
87
|
+
consumers.set(op, op.attrs.doubles as number[]);
|
|
88
|
+
}
|
|
89
|
+
if (!helper && op.opcode === 'call' && typeof op.attrs.floatReturn === 'string') {
|
|
90
|
+
returners.set(op, op.attrs.floatReturn);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (!sites.length && !consumers.size && !returners.size) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
// A projection nothing reads is not a read of a half: `dce` runs ahead of this pass only after a
|
|
98
|
+
// fold that changed the IR, so the frontend's split of the result back into its registers may stand.
|
|
99
|
+
const deadHalf = (u: Op | null) =>
|
|
100
|
+
u !== null && (u.opcode === 'lo32' || u.opcode === 'hi32') && !users.has(u.results[0]);
|
|
101
|
+
// A USE THAT TAKES THE VALUE AS A FLOAT: a helper the fold keeps, or a parameter the callee declares
|
|
102
|
+
// `double` (the frontend's `doubles`) — and only that parameter, so the same value handed to the
|
|
103
|
+
// same call as a word is a word read.
|
|
104
|
+
const floatUse = (u: Op | null, v: Value, calls: ReadonlySet<Op>): boolean =>
|
|
105
|
+
u !== null &&
|
|
106
|
+
((calls.has(u) && sidesOf(u).operands) ||
|
|
107
|
+
(consumers.has(u) && u.operands.every((o, i) => o !== v || consumers.get(u)!.includes(i))));
|
|
108
|
+
const floatsOnly = (v: Value, calls: ReadonlySet<Op>) => (users.get(v) ?? []).every((u) => floatUse(u, v, calls));
|
|
109
|
+
const entry = fn.blocks[0].params;
|
|
110
|
+
// A `concat` of the same two slots in the same order as `d`: the frontend builds one afresh at each
|
|
111
|
+
// call it hands the pair to, so `g(x); g(x)` reads one parameter through two of them.
|
|
112
|
+
const samePair = (u: Op | null, d: Op): u is Op =>
|
|
113
|
+
u?.opcode === 'concat' && u.operands[0] === d.operands[0] && u.operands[1] === d.operands[1];
|
|
114
|
+
// The argument slot itself, or the pair of them `isArgumentPair` names, read by nothing but
|
|
115
|
+
// `concat`s of that pair, each taken as a float; `arrivesAsDeclared` has already held each operand
|
|
116
|
+
// to its parameter's width.
|
|
117
|
+
const argument = (v: Value, calls: ReadonlySet<Op>): boolean => {
|
|
118
|
+
const d = def.get(v);
|
|
119
|
+
if (d === undefined) {
|
|
120
|
+
return entry.includes(v) && floatsOnly(v, calls);
|
|
121
|
+
}
|
|
122
|
+
return (
|
|
123
|
+
isArgumentPair(entry, d, (p) => users.get(p)!.filter((u) => !samePair(u, d)).length + 1) &&
|
|
124
|
+
users.get(d.operands[0])!.every((u) => samePair(u, d) && floatsOnly(u.results[0], calls))
|
|
125
|
+
);
|
|
126
|
+
};
|
|
127
|
+
// A SINGLE AN ORDINARY CALLEE IS DECLARED TO RETURN is one whole word in the return register, so
|
|
128
|
+
// it is a float source as a helper's result is. A double is not: the frontend reads one word of
|
|
129
|
+
// it (`declaredReturnWidth` sizes no floating type), and that word is not the value.
|
|
130
|
+
const declaredSingle = (d: Op) => returners.get(d) === 'float' && intWidth(d.results[0].type) === 32;
|
|
131
|
+
const fromCall = (v: Value, calls: ReadonlySet<Op>) => {
|
|
132
|
+
const d = def.get(v);
|
|
133
|
+
return d !== undefined && ((calls.has(d) && sidesOf(d).result) || declaredSingle(d));
|
|
134
|
+
};
|
|
135
|
+
// A CONSTANT WORD, as a compiler may build one in two instructions: agbcc moves a word that is a
|
|
136
|
+
// shifted byte as `mov`+`lsl` (thumb.md:66, `thumb_shiftable_const` at thumb.c:61) and one in
|
|
137
|
+
// -255..-1 as `mov`+`neg` (thumb.md:88), so the high word of 2.0 reaches r3 as `0x80 << 23`. Its
|
|
138
|
+
// value, or undefined where `v` is no constant — the shared word evaluator's answer (ir/bits.ts).
|
|
139
|
+
const words = { defs: def };
|
|
140
|
+
const wordOf = (v: Value): number | undefined => constWord(words, v) ?? undefined;
|
|
141
|
+
// A LITERAL IS TWO CONSTANT WORDS, HIGH WORD FIRST on a target whose double crosses a call in
|
|
142
|
+
// general words (`TargetDescription.doubleArgWords`) — the order that makes the long long naming
|
|
143
|
+
// of the same pair another number. Its bits, or undefined where the pair is no literal, the target
|
|
144
|
+
// states no such double, its compiler is not known to read the literal back
|
|
145
|
+
// (`compilerBehaviors.roundTripsDoubleLiterals`), or the double is not finite — which no C literal
|
|
146
|
+
// spells.
|
|
147
|
+
const literalBits = (v: Value): string | undefined => {
|
|
148
|
+
const d = def.get(v);
|
|
149
|
+
const [first, second] = (d?.opcode === 'concat' ? d.operands : []).map(wordOf);
|
|
150
|
+
if (
|
|
151
|
+
target.doubleArgWords === undefined ||
|
|
152
|
+
target.compilerBehaviors.roundTripsDoubleLiterals !== true ||
|
|
153
|
+
first === undefined ||
|
|
154
|
+
second === undefined
|
|
155
|
+
) {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
const bits = doubleBits(first, second);
|
|
159
|
+
return Number.isFinite(doubleOf(bits)) ? bits : undefined;
|
|
160
|
+
};
|
|
161
|
+
const literal = (v: Value, calls: ReadonlySet<Op>) => literalBits(v) !== undefined && floatsOnly(v, calls);
|
|
162
|
+
const source = (v: Value, calls: ReadonlySet<Op>) => argument(v, calls) || fromCall(v, calls) || literal(v, calls);
|
|
163
|
+
// A COMPARE FOLDS THROUGH THE TESTS OF ITS RESULT, which is libgcc's three-way answer: each reader
|
|
164
|
+
// must be a test of that integer against 0, and the compare the test then computes is read off
|
|
165
|
+
// the helper's contract — the sign that orders the operands, and its value on a NaN (`unordered`)
|
|
166
|
+
// — against `FCMP_TRUTH`. A test whose answer is no compare of the operands (always true, or true
|
|
167
|
+
// on both orders but not on equality), or a reader that is no test, refuses the helper.
|
|
168
|
+
const tests = new Map<Op, CompareTest[]>();
|
|
169
|
+
sites = sites.filter((op) => {
|
|
170
|
+
const unordered = helperOf(op)!.unordered;
|
|
171
|
+
if (unordered === undefined) {
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
const found = compareTests(op.results[0], users, def, wordOf, unordered);
|
|
175
|
+
if (found !== null) {
|
|
176
|
+
tests.set(op, found);
|
|
177
|
+
}
|
|
178
|
+
return found !== null;
|
|
179
|
+
});
|
|
180
|
+
for (;;) {
|
|
181
|
+
const calls = new Set(sites);
|
|
182
|
+
const kept = sites.filter((op) => {
|
|
183
|
+
const sides = sidesOf(op);
|
|
184
|
+
const read = (users.get(op.results[0]) ?? []).filter((u) => !deadHalf(u));
|
|
185
|
+
return (
|
|
186
|
+
(!sides.operands || op.operands.every((o) => source(o, calls))) &&
|
|
187
|
+
read.length > 0 &&
|
|
188
|
+
(!sides.result || read.every((u) => floatUse(u, op.results[0], calls) || u?.opcode === 'ret'))
|
|
189
|
+
);
|
|
190
|
+
});
|
|
191
|
+
if (kept.length === sites.length) {
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
sites = kept;
|
|
195
|
+
}
|
|
196
|
+
const calls = new Set(sites);
|
|
197
|
+
// A DECLARED FLOATING RETURN HAS NO FALLBACK EITHER. The candidate prints the call against its
|
|
198
|
+
// declaration, so its result stored, compared, narrowed, carried across an edge in an integer
|
|
199
|
+
// variable or handed to any other call is CONVERTED there — `u.f = f(); g = u.b[0];` lifted as
|
|
200
|
+
// `g = f();` compiles to `bl f; bl __fixsfsi` — where the bytes moved the bits. So a single may
|
|
201
|
+
// go only where a helper's result may (a helper the fold keeps, a `double` parameter, the
|
|
202
|
+
// return), and is retyped a float; a double read off its one word goes nowhere. A result nothing
|
|
203
|
+
// reads is a call statement, and stays one.
|
|
204
|
+
const returned = new Map<Op, Value>();
|
|
205
|
+
for (const [op, spelled] of returners) {
|
|
206
|
+
const r = op.results[0];
|
|
207
|
+
const read = (users.get(r) ?? []).filter((u) => !deadHalf(u));
|
|
208
|
+
if (read.length === 0) {
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (!declaredSingle(op) || !read.every((u) => floatUse(u, r, calls) || u?.opcode === 'ret')) {
|
|
212
|
+
throw new RaiseUnsupportedError(
|
|
213
|
+
`cannot lift '${fn.name}': '${String(op.attrs.target)}' is declared to return ${spelled}, which this ` +
|
|
214
|
+
'target hands back as bits in a general register, and its result is read here as something ' +
|
|
215
|
+
'other than that float — a conversion the bytes do not make',
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
returned.set(op, mkValue(T.f32()));
|
|
219
|
+
}
|
|
220
|
+
// A FLOAT RETURNED MAKES THE FUNCTION'S RETURN A FLOAT, so every other `ret` converts what it
|
|
221
|
+
// hands back: `return 5;` beside `return fget();` compiles to the bits of 5.0f where the asm
|
|
222
|
+
// hands back the word 5. Each value a `ret` reads must then be a float this pass makes — a kept
|
|
223
|
+
// helper's result or a declared single's.
|
|
224
|
+
const madeFloat = (v: Value) => {
|
|
225
|
+
const d = def.get(v);
|
|
226
|
+
return d !== undefined && (returned.has(d) || (calls.has(d) && sidesOf(d).result));
|
|
227
|
+
};
|
|
228
|
+
const rets = fn.blocks.flatMap((b) => b.ops.filter((op) => op.opcode === 'ret' && op.operands.length > 0));
|
|
229
|
+
if (rets.some((r) => r.operands.some(madeFloat)) && !rets.every((r) => r.operands.every(madeFloat))) {
|
|
230
|
+
throw new RaiseUnsupportedError(
|
|
231
|
+
`cannot lift '${fn.name}': it returns a float on one path, and another return of this function hands ` +
|
|
232
|
+
'back an integer word, which C would convert to that float',
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
// A DECLARED DOUBLE HAS NO FALLBACK. A helper the fold refuses stays a call for
|
|
236
|
+
// `refuseUnmodelledHelpers` to gap, but an ordinary callee is no helper, and its two words handed
|
|
237
|
+
// on as integers are the wrong number — so a double parameter nothing here can hand a double
|
|
238
|
+
// refuses the function. It counts arguments as the source does: an `sret` call's operand 0, the
|
|
239
|
+
// hidden pointer, is not one.
|
|
240
|
+
for (const [op, at] of consumers) {
|
|
241
|
+
for (const i of at) {
|
|
242
|
+
if (!source(op.operands[i], calls)) {
|
|
243
|
+
const arg = i + (op.attrs.sret === true ? 0 : 1);
|
|
244
|
+
throw new RaiseUnsupportedError(
|
|
245
|
+
`cannot lift '${fn.name}': argument ${arg} of the call to '${String(op.attrs.target)}' ` +
|
|
246
|
+
'is a `double` its callee declares, and its two words are not a double this function was ' +
|
|
247
|
+
'handed, a runtime helper returned or a finite literal staged, moved whole',
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (!sites.length && !consumers.size && !returned.size) {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
const floatOf = new Map<Value, Value>();
|
|
256
|
+
const float = (bits: number) => mkValue(bits > 32 ? T.f64() : T.f32());
|
|
257
|
+
// the `fconst` each literal's `concat` becomes, and the ops computing the constant words only it read
|
|
258
|
+
const literals = new Map<Op, Op>();
|
|
259
|
+
const spent = new Set<Op>();
|
|
260
|
+
const spend = (w: Value, by: Op) => {
|
|
261
|
+
const d = def.get(w)!;
|
|
262
|
+
if (users.get(w)!.every((u) => u === by)) {
|
|
263
|
+
spent.add(d);
|
|
264
|
+
d.operands.forEach((o) => spend(o, d));
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
// One parameter in its first slot's place, which is where the ABI put the value.
|
|
268
|
+
const retype = (o: Value, bits: number) => {
|
|
269
|
+
if (floatOf.has(o) || fromCall(o, calls)) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
const d = def.get(o);
|
|
273
|
+
const pattern = literalBits(o);
|
|
274
|
+
if (pattern !== undefined) {
|
|
275
|
+
const whole = float(64);
|
|
276
|
+
literals.set(d!, mkOp('fconst', { results: [whole], attrs: { bits: pattern } }));
|
|
277
|
+
d!.operands.forEach((w) => spend(w, d!));
|
|
278
|
+
floatOf.set(o, whole);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const slots = d === undefined ? [o] : d.operands;
|
|
282
|
+
const whole = float(bits);
|
|
283
|
+
entry.splice(entry.indexOf(slots[0]), slots.length, whole);
|
|
284
|
+
for (const u of d === undefined ? [] : users.get(slots[0])!) {
|
|
285
|
+
if (samePair(u, d!)) {
|
|
286
|
+
floatOf.set(u.results[0], whole);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
floatOf.set(o, whole);
|
|
290
|
+
};
|
|
291
|
+
for (const [op, now] of returned) {
|
|
292
|
+
floatOf.set(op.results[0], now);
|
|
293
|
+
}
|
|
294
|
+
for (const op of sites) {
|
|
295
|
+
const helper = helperOf(op)!;
|
|
296
|
+
const sides = floatSidesOf(helper);
|
|
297
|
+
if (sides.operands) {
|
|
298
|
+
op.operands.forEach((o, i) => retype(o, helper.params[i]));
|
|
299
|
+
}
|
|
300
|
+
if (sides.result) {
|
|
301
|
+
floatOf.set(op.results[0], float(helper.returns));
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
for (const [op, at] of consumers) {
|
|
305
|
+
at.forEach((i) => retype(op.operands[i], 64));
|
|
306
|
+
}
|
|
307
|
+
const folded = new Map<Op, Op>();
|
|
308
|
+
for (const [op, found] of tests) {
|
|
309
|
+
if (calls.has(op)) {
|
|
310
|
+
for (const t of found) {
|
|
311
|
+
const operands = op.operands.map((o) => floatOf.get(o)!);
|
|
312
|
+
const fcmp = helperOp(t.opcode, String(op.attrs.target), operands, t.at.results[0]);
|
|
313
|
+
// The relation the source spelled, where the test leaves it open: `__eqsf2` and `__nesf2`
|
|
314
|
+
// each fold to `fcmp_oeq` or to `fcmp_une`, by the sense of the branch that tests them
|
|
315
|
+
// (runtime-helpers.ts `RuntimeHelper.relation`).
|
|
316
|
+
const relation = helperOf(op)!.relation;
|
|
317
|
+
if (relation !== undefined) {
|
|
318
|
+
fcmp.attrs.relation = relation;
|
|
319
|
+
}
|
|
320
|
+
folded.set(t.at, fcmp);
|
|
321
|
+
t.spent.forEach((x) => spent.add(x));
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
for (const block of fn.blocks) {
|
|
326
|
+
block.ops = block.ops.flatMap((op) => {
|
|
327
|
+
// the call stays, its result now the float it was declared
|
|
328
|
+
if (returned.has(op)) {
|
|
329
|
+
return [mkOp('call', { operands: op.operands, results: [returned.get(op)!], attrs: op.attrs })];
|
|
330
|
+
}
|
|
331
|
+
if (folded.has(op)) {
|
|
332
|
+
return [folded.get(op)!];
|
|
333
|
+
}
|
|
334
|
+
if (calls.has(op) && tests.has(op)) {
|
|
335
|
+
return [];
|
|
336
|
+
}
|
|
337
|
+
if (calls.has(op)) {
|
|
338
|
+
const sides = sidesOf(op);
|
|
339
|
+
return [
|
|
340
|
+
helperOp(
|
|
341
|
+
helperOf(op)!.op!,
|
|
342
|
+
String(op.attrs.target),
|
|
343
|
+
op.operands.map((o) => (sides.operands ? floatOf.get(o)! : o)),
|
|
344
|
+
sides.result ? floatOf.get(op.results[0])! : op.results[0],
|
|
345
|
+
),
|
|
346
|
+
];
|
|
347
|
+
}
|
|
348
|
+
if (literals.has(op)) {
|
|
349
|
+
return [literals.get(op)!];
|
|
350
|
+
}
|
|
351
|
+
const retired = op.results.length === 1 && floatOf.has(op.results[0]);
|
|
352
|
+
return retired || spent.has(op) || (deadHalf(op) && floatOf.has(op.operands[0])) ? [] : [op];
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
for (const [was, now] of floatOf) {
|
|
356
|
+
replaceAllUsesWith(fn, was, now);
|
|
357
|
+
}
|
|
358
|
+
return true;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/** One test of a compare helper's result: the op whose 0/1 value is the test, the float compare
|
|
362
|
+
* it computes, and the ops between the result and it, which only it reads. */
|
|
363
|
+
interface CompareTest {
|
|
364
|
+
at: Op;
|
|
365
|
+
opcode: Opcode;
|
|
366
|
+
spent: Op[];
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/** The integer compares a test may apply to the helper's result, each against the constant 0. */
|
|
370
|
+
const INT_TEST: Readonly<Record<string, (x: number, y: number) => boolean>> = {
|
|
371
|
+
icmp_eq: (x, y) => x === y,
|
|
372
|
+
icmp_ne: (x, y) => x !== y,
|
|
373
|
+
icmp_slt: (x, y) => x < y,
|
|
374
|
+
icmp_sle: (x, y) => x <= y,
|
|
375
|
+
icmp_sgt: (x, y) => x > y,
|
|
376
|
+
icmp_sge: (x, y) => x >= y,
|
|
377
|
+
icmp_ult: (x, y) => x >>> 0 < y >>> 0,
|
|
378
|
+
icmp_ule: (x, y) => x >>> 0 <= y >>> 0,
|
|
379
|
+
icmp_ugt: (x, y) => x >>> 0 > y >>> 0,
|
|
380
|
+
icmp_uge: (x, y) => x >>> 0 >= y >>> 0,
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
/** Every test of `r`, a compare helper's result, or null where some reader of it is none. The tests
|
|
384
|
+
* are the spellings agbcc gives a result's relation to 0: an integer compare with the constant 0
|
|
385
|
+
* on either side (`cmp r0, #0` before a branch or a 0/1 select), and the three sign-bit forms of a
|
|
386
|
+
* value — `r >> 31` (`< 0`), `~r >> 31` (`>= 0`) and `(-r | r) >> 31` (`!= 0`), each a logical
|
|
387
|
+
* shift by 31. Each answers by the SIGN of `r` alone, so the helper's three-way result is read at
|
|
388
|
+
* -1, 0 and 1 and at its NaN value, and the four answers name the compare in `FCMP_TRUTH`. */
|
|
389
|
+
function compareTests(
|
|
390
|
+
r: Value,
|
|
391
|
+
users: ReadonlyMap<Value, ReadonlyArray<Op | null>>,
|
|
392
|
+
def: ReadonlyMap<Value, Op>,
|
|
393
|
+
wordOf: (v: Value) => number | undefined,
|
|
394
|
+
unordered: 1 | -1,
|
|
395
|
+
): CompareTest[] | null {
|
|
396
|
+
const readers = (v: Value) => users.get(v) ?? [];
|
|
397
|
+
const only = (v: Value): Op | null => (readers(v).length === 1 ? readers(v)[0] : null);
|
|
398
|
+
const signBit = (u: Op | null, of: Value): boolean =>
|
|
399
|
+
u?.opcode === 'shr_u' && u.operands.length === 1 && u.operands[0] === of && u.attrs.imm === 31;
|
|
400
|
+
const found: CompareTest[] = [];
|
|
401
|
+
const answer = (at: Op, spent: Op[], test: (x: number) => boolean): boolean => {
|
|
402
|
+
const truth = [test(-1), test(0), test(1), test(unordered)];
|
|
403
|
+
const opcode = fcmpOfTruth(truth);
|
|
404
|
+
if (opcode === undefined) {
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
found.push({ at, opcode, spent });
|
|
408
|
+
return true;
|
|
409
|
+
};
|
|
410
|
+
for (const u of readers(r)) {
|
|
411
|
+
if (u === null) {
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
const cmp = INT_TEST[u.opcode];
|
|
415
|
+
if (cmp !== undefined && u.operands.length === 2) {
|
|
416
|
+
const [x, y] = u.operands;
|
|
417
|
+
const ok =
|
|
418
|
+
x === r && y !== r && wordOf(y) === 0
|
|
419
|
+
? answer(u, [], (v) => cmp(v, 0))
|
|
420
|
+
: y === r && x !== r && wordOf(x) === 0 && answer(u, [], (v) => cmp(0, v));
|
|
421
|
+
if (!ok) {
|
|
422
|
+
return null;
|
|
423
|
+
}
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
if (signBit(u, r)) {
|
|
427
|
+
if (!answer(u, [], (v) => v < 0)) {
|
|
428
|
+
return null;
|
|
429
|
+
}
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
if (u.opcode === 'not' && u.operands.length === 1) {
|
|
433
|
+
const shift = only(u.results[0]);
|
|
434
|
+
if (!signBit(shift, u.results[0]) || !answer(shift!, [u], (v) => v >= 0)) {
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
// `(-r | r) >> 31`: `r` has two readers, the `neg` and the `or`, and the test is taken at the
|
|
440
|
+
// `neg`; the `or` is that same test, read when it is reached.
|
|
441
|
+
if (u.opcode === 'neg' && u.operands.length === 1) {
|
|
442
|
+
const or = only(u.results[0]);
|
|
443
|
+
const shift = or ? only(or.results[0]) : null;
|
|
444
|
+
const pairs =
|
|
445
|
+
or?.opcode === 'or' &&
|
|
446
|
+
or.operands.length === 2 &&
|
|
447
|
+
or.operands.includes(r) &&
|
|
448
|
+
or.operands.includes(u.results[0]) &&
|
|
449
|
+
readers(r).includes(or);
|
|
450
|
+
if (!pairs || !signBit(shift, or!.results[0]) || !answer(shift!, [u, or!], (v) => v !== 0)) {
|
|
451
|
+
return null;
|
|
452
|
+
}
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
if (
|
|
456
|
+
u.opcode === 'or' &&
|
|
457
|
+
u.operands.some((o) => o !== r && def.get(o)?.opcode === 'neg' && def.get(o)!.operands[0] === r)
|
|
458
|
+
) {
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
463
|
+
return found.length > 0 ? found : null;
|
|
464
|
+
}
|
package/src/raise/globalshape.ts
CHANGED
|
@@ -505,11 +505,12 @@ export const SHAPE_GATES: readonly Gate<ShapeEvidence>[] = [
|
|
|
505
505
|
id: 'index-materialized-first',
|
|
506
506
|
why: 'a scaling of the index precedes the pool load IN ITS OWN BLOCK — the pointer path, which is the cast spelling',
|
|
507
507
|
sound: true,
|
|
508
|
-
// Its SECOND reaching fixture is the compiler-shaped one:
|
|
509
|
-
//
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
508
|
+
// Its SECOND reaching fixture is the compiler-shaped one: the real agbcc output for a function
|
|
509
|
+
// that subscripts the name once outside a loop and once inside it.
|
|
510
|
+
guardedBy: [
|
|
511
|
+
'global-array-shape.test.ts: one index-first access refuses a symbol the others license',
|
|
512
|
+
'global-array-shape.test.ts: …and the same function cast-spelled still refuses, on the access that CAN be compared',
|
|
513
|
+
],
|
|
513
514
|
rejects: anIndexFirstAccess,
|
|
514
515
|
},
|
|
515
516
|
{
|
|
@@ -955,6 +956,11 @@ export function inferGlobalArrays(
|
|
|
955
956
|
}
|
|
956
957
|
const pos = positions(fn);
|
|
957
958
|
for (const [sym, accs] of accessesBySymbol(fn, target, gates.address)) {
|
|
959
|
+
// a function-scope static's shape is its definition's (structure/local-statics.ts), not an
|
|
960
|
+
// inference, and it is declared in the body rather than assumed
|
|
961
|
+
if (fn.localObjects?.has(sym)) {
|
|
962
|
+
continue;
|
|
963
|
+
}
|
|
958
964
|
const si = Array.isArray(accs) ? shapeOf(accs, pos, gates.shape) : null;
|
|
959
965
|
if (si !== null) {
|
|
960
966
|
out.set(sym, si);
|
package/src/raise/gvn.ts
CHANGED
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
// materializes a multi-use `const` that is live across a call into a named local, and
|
|
33
33
|
// its own comment records that this exact widening ("the small-constant regression")
|
|
34
34
|
// already cost matches once.
|
|
35
|
-
// `undef` — numbering it would be
|
|
36
|
-
// `
|
|
37
|
-
//
|
|
35
|
+
// `undef` — numbering it would be HARMFUL: two `undef`s of one `key` are a storage's content
|
|
36
|
+
// and a `copied` move of it into another storage's join (ir/opcodes.ts says so at the
|
|
37
|
+
// opcode), and one number would erase the move the structurer spells.
|
|
38
38
|
//
|
|
39
39
|
// The gate is a MATCHING policy, not a property of the opcode — which is why it is not a flag on
|
|
40
40
|
// the opcode table, where `const` would satisfy it.
|
|
@@ -93,11 +93,8 @@ function keyOf(op: Op): string {
|
|
|
93
93
|
return `${op.opcode}(${attrs})`;
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
/**
|
|
97
|
-
|
|
98
|
-
* block. Returns the number of definitions removed (0 when nothing changed).
|
|
99
|
-
*/
|
|
100
|
-
export function numberPureValues(fn: Fn): number {
|
|
96
|
+
/** The blocks numbering looks in: those the entry reaches through any op's successors. */
|
|
97
|
+
function numberedBlocks(fn: Fn): Set<Block> {
|
|
101
98
|
// REACHABLE blocks only. The entry dominates everything REACHABLE — it does not dominate an
|
|
102
99
|
// unreachable block, and verify()'s dominator fixpoint models that faithfully (a block with no
|
|
103
100
|
// predecessors converges to dom = {itself}). Numbering a group with a member in such a block
|
|
@@ -120,6 +117,15 @@ export function numberPureValues(fn: Fn): number {
|
|
|
120
117
|
if (fn.blocks[0]) {
|
|
121
118
|
walk(fn.blocks[0]);
|
|
122
119
|
}
|
|
120
|
+
return reachable;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Collapse operand-free pure definitions that share a key down to one apiece, defined in the entry
|
|
125
|
+
* block. Returns the number of definitions removed (0 when nothing changed).
|
|
126
|
+
*/
|
|
127
|
+
export function numberPureValues(fn: Fn): number {
|
|
128
|
+
const reachable = numberedBlocks(fn);
|
|
123
129
|
const groups = new Map<string, { op: Op; block: number }[]>();
|
|
124
130
|
fn.blocks.forEach((b, bi) => {
|
|
125
131
|
if (!reachable.has(b)) {
|
|
@@ -164,3 +170,18 @@ export function numberPureValues(fn: Fn): number {
|
|
|
164
170
|
entry.ops.unshift(...hoisted);
|
|
165
171
|
return removed;
|
|
166
172
|
}
|
|
173
|
+
|
|
174
|
+
/** The class {@link numberPureValues} puts a value in: the key of a numberable definition in a block
|
|
175
|
+
* it numbers, or the value itself. Two values of one class are one value once it has run. */
|
|
176
|
+
export function valueNumberOf(fn: Fn): (v: Value) => Value | string {
|
|
177
|
+
const reachable = numberedBlocks(fn);
|
|
178
|
+
const key = new Map<Value, string>();
|
|
179
|
+
for (const b of reachable) {
|
|
180
|
+
for (const op of b.ops) {
|
|
181
|
+
if (isNumberable(op.opcode) && op.results.length === 1) {
|
|
182
|
+
key.set(op.results[0], keyOf(op));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return (v) => key.get(v) ?? v;
|
|
187
|
+
}
|