@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/l3/regspell.ts
CHANGED
|
@@ -40,6 +40,7 @@ function isPure(e: Expr): boolean {
|
|
|
40
40
|
switch (e.k) {
|
|
41
41
|
case 'var':
|
|
42
42
|
case 'const':
|
|
43
|
+
case 'fconst':
|
|
43
44
|
return true;
|
|
44
45
|
case 'un':
|
|
45
46
|
case 'cast':
|
|
@@ -56,6 +57,9 @@ function renameVar(e: Expr, from: string, to: string): Expr {
|
|
|
56
57
|
if (e.k === 'var') {
|
|
57
58
|
return e.name === from ? { k: 'var', name: to } : e;
|
|
58
59
|
}
|
|
60
|
+
if (e.k === 'postincr') {
|
|
61
|
+
return e.name === from ? { ...e, name: to } : e;
|
|
62
|
+
}
|
|
59
63
|
return mapExprChildren(e, (c) => renameVar(c, from, to));
|
|
60
64
|
}
|
|
61
65
|
|
|
@@ -185,16 +189,21 @@ function condOtherMentions(cond: Expr, E: Expr, v: string): boolean {
|
|
|
185
189
|
};
|
|
186
190
|
return other ? mentions(other) : false;
|
|
187
191
|
}
|
|
188
|
-
/** cond compares E against a pure operand → same comparison reading the named var.
|
|
192
|
+
/** cond compares E against a pure operand → same comparison reading the named var.
|
|
193
|
+
* Both rebuilds below SPREAD the compare, so its `unsignedCmp` evidence (l3/ast.ts) rides along:
|
|
194
|
+
* the copy is declared at E's type, which keeps the compare's C meaning but not its asm meaning,
|
|
195
|
+
* and rank.ts's judgment of the finished tree (typing.ts `signedOverUnsignedCompare`) is what
|
|
196
|
+
* says whether `w0 >= a1` still compares the way the machine did. The negation keeps it too: the
|
|
197
|
+
* complement of an unsigned ordering is unsigned. */
|
|
189
198
|
function rewriteCond(cond: Expr, E: Expr, name: string): Expr | null {
|
|
190
199
|
if (cond.k !== 'bin' || !(cond.op in FLIP)) {
|
|
191
200
|
return null;
|
|
192
201
|
}
|
|
193
202
|
if (exprEq(cond.l, E) && isPure(cond.r)) {
|
|
194
|
-
return {
|
|
203
|
+
return { ...cond, l: { k: 'var', name } };
|
|
195
204
|
}
|
|
196
205
|
if (exprEq(cond.r, E) && isPure(cond.l)) {
|
|
197
|
-
return {
|
|
206
|
+
return { ...cond, r: { k: 'var', name } };
|
|
198
207
|
}
|
|
199
208
|
return null;
|
|
200
209
|
}
|
|
@@ -203,7 +212,7 @@ function flipCmp(cond: Expr): Expr | null {
|
|
|
203
212
|
return null;
|
|
204
213
|
}
|
|
205
214
|
const op = FLIP[cond.op];
|
|
206
|
-
return op ? {
|
|
215
|
+
return op ? { ...cond, op: op as Extract<Expr, { k: 'bin' }>['op'] } : null;
|
|
207
216
|
}
|
|
208
217
|
/** in `upd`, the occurrence of subtree E replaced by var `name` (E was just assigned to it). */
|
|
209
218
|
function renameSubexpr(e: Expr, E: Expr, name: string): Expr {
|
|
@@ -251,12 +260,22 @@ export function registerishSpellings(sfn: SFn): RegcopySpelling[] {
|
|
|
251
260
|
// • E and the cond pure; the cond's non-E operand must NOT mention v (a clamp's
|
|
252
261
|
// `if (a < v)` would compare against the POST-assignment v — reproduced);
|
|
253
262
|
// • the copy w carries E's RENDERED type, not v's declared one (retyping a u32
|
|
254
|
-
// comparison signed flipped its sense — reproduced)
|
|
255
|
-
|
|
263
|
+
// comparison signed flipped its sense — reproduced), PROMOTED: the update assigns
|
|
264
|
+
// back into w, and a narrow w would truncate what the update computed;
|
|
265
|
+
// • v not narrow, because then v's declaration IS the update's truncation, which a
|
|
266
|
+
// word-wide w would drop.
|
|
267
|
+
const vType = typeOf(v);
|
|
268
|
+
if (
|
|
269
|
+
!isPure(E) ||
|
|
270
|
+
!isPure(cond) ||
|
|
271
|
+
condOtherMentions(cond, E, v) ||
|
|
272
|
+
(vType.kind === 'int' && vType.width < 32)
|
|
273
|
+
) {
|
|
256
274
|
out.push(s);
|
|
257
275
|
continue;
|
|
258
276
|
}
|
|
259
|
-
const
|
|
277
|
+
const rendered = exprCType(E, declaredTypes({ ...sfn, locals })) ?? vType;
|
|
278
|
+
const wType = rendered.kind === 'int' && rendered.width < 32 ? T.s(32) : rendered;
|
|
260
279
|
const w = freshVar(wType);
|
|
261
280
|
const condOnW = rewriteCond(cond, E, w);
|
|
262
281
|
if (condOnW) {
|
package/src/l3/reindex.ts
CHANGED
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
// Past the table, the counter's DECLARATION is retired with its writes: nothing mentions it any
|
|
71
71
|
// more, and a declared-but-untouched local reads as the deliberate `uninit` slot it is not.
|
|
72
72
|
//
|
|
73
|
-
// v3 SCOPE — the up-counting
|
|
73
|
+
// v3 SCOPE — the up-counting walk with an EXPRESSION base (`p = (T *)(EXPR)` ahead of a
|
|
74
74
|
// counter-carried do-while): full rules at `tryExprWalk` below.
|
|
75
75
|
//
|
|
76
76
|
// v4 SCOPE — the UNGUARDED countdown, the shape agbcc emits for `for(i=0;i<C;i++) a[i]` with a
|
|
@@ -95,9 +95,11 @@ import {
|
|
|
95
95
|
Stmt,
|
|
96
96
|
mapExprChildren,
|
|
97
97
|
mapStmtExprs,
|
|
98
|
+
mentionedName,
|
|
98
99
|
rematerializableAddress,
|
|
99
100
|
stmtChildren,
|
|
100
101
|
stmtExprs,
|
|
102
|
+
walkExprs,
|
|
101
103
|
} from './ast';
|
|
102
104
|
import { type Gate, firstRejection } from './gates';
|
|
103
105
|
import { takenNames } from './hoist';
|
|
@@ -109,14 +111,15 @@ interface WalkLoop {
|
|
|
109
111
|
base: string; // the var `p` was initialised from
|
|
110
112
|
}
|
|
111
113
|
|
|
112
|
-
/** Total mentions of `name` across a statement list (reads, writes, everywhere)
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
114
|
+
/** Total mentions of `name` across a statement list (reads, writes, everywhere) — `mentionedName`'s
|
|
115
|
+
* whole vocabulary, because the accounting these recognizers rest on is "nothing else in the
|
|
116
|
+
* function touches this name", which a var-only count cannot say. `&x` is how a frame local's
|
|
117
|
+
* address renders (structure.ts laddrName), and an escaped address is a read of the object by
|
|
118
|
+
* whatever holds it. */
|
|
116
119
|
function countMentions(stmts: Stmt[], name: string): number {
|
|
117
120
|
let n = 0;
|
|
118
121
|
const inExpr = (e: Expr): void => {
|
|
119
|
-
if ((e
|
|
122
|
+
if (mentionedName(e) === name) {
|
|
120
123
|
n++;
|
|
121
124
|
}
|
|
122
125
|
mapExprChildren(e, (c) => {
|
|
@@ -170,8 +173,9 @@ function derefWidths(stmts: Stmt[], p: string): number[] {
|
|
|
170
173
|
|
|
171
174
|
/** Does `e` mention `name` anywhere — as a value or as an escaped address (see countMentions)? */
|
|
172
175
|
function mentionsVar(e: Expr, name: string): boolean {
|
|
173
|
-
|
|
174
|
-
|
|
176
|
+
const own = mentionedName(e);
|
|
177
|
+
if (own !== undefined) {
|
|
178
|
+
return own === name;
|
|
175
179
|
}
|
|
176
180
|
let found = false;
|
|
177
181
|
mapExprChildren(e, (c) => {
|
|
@@ -298,11 +302,13 @@ export const COUNTDOWN_GATES: readonly Gate<CountdownCtx>[] = [
|
|
|
298
302
|
];
|
|
299
303
|
|
|
300
304
|
/** A `break`/`continue` that would target the ENCLOSING loop: found at if/switch depth, but not
|
|
301
|
-
* inside a nested loop (whose own exits target itself).
|
|
305
|
+
* inside a nested loop (whose own exits target itself). A switch's own `break` (`ofSwitch`)
|
|
306
|
+
* leaves only its switch, and the loop's tail still runs after it, so it is not one. */
|
|
302
307
|
function hasLoopExit(stmts: Stmt[]): boolean {
|
|
303
308
|
return stmts.some((s) => {
|
|
304
309
|
switch (s.k) {
|
|
305
310
|
case 'break':
|
|
311
|
+
return s.ofSwitch !== true;
|
|
306
312
|
case 'continue':
|
|
307
313
|
return true;
|
|
308
314
|
case 'if':
|
|
@@ -421,6 +427,11 @@ function reindexCond(cond: Expr, walk: WalkLoop, iv: string): Expr | null {
|
|
|
421
427
|
return null;
|
|
422
428
|
}
|
|
423
429
|
const i: Expr = { k: 'var', name: iv };
|
|
430
|
+
// A fresh node, so the compare's `unsignedCmp` evidence (l3/ast.ts) is dropped, and that is
|
|
431
|
+
// PROVED rather than lost: `p < base + n` compared addresses, which C compares unsigned, and the
|
|
432
|
+
// walk makes `p == base + i` (scaled), so `i < n` compares the same values. Kept, the evidence
|
|
433
|
+
// would have rank.ts withhold the index spelling over an `s32` counter that the pointer
|
|
434
|
+
// spelling, judged on the same rule, never owed.
|
|
424
435
|
return swap ? { k: 'bin', op: cond.op, l: n, r: i } : { k: 'bin', op: cond.op, l: i, r: n };
|
|
425
436
|
}
|
|
426
437
|
|
|
@@ -501,46 +512,81 @@ function reindexStmt(s: Stmt, walk: WalkLoop, iv: string): Stmt | null {
|
|
|
501
512
|
}
|
|
502
513
|
}
|
|
503
514
|
|
|
504
|
-
|
|
515
|
+
/** Whether any statement here, at any depth, writes memory (`store`). */
|
|
516
|
+
function stmtsStore(stmts: Stmt[]): boolean {
|
|
517
|
+
return stmts.some((s) => s.k === 'store' || stmtsStore(stmtChildren(s)));
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/** `e + c`, spelled `e - |c|` for a negative c and `e` alone for 0. */
|
|
521
|
+
function plusConst(e: Expr, c: number): Expr {
|
|
522
|
+
return c === 0 ? e : { k: 'bin', op: c < 0 ? '-' : '+', l: e, r: { k: 'const', value: Math.abs(c) } };
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// ── v3: the up-counting walk with an EXPRESSION base ─────────────────────────────────────────
|
|
505
526
|
//
|
|
506
|
-
// p = (
|
|
527
|
+
// i = S; p = (T *)(EXPR); do { … *p … ; p = p + 1; i = i + 1; } while (i < n);
|
|
507
528
|
//
|
|
508
529
|
// The loop already carries its own integer counter, so the walk pointer is pure strength
|
|
509
|
-
// reduction: deleting its init and step and spelling the deref as BASE[i + REST]
|
|
510
|
-
//
|
|
511
|
-
// indexed source the compiler reduces back.
|
|
512
|
-
//
|
|
513
|
-
//
|
|
514
|
-
//
|
|
515
|
-
//
|
|
516
|
-
//
|
|
517
|
-
//
|
|
518
|
-
//
|
|
519
|
-
//
|
|
520
|
-
//
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
530
|
+
// reduction: deleting its init and step and spelling the deref as BASE[i - S + REST], REST in
|
|
531
|
+
// elements — the sole base addend of EXPR as the index base, the counter FIRST in the index sum —
|
|
532
|
+
// is the indexed source the compiler reduces back. One counter may carry SEVERAL walk pointers
|
|
533
|
+
// (KMC reduces `for (i = 1; …) { gH[i] = 7; gW[i] = 8; }` to one pointer per array): each keeps
|
|
534
|
+
// its own base and unit, and each re-spells on its own merits — one out of scope keeps walking,
|
|
535
|
+
// whatever the order of the steps. v3 SCOPE (decline over approximate):
|
|
536
|
+
// • each `p` is a `T *` over an integer T of W bytes, and every deref of `p` reads W. EXPR's sum
|
|
537
|
+
// counts in its BASE's unit. Over a W-element pointer base that is already elements, and REST
|
|
538
|
+
// stays verbatim, symbolic addends included. Over a byte base (a non-pointer var, a byte
|
|
539
|
+
// pointer, `(u32)&g`) a wider walk needs a constant total W divides (it is rescaled to
|
|
540
|
+
// elements), and a symbolic addend, which would need dividing, stays in the BYTE sum instead:
|
|
541
|
+
// the index base is `(T *)(BASE + SYM)`, the spelling `p = b + k; … p[i]` reduces back to;
|
|
542
|
+
// • the inits `i = S` and `p_k = EXPR_k` are the run of assignments immediately ahead of the
|
|
543
|
+
// `dowhile` (any order); EXPR (its pointer and 32-bit integer casts stripped; a narrowing cast
|
|
544
|
+
// declines) flattens over `+` into exactly ONE base addend — a bare var declared a non-pointer,
|
|
545
|
+
// a byte pointer or a pointer to W-byte integers (any other pointer strides a unit no element
|
|
546
|
+
// index names), or an object's address, `(u32)&g` beside other addends or `&g` alone — plus
|
|
547
|
+
// pure invariant addends (var/const/mul/shl trees whose vars the body never assigns, and,
|
|
548
|
+
// beside a call or a store in the loop, only params and locals whose address never escapes);
|
|
549
|
+
// • the body's contiguous TAIL is exactly one `p_k = p_k + 1` per walk pointer and one
|
|
550
|
+
// `i = i + 1` (any order), with `i` read by the do-while condition; each `p_k` is mentioned
|
|
551
|
+
// nowhere else in the function beyond its init, its step, and derefs AT OFFSET 0 inside this
|
|
552
|
+
// body, and not by the condition;
|
|
553
|
+
// • S is a constant and `i` a declared integer: any S at 32 bits, only S = 0 when narrower and
|
|
554
|
+
// unsigned, never when narrower and signed. The index is `i - S + REST` in elements, which is
|
|
555
|
+
// plain `i` for the init KMC folds from `for (i = S; …) base[i]`: `p = (u32)&g + S*W` over an
|
|
556
|
+
// object, `p = a0 + S` over a W-element pointer.
|
|
557
|
+
|
|
558
|
+
/** One v3 walk pointer's re-spelling: its deref `*p` becomes `base[i - S + idx… + elems]`. */
|
|
559
|
+
interface ExprWalkSpelling {
|
|
560
|
+
readonly p: string;
|
|
561
|
+
readonly base: Expr;
|
|
562
|
+
readonly idx: readonly Expr[];
|
|
563
|
+
readonly elems: number;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/** The per-pointer half of v3: `init` (`p = EXPR`) decomposed into the index base and REST in
|
|
567
|
+
* elements, or null when `p`'s walk is out of scope. The loop-wide half is `tryExprWalkRun`. */
|
|
568
|
+
function exprWalkSpelling(
|
|
569
|
+
init: Stmt & { k: 'assign' },
|
|
524
570
|
dw: Stmt & { k: 'dowhile' },
|
|
525
571
|
ptrVars: Map<string, IrType>,
|
|
526
|
-
fnBody: Stmt[],
|
|
527
572
|
declTypes: VarTypes,
|
|
528
573
|
volatileLocals: ReadonlySet<string>,
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
const pT = ptrVars.get(p)!;
|
|
535
|
-
if (pT.kind !== 'ptr' || pT.to.kind !== 'int' || pT.to.width !== 8) {
|
|
574
|
+
privateNames: ReadonlySet<string>,
|
|
575
|
+
): ExprWalkSpelling | null {
|
|
576
|
+
const p = init.name;
|
|
577
|
+
const pT = ptrVars.get(p);
|
|
578
|
+
if (pT?.kind !== 'ptr' || pT.to.kind !== 'int' || pT.to.width % 8 !== 0 || volatileLocals.has(p)) {
|
|
536
579
|
return null;
|
|
537
580
|
}
|
|
538
|
-
|
|
581
|
+
const W = pT.to.width / 8;
|
|
582
|
+
if (!derefWidths([dw], p).every((w) => w === W)) {
|
|
539
583
|
return null;
|
|
540
584
|
}
|
|
541
|
-
// EXPR decomposition
|
|
542
|
-
|
|
543
|
-
|
|
585
|
+
// EXPR decomposition. Only casts that keep all 32 bits are stripped — a pointer cast, or an
|
|
586
|
+
// integer cast at 32 bits. A narrowing cast over EXPR (`(u16 *)(s16)((u32)&g + 2)`) truncates the
|
|
587
|
+
// address, which no index off the untruncated base reproduces, so it stays and the walk declines.
|
|
588
|
+
let expr: Expr = init.value;
|
|
589
|
+
while (expr.k === 'cast' && (expr.to.kind === 'ptr' || (expr.to.kind === 'int' && expr.to.width === 32))) {
|
|
544
590
|
expr = expr.e;
|
|
545
591
|
}
|
|
546
592
|
const addends: Expr[] = [];
|
|
@@ -553,21 +599,40 @@ function tryExprWalk(
|
|
|
553
599
|
}
|
|
554
600
|
};
|
|
555
601
|
flatten(expr);
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
602
|
+
// The base candidates: a bare var, or an object's address. Beside other addends the address
|
|
603
|
+
// must sit under a 32-bit integer cast — the cast is what makes EXPR's `+` byte arithmetic (a
|
|
604
|
+
// bare `&g + 2` strides g's own size). ALONE it needs no cast: there is no `+` to stride, and
|
|
605
|
+
// the zero-offset init structure prints is `p = (T *)&g`, whose casts the strip above removed.
|
|
606
|
+
const addrOf = (a: Expr): Extract<Expr, { k: 'addr' }> | null =>
|
|
607
|
+
a.k === 'addr' && addends.length === 1
|
|
608
|
+
? a
|
|
609
|
+
: a.k === 'cast' && a.to.kind === 'int' && a.to.width === 32 && a.e.k === 'addr'
|
|
610
|
+
? a.e
|
|
611
|
+
: null;
|
|
612
|
+
const bases = addends.filter((a) => a.k === 'var' || addrOf(a) !== null);
|
|
613
|
+
if (bases.length !== 1) {
|
|
567
614
|
return null;
|
|
568
615
|
}
|
|
569
|
-
|
|
570
|
-
|
|
616
|
+
const rest = addends.filter((a) => a !== bases[0]);
|
|
617
|
+
const base: Extract<Expr, { k: 'var' | 'addr' }> = bases[0].k === 'var' ? bases[0] : addrOf(bases[0])!;
|
|
618
|
+
// The UNIT EXPR's `+` counts in is the base's own stride (declTypes carries params, locals AND
|
|
619
|
+
// globals). KMC folds `for (i = S; …) a0[i]` over a `u16 *a0` to `p = a0 + S`, element
|
|
620
|
+
// arithmetic. A var declared nowhere has an unknowable stride (its project declaration decides).
|
|
621
|
+
// An address needs no declaration: `&g` is g's address whatever g's type, and the printer always
|
|
622
|
+
// casts it to the access width (cfamily.ts legalizedIndexBase: the type walk answers nothing for
|
|
623
|
+
// an `addr`, so even a g declared with W-byte elements prints the cast).
|
|
624
|
+
let unit = 1;
|
|
625
|
+
if (base.k === 'var') {
|
|
626
|
+
const baseT = declTypes(base.name);
|
|
627
|
+
if (baseT === undefined) {
|
|
628
|
+
return null;
|
|
629
|
+
}
|
|
630
|
+
if (baseT.kind === 'ptr') {
|
|
631
|
+
if (baseT.to.kind !== 'int' || (baseT.to.width !== 8 && baseT.to.width !== W * 8)) {
|
|
632
|
+
return null;
|
|
633
|
+
}
|
|
634
|
+
unit = baseT.to.width / 8;
|
|
635
|
+
}
|
|
571
636
|
}
|
|
572
637
|
const pure = (e: Expr): boolean =>
|
|
573
638
|
e.k === 'var' || e.k === 'const'
|
|
@@ -577,7 +642,6 @@ function tryExprWalk(
|
|
|
577
642
|
: e.k === 'cast'
|
|
578
643
|
? pure(e.e)
|
|
579
644
|
: false;
|
|
580
|
-
const assignedIn = (stmts: Stmt[], name: string): boolean => stmts.some((st) => stmtAssigns(st, name));
|
|
581
645
|
const restVars = new Set<string>();
|
|
582
646
|
const collectVars = (e: Expr): void => {
|
|
583
647
|
if (e.k === 'var') {
|
|
@@ -589,69 +653,192 @@ function tryExprWalk(
|
|
|
589
653
|
});
|
|
590
654
|
};
|
|
591
655
|
rest.forEach(collectVars);
|
|
592
|
-
|
|
593
|
-
|
|
656
|
+
if (base.k === 'var') {
|
|
657
|
+
restVars.add(base.name);
|
|
658
|
+
}
|
|
659
|
+
// INVARIANT across the loop, because the indexed spelling re-reads every name on every pass
|
|
660
|
+
// where the walk read it once, at the init: no `assign` in the body writes it, and — unless it
|
|
661
|
+
// is a param or a local whose address is never taken — no call and no memory store in the loop
|
|
662
|
+
// can either. A global (`p = (u16 *)(b + gK * 2)`, `g()` in the body) or an escaped local is
|
|
663
|
+
// written by a callee or through an aliasing pointer with no `assign` in sight.
|
|
664
|
+
// (a `postincr` is an assign in expression clothing; a `marker` is an instruction nobody modelled)
|
|
665
|
+
const loopExprs = [...walkExprs([dw])];
|
|
666
|
+
const loopWritesMemory = loopExprs.some((e) => e.k === 'call' || e.k === 'marker') || stmtsStore(dw.body);
|
|
667
|
+
if (
|
|
668
|
+
!rest.every(pure) ||
|
|
669
|
+
[...restVars].some(
|
|
670
|
+
(n) =>
|
|
671
|
+
volatileLocals.has(n) ||
|
|
672
|
+
dw.body.some((st) => stmtAssigns(st, n)) ||
|
|
673
|
+
loopExprs.some((e) => e.k === 'postincr' && e.name === n) ||
|
|
674
|
+
(loopWritesMemory && !privateNames.has(n)),
|
|
675
|
+
)
|
|
676
|
+
) {
|
|
594
677
|
return null;
|
|
595
678
|
}
|
|
596
|
-
//
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
//
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
const
|
|
605
|
-
|
|
606
|
-
|
|
679
|
+
// REST in ELEMENTS. Counted in the walk's own unit (a byte walk, or a W-element pointer base)
|
|
680
|
+
// it is already elements: the symbolic addends stay verbatim and the constants fold into one.
|
|
681
|
+
// Counted in bytes under a wider walk, the constants must total a multiple of W (rescaled to
|
|
682
|
+
// elements), and the symbolic addends would need dividing — so they stay in the BYTE sum, which
|
|
683
|
+
// becomes the index base: `(T *)(a0 + (a1 << 1))`, every non-constant addend in EXPR's order,
|
|
684
|
+
// the address keeping its `(u32)` (the printer casts the sum to the access width).
|
|
685
|
+
const consts = rest.filter((a): a is Extract<Expr, { k: 'const' }> => a.k === 'const');
|
|
686
|
+
const symbolic = rest.filter((a) => a.k !== 'const');
|
|
687
|
+
const restUnits = consts.reduce((acc, c) => acc + c.value, 0) | 0; // the 32-bit sum, signed
|
|
688
|
+
if (unit === W) {
|
|
689
|
+
return { p, base: { ...base }, idx: symbolic, elems: restUnits };
|
|
690
|
+
}
|
|
691
|
+
if (restUnits % W !== 0) {
|
|
607
692
|
return null;
|
|
608
693
|
}
|
|
609
|
-
const
|
|
610
|
-
|
|
694
|
+
const byteSum = addends
|
|
695
|
+
.filter((a) => a.k !== 'const')
|
|
696
|
+
.reduce<Expr | null>((acc, a) => (acc === null ? a : { k: 'bin', op: '+', l: acc, r: a }), null)!;
|
|
697
|
+
return { p, base: symbolic.length === 0 ? { ...base } : byteSum, idx: [], elems: restUnits / W };
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/** v3 on one do-while: `out` is the statement list already walked ahead of it. Returns the
|
|
701
|
+
* re-spelled loop and the statements that replace `out`'s trailing init run (the counter's init
|
|
702
|
+
* kept, the walk pointers' deleted), or null. Tries the longest init run first. */
|
|
703
|
+
function tryExprWalk(
|
|
704
|
+
out: readonly Stmt[],
|
|
705
|
+
dw: Stmt & { k: 'dowhile' },
|
|
706
|
+
ptrVars: Map<string, IrType>,
|
|
707
|
+
fnBody: Stmt[],
|
|
708
|
+
declTypes: VarTypes,
|
|
709
|
+
volatileLocals: ReadonlySet<string>,
|
|
710
|
+
privateNames: ReadonlySet<string>,
|
|
711
|
+
): { loop: Stmt; run: number; kept: Stmt[] } | null {
|
|
712
|
+
let nTail = 0;
|
|
713
|
+
while (nTail < dw.body.length && isUnitIncrement(dw.body[dw.body.length - 1 - nTail]) !== null) {
|
|
714
|
+
nTail++;
|
|
715
|
+
}
|
|
716
|
+
let nInit = 0;
|
|
717
|
+
while (nInit < out.length && out[out.length - 1 - nInit].k === 'assign') {
|
|
718
|
+
nInit++;
|
|
719
|
+
}
|
|
720
|
+
for (let n = Math.min(nTail, nInit); n >= 2; n--) {
|
|
721
|
+
const r = tryExprWalkRun(
|
|
722
|
+
out.slice(-n) as (Stmt & { k: 'assign' })[],
|
|
723
|
+
n,
|
|
724
|
+
dw,
|
|
725
|
+
ptrVars,
|
|
726
|
+
fnBody,
|
|
727
|
+
declTypes,
|
|
728
|
+
volatileLocals,
|
|
729
|
+
privateNames,
|
|
730
|
+
);
|
|
731
|
+
if (r) {
|
|
732
|
+
return r;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
return null;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/** v3 with the last `n` statements of the body as the step tail and `inits` (the last `n` ahead
|
|
739
|
+
* of the loop) as their inits: one counter, n - 1 walk pointers. */
|
|
740
|
+
function tryExprWalkRun(
|
|
741
|
+
inits: (Stmt & { k: 'assign' })[],
|
|
742
|
+
n: number,
|
|
743
|
+
dw: Stmt & { k: 'dowhile' },
|
|
744
|
+
ptrVars: Map<string, IrType>,
|
|
745
|
+
fnBody: Stmt[],
|
|
746
|
+
declTypes: VarTypes,
|
|
747
|
+
volatileLocals: ReadonlySet<string>,
|
|
748
|
+
privateNames: ReadonlySet<string>,
|
|
749
|
+
): { loop: Stmt; run: number; kept: Stmt[] } | null {
|
|
750
|
+
// BOTH kinds of step as the body's contiguous TAIL (any order): every deref then runs with `i`
|
|
751
|
+
// completed steps equal to each `p`'s, which is what `BASE[i + REST]` states. A step anywhere
|
|
752
|
+
// earlier (the `*++p` walk, a deref straddling the step) reads a different address. The tail
|
|
753
|
+
// and the init run name the same variables, each once.
|
|
754
|
+
const tail = dw.body.slice(-n);
|
|
755
|
+
const tailNames = tail.map((st) => isUnitIncrement(st)!);
|
|
756
|
+
const initNames = inits.map((st) => st.name);
|
|
757
|
+
if (
|
|
758
|
+
new Set(tailNames).size !== n ||
|
|
759
|
+
new Set(initNames).size !== n ||
|
|
760
|
+
!initNames.every((x) => tailNames.includes(x))
|
|
761
|
+
) {
|
|
611
762
|
return null;
|
|
612
763
|
}
|
|
613
|
-
const
|
|
614
|
-
|
|
615
|
-
if (ivT !== undefined && ivT.kind !== 'int') {
|
|
764
|
+
const counters = inits.filter((st) => !ptrVars.has(st.name));
|
|
765
|
+
if (counters.length !== 1) {
|
|
616
766
|
return null;
|
|
617
767
|
}
|
|
618
|
-
|
|
768
|
+
const ivInit = counters[0];
|
|
769
|
+
const iv = ivInit.name;
|
|
770
|
+
if (!mentionsVar(dw.cond, iv)) {
|
|
619
771
|
return null;
|
|
620
772
|
}
|
|
621
|
-
// The counter
|
|
622
|
-
// steps
|
|
623
|
-
|
|
773
|
+
// The counter starts at a CONSTANT S and its ONLY write anywhere in the body is the tail step:
|
|
774
|
+
// k completed steps put each `p` at EXPR + k*W and `i` at S + k, so the deref is
|
|
775
|
+
// BASE[i - S + REST]. That holds while `i` takes the values S + k, which a DECLARED integer
|
|
776
|
+
// counter does: at 32 bits always (it wraps with the address), and narrower only from S = 0 and
|
|
777
|
+
// unsigned, where it names elements 0 … 2^w - 1 before it wraps. A signed narrow counter wraps
|
|
778
|
+
// at 2^(w-1) (an s8 goes 127 -> -128 and names an element 256 behind the walk), and from a
|
|
779
|
+
// nonzero S `i - S` wraps sooner still; an undeclared one has no width to argue from.
|
|
780
|
+
if (ivInit.value.k !== 'const') {
|
|
624
781
|
return null;
|
|
625
782
|
}
|
|
626
|
-
|
|
783
|
+
const start = ivInit.value.value | 0;
|
|
784
|
+
const ivT = declTypes(iv);
|
|
785
|
+
if (ivT?.kind !== 'int' || !(ivT.width === 32 || (start === 0 && !ivT.signed)) || volatileLocals.has(iv)) {
|
|
627
786
|
return null;
|
|
628
787
|
}
|
|
629
|
-
if (
|
|
788
|
+
if (dw.body.slice(0, -n).some((st) => stmtAssigns(st, iv))) {
|
|
630
789
|
return null;
|
|
631
790
|
}
|
|
632
|
-
//
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
791
|
+
// Each walk pointer re-spells on its OWN merits: one out of scope (its init, an off-0 deref, a
|
|
792
|
+
// mention outside the walk, a mention in the condition) keeps walking, init and step in place,
|
|
793
|
+
// beside the ones that re-spell — each `p` is its own address, so deleting one walk changes no
|
|
794
|
+
// other. Which pointers re-spell is therefore a property of each, not of the tail's step order.
|
|
795
|
+
// Dropping one can only remove rewrites, so the loop below shrinks the set to a fixpoint.
|
|
796
|
+
const spellings = new Map<string, ExprWalkSpelling>();
|
|
797
|
+
for (const st of inits) {
|
|
798
|
+
if (st === ivInit || mentionsVar(dw.cond, st.name)) {
|
|
799
|
+
continue;
|
|
800
|
+
}
|
|
801
|
+
const s = exprWalkSpelling(st, dw, ptrVars, declTypes, volatileLocals, privateNames);
|
|
802
|
+
if (s !== null) {
|
|
803
|
+
spellings.set(s.p, s);
|
|
643
804
|
}
|
|
644
|
-
return m;
|
|
645
|
-
};
|
|
646
|
-
const body = dw.body.filter((_, k) => k !== pStepIdx).map((st) => mapStmtExprs(st, rewriteExpr));
|
|
647
|
-
const dw2: Stmt = { ...dw, body };
|
|
648
|
-
if (!respelled || !confinedToWalk(fnBody, p, countMentions([prev], p), dw)) {
|
|
649
|
-
return null;
|
|
650
805
|
}
|
|
651
|
-
|
|
652
|
-
|
|
806
|
+
const idxOf = (s: ExprWalkSpelling): Expr =>
|
|
807
|
+
plusConst(
|
|
808
|
+
s.idx.reduce<Expr>((acc, r) => ({ k: 'bin', op: '+', l: acc, r }), { k: 'var', name: iv }),
|
|
809
|
+
(s.elems - start) | 0,
|
|
810
|
+
);
|
|
811
|
+
while (spellings.size > 0) {
|
|
812
|
+
// rewrite the derefs, then verify nothing of any re-spelled `p` survives
|
|
813
|
+
const offsetDerefs = new Set<string>();
|
|
814
|
+
const rewriteExpr = (e: Expr): Expr => {
|
|
815
|
+
const m = mapExprChildren(e, rewriteExpr);
|
|
816
|
+
const s = m.k === 'index' && m.base.k === 'var' ? spellings.get(m.base.name) : undefined;
|
|
817
|
+
if (m.k === 'index' && s !== undefined) {
|
|
818
|
+
if (m.idx.k !== 'const' || m.idx.value !== 0) {
|
|
819
|
+
offsetDerefs.add(s.p);
|
|
820
|
+
return m;
|
|
821
|
+
}
|
|
822
|
+
return { ...m, base: { ...s.base }, idx: idxOf(s) };
|
|
823
|
+
}
|
|
824
|
+
return m;
|
|
825
|
+
};
|
|
826
|
+
const steps = new Set(tail.filter((st) => spellings.has(isUnitIncrement(st)!)));
|
|
827
|
+
const body = dw.body.filter((st) => !steps.has(st)).map((st) => mapStmtExprs(st, rewriteExpr));
|
|
828
|
+
const dw2: Stmt = { ...dw, body };
|
|
829
|
+
const failed = inits.filter(
|
|
830
|
+
(st) =>
|
|
831
|
+
spellings.has(st.name) &&
|
|
832
|
+
(offsetDerefs.has(st.name) ||
|
|
833
|
+
!confinedToWalk(fnBody, st.name, countMentions([st], st.name), dw) ||
|
|
834
|
+
countMentions([dw2], st.name) !== 0),
|
|
835
|
+
);
|
|
836
|
+
if (failed.length === 0) {
|
|
837
|
+
return { loop: dw2, run: n, kept: inits.filter((st) => !spellings.has(st.name)) };
|
|
838
|
+
}
|
|
839
|
+
failed.forEach((st) => spellings.delete(st.name));
|
|
653
840
|
}
|
|
654
|
-
return
|
|
841
|
+
return null;
|
|
655
842
|
}
|
|
656
843
|
|
|
657
844
|
/** Try the walk→index re-spelling on one function. Returns the transformed COPY when at least
|
|
@@ -676,6 +863,10 @@ export function reindexWalks(
|
|
|
676
863
|
sfn.locals.filter((l) => l.volatile === true || l.pointeeVolatile === true).map((l) => l.name),
|
|
677
864
|
);
|
|
678
865
|
const storage = nameStorage(sfn);
|
|
866
|
+
// v3's invariant-addend test: the names no callee and no aliasing store can write — a param or a
|
|
867
|
+
// local whose address the function never takes
|
|
868
|
+
const addressTaken = new Set([...walkExprs(sfn.body)].flatMap((e) => (e.k === 'addr' ? [e.name] : [])));
|
|
869
|
+
const privateNames = new Set([...storage].filter(([n, c]) => c !== 'global' && !addressTaken.has(n)).map(([n]) => n));
|
|
679
870
|
for (const v of [...sfn.params, ...sfn.locals]) {
|
|
680
871
|
if (v.type.kind === 'ptr') {
|
|
681
872
|
ptrVars.set(v.name, v.type);
|
|
@@ -778,15 +969,14 @@ export function reindexWalks(
|
|
|
778
969
|
}
|
|
779
970
|
}
|
|
780
971
|
}
|
|
781
|
-
// v3 — the up-counting
|
|
972
|
+
// v3 — the up-counting walk with an expression base: `p = (T *)(EXPR)` immediately
|
|
782
973
|
// ahead of a counter-carried do-while (see tryExprWalk)
|
|
783
974
|
if (s.k === 'dowhile') {
|
|
784
|
-
const
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
out.
|
|
789
|
-
out.push(recurse(dw2));
|
|
975
|
+
const v3 = tryExprWalk(out, s, ptrVars, sfn.body, declTypes, volatileLocals, privateNames);
|
|
976
|
+
if (v3) {
|
|
977
|
+
// the walk inits are subsumed by the indexed spelling; the counter's init stays
|
|
978
|
+
out.splice(out.length - v3.run, v3.run, ...v3.kept);
|
|
979
|
+
out.push(recurse(v3.loop));
|
|
790
980
|
fired++;
|
|
791
981
|
continue;
|
|
792
982
|
}
|