@asmlift/core 0.5.0 → 0.6.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 +22 -16
- package/package.json +1 -1
- package/src/backend/c.ts +1 -0
- package/src/backend/cfamily.ts +238 -167
- package/src/backend/cpp.ts +1 -0
- package/src/backend/pascal.ts +26 -12
- package/src/contracts.ts +194 -39
- package/src/declare.ts +41 -4
- package/src/frontend/mips.ts +11 -0
- package/src/frontend/ppc.ts +43 -7
- package/src/frontend/ssa.ts +404 -29
- package/src/frontend/thumb.ts +2176 -686
- package/src/ir/alias.ts +54 -0
- package/src/ir/bits.ts +75 -0
- package/src/ir/core.ts +337 -2
- package/src/ir/opcodes.ts +140 -21
- package/src/ir/parse.ts +19 -2
- package/src/ir/print.ts +27 -2
- package/src/ir/simplify.ts +190 -3
- package/src/ir/struct-names.ts +42 -0
- package/src/ir/verify.ts +43 -49
- package/src/l3/address.ts +62 -0
- package/src/l3/argbase.ts +2 -1
- package/src/l3/ast.ts +464 -57
- package/src/l3/basecse.ts +664 -76
- package/src/l3/coalesce.ts +429 -43
- package/src/l3/dce.ts +31 -9
- package/src/l3/gates.ts +21 -0
- package/src/l3/hoist.ts +293 -14
- package/src/l3/homesplit.ts +285 -0
- package/src/l3/initfirst.ts +301 -0
- package/src/l3/inlinebase.ts +193 -0
- package/src/l3/mentions.ts +113 -0
- package/src/l3/mulfirst.ts +42 -0
- package/src/l3/nearbase.ts +152 -0
- package/src/l3/offmember.ts +371 -0
- package/src/l3/parkfirst.ts +96 -0
- package/src/l3/pollguard.ts +154 -0
- package/src/l3/ptrfield.ts +227 -0
- package/src/l3/regspell.ts +110 -85
- package/src/l3/reindex.ts +715 -78
- package/src/l3/scopebase.ts +644 -218
- package/src/l3/sinkinit.ts +40 -0
- package/src/l3/slotorder.ts +123 -0
- package/src/l3/storage.ts +48 -0
- package/src/l3/symbol-refs.ts +41 -8
- package/src/l3/tailmerge.ts +15 -0
- package/src/l3/typing.ts +198 -9
- package/src/l3/unmerge.ts +263 -0
- package/src/l3/unreduce.ts +971 -0
- package/src/l3/volatileptr.ts +207 -0
- package/src/l3/volatileval.ts +130 -0
- package/src/l3/volstore.ts +229 -0
- package/src/l3/zerosub.ts +62 -0
- package/src/pattern/engine.ts +236 -13
- package/src/pipeline.ts +157 -56
- package/src/proto.ts +112 -14
- package/src/raise/arrays.ts +6 -1
- package/src/raise/divpow2.ts +2 -2
- package/src/raise/globalshape.ts +1038 -0
- package/src/raise/gvn.ts +33 -18
- package/src/raise/latch.ts +126 -0
- package/src/raise/memberarrays.ts +594 -0
- package/src/raise/narrow.ts +124 -0
- package/src/raise/narrowlocal.ts +556 -0
- package/src/raise/paramwidth.ts +179 -0
- package/src/raise/pre-recovery.ts +97 -14
- package/src/raise/recover.ts +56 -23
- package/src/raise/retsink.ts +210 -10
- package/src/raise/shortcircuit.ts +474 -74
- package/src/raise/struct-arrays.ts +19 -2
- package/src/raise/structs.ts +33 -3
- package/src/rank-axes.ts +630 -0
- package/src/rank-declare.ts +256 -0
- package/src/rank.ts +1723 -272
- package/src/structure/analysis.ts +1392 -141
- package/src/structure/bitfields.ts +332 -0
- package/src/structure/globalaccess.ts +274 -0
- package/src/structure/hazards.ts +411 -20
- package/src/structure/loops.ts +2 -49
- package/src/structure/namecoalesce.ts +435 -0
- package/src/structure/structure.ts +2678 -526
- package/src/structure/switch-recover.ts +616 -144
- package/src/symbols.ts +62 -1
- package/src/target.ts +367 -24
- package/src/trace.ts +111 -32
package/src/ir/verify.ts
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
// 1. every block ends in exactly one terminator (and it is the last op)
|
|
4
4
|
// 2. operands well-formed: opcode registered, correct arity/attrs
|
|
5
5
|
// 3. SSA: each value defined once; every use is defined; def dominates use
|
|
6
|
-
|
|
6
|
+
// 4. side data: a fn that carries a write-order record carries one for EVERY block, with every
|
|
7
|
+
// ordinal inside that block's own write count (ir/core.ts `WriteOrder`)
|
|
8
|
+
import { Block, Fn, Value, dominators } from './core';
|
|
7
9
|
import { opSig } from './opcodes';
|
|
8
10
|
|
|
9
11
|
export class VerifyError extends Error {}
|
|
@@ -133,42 +135,8 @@ export function verify(fn: Fn): void {
|
|
|
133
135
|
);
|
|
134
136
|
}
|
|
135
137
|
|
|
136
|
-
// --- dominance (
|
|
137
|
-
const
|
|
138
|
-
const preds = predecessors(fn);
|
|
139
|
-
const dom = new Map<Block, Set<Block>>();
|
|
140
|
-
const allBlocks = new Set(fn.blocks);
|
|
141
|
-
for (const b of fn.blocks) {
|
|
142
|
-
dom.set(b, b === entry ? new Set([entry]) : new Set(allBlocks));
|
|
143
|
-
}
|
|
144
|
-
let changed = true;
|
|
145
|
-
while (changed) {
|
|
146
|
-
changed = false;
|
|
147
|
-
for (const b of fn.blocks) {
|
|
148
|
-
if (b === entry) {
|
|
149
|
-
continue;
|
|
150
|
-
}
|
|
151
|
-
let inter: Set<Block> | null = null;
|
|
152
|
-
for (const p of preds.get(b)!) {
|
|
153
|
-
const dp = dom.get(p)!;
|
|
154
|
-
if (inter === null) {
|
|
155
|
-
inter = new Set(dp);
|
|
156
|
-
continue;
|
|
157
|
-
}
|
|
158
|
-
for (const x of inter) {
|
|
159
|
-
if (!dp.has(x)) {
|
|
160
|
-
inter.delete(x);
|
|
161
|
-
}
|
|
162
|
-
} // intersect in place (spec-safe delete-in-iter)
|
|
163
|
-
}
|
|
164
|
-
const next = new Set<Block>(inter ?? []);
|
|
165
|
-
next.add(b);
|
|
166
|
-
if (!setEq(next, dom.get(b)!)) {
|
|
167
|
-
dom.set(b, next);
|
|
168
|
-
changed = true;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
138
|
+
// --- dominance: def must dominate every use (ir/core.ts owns the analysis) ---
|
|
139
|
+
const dom = dominators(fn);
|
|
172
140
|
const dominates = (a: Block, b: Block) => dom.get(b)!.has(a);
|
|
173
141
|
|
|
174
142
|
for (const b of fn.blocks) {
|
|
@@ -193,6 +161,44 @@ export function verify(fn: Fn): void {
|
|
|
193
161
|
),
|
|
194
162
|
);
|
|
195
163
|
}
|
|
164
|
+
|
|
165
|
+
checkWriteOrder(fn);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** THE WRITE-ORDER RECORD'S CROSS-PASS OBLIGATION, checked rather than trusted (ir/core.ts
|
|
169
|
+
* `WriteOrder`, `foldWriteOrder`), because the consequence of missing it is silent: an unmeasured
|
|
170
|
+
* block's edge copies sort with no records at all, changing the order the structurer emits them in
|
|
171
|
+
* and, on a cycle, which register it spills. No decline, no marker.
|
|
172
|
+
*
|
|
173
|
+
* WHAT IT CATCHES: a pass that MINTS a block into a measured fn, or drops a block's entry, leaving
|
|
174
|
+
* a hole indistinguishable from a fn nobody measured. Also an ordinal outside its block's own
|
|
175
|
+
* write count — a `foldWriteOrder` that moved records without growing the count. WHAT IT CANNOT:
|
|
176
|
+
* ops moved from one MEASURED block into another with the fold forgotten, since both blocks still
|
|
177
|
+
* have entries and no snapshot of the IR shows the move. That half stays a review obligation until
|
|
178
|
+
* the record hangs on `Successor` itself.
|
|
179
|
+
*
|
|
180
|
+
* A fn is "measured" iff it has any entry at all; one with none is parsed or hand-built IR, whose
|
|
181
|
+
* edges take the def-position proxy by design. */
|
|
182
|
+
function checkWriteOrder(fn: Fn): void {
|
|
183
|
+
const order = fn.writeOrder;
|
|
184
|
+
if (order === undefined || order.writes.size === 0) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
for (const b of fn.blocks) {
|
|
188
|
+
const writes = order.writes.get(b);
|
|
189
|
+
if (writes === undefined) {
|
|
190
|
+
throw new VerifyError(
|
|
191
|
+
`fn '${fn.name}': block ^bb${fn.blocks.indexOf(b)} carries no write-order entry, but the fn is measured`,
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
for (const at of order.lastWrite.get(b)?.values() ?? []) {
|
|
195
|
+
if (at < 0 || at >= writes) {
|
|
196
|
+
throw new VerifyError(
|
|
197
|
+
`fn '${fn.name}': write-order ordinal ${at} on block ^bb${fn.blocks.indexOf(b)} is outside its ${writes} writes`,
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
196
202
|
}
|
|
197
203
|
|
|
198
204
|
/** Run a check body; a VerifyError it throws is re-thrown with the op's location appended —
|
|
@@ -207,15 +213,3 @@ function locate(body: () => void, where: () => string): void {
|
|
|
207
213
|
throw e;
|
|
208
214
|
}
|
|
209
215
|
}
|
|
210
|
-
|
|
211
|
-
function setEq(a: Set<Block>, b: Set<Block>): boolean {
|
|
212
|
-
if (a.size !== b.size) {
|
|
213
|
-
return false;
|
|
214
|
-
}
|
|
215
|
-
for (const x of a) {
|
|
216
|
-
if (!b.has(x)) {
|
|
217
|
-
return false;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
return true;
|
|
221
|
-
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// asmlift — THE THREE READINGS OF A CONSTANT ADDRESS, in one place because they are three and not
|
|
2
|
+
// one, and because the same four lines had been re-typed into five files at three strengths.
|
|
3
|
+
//
|
|
4
|
+
// A raw address reaches L3 as a cast chain over a `const`, and "which numeric cell is this" has
|
|
5
|
+
// three different right answers depending on what the caller intends to DO with the answer. The
|
|
6
|
+
// readings are deliberately not merged; what this module supplies is names for them, so a pass
|
|
7
|
+
// declares which one it means instead of restating four lines and drifting.
|
|
8
|
+
//
|
|
9
|
+
// • baseConst — a deref BASE, through SCALAR pointer casts only. A cast to a STRUCT pointer is
|
|
10
|
+
// the dot-form's base and is refused, because a lever that re-spells THROUGH it collapses the
|
|
11
|
+
// stride (`((struct S *)K)[i].f` is not `((u8 *)K)[…]`). This is the reading a lever that
|
|
12
|
+
// REWRITES the base needs: l3/nearbase.ts's clusters, l3/volstore.ts's qualifier.
|
|
13
|
+
// • addrConst — the address an expression IS, through ANY pointer cast. Wider, and safe
|
|
14
|
+
// because nothing re-spells through it: l3/volatileptr.ts counts volatility claims with it.
|
|
15
|
+
// • rootConst — the constant an ACCESS CHAIN is rooted at, through any cast and any number of
|
|
16
|
+
// subscripts and field selections. Wide enough to place an object whose element is not known,
|
|
17
|
+
// which is what a read with a runtime subscript needs (l3/unreduce.ts's aliasing gate).
|
|
18
|
+
//
|
|
19
|
+
// And `cellAddress`, which is not a fourth reading of a base but the WHOLE address an `index`
|
|
20
|
+
// denotes — base plus subscript × width — or null when any part of it is not constant.
|
|
21
|
+
//
|
|
22
|
+
// A CAUTION THE HISTORY EARNED: `rootConst` and `cellAddress` disagree, and a predicate that uses
|
|
23
|
+
// one on its write side and the other on its read side is not the same predicate on both. A read
|
|
24
|
+
// rooted at 0x03FFFFF0 whose element is `[8]` denotes 0x04000010 — a device register the root
|
|
25
|
+
// reports as EWRAM. Ask for both where both can be had.
|
|
26
|
+
import type { Expr } from './ast';
|
|
27
|
+
|
|
28
|
+
/** the numeric address behind a deref base, through SCALAR pointer casts only */
|
|
29
|
+
export const baseConst = (e: Expr): number | null =>
|
|
30
|
+
e.k === 'const'
|
|
31
|
+
? e.value
|
|
32
|
+
: e.k === 'cast' && !(e.to.kind === 'ptr' && e.to.to.kind === 'struct')
|
|
33
|
+
? baseConst(e.e)
|
|
34
|
+
: null;
|
|
35
|
+
|
|
36
|
+
/** the numeric address an expression IS, through any number of pointer casts */
|
|
37
|
+
export const addrConst = (e: Expr): number | null =>
|
|
38
|
+
e.k === 'const' ? e.value : e.k === 'cast' && e.to.kind === 'ptr' ? addrConst(e.e) : null;
|
|
39
|
+
|
|
40
|
+
/** the constant an ACCESS CHAIN is rooted at, through any cast, subscript and field selection */
|
|
41
|
+
export const rootConst = (e: Expr): number | null =>
|
|
42
|
+
e.k === 'const'
|
|
43
|
+
? e.value
|
|
44
|
+
: e.k === 'cast'
|
|
45
|
+
? rootConst(e.e)
|
|
46
|
+
: e.k === 'index' || e.k === 'field'
|
|
47
|
+
? rootConst(e.base)
|
|
48
|
+
: null;
|
|
49
|
+
|
|
50
|
+
/** The WHOLE address an `index` access denotes, or null when any part of it is not constant. A
|
|
51
|
+
* `field` never resolves here: its offset is the struct's, which this module has no layout for. */
|
|
52
|
+
export function cellAddress(e: Expr): number | null {
|
|
53
|
+
if (e.k !== 'index' || e.lead !== undefined || e.idx.k !== 'const') {
|
|
54
|
+
return null; // a `lead` is a multidimensional global's leading dimension, not a numeric address
|
|
55
|
+
}
|
|
56
|
+
const base = baseConst(e.base);
|
|
57
|
+
return base === null ? null : base + e.idx.value * e.width;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** is `a` inside the half-open range `w`? A null address, or no declared range, is never inside. */
|
|
61
|
+
export const inRange = (a: number | null, w?: readonly [number, number]): boolean =>
|
|
62
|
+
w !== undefined && a !== null && a >= w[0] && a < w[1];
|
package/src/l3/argbase.ts
CHANGED
|
@@ -41,6 +41,7 @@ import { type IrType, T, scalarTypeForAccess } from '../ir/types';
|
|
|
41
41
|
import type { Expr, SFn, Stmt } from './ast';
|
|
42
42
|
import { mapExprChildren, stmtExprs } from './ast';
|
|
43
43
|
import { nameAllocator } from './hoist';
|
|
44
|
+
import { declaredGlobals } from './storage';
|
|
44
45
|
|
|
45
46
|
/** A base this pass may evaluate early: pure, and not something a store can change under us.
|
|
46
47
|
*
|
|
@@ -103,7 +104,7 @@ function baseKey(n: Extract<Expr, { k: 'index' }>): string {
|
|
|
103
104
|
* candidate at all rather than a duplicate of the primary).
|
|
104
105
|
*/
|
|
105
106
|
export function materializeArgBases(sfn: SFn): SFn | null {
|
|
106
|
-
const globals =
|
|
107
|
+
const globals = declaredGlobals(sfn);
|
|
107
108
|
const fresh = nameAllocator(sfn);
|
|
108
109
|
const newLocals: { name: string; type: IrType }[] = [];
|
|
109
110
|
let fired = false;
|