@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/raise/latch.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// A loop whose back-edge carries a register copy — `add r3, r0, #0` then `b .L6` — lifts to a block
|
|
4
4
|
// holding nothing but that branch, because SSA construction turns the copy into an EDGE ARGUMENT.
|
|
5
5
|
// The loop is unchanged; its latch has just become a separate empty block between the exit test and
|
|
6
|
-
// the header. Loop DISCOVERY still finds the loop — `
|
|
6
|
+
// the header. Loop DISCOVERY still finds the loop — `ir/loops.ts` reads the same back-edge —
|
|
7
7
|
// but the do-while emitter requires the latch to end in a `cond_br`, an empty one ends in `br`, and
|
|
8
8
|
// the test is at the bottom so the `while` form is unavailable too. The back-edge survives to the
|
|
9
9
|
// `onStack` refusal and the whole function declines with "unrecovered back-edge".
|
|
@@ -24,11 +24,13 @@
|
|
|
24
24
|
// `if` (declining loud on the hazards). The guard therefore survives the fold either way; what the
|
|
25
25
|
// gate preserves is the SHAPE — a forward trampoline is not a latch, and folding one re-casts an
|
|
26
26
|
// unrelated branch as a loop guard — which is the `target-dominates` entry's own note below.
|
|
27
|
-
import { Fn, dominators, foldWriteOrder } from '../ir/core';
|
|
27
|
+
import { Block, Fn, dominators, foldWriteOrder, predecessors, reachForward, successorsOf } from '../ir/core';
|
|
28
|
+
import { effectful } from '../ir/discipline';
|
|
29
|
+
import { analyzeLoops } from '../ir/loops';
|
|
28
30
|
import { type Gate, firstRejection } from '../l3/gates';
|
|
29
31
|
|
|
30
32
|
/** What the gates below judge: one candidate block and the block its `br` goes to. */
|
|
31
|
-
interface LatchCandidate {
|
|
33
|
+
export interface LatchCandidate {
|
|
32
34
|
block: Fn['blocks'][number];
|
|
33
35
|
target: Fn['blocks'][number];
|
|
34
36
|
dominatesBlock: boolean;
|
|
@@ -80,9 +82,9 @@ export const LATCH_GATES: readonly Gate<LatchCandidate>[] = [
|
|
|
80
82
|
* SUCCESSOR changes, so an edge that was not a back-edge becomes one. (Dominator sets themselves
|
|
81
83
|
* only ever shrink — removing a block removes it from every set it was in.)
|
|
82
84
|
*
|
|
83
|
-
* A predecessor that already branches to the target ends up with two edges into one block
|
|
84
|
-
*
|
|
85
|
-
*
|
|
85
|
+
* A predecessor that already branches to the target ends up with two edges into one block, each
|
|
86
|
+
* carrying its own arguments. Where that predecessor has no other edge the loop has no exit, and
|
|
87
|
+
* loop recovery spells it `while (1)` with each edge's own update copy.
|
|
86
88
|
*/
|
|
87
89
|
export function foldEmptyLatches(fn: Fn, gates: readonly Gate<LatchCandidate>[] = LATCH_GATES): number {
|
|
88
90
|
let folded = 0;
|
|
@@ -124,3 +126,157 @@ export function foldEmptyLatches(fn: Fn, gates: readonly Gate<LatchCandidate>[]
|
|
|
124
126
|
folded++;
|
|
125
127
|
}
|
|
126
128
|
}
|
|
129
|
+
|
|
130
|
+
// ── EMPTY EXIT TRAMPOLINES ──────────────────────────────────────────────────────────────────────
|
|
131
|
+
//
|
|
132
|
+
// The same empty block on a loop's way OUT. `for (…) { if (t[i] == k) { r = i; break; } }` keeps
|
|
133
|
+
// `r` in a register the break copies into (`add r2, r1, #0`), and the copy becomes an edge argument
|
|
134
|
+
// of a block that holds nothing but its `br` to the code after the loop — beside a second exit edge
|
|
135
|
+
// (the loop's own test failing) that reaches that code directly. The loop then has two exit targets
|
|
136
|
+
// and none of them is a `return`, so every loop form refuses it, and the back-edge declines loud at
|
|
137
|
+
// `onStack`. Which exit edge carries the block is the compiler's choice: agbcc puts it on the break
|
|
138
|
+
// (`synthetic:break_value`) or on the latch's own exit with the break going direct (kleod
|
|
139
|
+
// `sub_0801E664`).
|
|
140
|
+
//
|
|
141
|
+
// The second exit need not land on the target itself. ido, gcc2.7.2kmc and mwcc lay an `else` the
|
|
142
|
+
// loop sits beside between the loop and its join — `if (f) { for (..) if (a(i) == k) { id = i + 100;
|
|
143
|
+
// break; } } else id = f + 26; out(id);` (`synthetic:break_elsetail`) — so the loop's own exit is an
|
|
144
|
+
// empty `b`/`j` over the `else`, and the break reaches the join only through the code its arm runs.
|
|
145
|
+
// Folding the block there leaves the loop two exit targets all the same — the break arm's head is
|
|
146
|
+
// one. What it buys is that the loop's own exit becomes the join the break arm runs on into, which
|
|
147
|
+
// is the exit a `break` that runs code has to leave for (`codeBreakArm`'s region must reach it). So
|
|
148
|
+
// the scope gate asks whether another exit edge's target REACHES the target, going forward and
|
|
149
|
+
// outside the loop's body.
|
|
150
|
+
//
|
|
151
|
+
// Splicing it out re-points each edge into it at its target with its own arguments, and the loop
|
|
152
|
+
// exits to one block: a `break` that carries its copies, which the structurer already spells. The
|
|
153
|
+
// arguments move soundly for the reason the latch's do: the block has no params and one op, so
|
|
154
|
+
// every value its `br` passes dominates it, and therefore the end of each of its predecessors.
|
|
155
|
+
|
|
156
|
+
/** One candidate trampoline: the block, where its `br` goes, and the loops it may leave. */
|
|
157
|
+
export interface ExitTrampoline {
|
|
158
|
+
block: Block;
|
|
159
|
+
target: Block;
|
|
160
|
+
/** the predecessors of `block` */
|
|
161
|
+
preds: readonly Block[];
|
|
162
|
+
/** does some loop hold every predecessor of `block` but not `block` itself, and leave by another
|
|
163
|
+
* edge for `target` as well — landing on it, or on a block that reaches it forward without
|
|
164
|
+
* re-entering the loop (`reachForward`)? */
|
|
165
|
+
sharedExit: boolean;
|
|
166
|
+
/** does every path from `target` reach a `ret` without an effect on the way? */
|
|
167
|
+
pureReturn: boolean;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export const EXIT_TRAMPOLINE_GATES: readonly Gate<ExitTrampoline>[] = [
|
|
171
|
+
{
|
|
172
|
+
id: 'trampoline-has-params',
|
|
173
|
+
why: 'a param is a join: the br args may read it, and they would not exist at a predecessor',
|
|
174
|
+
// the fold then hands a predecessor a value it does not dominate, which `verify` refuses loud
|
|
175
|
+
sound: false,
|
|
176
|
+
rejects: (c) => c.block.params.length > 0,
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
id: 'trampoline-does-work',
|
|
180
|
+
why: 'the sole terminator must be an unconditional `br`: folding a `cond_br` block discards its second successor',
|
|
181
|
+
sound: true,
|
|
182
|
+
guardedBy: 'latch.test.ts: an exit block whose sole op is a cond_br is never folded',
|
|
183
|
+
rejects: (c) => c.block.ops[0]?.opcode !== 'br',
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
id: 'trampoline-self-branch',
|
|
187
|
+
why: 'a block branching to itself is an infinite loop rather than a trampoline',
|
|
188
|
+
sound: false,
|
|
189
|
+
rejects: (c) => c.target === c.block,
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
// The SCOPE of the fold, not its soundness: an exit edge's empty block is harmless to the
|
|
193
|
+
// structurer wherever it is the loop's one way out, and folding it there would only re-spell
|
|
194
|
+
// functions that already lift.
|
|
195
|
+
id: 'trampoline-not-a-second-exit',
|
|
196
|
+
why: 'only a loop that also leaves for the same target by another edge, or for a block that runs on into it, gains from the fold an exit every edge out can leave for',
|
|
197
|
+
sound: false,
|
|
198
|
+
rejects: (c) => !c.sharedExit,
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
// A target that only returns already lifts: the structurer takes the edge through the
|
|
202
|
+
// trampoline as an early `return` and copies the pure tail into it. Kept that way, so the fold
|
|
203
|
+
// re-spells no function that lifted before it.
|
|
204
|
+
id: 'trampoline-returns-already',
|
|
205
|
+
why: 'an exit that only returns is an early `return` arm, which the structurer spells without the fold',
|
|
206
|
+
sound: false,
|
|
207
|
+
rejects: (c) => c.pureReturn,
|
|
208
|
+
},
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
/** Every path from `b` ends in a `ret` and runs no effect on the way. */
|
|
212
|
+
export function returnsPurely(b: Block): boolean {
|
|
213
|
+
const seen = new Set<Block>();
|
|
214
|
+
for (const stack = [b]; stack.length > 0;) {
|
|
215
|
+
const x = stack.pop()!;
|
|
216
|
+
if (seen.has(x)) {
|
|
217
|
+
continue; // a join two paths share, or a cycle — either way judged on its first visit
|
|
218
|
+
}
|
|
219
|
+
seen.add(x);
|
|
220
|
+
if (x.ops.some(effectful)) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
const t = x.ops[x.ops.length - 1];
|
|
224
|
+
if (t?.opcode === 'ret') {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (t?.opcode !== 'br' && t?.opcode !== 'cond_br') {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
stack.push(...successorsOf(x));
|
|
231
|
+
}
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Splice out every empty EXIT TRAMPOLINE the gates above admit; returns how many were removed.
|
|
236
|
+
* Iterated, as the latch fold is: one splice can make another block's exit edges shared. */
|
|
237
|
+
export function foldExitTrampolines(fn: Fn, gates: readonly Gate<ExitTrampoline>[] = EXIT_TRAMPOLINE_GATES): number {
|
|
238
|
+
let folded = 0;
|
|
239
|
+
for (;;) {
|
|
240
|
+
const preds = predecessors(fn);
|
|
241
|
+
const dom = dominators(fn);
|
|
242
|
+
const loops = [...analyzeLoops(fn, dom).byHeader.values()];
|
|
243
|
+
const found = fn.blocks.find((b) => {
|
|
244
|
+
const target = b.ops[0]?.successors[0]?.block;
|
|
245
|
+
if (target === undefined) {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
const into = preds.get(b) ?? [];
|
|
249
|
+
const sharedExit =
|
|
250
|
+
into.length > 0 &&
|
|
251
|
+
loops.some(
|
|
252
|
+
(nl) =>
|
|
253
|
+
!nl.body.has(b) &&
|
|
254
|
+
into.every((p) => nl.body.has(p)) &&
|
|
255
|
+
nl.exitEdges.some((e) => e.to !== b && reachForward(e.to, dom, nl.body).has(target)),
|
|
256
|
+
);
|
|
257
|
+
return (
|
|
258
|
+
firstRejection(gates, { block: b, target, preds: into, sharedExit, pureReturn: returnsPurely(target) }) === null
|
|
259
|
+
);
|
|
260
|
+
});
|
|
261
|
+
if (!found) {
|
|
262
|
+
return folded;
|
|
263
|
+
}
|
|
264
|
+
const onward = found.ops[0].successors[0];
|
|
265
|
+
for (const b of fn.blocks) {
|
|
266
|
+
let repointed = false;
|
|
267
|
+
for (const op of b.ops) {
|
|
268
|
+
op.successors.forEach((s, i) => {
|
|
269
|
+
if (s.block === found) {
|
|
270
|
+
op.successors[i] = { block: onward.block, args: [...onward.args] };
|
|
271
|
+
repointed = true;
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
if (repointed) {
|
|
276
|
+
foldWriteOrder(fn.writeOrder, found, b);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
fn.blocks = fn.blocks.filter((b) => b !== found);
|
|
280
|
+
folded++;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
@@ -71,6 +71,7 @@
|
|
|
71
71
|
import { type Fn, type Op, type Value, defOpMap } from '../ir/core';
|
|
72
72
|
import { type IrType, type StructField, T, scalarTypeForAccess } from '../ir/types';
|
|
73
73
|
import { type Gate, firstRejection } from '../l3/gates';
|
|
74
|
+
import { storeSigned } from './storesign';
|
|
74
75
|
import { firstFreeStructIndex } from './structs';
|
|
75
76
|
|
|
76
77
|
/** One variable-index access, resolved onto (base value, member offset). */
|
|
@@ -260,7 +261,9 @@ function stripExt(v: Value, defs: Map<Value, Op>): Value {
|
|
|
260
261
|
}
|
|
261
262
|
}
|
|
262
263
|
|
|
263
|
-
/** `add(x, const)` read as (other operand, constant), or null. The add is commutative.
|
|
264
|
+
/** `add(x, const)` read as (other operand, constant), or null. The add is commutative. ONE link, not
|
|
265
|
+
* a chain, and no `sub`: the shapes this pass reads are a single `add` of a scaled index or of a
|
|
266
|
+
* member offset. A chain of constant displacements is ir/alias.ts `constDisplacementOf`. */
|
|
264
267
|
function constAddend(op: Op, defs: Map<Value, Op>): { of: Value; k: number } | null {
|
|
265
268
|
if (op.opcode !== 'add' || op.operands.length !== 2) {
|
|
266
269
|
return null;
|
|
@@ -389,7 +392,7 @@ function accessesByBase(fn: Fn, defs: Map<Value, Op>): Map<Value, MemberAccess[]
|
|
|
389
392
|
op,
|
|
390
393
|
off: add === null ? 0 : add.k,
|
|
391
394
|
elemSize,
|
|
392
|
-
signed: isLoad ? (op.attrs.signed as boolean) : elemSize
|
|
395
|
+
signed: isLoad ? (op.attrs.signed as boolean) : storeSigned(op, elemSize),
|
|
393
396
|
isLoad,
|
|
394
397
|
index: op.operands[1],
|
|
395
398
|
addr: add === null ? null : op.operands[0],
|
|
@@ -583,8 +586,11 @@ export function recognizeMemberArrays(
|
|
|
583
586
|
a.op.operands[0] = g.base;
|
|
584
587
|
a.op.attrs.memberOff = a.off;
|
|
585
588
|
if (!a.isLoad) {
|
|
586
|
-
//
|
|
587
|
-
//
|
|
589
|
+
// the member's declaration decides how the index node types, so the store spells through
|
|
590
|
+
// the same declaration the load does, over any stamp raise/storesign.ts left. No probed
|
|
591
|
+
// input reaches here with a stamp that disagrees: agbcc lifts a signed narrow constant as
|
|
592
|
+
// `neg`/`shl`, and the MIPS shapes (`p->t[i] = -1`, `-104` beside a u16 read) take a flat
|
|
593
|
+
// cast. One that does should keep its stamp, and the index node cast the cell to it.
|
|
588
594
|
a.op.attrs.signed = g.members.find((m) => m.off === a.off)!.signed;
|
|
589
595
|
}
|
|
590
596
|
}
|