@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
package/src/raise/paramwidth.ts
CHANGED
|
@@ -122,11 +122,46 @@
|
|
|
122
122
|
// pool load in the benchmark's agbcc references (extscale.ts's header has the census). Only the
|
|
123
123
|
// fold's own extensions are judged this way; a plain cast's extension sits at its right shift,
|
|
124
124
|
// where its position says nothing about its `lsl`'s.
|
|
125
|
-
|
|
125
|
+
//
|
|
126
|
+
// EVERY READER AN EXTENSION, the `/narrow-param` lift variation (`narrowExtendedParams`). agbcc does
|
|
127
|
+
// not always widen a narrow declared parameter in the prologue: where every use of it is itself an
|
|
128
|
+
// extension it widens at each use instead, and then the object carries no declaration to read.
|
|
129
|
+
// `pokeemerald:LoadMonInfo` declares `s16 partyId` and reads it as `lsl #16` behind a pool load, an
|
|
130
|
+
// `asr #14` for the scaled index and an `lsr #16` for a `u16` argument, so `raw-reader` refuses it
|
|
131
|
+
// (two readers), and with that ablated `not-prologue` (behind the pool load) and `fused-behind-pool`.
|
|
132
|
+
// Declared `s16` and read `a << 2` and `(u16)a`, or declared `s32` and read `(s16)a << 2` and
|
|
133
|
+
// `(u16)a`, the two are one object in isolation and differ only through the register allocation
|
|
134
|
+
// around them (a sibling `u8` parameter's prologue copy), so nothing in the asm decides it and the
|
|
135
|
+
// differ referees. Declaring the parameter at width w changes no reader's value when every reader is
|
|
136
|
+
// an extension to w' ≤ w: ext_w'(trunc_w x) = ext_w'(x). The width is the widest reader's, its
|
|
137
|
+
// signedness the caller's declaration where that spells one and the first reader of that width's
|
|
138
|
+
// otherwise; each reader of exactly that type becomes the parameter itself. Where no prototype
|
|
139
|
+
// declares the width, the candidate loses every score tie (rank.ts `guessesParamWidth`): without
|
|
140
|
+
// a sibling to separate them the two declarations are often one object, and the narrow one costs
|
|
141
|
+
// each prototyped caller the truncation `proto-width` is about.
|
|
142
|
+
//
|
|
143
|
+
// A LOW MASK IS AN EXTENSION READER. mwcc reads a `u16` parameter as `clrlwi rD,rS,16`, which the
|
|
144
|
+
// PPC frontend lifts as `and rS, 0xffff` — as it must, since the same instruction is every other
|
|
145
|
+
// mask too — so a parameter mwcc reads only through it reaches this pass with no `zext` reader at
|
|
146
|
+
// all. `and v, 0xffff` is `zext v {width=16}` value for value, so it counts as one (`extensionOf`),
|
|
147
|
+
// unsigned, and becomes the parameter itself when the parameter is declared `u16`. The two
|
|
148
|
+
// declarations are not one object there: over a `long *a`, mwcc_242_81 compiles
|
|
149
|
+
// `*a = *a & ~b; *a = *a | c;` to `not; and` when `b` and `c` are `unsigned short`, and to `andc` when
|
|
150
|
+
// they are `unsigned long` and the body masks them, at `-O4,p` and at Mario Party 4's unit flags.
|
|
151
|
+
//
|
|
152
|
+
// The rule is about the IR, not the ISA, so it reaches every frontend that lifts a mask as `and`.
|
|
153
|
+
// IDO reads a declared `u16`/`u8` parameter as `sw a0,0(sp); andi a0,a0,0xffff`, and frontend/mips.ts
|
|
154
|
+
// lifts the `andi` as that mask too: `s32 f(u16 a, s32 b){ return a + b; }` scores 3 wide and 0
|
|
155
|
+
// narrowed. The witness gates still decide whether IDO may narrow: a mask applied in place with no
|
|
156
|
+
// home store (`a = a & 0xffff`) is refused by `unhomed-param`. agbcc spells a mask as `and` with a
|
|
157
|
+
// pooled `0xffff` or a `mov #0xff`, so the variation is offered there too, and loses, because agbcc
|
|
158
|
+
// extends a declared narrow parameter with a shift pair: `s32 f(u32 a, s32 b){ return (a & 0xffff)
|
|
159
|
+
// + b; }` scores 0 wide and 6 narrowed.
|
|
160
|
+
import { type Fn, type Op, type Value, defOpMap, replaceAllUsesWith, successorsOf } from '../ir/core';
|
|
126
161
|
import { CAST_WIDTHS, MATERIALIZING_OPS } from '../ir/opcodes';
|
|
127
162
|
import { T } from '../ir/types';
|
|
128
163
|
import { type Gate, firstRejection } from '../l3/gates';
|
|
129
|
-
import { type FnProto, declaredWidth } from '../proto';
|
|
164
|
+
import { type FnProto, declaredSigned, declaredWidth } from '../proto';
|
|
130
165
|
import type { NarrowParamWitness } from '../target';
|
|
131
166
|
|
|
132
167
|
/** What the gates below judge: one entry parameter and the extension that reads it. */
|
|
@@ -141,6 +176,8 @@ export interface NarrowParamCandidate {
|
|
|
141
176
|
inPrologue: boolean;
|
|
142
177
|
/** reads of the RAW parameter anywhere in the function */
|
|
143
178
|
uses: number;
|
|
179
|
+
/** reads of it that are not an extension to `width` or narrower */
|
|
180
|
+
wideReaders: number;
|
|
144
181
|
/** the width the caller's own prototype declares for this parameter, if it declares one */
|
|
145
182
|
declared: number | undefined;
|
|
146
183
|
/** the extension is one raise/extscale.ts re-split from a fused pair whose `shl` the machine ran
|
|
@@ -192,14 +229,14 @@ export const PARAM_WIDTH_GATES: readonly Gate<NarrowParamCandidate>[] = [
|
|
|
192
229
|
},
|
|
193
230
|
{
|
|
194
231
|
id: 'not-prologue',
|
|
195
|
-
why: 'an extension behind body code
|
|
232
|
+
why: 'an extension behind body code may be the source writing the cast, and nothing in the fan referees a narrow signature against the wide one',
|
|
196
233
|
sound: true,
|
|
197
234
|
guardedBy: 'param-width.test.ts: an extension behind a nullary call is body code',
|
|
198
235
|
rejects: (c) => !c.inPrologue,
|
|
199
236
|
},
|
|
200
237
|
{
|
|
201
238
|
id: 'no-declaration-witness',
|
|
202
|
-
why: '
|
|
239
|
+
why: 'the compiler spells a narrow declaration and a body cast the same way, so the assembly decides nothing',
|
|
203
240
|
sound: true,
|
|
204
241
|
guardedBy:
|
|
205
242
|
'param-width.test.ts: a compiler whose two spellings are one object refuses the narrowing, measured or not',
|
|
@@ -207,14 +244,14 @@ export const PARAM_WIDTH_GATES: readonly Gate<NarrowParamCandidate>[] = [
|
|
|
207
244
|
},
|
|
208
245
|
{
|
|
209
246
|
id: 'unhomed-param',
|
|
210
|
-
why: '
|
|
247
|
+
why: 'the compiler stores a narrow declared parameter to the stack on entry, so one it did not store was declared wide',
|
|
211
248
|
sound: true,
|
|
212
249
|
guardedBy: 'param-width.test.ts: a homing compiler refuses the parameter it did not home',
|
|
213
250
|
rejects: (c) => c.witness === 'home-store-and-in-place' && !c.homed,
|
|
214
251
|
},
|
|
215
252
|
{
|
|
216
253
|
id: 'widened-elsewhere',
|
|
217
|
-
why: '
|
|
254
|
+
why: 'the compiler widens a narrow declared parameter in its own argument register, so a widening into another register is no declaration',
|
|
218
255
|
sound: true,
|
|
219
256
|
guardedBy:
|
|
220
257
|
'param-width.test.ts: \u2026and refuses one it homed but widened SOMEWHERE ELSE \u2014 that is a 64-bit half',
|
|
@@ -229,6 +266,96 @@ export const PARAM_WIDTH_GATES: readonly Gate<NarrowParamCandidate>[] = [
|
|
|
229
266
|
},
|
|
230
267
|
];
|
|
231
268
|
|
|
269
|
+
const byId = (id: string): Gate<NarrowParamCandidate> => PARAM_WIDTH_GATES.find((g) => g.id === id)!;
|
|
270
|
+
|
|
271
|
+
/** `/narrow-param`'s table (`narrowExtendedParams`): a second CONSUMER of the default's rules, not an
|
|
272
|
+
* ablation of its table, so it is built by id rather than through `ablateHeuristic`. It leaves out
|
|
273
|
+
* the three rules that read the prologue's evidence and adds the one that reads the readers.
|
|
274
|
+
* `raw-reader` gives way to `wide-reader`, which is sound for the same reason it is: every reader
|
|
275
|
+
* is an extension the pass rewrites. `not-prologue` and `fused-behind-pool` read fields this pass
|
|
276
|
+
* does not observe (it fills `inPrologue` and `fusedBehindPool` false). Their `sound` is a claim
|
|
277
|
+
* about the DEFAULT, whose narrowing reaches every candidate with no wide sibling, so a wrong width
|
|
278
|
+
* there is a signature nothing referees and every prototyped caller pays for. A /narrow-param
|
|
279
|
+
* candidate keeps the wide default beside it and loses every tie unless a prototype declares the
|
|
280
|
+
* width (rank.ts `guessesParamWidth`), so for it the same question is refereed: a rule sound for a
|
|
281
|
+
* declaration is a heuristic for a generated candidate (l3/gates.ts, `ORDER_SHAPE_GATES`).
|
|
282
|
+
* param-width.test.ts holds the set left out to exactly these three, so a sound rule added to the
|
|
283
|
+
* default is either added here or argued out by name. */
|
|
284
|
+
export const PARAM_READER_WIDTH_GATES: readonly Gate<NarrowParamCandidate>[] = [
|
|
285
|
+
byId('entry-is-join'),
|
|
286
|
+
byId('param-typed'),
|
|
287
|
+
byId('cast-width'),
|
|
288
|
+
{
|
|
289
|
+
id: 'wide-reader',
|
|
290
|
+
why: 'a reader that is not an extension to this width or narrower reads bits a narrow declaration drops',
|
|
291
|
+
sound: true,
|
|
292
|
+
guardedBy: 'param-width.test.ts: a reader that is no narrow extension keeps the parameter wide',
|
|
293
|
+
rejects: (c) => c.wideReaders !== 0,
|
|
294
|
+
},
|
|
295
|
+
byId('proto-width'),
|
|
296
|
+
byId('no-declaration-witness'),
|
|
297
|
+
byId('unhomed-param'),
|
|
298
|
+
byId('widened-elsewhere'),
|
|
299
|
+
];
|
|
300
|
+
|
|
301
|
+
const isExtension = (op: Op): boolean => op.opcode === 'sext' || op.opcode === 'zext';
|
|
302
|
+
|
|
303
|
+
/** The unsigned extension a MASK spells: `and v, 0xff` is `zext v {width=8}` and `and v, 0xffff` is
|
|
304
|
+
* `zext v {width=16}`, value for value. */
|
|
305
|
+
const MASK_WIDTHS: ReadonlyMap<number, number> = new Map([
|
|
306
|
+
[0xff, 8],
|
|
307
|
+
[0xffff, 16],
|
|
308
|
+
]);
|
|
309
|
+
|
|
310
|
+
/** What `op` reads `v` as, when it reads it ONLY through an extension: its width and signedness.
|
|
311
|
+
* An `and` with a low mask counts, because that is the extension's spelling where the machine
|
|
312
|
+
* has no extension of its own to lift: PPC's `clrlwi rD,rS,16` and MIPS's `andi rD,rS,0xffff`
|
|
313
|
+
* (each frontend lifts it as the mask, and must, because every other mask is the same instruction).
|
|
314
|
+
* Null for any other reader, including an extension or a mask of something besides `v` alone. */
|
|
315
|
+
function extensionOf(op: Op, v: Value, defs: ReadonlyMap<Value, Op>): { width: number; signed: boolean } | null {
|
|
316
|
+
if (isExtension(op)) {
|
|
317
|
+
return op.operands.length === 1 && op.operands[0] === v
|
|
318
|
+
? { width: op.attrs.width as number, signed: op.opcode === 'sext' }
|
|
319
|
+
: null;
|
|
320
|
+
}
|
|
321
|
+
if (op.opcode !== 'and' || op.operands.length !== 2 || !op.operands.includes(v)) {
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
const mask = defs.get(op.operands[op.operands[0] === v ? 1 : 0]);
|
|
325
|
+
const width = mask?.opcode === 'const' ? MASK_WIDTHS.get(mask.attrs.value as number) : undefined;
|
|
326
|
+
return width === undefined ? null : { width, signed: false };
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/** The ops that read `v` and the number of branch arguments that pass it, anywhere in `fn`. */
|
|
330
|
+
function readersOf(fn: Fn, v: Value): { ops: Op[]; edges: number } {
|
|
331
|
+
const ops: Op[] = [];
|
|
332
|
+
let edges = 0;
|
|
333
|
+
for (const b of fn.blocks) {
|
|
334
|
+
for (const op of b.ops) {
|
|
335
|
+
if (op.operands.includes(v)) {
|
|
336
|
+
ops.push(op);
|
|
337
|
+
}
|
|
338
|
+
for (const s of op.successors) {
|
|
339
|
+
edges += s.args.filter((a) => a === v).length;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return { ops, edges };
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** How many reads of `v` are not an extension of `v` alone (`extensionOf`) to `width` or narrower. */
|
|
347
|
+
function wideReaderCount(fn: Fn, v: Value, width: number, defs: ReadonlyMap<Value, Op>): number {
|
|
348
|
+
const { ops, edges } = readersOf(fn, v);
|
|
349
|
+
return (
|
|
350
|
+
edges +
|
|
351
|
+
ops.reduce(
|
|
352
|
+
(n, op) =>
|
|
353
|
+
n + ((extensionOf(op, v, defs)?.width ?? Infinity) <= width ? 0 : op.operands.filter((o) => o === v).length),
|
|
354
|
+
0,
|
|
355
|
+
)
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
|
|
232
359
|
/** How many times `v` is read anywhere in `fn` — op operands and branch arguments alike. */
|
|
233
360
|
function useCount(fn: Fn, v: Value): number {
|
|
234
361
|
let n = 0;
|
|
@@ -259,6 +386,13 @@ export function narrowEntryParams(
|
|
|
259
386
|
// function nobody measured (parsed IR, a hand-built fn) is never narrowed on evidence never taken.
|
|
260
387
|
const evidence = fn.paramEvidence;
|
|
261
388
|
const entry = fn.blocks[0];
|
|
389
|
+
// PER PARAMETER, AND NOT `proto.ts` `declaredCallArgs`, WHICH ASKS A DIFFERENT QUESTION. That
|
|
390
|
+
// reader abstains for a whole list over one spelling it cannot size, because its caller is
|
|
391
|
+
// laying out argument registers and one unknown width displaces every later argument. Here every
|
|
392
|
+
// entry stands alone: `undefined` at position k is the `proto-width` gate ABSTAINING for
|
|
393
|
+
// parameter k, which is the right answer for a project typedef, and it says nothing about
|
|
394
|
+
// parameter k+1. A shared reader would make one unreadable spelling drop the narrowing of every
|
|
395
|
+
// other parameter.
|
|
262
396
|
const declared = Array.isArray(self?.params) ? self.params.map(declaredWidth) : [];
|
|
263
397
|
const entryIsJoin = fn.blocks.some((b) => successorsOf(b).includes(entry));
|
|
264
398
|
const params = new Set(entry.params);
|
|
@@ -275,6 +409,7 @@ export function narrowEntryParams(
|
|
|
275
409
|
}
|
|
276
410
|
prologue.add(op);
|
|
277
411
|
}
|
|
412
|
+
const defs = defOpMap(fn);
|
|
278
413
|
let narrowed = 0;
|
|
279
414
|
for (const op of [...entry.ops]) {
|
|
280
415
|
if (op.opcode !== 'sext' && op.opcode !== 'zext') {
|
|
@@ -291,6 +426,7 @@ export function narrowEntryParams(
|
|
|
291
426
|
entryIsJoin,
|
|
292
427
|
inPrologue: prologue.has(op),
|
|
293
428
|
uses: useCount(fn, p),
|
|
429
|
+
wideReaders: wideReaderCount(fn, p, width, defs),
|
|
294
430
|
declared: declared[entry.params.indexOf(p)],
|
|
295
431
|
fusedBehindPool: fusedBehindPool.has(op),
|
|
296
432
|
witness,
|
|
@@ -307,3 +443,64 @@ export function narrowEntryParams(
|
|
|
307
443
|
}
|
|
308
444
|
return narrowed;
|
|
309
445
|
}
|
|
446
|
+
|
|
447
|
+
/** `/narrow-param`: type each entry parameter every reader of which is an extension of it at the
|
|
448
|
+
* widest reader's width, and drop the readers of exactly that type (EVERY READER AN EXTENSION).
|
|
449
|
+
* `apply: false` changes nothing and reports what it would narrow. Returns each parameter it
|
|
450
|
+
* narrowed. */
|
|
451
|
+
export function narrowExtendedParams(
|
|
452
|
+
fn: Fn,
|
|
453
|
+
witness: NarrowParamWitness,
|
|
454
|
+
self?: FnProto,
|
|
455
|
+
gates: readonly Gate<NarrowParamCandidate>[] = PARAM_READER_WIDTH_GATES,
|
|
456
|
+
apply = true,
|
|
457
|
+
): NarrowParamCandidate[] {
|
|
458
|
+
const evidence = fn.paramEvidence;
|
|
459
|
+
const entry = fn.blocks[0];
|
|
460
|
+
const declaredTypes = Array.isArray(self?.params) ? self.params : [];
|
|
461
|
+
const entryIsJoin = fn.blocks.some((b) => successorsOf(b).includes(entry));
|
|
462
|
+
const narrowed: NarrowParamCandidate[] = [];
|
|
463
|
+
const defs = defOpMap(fn);
|
|
464
|
+
for (const [i, p] of entry.params.entries()) {
|
|
465
|
+
const extensions = readersOf(fn, p).ops.flatMap((op) => {
|
|
466
|
+
const ext = extensionOf(op, p, defs);
|
|
467
|
+
return ext === null ? [] : [{ op, ...ext }];
|
|
468
|
+
});
|
|
469
|
+
if (extensions.length === 0) {
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
const width = Math.max(...extensions.map((e) => e.width));
|
|
473
|
+
const first = extensions.find((e) => e.width === width)!;
|
|
474
|
+
const declared = declaredTypes[i];
|
|
475
|
+
const c: NarrowParamCandidate = {
|
|
476
|
+
param: p,
|
|
477
|
+
width,
|
|
478
|
+
entryIsJoin,
|
|
479
|
+
inPrologue: false,
|
|
480
|
+
uses: useCount(fn, p),
|
|
481
|
+
wideReaders: wideReaderCount(fn, p, width, defs),
|
|
482
|
+
declared: declared === undefined ? undefined : declaredWidth(declared),
|
|
483
|
+
fusedBehindPool: false,
|
|
484
|
+
witness,
|
|
485
|
+
homed: evidence?.get(p)?.deadHome ?? false,
|
|
486
|
+
selfRedefined: evidence?.get(p)?.selfRedefined ?? false,
|
|
487
|
+
};
|
|
488
|
+
if (firstRejection(gates, c) !== null) {
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
narrowed.push(c);
|
|
492
|
+
if (!apply) {
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
const signed = (declared === undefined ? undefined : declaredSigned(declared)) ?? first.signed;
|
|
496
|
+
p.type = T.int(width, signed);
|
|
497
|
+
for (const { op, ...ext } of extensions) {
|
|
498
|
+
if (ext.width === width && ext.signed === signed) {
|
|
499
|
+
replaceAllUsesWith(fn, op.results[0], p);
|
|
500
|
+
const home = fn.blocks.find((b) => b.ops.includes(op))!;
|
|
501
|
+
home.ops.splice(home.ops.indexOf(op), 1);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
return narrowed;
|
|
506
|
+
}
|
|
@@ -12,9 +12,10 @@
|
|
|
12
12
|
// in the driver.
|
|
13
13
|
import { Block, Fn } from '../ir/core';
|
|
14
14
|
import { simplifyTrivialPhis } from '../ir/simplify';
|
|
15
|
-
import { dce } from '../pattern/engine';
|
|
16
|
-
import type
|
|
15
|
+
import { NOT_FCMP_PATTERNS, applyPattern, dce } from '../pattern/engine';
|
|
16
|
+
import { type FnProto, declaresVoidReturn } from '../proto';
|
|
17
17
|
import type { TargetDescription } from '../target';
|
|
18
|
+
import { readDeclaredMembers } from './aggmembers';
|
|
18
19
|
import { recognizeArrays } from './arrays';
|
|
19
20
|
import { recognizeConsts } from './const';
|
|
20
21
|
import { recognizeDivPow2 } from './divpow2';
|
|
@@ -27,16 +28,21 @@ import {
|
|
|
27
28
|
poolOrderOf,
|
|
28
29
|
restoreUnclaimedScales,
|
|
29
30
|
} from './extscale';
|
|
31
|
+
import { recognizeFloatHelpers } from './floathelpers';
|
|
30
32
|
import { numberPureValues } from './gvn';
|
|
31
33
|
import { recognizeMagicDivision } from './magicdiv';
|
|
32
34
|
import { recognizeMemberArrays } from './memberarrays';
|
|
33
35
|
import { rerootNarrowReads } from './narrow';
|
|
34
36
|
import { type MergeShape, mergeShapes, narrowBlockLocals } from './narrowlocal';
|
|
35
|
-
import {
|
|
37
|
+
import { fuseParamPairs, refuseSplitWideParams } from './pairparams';
|
|
38
|
+
import { PARAM_READER_WIDTH_GATES, PARAM_WIDTH_GATES, narrowEntryParams, narrowExtendedParams } from './paramwidth';
|
|
36
39
|
import { type BranchShortCircuitOptions, recognizeBranchShortCircuit, recognizeShortCircuit } from './shortcircuit';
|
|
37
40
|
import { recognizeSoftDiv } from './softdiv';
|
|
41
|
+
import { seedNarrowStoreSigns } from './storesign';
|
|
38
42
|
import { recognizeStructArrays } from './struct-arrays';
|
|
39
|
-
import { recognizeStructs } from './structs';
|
|
43
|
+
import { type DisplacedOptions, foldDisplacedAccesses, recognizeStructs } from './structs';
|
|
44
|
+
import { TRUNC_LOAD_GATES, foldTruncatedLoads } from './truncload';
|
|
45
|
+
import { recognizeWideHelpers, refuseUnmodelledHelpers } from './widehelpers';
|
|
40
46
|
|
|
41
47
|
/** Per-call options for the pass list — ONE field per pass that takes any, named for the pass, so
|
|
42
48
|
* a caller reads which recognizer it is steering and a pass that takes none says so by absence.
|
|
@@ -45,6 +51,19 @@ import { recognizeStructs } from './structs';
|
|
|
45
51
|
export interface PreRecoveryOptions {
|
|
46
52
|
/** raise/shortcircuit.ts `recognizeBranchShortCircuit` — the connective-vs-comparison-tree question. */
|
|
47
53
|
shortCircuit?: BranchShortCircuitOptions;
|
|
54
|
+
/** raise/paramwidth.ts `narrowExtendedParams` — the `/narrow-param` lift. */
|
|
55
|
+
paramWidth?: ParamWidthOptions;
|
|
56
|
+
/** raise/structs.ts `foldDisplacedAccesses` — the `/far-cast` lift. */
|
|
57
|
+
displaced?: DisplacedOptions;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface ParamWidthOptions {
|
|
61
|
+
/** also narrow a parameter every reader of which is an extension (`/narrow-param`) */
|
|
62
|
+
extendedReaders?: boolean;
|
|
63
|
+
/** called when a lift without `extendedReaders` has a parameter that would narrow with it */
|
|
64
|
+
onExtendedReaders?: () => void;
|
|
65
|
+
/** called when the `extendedReaders` lift narrows a parameter whose width no prototype declares */
|
|
66
|
+
onUndeclaredWidth?: () => void;
|
|
48
67
|
}
|
|
49
68
|
|
|
50
69
|
/** CFG facts read off the function ONCE, before the first pass below rewrites it.
|
|
@@ -91,8 +110,9 @@ export interface PreRecoveryPass {
|
|
|
91
110
|
|
|
92
111
|
/** THE ordered pre-recovery pass list — the single source of truth shared by pipeline / rank / report.
|
|
93
112
|
* address-numbering → const-materialize → magic-division → pow2-division → soft-division →
|
|
94
|
-
* scaled-extension →
|
|
95
|
-
* branch-short-circuit → narrow-reads → narrow-local →
|
|
113
|
+
* scaled-extension → store-sign → array-legalize → truncated-load → struct-array → member-array →
|
|
114
|
+
* struct-pointer → short-circuit → branch-short-circuit → narrow-reads → narrow-local →
|
|
115
|
+
* parameter-width → scaled-extension-restore.
|
|
96
116
|
* See each recognizer's file for the rationale. */
|
|
97
117
|
export const PRE_RECOVERY_PASSES: PreRecoveryPass[] = [
|
|
98
118
|
// FIRST: collapsing duplicate address definitions removes block params every later recognizer
|
|
@@ -121,7 +141,67 @@ export const PRE_RECOVERY_PASSES: PreRecoveryPass[] = [
|
|
|
121
141
|
// ending in `br`. Verified by running it before and after each of them — same result. It sits
|
|
122
142
|
// beside magicdiv so that a reader looking for division recovery finds both together.
|
|
123
143
|
{ id: 'divpow2', run: recognizeDivPow2, dce: true },
|
|
124
|
-
{
|
|
144
|
+
{
|
|
145
|
+
id: 'softdiv',
|
|
146
|
+
run: (fn, _self, _opts, target) => recognizeSoftDiv(fn, target),
|
|
147
|
+
dce: false,
|
|
148
|
+
gate: (t) => !t.capabilities.hwDivide,
|
|
149
|
+
},
|
|
150
|
+
// NOT gated, where `softdiv` is: a float helper computes on floats, which an ISA with no FPU has no
|
|
151
|
+
// instruction for, and the target's runtime table is what says whether it calls one. A pass apart
|
|
152
|
+
// from `widehelpers` because it re-types words as floats — a helper's, a parameter a callee
|
|
153
|
+
// declares `double`, a literal — with its own closed-set refusal, and a single-precision helper
|
|
154
|
+
// is a float helper that is not wide.
|
|
155
|
+
// A fold re-runs the float compares' negation idioms (pattern/engine.ts `NOT_FCMP_PATTERNS`),
|
|
156
|
+
// which ran before there was a float compare to negate.
|
|
157
|
+
{
|
|
158
|
+
id: 'floathelpers',
|
|
159
|
+
run: (fn, _self, _opts, target) => {
|
|
160
|
+
if (!recognizeFloatHelpers(fn, target)) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
if (NOT_FCMP_PATTERNS.reduce((n, p) => n + applyPattern(fn, p, target), 0) > 0) {
|
|
164
|
+
dce(fn);
|
|
165
|
+
}
|
|
166
|
+
return true;
|
|
167
|
+
},
|
|
168
|
+
dce: false,
|
|
169
|
+
},
|
|
170
|
+
// NOT gated, where `softdiv` is, and the asymmetry is the argument: a soft DIVISION is
|
|
171
|
+
// a division the ISA has no instruction for, so a target with a divider never emits one — while
|
|
172
|
+
// no ISA this repo targets has 64-bit integer arithmetic at all, so a 64-bit helper is software
|
|
173
|
+
// on every one of them whatever `hwDivide` says.
|
|
174
|
+
{
|
|
175
|
+
id: 'widehelpers',
|
|
176
|
+
run: (fn, _self, _opts, target) => recognizeWideHelpers(fn, target),
|
|
177
|
+
dce: false,
|
|
178
|
+
},
|
|
179
|
+
// AFTER the three helper recognizers, because what it refuses is precisely what they declined: a
|
|
180
|
+
// call to a name the target's runtime table carries that none of `softdiv`, `floathelpers` and
|
|
181
|
+
// `widehelpers` folded.
|
|
182
|
+
// Re-emitting the compiler's own runtime call as source is the one failure that MATCHES, so the
|
|
183
|
+
// gap has to be written here rather than left to the backend. See raise/widehelpers.ts.
|
|
184
|
+
//
|
|
185
|
+
// `dce: false` — an `opaque` carries `effects`, so nothing it replaces becomes dead.
|
|
186
|
+
{
|
|
187
|
+
id: 'helperrefuse',
|
|
188
|
+
run: (fn, _self, _opts, target) => refuseUnmodelledHelpers(fn, target),
|
|
189
|
+
dce: false,
|
|
190
|
+
},
|
|
191
|
+
// AFTER it, because the `concat`s it reads are the ones the frontend built at the call the pass
|
|
192
|
+
// above has just rewritten — and because a pair the rewrite refused is a pair this must not fuse
|
|
193
|
+
// into a signature either.
|
|
194
|
+
// It also refuses a declared 64-bit parameter the lift left in halves, which is the one place the
|
|
195
|
+
// fusion's answer is final (`refuseSplitWideParams`).
|
|
196
|
+
{
|
|
197
|
+
id: 'pairparams',
|
|
198
|
+
run: (fn, self) => {
|
|
199
|
+
const fused = fuseParamPairs(fn);
|
|
200
|
+
refuseSplitWideParams(fn, self);
|
|
201
|
+
return fused;
|
|
202
|
+
},
|
|
203
|
+
dce: false,
|
|
204
|
+
},
|
|
125
205
|
// AFTER `const`, which folds a shift pair over a constant to the constant it computes, and BEFORE
|
|
126
206
|
// the three array recognizers, whose input this pass produces: `shl(ext(x), k)` is an element
|
|
127
207
|
// scale they legalize and the fused pair is not. `dce: true` — the `shl` a fold leaves readerless.
|
|
@@ -133,11 +213,45 @@ export const PRE_RECOVERY_PASSES: PreRecoveryPass[] = [
|
|
|
133
213
|
dce: true,
|
|
134
214
|
gate: foldsShiftPairCasts,
|
|
135
215
|
},
|
|
216
|
+
// BEFORE `arrays` and `struct-arrays`, which carry the stamp into the `astore` they make (and
|
|
217
|
+
// struct-arrays types a field only ever stored from it), and before `structs`, which does the
|
|
218
|
+
// same for a constant-offset field. `member-arrays` overwrites an astore's `signed` with its
|
|
219
|
+
// member's (raise/memberarrays.ts says why). `dce: false` — it changes no op.
|
|
220
|
+
{ id: 'storesign', run: (fn, _self, _opts, target) => seedNarrowStoreSigns(fn, target), dce: false },
|
|
136
221
|
{ id: 'arrays', run: recognizeArrays, dce: true },
|
|
222
|
+
// BEFORE the three struct synthesizers, and that is what the pass is FOR: `raise/structs.ts` is
|
|
223
|
+
// the pass that refuses one offset read at two widths, and it can only see one width per offset
|
|
224
|
+
// if the fold has already run. Below it the fold is dead — moved after `structs`,
|
|
225
|
+
// `pokeemerald:AnimTask_FlashHealthboxOnLevelUp_Step:agbcc` declines again on the same throw.
|
|
226
|
+
//
|
|
227
|
+
// EVERY POSITION ABOVE `structs` IS FREE, and that is measured rather than argued: two full
|
|
228
|
+
// `bench sweep --json` runs, this seat against the entry moved to index 0 — above `addrnum`,
|
|
229
|
+
// `const`, the three division recognizers, `extscale` and `arrays` — agree on all 2,430 records
|
|
230
|
+
// over 1,215 rows, compared by ROW IDENTITY on `src`/`len`/`diag`/`marks`/`asm`. (The 84
|
|
231
|
+
// `af:*:ido7.1` records whose `opts` digest churns between any two sweeps are the corpus-wide
|
|
232
|
+
// defect, which moves whatever anyone does; a LINE diff would report them as disagreement.)
|
|
233
|
+
//
|
|
234
|
+
// So the seat is a READING ORDER rather than a constraint: the three struct synthesizers follow
|
|
235
|
+
// each other here, and a reader looking for what happens to a base's access set finds them
|
|
236
|
+
// together. The one position that LOOKS like a prerequisite is `addrnum` — the fold groups
|
|
237
|
+
// accesses by base VALUE identity, so collapsing duplicate address definitions first reads as
|
|
238
|
+
// one. It is not, and index 0 is the run that shows it.
|
|
239
|
+
//
|
|
240
|
+
// `dce: false` — the rewrite retires nothing (the base keeps its use in the load the fold makes).
|
|
241
|
+
{
|
|
242
|
+
id: 'truncload',
|
|
243
|
+
run: (fn, _self, _opts, target) =>
|
|
244
|
+
foldTruncatedLoads(fn, target.capabilities.endianness === 'little', TRUNC_LOAD_GATES),
|
|
245
|
+
dce: false,
|
|
246
|
+
},
|
|
137
247
|
// struct-arrays AFTER arrays (scalar stride==width shapes are claimed first — see the
|
|
138
248
|
// discriminator note in raise/struct-arrays.ts) and BEFORE structs (an element's field
|
|
139
249
|
// accesses must not be re-derived as constant-offset struct-pointer accesses).
|
|
140
|
-
{
|
|
250
|
+
{
|
|
251
|
+
id: 'struct-arrays',
|
|
252
|
+
run: (fn, _self, _opts, target) => recognizeStructArrays(fn, target.compilerBehaviors.aggregateBoundary),
|
|
253
|
+
dce: true,
|
|
254
|
+
},
|
|
141
255
|
// member-arrays AFTER struct-arrays, and that order IS load-bearing — but the order alone does
|
|
142
256
|
// not deconflict them, because the two passes claim different VALUES: struct-arrays types the
|
|
143
257
|
// materialized `add(P, K)` and member-arrays groups on `P`, so `P->tbl[i].f` reaches both. What
|
|
@@ -147,7 +261,28 @@ export const PRE_RECOVERY_PASSES: PreRecoveryPass[] = [
|
|
|
147
261
|
// sits there so the three struct synthesizers read in the order of their evidence. `narrowlocal`
|
|
148
262
|
// reads the carrier's extension chain rather than any address, so nothing here orders against it.
|
|
149
263
|
{ id: 'member-arrays', run: (fn) => recognizeMemberArrays(fn), dce: true },
|
|
150
|
-
|
|
264
|
+
// RIGHT BEFORE `structs`, whose input it widens: an access through a materialised `add(p, K)`
|
|
265
|
+
// joins `p`'s access set at `off + K`. After `truncload`, `struct-arrays` and `member-arrays`, so
|
|
266
|
+
// none of them sees a folded access, and a displacement either of the last two typed is refused.
|
|
267
|
+
// `dce: true` — the `add` chain a fold leaves readerless. `opts.displaced` is `/far-cast`.
|
|
268
|
+
{
|
|
269
|
+
id: 'displaced',
|
|
270
|
+
run: (fn, self, opts, target) =>
|
|
271
|
+
foldDisplacedAccesses(fn, opts.displaced, target.compilerBehaviors, declaresVoidReturn(self)),
|
|
272
|
+
dce: true,
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
id: 'structs',
|
|
276
|
+
run: (fn, _self, _opts, target) => recognizeStructs(fn, target.compilerBehaviors.aggregateBoundary),
|
|
277
|
+
dce: false,
|
|
278
|
+
},
|
|
279
|
+
// AFTER `structs`, which synthesizes layouts for bases it can type and never for a frame object
|
|
280
|
+
// the audit already typed as a declared struct — the only base this reads.
|
|
281
|
+
{
|
|
282
|
+
id: 'aggmembers',
|
|
283
|
+
run: (fn, _self, _opts, target) => readDeclaredMembers(fn, target.capabilities.endianness === 'little'),
|
|
284
|
+
dce: true,
|
|
285
|
+
},
|
|
151
286
|
{ id: 'shortcircuit', run: recognizeShortCircuit, dce: true },
|
|
152
287
|
// The control-flow sibling, value form FIRST.
|
|
153
288
|
//
|
|
@@ -204,16 +339,13 @@ export const PRE_RECOVERY_PASSES: PreRecoveryPass[] = [
|
|
|
204
339
|
// The `target` argument is read by TWO of this pass's gates, and unlike `narrowlocal`'s single
|
|
205
340
|
// conjunct it is not a tuning knob: `narrowParamWitness` says WHICH fact in the object settles a
|
|
206
341
|
// parameter's declared width on this compiler, and a target that names none refuses outright.
|
|
342
|
+
//
|
|
343
|
+
// `opts.paramWidth` adds the `/narrow-param` lift after it, over the parameters it left wide
|
|
344
|
+
// (raise/paramwidth.ts, EVERY READER AN EXTENSION), or asks whether that lift would narrow any.
|
|
207
345
|
{
|
|
208
346
|
id: 'paramwidth',
|
|
209
|
-
run: (fn, self,
|
|
210
|
-
|
|
211
|
-
fn,
|
|
212
|
-
target.compilerBehaviors.narrowParamWitness ?? 'none',
|
|
213
|
-
self,
|
|
214
|
-
PARAM_WIDTH_GATES,
|
|
215
|
-
lifted.scales.behindPool,
|
|
216
|
-
),
|
|
347
|
+
run: (fn, self, opts, target, lifted) =>
|
|
348
|
+
narrowParamWidths(fn, self, opts.paramWidth, target, lifted, PARAM_WIDTH_GATES, PARAM_READER_WIDTH_GATES),
|
|
217
349
|
dce: false,
|
|
218
350
|
},
|
|
219
351
|
// LAST, after every pass that can CLAIM what `extscale` exposed — the two width passes above take
|
|
@@ -228,6 +360,31 @@ export const PRE_RECOVERY_PASSES: PreRecoveryPass[] = [
|
|
|
228
360
|
},
|
|
229
361
|
];
|
|
230
362
|
|
|
363
|
+
/** The `paramwidth` entry's body, taking both of its tables, so a census can pass wrapped ones. */
|
|
364
|
+
export function narrowParamWidths(
|
|
365
|
+
fn: Fn,
|
|
366
|
+
self: FnProto | undefined,
|
|
367
|
+
opts: ParamWidthOptions | undefined,
|
|
368
|
+
target: TargetDescription,
|
|
369
|
+
lifted: PreRecoveryFacts,
|
|
370
|
+
prologueGates: typeof PARAM_WIDTH_GATES,
|
|
371
|
+
readerGates: typeof PARAM_READER_WIDTH_GATES,
|
|
372
|
+
): number {
|
|
373
|
+
const witness = target.compilerBehaviors.narrowParamWitness ?? 'none';
|
|
374
|
+
const n = narrowEntryParams(fn, witness, self, prologueGates, lifted.scales.behindPool);
|
|
375
|
+
if (opts?.extendedReaders) {
|
|
376
|
+
const lifted = narrowExtendedParams(fn, witness, self, readerGates);
|
|
377
|
+
if (lifted.some((c) => c.declared === undefined)) {
|
|
378
|
+
opts.onUndeclaredWidth?.();
|
|
379
|
+
}
|
|
380
|
+
return n + lifted.length;
|
|
381
|
+
}
|
|
382
|
+
if (opts?.onExtendedReaders && narrowExtendedParams(fn, witness, self, readerGates, false).length > 0) {
|
|
383
|
+
opts.onExtendedReaders();
|
|
384
|
+
}
|
|
385
|
+
return n;
|
|
386
|
+
}
|
|
387
|
+
|
|
231
388
|
/** Run the pre-recovery passes in order. For each pass whose gate passes and that CHANGES the IR, run
|
|
232
389
|
* `dce` when the pass declares it, then invoke `afterPass(pass, result)` (the caller's verify/trace
|
|
233
390
|
* hook), if given.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// asmlift — the CFG folds that run last before structuring, after return-sinking: each rewrites a
|
|
2
|
+
// loop's edges into a shape a loop spelling owns, and each is its own stage in the trace, so a
|
|
3
|
+
// re-spelled function names the fold that moved its CFG.
|
|
4
|
+
//
|
|
5
|
+
// A MUTABLE RECORD, not a sum of four calls, for the reason `PRE_RECOVERY_PASSES` is one: it is the
|
|
6
|
+
// caller-side seam a process outside core swaps to census a fold's gate table (`pnpm bench gates`,
|
|
7
|
+
// apps/benchmark/src/run/gate-census.ts). A static import binding cannot be swapped.
|
|
8
|
+
import { Fn } from '../ir/core';
|
|
9
|
+
import { foldEmptyLatches, foldExitTrampolines } from './latch';
|
|
10
|
+
import { rerollPeeledTests } from './reroll';
|
|
11
|
+
import { unthreadLoopExits } from './unthread';
|
|
12
|
+
|
|
13
|
+
export interface PreStructureFold {
|
|
14
|
+
/** stable; the trace stage is `stage:<id>` */
|
|
15
|
+
readonly id: string;
|
|
16
|
+
/** the trace stage's title */
|
|
17
|
+
readonly title: string;
|
|
18
|
+
/** how many rewrites the fold made; 0 leaves the function as it was */
|
|
19
|
+
run: (fn: Fn) => number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** THE ORDER CARRIES LOAD, and each fold runs ONCE: there is no fixpoint across folds, so a fold
|
|
23
|
+
* that exposes another's shape must run before it. The re-roll needs the latch's way out and the
|
|
24
|
+
* entry's to reach ONE block, and on `synthetic:break_value:agbcc` they reach it only after the
|
|
25
|
+
* trampoline fold has spliced out the empty block the break leaves through; ahead of it, the re-roll
|
|
26
|
+
* declines and the loop with it ('unrecovered back-edge'). `reroll.test.ts` pins it by lifting that
|
|
27
|
+
* row's pre-fold IR through this record. The latch fold comes first for the same reason in kind:
|
|
28
|
+
* the folds after it read the back edge it leaves. The un-threading reads loop exits, which the
|
|
29
|
+
* trampoline fold reshapes, and sits ahead of the re-roll so a loop it gives one way out is one
|
|
30
|
+
* the re-roll may then read. That place is a precaution: `synthetic:thrbreak:agbcc` and the
|
|
31
|
+
* pointer walk unthread.ts names lift the same C wherever the fold sits, so `unthread.test.ts`
|
|
32
|
+
* pins its position, not a lift that needs it. */
|
|
33
|
+
export const PRE_STRUCTURE_FOLDS: PreStructureFold[] = [
|
|
34
|
+
{
|
|
35
|
+
id: 'latchfold',
|
|
36
|
+
title: 'Empty-latch folding (a latch that only jumps back becomes its back edge)',
|
|
37
|
+
run: (fn) => foldEmptyLatches(fn),
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: 'trampoline',
|
|
41
|
+
title: 'Exit-trampoline folding (an empty block on a loop exit becomes that edge)',
|
|
42
|
+
run: (fn) => foldExitTrampolines(fn),
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: 'unthread',
|
|
46
|
+
title: 'Loop-exit un-threading (an exit jump-threaded past the re-test after the loop goes back to it)',
|
|
47
|
+
run: (fn) => unthreadLoopExits(fn),
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 'reroll',
|
|
51
|
+
title: 'Peeled-test re-rolling (a loop test peeled into the entry becomes the first iteration)',
|
|
52
|
+
run: (fn) => rerollPeeledTests(fn),
|
|
53
|
+
},
|
|
54
|
+
];
|