@asmlift/core 0.9.0 → 0.11.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 +2 -0
- package/src/backend/cfamily.ts +197 -61
- package/src/backend/cpp.ts +3 -1
- package/src/backend/lines.ts +10 -0
- package/src/backend/pascal.ts +47 -16
- package/src/cdecl/parse.ts +21 -0
- package/src/contracts.ts +246 -36
- package/src/declare.ts +69 -20
- package/src/frontend/arm-body.ts +155 -0
- package/src/frontend/asmdata.ts +9 -2
- package/src/frontend/call-plan.ts +1117 -61
- package/src/frontend/callee-body.ts +370 -0
- package/src/frontend/dead-reads.ts +628 -0
- package/src/frontend/device-pins.ts +33 -4
- package/src/frontend/emit.ts +4 -3
- package/src/frontend/fpu.ts +224 -17
- package/src/frontend/frame-objects.ts +1689 -206
- package/src/frontend/frontend.ts +84 -3
- package/src/frontend/high-half.ts +53 -4
- package/src/frontend/jump-table.ts +545 -0
- package/src/frontend/local-object.ts +158 -32
- package/src/frontend/mips.ts +2296 -217
- package/src/frontend/own-struct-return.ts +500 -0
- package/src/frontend/ppc.ts +2748 -245
- package/src/frontend/register-struct-call.ts +399 -0
- package/src/frontend/reloc-symbol.ts +22 -7
- package/src/frontend/ssa.ts +1721 -43
- package/src/frontend/stackargs.ts +199 -72
- package/src/frontend/thumb-entry.ts +340 -0
- package/src/frontend/thumb.ts +2400 -325
- package/src/ir/alias.ts +65 -2
- package/src/ir/bits.ts +1699 -4
- package/src/ir/core.ts +254 -9
- package/src/ir/discipline.ts +86 -21
- package/src/ir/entry.ts +157 -0
- package/src/{structure → ir}/loops.ts +6 -2
- package/src/ir/opcodes.ts +137 -9
- package/src/ir/parse.ts +1 -0
- package/src/ir/simplify.ts +61 -10
- package/src/ir/types.ts +86 -2
- package/src/ir/verify.ts +96 -11
- package/src/l3/argbase.ts +2 -0
- package/src/l3/ast.ts +582 -18
- package/src/l3/basecse.ts +68 -15
- package/src/l3/caseendgoto.ts +223 -0
- package/src/l3/coalesce.ts +42 -14
- package/src/l3/constinit.ts +410 -0
- package/src/l3/dce.ts +53 -9
- 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 +33 -34
- package/src/l3/ptrfield.ts +123 -8
- package/src/l3/regspell.ts +11 -4
- package/src/l3/reindex.ts +295 -99
- package/src/l3/scopebase.ts +2 -0
- package/src/l3/storage.ts +98 -1
- package/src/l3/symbol-refs.ts +84 -6
- package/src/l3/tailmerge.ts +6 -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 +1039 -38
- package/src/proto-context.ts +1039 -45
- package/src/proto.ts +555 -40
- package/src/raise/aggmembers.ts +290 -0
- package/src/raise/arrays.ts +5 -1
- package/src/raise/const.ts +25 -0
- package/src/raise/declared-volatile.ts +207 -31
- package/src/raise/divpow2.ts +192 -0
- 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 +422 -36
- package/src/raise/pairparams.ts +33 -0
- package/src/raise/paramwidth.ts +196 -6
- package/src/raise/pre-recovery.ts +107 -21
- package/src/raise/pre-structure.ts +73 -0
- package/src/raise/recover.ts +12 -5
- package/src/raise/reroll.ts +674 -0
- package/src/raise/shortcircuit.ts +217 -34
- 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/unpre.ts +594 -0
- package/src/raise/unthread.ts +400 -0
- package/src/rank-declare.ts +118 -12
- package/src/rank-variations.ts +186 -35
- package/src/rank.ts +536 -68
- package/src/runtime-helpers.ts +140 -22
- package/src/structure/analysis.ts +217 -8
- package/src/structure/arith-ops.ts +23 -0
- package/src/structure/cross-jumped-tail.ts +216 -0
- package/src/structure/globalaccess.ts +157 -10
- 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 +6192 -1213
- package/src/structure/switch-recover.ts +583 -120
- package/src/structure/unfolded-branch-constant.ts +90 -0
- package/src/symbols.ts +58 -15
- package/src/target.ts +1028 -54
- package/src/trace.ts +49 -17
- package/src/variation-definitions.ts +333 -20
- package/src/variation-gates.ts +4 -0
- package/src/variation-tokens.ts +68 -3
package/src/runtime-helpers.ts
CHANGED
|
@@ -29,9 +29,10 @@
|
|
|
29
29
|
// REGISTERS the list occupies, and `irWidthOf` gives the IR width one parameter ARRIVES at once a
|
|
30
30
|
// frontend has paired those registers up.
|
|
31
31
|
import { type Op, type Value, mkOp } from './ir/core';
|
|
32
|
-
import {
|
|
32
|
+
import { FLOAT_COMPUTING, FLOAT_TO_INT, INT_TO_FLOAT, NEGATED_FCMP, type Opcode, WIDE_BITS } from './ir/opcodes';
|
|
33
33
|
import { type IrType, intWidth } from './ir/types';
|
|
34
34
|
import { type Prototypes, wordsOf } from './proto';
|
|
35
|
+
import type { TargetDescription } from './target';
|
|
35
36
|
|
|
36
37
|
export interface RuntimeHelper {
|
|
37
38
|
/** the op this helper computes, where asmlift has one. Absent for a helper it can only SIGN. */
|
|
@@ -40,6 +41,19 @@ export interface RuntimeHelper {
|
|
|
40
41
|
params: readonly number[];
|
|
41
42
|
/** the returned width: 0 (void), 32, or 64 (a register PAIR). */
|
|
42
43
|
returns: 0 | 32 | 64;
|
|
44
|
+
/** A THREE-WAY FLOAT COMPARE: its result's SIGN orders its two operands (negative, zero, positive
|
|
45
|
+
* for less, equal, greater), and this is the value it returns where either is a NaN. A compare
|
|
46
|
+
* folds through the integer test its caller applies to the result (raise/floathelpers.ts), so it
|
|
47
|
+
* carries this in place of an `op`. */
|
|
48
|
+
unordered?: 1 | -1;
|
|
49
|
+
/** THE RELATION THE SOURCE SPELLED, for a compare helper whose relation and its negation are both
|
|
50
|
+
* C operators — `__eqsf2` for `==`, `__nesf2` for `!=`. agbcc's gcc folds `!(a == b)` into
|
|
51
|
+
* `a != b` before it picks a helper, so the helper names the relation the source wrote, and a
|
|
52
|
+
* lift that spells its negation (or the negation's `!`) recompiles to the other helper. The fold
|
|
53
|
+
* stamps it on the compare as `attrs.relation` (raise/floathelpers.ts), and the structurer keeps
|
|
54
|
+
* that relation's spelling by choosing the arm order instead (l3/ast.ts `Expr.helperRel`). The
|
|
55
|
+
* ordered four need none: `!(a > b)` keeps `__gtsf2`, and no other helper computes it. */
|
|
56
|
+
relation?: 'fcmp_oeq' | 'fcmp_une';
|
|
43
57
|
}
|
|
44
58
|
|
|
45
59
|
/** The entry a target's table holds for `callee`, or undefined. THE ONE READER, because the table
|
|
@@ -118,19 +132,34 @@ export function helperOp(opcode: Opcode, helper: string, operands: readonly Valu
|
|
|
118
132
|
return mkOp(opcode, { operands: [...operands], results: [result], attrs: { helper } });
|
|
119
133
|
}
|
|
120
134
|
|
|
121
|
-
/** Whether a helper computes on floats: its op is a float op,
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
* values re-typed as floats
|
|
135
|
+
/** Whether a helper computes on floats: its op is a float op, a conversion or a compare, so the
|
|
136
|
+
* operands and the result that op takes as floats are floats of the widths its signature states,
|
|
137
|
+
* carried in integer registers by a soft-float runtime. Every reader of the table asks this and
|
|
138
|
+
* not the width, because the two families fold into different IR: an integer helper into its op
|
|
139
|
+
* over the same values, a float helper into its op over values re-typed as floats where the op
|
|
140
|
+
* takes one (`raise/floathelpers.ts`). */
|
|
126
141
|
export function isFloatHelper(h: RuntimeHelper): boolean {
|
|
127
|
-
return h.op !== undefined &&
|
|
142
|
+
return h.unordered !== undefined || (h.op !== undefined && FLOAT_COMPUTING.has(h.op));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Which sides of a float helper are floats: its operands (one kind for all of them, in every op
|
|
146
|
+
* here) and its result. A conversion has an integer on one side; a compare returns one. */
|
|
147
|
+
export function floatSidesOf(h: RuntimeHelper): { operands: boolean; result: boolean } {
|
|
148
|
+
const op = h.op ?? '';
|
|
149
|
+
return {
|
|
150
|
+
operands: !INT_TO_FLOAT.has(op),
|
|
151
|
+
result: h.unordered === undefined && !FLOAT_TO_INT.has(op) && NEGATED_FCMP[op] === undefined,
|
|
152
|
+
};
|
|
128
153
|
}
|
|
129
154
|
|
|
130
155
|
/** Signatures for a target's helpers, in the WORD arity the frontend's prototype lookup speaks.
|
|
131
156
|
* Consumed behind any caller-supplied prototype — the project's own headers win. */
|
|
132
|
-
export function helperPrototypes(
|
|
133
|
-
|
|
157
|
+
export function helperPrototypes(
|
|
158
|
+
target: Pick<TargetDescription, 'runtimeHelpers' | 'evenWordAlignedArgs'>,
|
|
159
|
+
): Prototypes {
|
|
160
|
+
return Object.fromEntries(
|
|
161
|
+
Object.entries(target.runtimeHelpers ?? {}).map(([sym, h]) => [sym, { params: wordsOf(h.params, target) }]),
|
|
162
|
+
);
|
|
134
163
|
}
|
|
135
164
|
|
|
136
165
|
/** agbcc's runtime, which is GCC 2.9's libgcc for ARM.
|
|
@@ -142,20 +171,32 @@ export function helperPrototypes(table: Readonly<Record<string, RuntimeHelper>>
|
|
|
142
171
|
* operand SET-UP instead — `asr rN,rM,#31` per half for signed, `mov rN,#0` for unsigned. The
|
|
143
172
|
* DIVISIONS do split, and their entries say so.
|
|
144
173
|
*
|
|
145
|
-
* THE
|
|
146
|
-
* `init_floating_libfuncs` names
|
|
147
|
-
* `thumb.md` has no DF pattern but the move, so every one of them is a call;
|
|
148
|
-
* long's (thumb.h:632, 655).
|
|
174
|
+
* THE FLOAT ARITHMETIC OF BOTH PRECISIONS IS HERE, AND FOLDS. `optabs.c:4022`
|
|
175
|
+
* `init_floating_libfuncs` names each mode's add, sub, mul, div (4129-4140) and neg (4155), and
|
|
176
|
+
* `thumb.md` has no SF or DF pattern but the move, so every one of them is a call; a single
|
|
177
|
+
* travels as an int's word and a double as a long long's pair (thumb.h:632, 655).
|
|
178
|
+
* `raise/floathelpers.ts` folds them to the float ops over a `float` or a `double`.
|
|
149
179
|
*
|
|
150
|
-
* NAMING THEM IS A TRADE, and these
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
180
|
+
* NAMING THEM IS A TRADE, and these ten make it: a call the fold refuses declines where an unnamed
|
|
181
|
+
* helper would publish as a pass-through. So a function whose float arithmetic feeds a compare, a
|
|
182
|
+
* conversion or an ordinary call declines, where unnamed it would print `__muldf3(); g();`. For
|
|
183
|
+
* the singles that pass-through is wrong C: unnamed, `__mulsf3`'s arity is GUESSED (frontend/ssa.ts
|
|
184
|
+
* `fallbackArgc`), and a guess drops what the function passes on unchanged — `a * b + c` as
|
|
185
|
+
* `__addsf3(__mulsf3(), a2)`, `a + b` as `__addsf3()`.
|
|
186
|
+
*
|
|
187
|
+
* THE CONVERSIONS FOLD TOO, to the cast each computes (`fptosi`, `sitofp`, `fpext` …), with an
|
|
188
|
+
* integer on one side that `raise/floathelpers.ts` takes as any integer. THE COMPARES fold through
|
|
189
|
+
* the test their caller applies to the integer they return, by libgcc's contract for it
|
|
190
|
+
* (`unordered`), and one whose result is read any other way refuses (`refuseUnmodelledHelpers`).
|
|
191
|
+
* Unnamed, each would be the same guess, dropping the argument passed on unchanged and matching
|
|
192
|
+
* byte for byte: `(int)x` as `__fixsfsi()`, `(float)i` as `__floatsisf()`, `a > b` as
|
|
193
|
+
* `__gtsf2() > 0`. The names are the ones agbcc
|
|
194
|
+
* emits for every compare, every conversion between `int`, `unsigned`, `long long` and either
|
|
195
|
+
* precision, and every widening and narrowing between the two precisions (compiled evidence: one
|
|
196
|
+
* function per operation at the canonical flags). There is no conversion of an unsigned integer
|
|
197
|
+
* INTO a float: agbcc open-codes `(float)u` around `__floatsisf` and `__addsf3`. `__cmpdi2`, which
|
|
198
|
+
* it also calls converting an `unsigned long long`, is an integer compare and outside this
|
|
199
|
+
* family. */
|
|
159
200
|
export const AGBCC_RUNTIME_HELPERS: Readonly<Record<string, RuntimeHelper>> = {
|
|
160
201
|
// 32-bit software division — the ops `raise/softdiv.ts` rewrites, gated on the target having no
|
|
161
202
|
// hardware divider, which is what those four are about.
|
|
@@ -180,6 +221,45 @@ export const AGBCC_RUNTIME_HELPERS: Readonly<Record<string, RuntimeHelper>> = {
|
|
|
180
221
|
__muldf3: { op: 'fmul', params: [64, 64], returns: 64 },
|
|
181
222
|
__divdf3: { op: 'fdiv', params: [64, 64], returns: 64 },
|
|
182
223
|
__negdf2: { op: 'fneg', params: [64], returns: 64 },
|
|
224
|
+
// Single arithmetic, one float in one register each way (thumb.h:632's word).
|
|
225
|
+
__addsf3: { op: 'fadd', params: [32, 32], returns: 32 },
|
|
226
|
+
__subsf3: { op: 'fsub', params: [32, 32], returns: 32 },
|
|
227
|
+
__mulsf3: { op: 'fmul', params: [32, 32], returns: 32 },
|
|
228
|
+
__divsf3: { op: 'fdiv', params: [32, 32], returns: 32 },
|
|
229
|
+
__negsf2: { op: 'fneg', params: [32], returns: 32 },
|
|
230
|
+
// The compares, one int out of two floats of either precision, each libgcc's three-way
|
|
231
|
+
// `__fpcmp_parts` when both are ordered (fp-bit.c:1117-1188). On a NaN each returns the value its
|
|
232
|
+
// own relation tests FALSE on — `== 0`, `< 0`, `<= 0` are false on 1, `> 0`, `>= 0` on -1 — except
|
|
233
|
+
// `__nesf2`, whose 1 makes `!=` true (fp-bit.c:1214-1309). agbcc tests the result against 0 with
|
|
234
|
+
// the relation the helper is named for.
|
|
235
|
+
__eqsf2: { params: [32, 32], returns: 32, unordered: 1, relation: 'fcmp_oeq' },
|
|
236
|
+
__nesf2: { params: [32, 32], returns: 32, unordered: 1, relation: 'fcmp_une' },
|
|
237
|
+
__gtsf2: { params: [32, 32], returns: 32, unordered: -1 },
|
|
238
|
+
__gesf2: { params: [32, 32], returns: 32, unordered: -1 },
|
|
239
|
+
__ltsf2: { params: [32, 32], returns: 32, unordered: 1 },
|
|
240
|
+
__lesf2: { params: [32, 32], returns: 32, unordered: 1 },
|
|
241
|
+
__eqdf2: { params: [64, 64], returns: 32, unordered: 1, relation: 'fcmp_oeq' },
|
|
242
|
+
__nedf2: { params: [64, 64], returns: 32, unordered: 1, relation: 'fcmp_une' },
|
|
243
|
+
__gtdf2: { params: [64, 64], returns: 32, unordered: -1 },
|
|
244
|
+
__gedf2: { params: [64, 64], returns: 32, unordered: -1 },
|
|
245
|
+
__ltdf2: { params: [64, 64], returns: 32, unordered: 1 },
|
|
246
|
+
__ledf2: { params: [64, 64], returns: 32, unordered: 1 },
|
|
247
|
+
// The conversions: to and from a word, to and from a long long, and between the precisions.
|
|
248
|
+
// `__fix*` truncates toward zero, as C's conversion out of a float does.
|
|
249
|
+
__fixsfsi: { op: 'fptosi', params: [32], returns: 32 },
|
|
250
|
+
__fixunssfsi: { op: 'fptoui', params: [32], returns: 32 },
|
|
251
|
+
__floatsisf: { op: 'sitofp', params: [32], returns: 32 },
|
|
252
|
+
__fixdfsi: { op: 'fptosi', params: [64], returns: 32 },
|
|
253
|
+
__fixunsdfsi: { op: 'fptoui', params: [64], returns: 32 },
|
|
254
|
+
__floatsidf: { op: 'sitofp', params: [32], returns: 64 },
|
|
255
|
+
__fixsfdi: { op: 'fptosi', params: [32], returns: 64 },
|
|
256
|
+
__fixunssfdi: { op: 'fptoui', params: [32], returns: 64 },
|
|
257
|
+
__floatdisf: { op: 'sitofp', params: [64], returns: 32 },
|
|
258
|
+
__fixdfdi: { op: 'fptosi', params: [64], returns: 64 },
|
|
259
|
+
__fixunsdfdi: { op: 'fptoui', params: [64], returns: 64 },
|
|
260
|
+
__floatdidf: { op: 'sitofp', params: [64], returns: 64 },
|
|
261
|
+
__extendsfdf2: { op: 'fpext', params: [32], returns: 64 },
|
|
262
|
+
__truncdfsf2: { op: 'fptrunc', params: [64], returns: 32 },
|
|
183
263
|
};
|
|
184
264
|
|
|
185
265
|
/** CodeWarrior's PowerPC runtime (`Runtime.PPCEABI.H`), as the GameCube projects vendor it.
|
|
@@ -220,6 +300,44 @@ export const PPC_MWCC_RUNTIME_HELPERS: Readonly<Record<string, RuntimeHelper>> =
|
|
|
220
300
|
__shr2u: { op: 'shr_u', params: [64, 32], returns: 64 },
|
|
221
301
|
};
|
|
222
302
|
|
|
303
|
+
/** A compiler runtime's OUT-OF-LINE REGISTER SAVE AND RESTORE ROUTINES: a prologue that saves a run
|
|
304
|
+
* of callee-saved registers by calling into the runtime instead of storing each one inline. A
|
|
305
|
+
* compiler fact, like the table above, and it hangs off `TargetDescription` for the same reason:
|
|
306
|
+
* which compiler emits the family, at which level, is measured per build, and a frontend that
|
|
307
|
+
* hard-coded the names would recognise them on a target whose runtime has no such routine.
|
|
308
|
+
*
|
|
309
|
+
* A call to one is FRAME BOOKKEEPING, not a call: it writes no register but the link register, and
|
|
310
|
+
* the frame address in `frameReg` is the only thing it reads (frontend/ppc.ts `saveRoutine`). */
|
|
311
|
+
export interface RegisterSaveRoutines {
|
|
312
|
+
/** the register a routine takes its frame address in. The save area is the words just BELOW it. */
|
|
313
|
+
frameReg: string;
|
|
314
|
+
/** every routine name, with three groups: `save` or `rest`, the register file (`gpr`/`fpr`), and
|
|
315
|
+
* the first register the routine moves — it moves that one up to the file's last, 31. */
|
|
316
|
+
name: RegExp;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/** The routine a callee symbol names under `routines`, or null when it names none. */
|
|
320
|
+
export function registerSaveRoutineOf(
|
|
321
|
+
routines: RegisterSaveRoutines | undefined,
|
|
322
|
+
sym: string,
|
|
323
|
+
): { sym: string; restore: boolean; fpr: boolean; first: number } | null {
|
|
324
|
+
const m = routines?.name.exec(sym);
|
|
325
|
+
return m ? { sym: m[0], restore: m[1] === 'rest', fpr: m[2] === 'fpr', first: Number(m[3]) } : null;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/** CodeWarrior's `_savegpr_NN` / `_restgpr_NN` and `_savefpr_NN` / `_restfpr_NN`, called after
|
|
329
|
+
* `addi r11,r1,N`. `_savegpr_NN` stores rNN..r31 into the 4*(32-NN) bytes just below r11, and
|
|
330
|
+
* `_savefpr_NN` stores fNN..f31 into the 8*(32-NN) bytes below it.
|
|
331
|
+
*
|
|
332
|
+
* COMPILED, per build: 2.4.2 and 2.4.7 emit the family at every level (`-O4,p`, `-O4,s`, `-O0,p`).
|
|
333
|
+
* 2.3.3 never does at Pikmin's `-O4,p`, where it saves with `stmw` and inline `stfd`, but does at
|
|
334
|
+
* `-O0,p` and `-O4,s`; all three builds share `PPC_MWCC`, so the description carries it. The bench's
|
|
335
|
+
* feature tagger reads this same pattern (apps/benchmark `features.ts`). */
|
|
336
|
+
export const PPC_MWCC_SAVE_ROUTINES: RegisterSaveRoutines = {
|
|
337
|
+
frameReg: 'r11',
|
|
338
|
+
name: /^_(save|rest)(gpr|fpr)_(\d+)$/,
|
|
339
|
+
};
|
|
340
|
+
|
|
223
341
|
/** IDO 7.1's runtime for the 64-bit operations MIPS II has no instruction for, a family of its own
|
|
224
342
|
* (`__ll_*` for signed, `__ull_*` for unsigned): a scan for `__*di3` reports zero on it.
|
|
225
343
|
*
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
defOpMap,
|
|
29
29
|
dominators,
|
|
30
30
|
mergeClasses,
|
|
31
|
+
opShape,
|
|
31
32
|
predecessors,
|
|
32
33
|
successorsOf,
|
|
33
34
|
} from '../ir/core';
|
|
@@ -892,7 +893,7 @@ export interface AnalyzeOptions {
|
|
|
892
893
|
* A compiler behavior (TargetDescription.compilerBehaviors `readsStayWhereWritten`), not
|
|
893
894
|
* a differ-refereed variation, and that distinction is the argument: a variation exists where the asm
|
|
894
895
|
* UNDERDETERMINES the source, some pass having collapsed two spellings onto one output
|
|
895
|
-
* (`/
|
|
896
|
+
* (`/signed-cmp`'s non-negativity proof is the type case). Where the compiler emits a read in the
|
|
896
897
|
* block the source spelled it in, re-spelling it at the block the asm read in reproduces that
|
|
897
898
|
* asm while the sunk spelling is one it emits only for a source that read per arm — nothing for
|
|
898
899
|
* the differ to referee, and the extra candidate is pure cost. Which compilers may declare
|
|
@@ -1006,12 +1007,18 @@ function makeMemWriteBetween(deps: {
|
|
|
1006
1007
|
if (wDirty(b.ops, oi + 1, b.ops.length) || wDirty(render.blk.ops, 0, render.idx)) {
|
|
1007
1008
|
return true;
|
|
1008
1009
|
}
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1010
|
+
// The between-blocks: every `x` the def's block reaches without re-crossing it from which the
|
|
1011
|
+
// render block is reached again in at least one step, by the same def-avoiding walk. The render
|
|
1012
|
+
// block itself counts only where it reaches itself so (its head is checked above); any other
|
|
1013
|
+
// block only where it lies on a def→render path.
|
|
1014
|
+
const forward = reachAvoiding(b, b);
|
|
1015
|
+
if (!forward.has(render.blk)) {
|
|
1016
|
+
return false; // every block on a def→render path is one the def's block reaches
|
|
1017
|
+
}
|
|
1018
|
+
const onPath = blocksReaching(forward, render.blk, b);
|
|
1019
|
+
for (const x of forward) {
|
|
1020
|
+
if (!successorsOf(x).some((s) => s !== b && onPath.has(s))) {
|
|
1021
|
+
continue; // not on a def→render path (or, for the render block, acyclic: head checked)
|
|
1015
1022
|
}
|
|
1016
1023
|
if (wDirty(x.ops, 0, x.ops.length)) {
|
|
1017
1024
|
return true;
|
|
@@ -1021,6 +1028,49 @@ function makeMemWriteBetween(deps: {
|
|
|
1021
1028
|
};
|
|
1022
1029
|
}
|
|
1023
1030
|
|
|
1031
|
+
/** The blocks of `within` that reach `target` in zero or more steps without passing `avoid`,
|
|
1032
|
+
* `target` included: one backward walk over `within`'s own edges.
|
|
1033
|
+
*
|
|
1034
|
+
* `makeMemWriteBetween` asks, for each block `x` the def's block reaches, whether `target` is in
|
|
1035
|
+
* `reachAvoiding(x, avoid)`. That is a forward walk per `x`, quadratic in the function's size per
|
|
1036
|
+
* question, and the question is asked per op `analyze` names (`nameAccess`): Klonoa's
|
|
1037
|
+
* sub_0800D188 (1,783 blocks) spent most of its structuring in those walks. `target` is in
|
|
1038
|
+
* `reachAvoiding(x, avoid)` exactly where some successor of `x` other than `avoid` is in this set,
|
|
1039
|
+
* since every block on such a path after `x` is in `within` (`within` being everything the def's
|
|
1040
|
+
* block reaches without passing `avoid`, from which `x` was drawn). */
|
|
1041
|
+
function blocksReaching(within: ReadonlySet<Block>, target: Block, avoid: Block): Set<Block> {
|
|
1042
|
+
const into = new Map<Block, Block[]>();
|
|
1043
|
+
for (const y of within) {
|
|
1044
|
+
if (y === avoid) {
|
|
1045
|
+
continue;
|
|
1046
|
+
}
|
|
1047
|
+
for (const s of successorsOf(y)) {
|
|
1048
|
+
if (s !== avoid && within.has(s)) {
|
|
1049
|
+
const list = into.get(s);
|
|
1050
|
+
if (list === undefined) {
|
|
1051
|
+
into.set(s, [y]);
|
|
1052
|
+
} else {
|
|
1053
|
+
list.push(y);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
const out = new Set<Block>();
|
|
1059
|
+
if (target === avoid) {
|
|
1060
|
+
return out;
|
|
1061
|
+
}
|
|
1062
|
+
out.add(target);
|
|
1063
|
+
for (const work = [target]; work.length;) {
|
|
1064
|
+
for (const y of into.get(work.pop()!) ?? []) {
|
|
1065
|
+
if (!out.has(y)) {
|
|
1066
|
+
out.add(y);
|
|
1067
|
+
work.push(y);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
return out;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1024
1074
|
/** INTERDEPENDENT PARALLEL-COPY ARGS. A terminator's successor args are ONE parallel copy
|
|
1025
1075
|
* (argAssigns), whose every read means the PRE-copy value. An arg whose def-tree reads a SIBLING
|
|
1026
1076
|
* arg of the same edge cannot read it by name there — so the sibling's whole expression is
|
|
@@ -1521,6 +1571,9 @@ interface LoopRules {
|
|
|
1521
1571
|
/** The def's value enters some loop's header live and every consumer sits outside that loop, as
|
|
1522
1572
|
* does the def: the value is carried ACROSS the loop, not into it. */
|
|
1523
1573
|
liveAcrossLoop(def: Op, r: Value, consumers: Op[]): boolean;
|
|
1574
|
+
/** `liveAcrossLoop`, and neither the def nor any consumer sits in any loop, enclosing ones
|
|
1575
|
+
* included: never a value the compiler could have hoisted out of one. */
|
|
1576
|
+
carriedPastLoops(def: Op, r: Value, consumers: Op[]): boolean;
|
|
1524
1577
|
/** Does `r`, computed by `op` in `L`'s body, read a loop variable the update overwrites? */
|
|
1525
1578
|
readsPreUpdate(L: BottomTestedLoop, op: Op, r: Value): boolean;
|
|
1526
1579
|
escapesAheadOfUpdate(op: Op, r: Value, consumers: Op[]): boolean;
|
|
@@ -1551,6 +1604,9 @@ function makeLoopRules(a: {
|
|
|
1551
1604
|
!L.body.has(opBlock.get(def)!) &&
|
|
1552
1605
|
consumers.every((c) => !L.body.has(opBlock.get(c)!)),
|
|
1553
1606
|
);
|
|
1607
|
+
const carriedPastLoops = (def: Op, r: Value, consumers: Op[]): boolean =>
|
|
1608
|
+
liveAcrossLoop(def, r, consumers) &&
|
|
1609
|
+
loopBodies.every((L) => !L.body.has(opBlock.get(def)!) && consumers.every((c) => !L.body.has(opBlock.get(c)!)));
|
|
1554
1610
|
/** THE PRE-UPDATE ESCAPE. A value a bottom-tested loop computes and the code after it reads,
|
|
1555
1611
|
* whose expression reads a loop variable — `do { s = &f->v; f = f->next; } while (--n); *out =
|
|
1556
1612
|
* s;`. Inlined at the post-loop read it re-reads that variable's NAME, which by then holds the
|
|
@@ -1710,6 +1766,7 @@ function makeLoopRules(a: {
|
|
|
1710
1766
|
multiBlockHeaders,
|
|
1711
1767
|
bottomTested,
|
|
1712
1768
|
liveAcrossLoop,
|
|
1769
|
+
carriedPastLoops,
|
|
1713
1770
|
readsPreUpdate,
|
|
1714
1771
|
escapesAheadOfUpdate,
|
|
1715
1772
|
rebuiltPast,
|
|
@@ -2042,6 +2099,20 @@ function namePureOp(rules: MaterializeRules, state: MaterializeState, op: Op, b:
|
|
|
2042
2099
|
} else if (op.opcode !== 'const' && pr && copyInterdependent.has(pr) && !scopes.addressCone(op)) {
|
|
2043
2100
|
materialize.add(op);
|
|
2044
2101
|
}
|
|
2102
|
+
// A PURE VALUE CARRIED ACROSS A LOOP, computed and read outside every loop, as `nameAccess` names
|
|
2103
|
+
// a read (`carriedPastLoops`; a value read inside a loop is `/expr-home`'s question, where loop.c's
|
|
2104
|
+
// invariant motion may have put it). `j = n * 3; for (…) { if (t[i] == 7) { gHit = j; break; } }`
|
|
2105
|
+
// computes `j` ahead of the loop and holds it in a register to the break arm, which sits outside
|
|
2106
|
+
// the natural loop. Inlined, `gHit = n * 3` moves the computation into the arm, and every profile
|
|
2107
|
+
// compiles it there (IDO scores the two alike). A `const` is re-materialized where it is used, and
|
|
2108
|
+
// an address loses its inline byte-stride cast standing alone and is what a compiler CSEs and
|
|
2109
|
+
// moves freely, so neither is named.
|
|
2110
|
+
if (op.opcode !== 'const' && pr && useSitesOf.has(pr) && !scopes.addressCone(op) && !rendersAsAddress(op)) {
|
|
2111
|
+
const cons = [...new Set((useSitesOf.get(pr) ?? []).map((s) => s.op))];
|
|
2112
|
+
if (loops.carriedPastLoops(op, pr, cons)) {
|
|
2113
|
+
materialize.add(op);
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2045
2116
|
// A float PRODUCT read by a float add or subtract, directly or through a negation, is named
|
|
2046
2117
|
// on a compiler that contracts (target.ts `contractsFloatProducts`): it fuses only WITHIN
|
|
2047
2118
|
// one expression, so the inline `a * b + c` recompiles to one `fmadds` — one rounding, a
|
|
@@ -2248,7 +2319,7 @@ function isBarrier(rules: MaterializeRules, materialize: ReadonlySet<Op>, access
|
|
|
2248
2319
|
// order is the compiler's to choose (`gY = VCOUNT - TM0CNT_L` reads VCOUNT first); a
|
|
2249
2320
|
// qualified read spelled as a bare statement because nothing uses it is an access too.
|
|
2250
2321
|
if (once && !isCall && (x.opcode === 'load' || x.opcode === 'aload') && counted(x)) {
|
|
2251
|
-
return
|
|
2322
|
+
return !interchangeableReads(rules, x, op);
|
|
2252
2323
|
}
|
|
2253
2324
|
if (!isCall) {
|
|
2254
2325
|
return false;
|
|
@@ -2261,6 +2332,144 @@ function isBarrier(rules: MaterializeRules, materialize: ReadonlySet<Op>, access
|
|
|
2261
2332
|
return false;
|
|
2262
2333
|
}
|
|
2263
2334
|
|
|
2335
|
+
const COMMUTATIVE_OPS: ReadonlySet<string> = new Set(['add', 'mul', 'and', 'or', 'xor']);
|
|
2336
|
+
|
|
2337
|
+
/** Do two values name the same address: one value, or two `gaddr`s of one symbol? */
|
|
2338
|
+
function sameAddress(defOf: Map<Value, Op>, a: Value, b: Value): boolean {
|
|
2339
|
+
if (a === b) {
|
|
2340
|
+
return true;
|
|
2341
|
+
}
|
|
2342
|
+
const da = defOf.get(a);
|
|
2343
|
+
const db = defOf.get(b);
|
|
2344
|
+
return (
|
|
2345
|
+
da !== undefined &&
|
|
2346
|
+
db !== undefined &&
|
|
2347
|
+
da.opcode === 'gaddr' &&
|
|
2348
|
+
db.opcode === 'gaddr' &&
|
|
2349
|
+
opShape(da) === opShape(db)
|
|
2350
|
+
);
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2353
|
+
/** Are two qualified reads of ONE cell interchangeable in the one expression they feed: the same
|
|
2354
|
+
* access of the same address, each the sole use of its value up a chain of ops alike but for it,
|
|
2355
|
+
* the two chains meeting as the two operands of one commutative op? Then the expression's value is
|
|
2356
|
+
* the same whichever read the compiler makes first, and its order is the compiler's to choose:
|
|
2357
|
+
* `gR + gR` over `extern volatile u32 gR;` and `*gPV + *gPV` over `u16 *volatile gPV` read the
|
|
2358
|
+
* object twice, in an order the source never stated, so naming the first read into a temp spells
|
|
2359
|
+
* an order the source did not have (ido 7.1 loads `gR + gR` in the other order). The meeting op
|
|
2360
|
+
* may also be a sum both reads enter with one sign, past a reassociation (`sameSignInOneSum`).
|
|
2361
|
+
* `gR - gR` is not symmetric — the order decides which read is the minuend — and stays sequenced.
|
|
2362
|
+
* `a` is the later read, `b` the earlier one placed past it. */
|
|
2363
|
+
function interchangeableReads(rules: MaterializeRules, a: Op, b: Op): boolean {
|
|
2364
|
+
const { defOf } = rules;
|
|
2365
|
+
if (
|
|
2366
|
+
a === b ||
|
|
2367
|
+
opShape(a) !== opShape(b) ||
|
|
2368
|
+
a.operands.length !== b.operands.length ||
|
|
2369
|
+
!a.operands.every((v, i) => sameAddress(defOf, v, b.operands[i]))
|
|
2370
|
+
) {
|
|
2371
|
+
return false;
|
|
2372
|
+
}
|
|
2373
|
+
return mirroredChains(rules, a, b) || sameSignInOneSum(rules, b, a);
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
/** The two reads' sole-use chains are alike but for them and meet as the two operands of one
|
|
2377
|
+
* commutative op: `(gR << 1) * (gR << 1)`. */
|
|
2378
|
+
function mirroredChains(rules: MaterializeRules, a: Op, b: Op): boolean {
|
|
2379
|
+
const { useSitesOf } = rules.uses;
|
|
2380
|
+
let ca = a;
|
|
2381
|
+
let cb = b;
|
|
2382
|
+
for (let depth = 0; depth < 8; depth++) {
|
|
2383
|
+
const va = ca.results[0];
|
|
2384
|
+
const vb = cb.results[0];
|
|
2385
|
+
const ua = va === undefined ? undefined : useSitesOf.get(va);
|
|
2386
|
+
const ub = vb === undefined ? undefined : useSitesOf.get(vb);
|
|
2387
|
+
if (ua?.length !== 1 || ub?.length !== 1) {
|
|
2388
|
+
return false;
|
|
2389
|
+
}
|
|
2390
|
+
const na = ua[0].op;
|
|
2391
|
+
const nb = ub[0].op;
|
|
2392
|
+
if (na === nb) {
|
|
2393
|
+
return (
|
|
2394
|
+
COMMUTATIVE_OPS.has(na.opcode) &&
|
|
2395
|
+
na.successors.length === 0 &&
|
|
2396
|
+
na.operands.length === 2 &&
|
|
2397
|
+
((na.operands[0] === va && na.operands[1] === vb) || (na.operands[0] === vb && na.operands[1] === va))
|
|
2398
|
+
);
|
|
2399
|
+
}
|
|
2400
|
+
if (
|
|
2401
|
+
opShape(na) !== opShape(nb) ||
|
|
2402
|
+
na.successors.length > 0 ||
|
|
2403
|
+
nb.successors.length > 0 ||
|
|
2404
|
+
(MEM_BASE_OPS.has(na.opcode) && counted(na)) ||
|
|
2405
|
+
na.operands.length !== nb.operands.length ||
|
|
2406
|
+
!na.operands.every((v, i) => (v === va ? nb.operands[i] === vb : v === nb.operands[i] && v !== vb))
|
|
2407
|
+
) {
|
|
2408
|
+
return false;
|
|
2409
|
+
}
|
|
2410
|
+
ca = na;
|
|
2411
|
+
cb = nb;
|
|
2412
|
+
}
|
|
2413
|
+
return false;
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2416
|
+
/** Each read's sole-use chain up through `add`/`sub` ops, as the ops it passes and the read's sign in
|
|
2417
|
+
* each: entering an `add`, or a `sub` as its minuend, keeps the sign; entering a `sub` as its
|
|
2418
|
+
* subtrahend flips it. The value of every op on the chain is then that sign times the read, plus
|
|
2419
|
+
* terms the read is not in. */
|
|
2420
|
+
function additiveChain(rules: MaterializeRules, read: Op): Map<Op, { sign: number; operand: number }> {
|
|
2421
|
+
const { useSitesOf } = rules.uses;
|
|
2422
|
+
const chain = new Map<Op, { sign: number; operand: number }>();
|
|
2423
|
+
let v = read.results[0];
|
|
2424
|
+
let sign = 1;
|
|
2425
|
+
for (let depth = 0; depth < 8 && v !== undefined; depth++) {
|
|
2426
|
+
const uses = useSitesOf.get(v);
|
|
2427
|
+
if (uses?.length !== 1) {
|
|
2428
|
+
break;
|
|
2429
|
+
}
|
|
2430
|
+
const n = uses[0].op;
|
|
2431
|
+
const operand = n.operands.indexOf(v);
|
|
2432
|
+
if (
|
|
2433
|
+
(n.opcode !== 'add' && n.opcode !== 'sub') ||
|
|
2434
|
+
n.operands.length !== 2 ||
|
|
2435
|
+
n.results.length !== 1 ||
|
|
2436
|
+
n.successors.length > 0 ||
|
|
2437
|
+
n.operands.lastIndexOf(v) !== operand
|
|
2438
|
+
) {
|
|
2439
|
+
break;
|
|
2440
|
+
}
|
|
2441
|
+
sign = n.opcode === 'sub' && operand === 1 ? -sign : sign;
|
|
2442
|
+
chain.set(n, { sign, operand });
|
|
2443
|
+
v = n.results[0];
|
|
2444
|
+
}
|
|
2445
|
+
return chain;
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2448
|
+
/** The two reads meet in one sum with the same sign: their sole-use chains run through `add`/`sub`
|
|
2449
|
+
* ops only, to an op one reaches through each operand, where each counts with the same sign. The
|
|
2450
|
+
* sum is then the same whichever read is made first, however the compiler reassociated it:
|
|
2451
|
+
* ido 7.1 compiles `(gR - x) + (gR - x)` as `((gR - x) + gR) - x`, where one read passes a `sub`
|
|
2452
|
+
* before the `add` they meet in and the other does not, so the chains are not mirrored. `gR - gR`
|
|
2453
|
+
* meets with opposite signs and stays sequenced. Wrapping arithmetic keeps the sum associative.
|
|
2454
|
+
* The value is then right in either order, but the match is not: the earlier read must also be
|
|
2455
|
+
* the one the expression spells first, entering the meeting op as its left operand, or a compiler
|
|
2456
|
+
* that reads operands left to right reverses the asm's two loads. mwcc 2.4.2 reads
|
|
2457
|
+
* `gMS.f + ((t + gMS.f) + t)`'s right-hand `gMS.f` first, so spelling both in one expression
|
|
2458
|
+
* there puts the asm's second load first. */
|
|
2459
|
+
function sameSignInOneSum(rules: MaterializeRules, earlier: Op, later: Op): boolean {
|
|
2460
|
+
const ofEarlier = additiveChain(rules, earlier);
|
|
2461
|
+
if (ofEarlier.size === 0) {
|
|
2462
|
+
return false;
|
|
2463
|
+
}
|
|
2464
|
+
for (const [n, inLater] of additiveChain(rules, later)) {
|
|
2465
|
+
const inEarlier = ofEarlier.get(n);
|
|
2466
|
+
if (inEarlier !== undefined) {
|
|
2467
|
+
return inEarlier.operand === 0 && inLater.operand === 1 && inEarlier.sign === inLater.sign;
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
return false;
|
|
2471
|
+
}
|
|
2472
|
+
|
|
2264
2473
|
/** The rules for a memory access — a `call`, `load` or `aload` — in order; the first that names
|
|
2265
2474
|
* the op ends the walk. */
|
|
2266
2475
|
function nameAccess(rules: MaterializeRules, state: MaterializeState, op: Op, b: Block): void {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// asmlift structurer — the IR's integer arithmetic opcodes and the L3 operator each one renders
|
|
2
|
+
// as. The float ops are FLOAT_TO_BIN in structure.ts, which says why they are not here.
|
|
3
|
+
import type { BinOp } from '../l3/ast';
|
|
4
|
+
|
|
5
|
+
export const ARITH_TO_BIN: Record<string, BinOp> = {
|
|
6
|
+
add: '+',
|
|
7
|
+
sub: '-',
|
|
8
|
+
mul: '*',
|
|
9
|
+
sdiv: '/',
|
|
10
|
+
// the UNSIGNED quotient/remainder — the C backend spells them `/`/`%` over an operand it casts
|
|
11
|
+
// unsigned (l3/ast.ts BinOp, backend/cfamily.ts C_SPELLING)
|
|
12
|
+
udiv: '/u',
|
|
13
|
+
smod: '%',
|
|
14
|
+
umod: '%u',
|
|
15
|
+
or: '|',
|
|
16
|
+
and: '&',
|
|
17
|
+
xor: '^',
|
|
18
|
+
shl: '<<',
|
|
19
|
+
shr_u: '>>>', // the LOGICAL right shift; the C backend spells it `>>` over an unsigned operand
|
|
20
|
+
shr_s: '>>',
|
|
21
|
+
logic_and: '&&',
|
|
22
|
+
logic_or: '||', // short-circuit connectives (raise/shortcircuit.ts)
|
|
23
|
+
};
|