@asmlift/core 0.4.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 -164
- package/src/backend/cpp.ts +1 -0
- package/src/backend/pascal.ts +26 -12
- package/src/contracts.ts +341 -22
- package/src/declare.ts +41 -4
- package/src/frontend/mips.ts +24 -6
- package/src/frontend/opaque.ts +31 -18
- package/src/frontend/ppc.ts +54 -7
- package/src/frontend/ssa.ts +632 -13
- package/src/frontend/thumb.ts +2786 -286
- package/src/ir/alias.ts +129 -0
- package/src/ir/bits.ts +75 -0
- package/src/ir/core.ts +337 -2
- package/src/ir/opcodes.ts +156 -27
- 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 +8 -2
- package/src/l3/ast.ts +464 -49
- package/src/l3/basecse.ts +709 -88
- package/src/l3/coalesce.ts +521 -66
- package/src/l3/dce.ts +54 -19
- package/src/l3/gates.ts +88 -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 +649 -219
- 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 +23 -4
- 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 +206 -49
- package/src/proto.ts +112 -14
- package/src/raise/arrays.ts +6 -1
- package/src/raise/divpow2.ts +4 -3
- package/src/raise/globalshape.ts +1038 -0
- package/src/raise/gvn.ts +44 -19
- 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 +101 -16
- package/src/raise/recover.ts +56 -23
- package/src/raise/retsink.ts +215 -14
- package/src/raise/shortcircuit.ts +477 -79
- package/src/raise/struct-arrays.ts +21 -3
- package/src/raise/structs.ts +61 -3
- package/src/rank-axes.ts +630 -0
- package/src/rank-declare.ts +256 -0
- package/src/rank.ts +1726 -251
- package/src/structure/analysis.ts +1516 -220
- 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 +2850 -533
- package/src/structure/switch-recover.ts +688 -147
- package/src/symbols.ts +62 -1
- package/src/target.ts +367 -24
- package/src/trace.ts +111 -32
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// A NARROW LOOP VARIABLE, kept zero-extended and read sign-extended.
|
|
2
|
+
//
|
|
3
|
+
// agbcc holds an `s16`/`s8` local in its ZERO-extended form and sign-extends it at each signed use,
|
|
4
|
+
// so `for (s16 i = 0; i <= 5; i++)` leaves two extensions of the same increment side by side — one
|
|
5
|
+
// for the back edge, one for the loop test:
|
|
6
|
+
//
|
|
7
|
+
// %21 = zext %20 {16} what `i` holds next iteration
|
|
8
|
+
// %22 = sext %20 {16} the loop test
|
|
9
|
+
//
|
|
10
|
+
// `sext(zext(v,w),w) === sext(v,w)` — the zext keeps the low `w` bits and the sext reads only
|
|
11
|
+
// those — so `%22` IS `sext %21`: a function of the value the loop variable will hold. Spelled
|
|
12
|
+
// through `%20` it reaches the variable's PRE-update value, and the structurer's pre-update guard
|
|
13
|
+
// refuses that (rightly — rendered as the loop variable it would be one iteration off). Re-rooted
|
|
14
|
+
// on `%21` it is `(s16)i`, which is what the source wrote.
|
|
15
|
+
//
|
|
16
|
+
// The same fact has a second spelling, which the cast idiom cannot fold because the value under the
|
|
17
|
+
// shifts is not a bare `shl`: agbcc keeps `i << 16` live when the body wants `i` scaled, and adds
|
|
18
|
+
// the increment in the shifted domain.
|
|
19
|
+
//
|
|
20
|
+
// %133 = shr_u %132 {16} what `i` holds next iteration
|
|
21
|
+
// %134 = shr_s %132 {16} the loop test
|
|
22
|
+
//
|
|
23
|
+
// A signed and an unsigned right shift of the same value by the same amount differ only in the bits
|
|
24
|
+
// the shift brings in, so `%134` is `sext(%133, 32 - 16)` — the same rewrite, and the reason this
|
|
25
|
+
// pass matches the pair rather than either extension on its own.
|
|
26
|
+
import { Fn, Op, Value, defOpMap } from '../ir/core';
|
|
27
|
+
import { CAST_WIDTHS, Opcode } from '../ir/opcodes';
|
|
28
|
+
|
|
29
|
+
/** WHICH BITS a narrowing op keeps, as a key two ops can be compared on. The two spellings live in
|
|
30
|
+
* different bit-domains — `zext {width:w}` keeps the LOW `w` bits, `shr_u {imm:k}` keeps bits
|
|
31
|
+
* `[k,32)` and moves them down — so a shared integer key would pair a `zext` with a `shr_s` of the
|
|
32
|
+
* same operand and rewrite `x >> 16` into `(s16)(u16)x`: a different value, silently. `width` is
|
|
33
|
+
* the C cast the pair collapses to. */
|
|
34
|
+
const domainOf = (op: Op, low: Opcode, high: Opcode): { key: string; width: number } | null => {
|
|
35
|
+
if (op.opcode === low && CAST_WIDTHS.has(op.attrs.width as number)) {
|
|
36
|
+
return { key: `low${op.attrs.width}`, width: op.attrs.width as number };
|
|
37
|
+
}
|
|
38
|
+
if (op.opcode === high && typeof op.attrs.imm === 'number' && CAST_WIDTHS.has(32 - op.attrs.imm)) {
|
|
39
|
+
return { key: `high${op.attrs.imm}`, width: 32 - (op.attrs.imm as number) };
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/** Re-root a sign extension on the co-existing zero extension of the same bits. Returns the number
|
|
45
|
+
* of rewrites. */
|
|
46
|
+
export function rerootNarrowReads(fn: Fn): number {
|
|
47
|
+
// LOOP-CARRIED edge arguments only. The rewrite is sound for ANY co-existing pair — it only
|
|
48
|
+
// re-associates two extensions of the same bits — so this gate is about what it BUYS, not about
|
|
49
|
+
// what is safe. It buys a name: an argument whose own operand chain reads the parameter it feeds
|
|
50
|
+
// is that parameter's next value, i.e. a loop variable, which the structurer materializes and
|
|
51
|
+
// renders `(s16)i`. Anywhere else the re-root only puts `(s16)(u16)x` where `(s16)x` stood —
|
|
52
|
+
// byte-identical through agbcc, so noise for nothing.
|
|
53
|
+
const defs = defOpMap(fn);
|
|
54
|
+
const feedsBack = (from: Value, param: Value): boolean => {
|
|
55
|
+
const seen = new Set<Value>();
|
|
56
|
+
for (const stack = [from]; stack.length;) {
|
|
57
|
+
const v = stack.pop()!;
|
|
58
|
+
if (v === param) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
if (seen.has(v)) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
seen.add(v);
|
|
65
|
+
for (const o of defs.get(v)?.operands ?? []) {
|
|
66
|
+
stack.push(o);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
};
|
|
71
|
+
const carried = new Set<Value>();
|
|
72
|
+
for (const b of fn.blocks) {
|
|
73
|
+
for (const op of b.ops) {
|
|
74
|
+
for (const s of op.successors) {
|
|
75
|
+
s.args.forEach((a, i) => {
|
|
76
|
+
const param = s.block.params[i];
|
|
77
|
+
if (param !== undefined && feedsBack(a, param)) {
|
|
78
|
+
carried.add(a);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
let rewritten = 0;
|
|
85
|
+
for (const b of fn.blocks) {
|
|
86
|
+
// Same block, zero extension FIRST: the rewrite makes the sign extension read it, so anywhere
|
|
87
|
+
// else this could be a use before a def. That is the shape the idiom emits — one increment,
|
|
88
|
+
// both extensions of it, in the block that computes it.
|
|
89
|
+
const unsigned = new Map<Value, Map<string, Value>>();
|
|
90
|
+
for (const op of b.ops) {
|
|
91
|
+
const u = domainOf(op, 'zext', 'shr_u');
|
|
92
|
+
if (u !== null && carried.has(op.results[0])) {
|
|
93
|
+
const byDomain = unsigned.get(op.operands[0]) ?? new Map<string, Value>();
|
|
94
|
+
byDomain.set(u.key, op.results[0]);
|
|
95
|
+
unsigned.set(op.operands[0], byDomain);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const sgn = domainOf(op, 'sext', 'shr_s');
|
|
99
|
+
if (sgn === null) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const zx = unsigned.get(op.operands[0])?.get(sgn.key);
|
|
103
|
+
if (zx === undefined) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
// Both spellings collapse to the one op the backend prints as a cast, so the `shr_s` form
|
|
107
|
+
// drops its `imm` with the rest of its attrs. Rewritten IN PLACE, against the engine's usual
|
|
108
|
+
// discipline (pattern/engine.ts), because the result `Value` identity has to survive: every
|
|
109
|
+
// existing use of the sign extension must keep reading it.
|
|
110
|
+
//
|
|
111
|
+
// SLOT HOMES (ir/core.ts `SlotHomes`) do not move here, and unlike the other two passes that
|
|
112
|
+
// write an operand directly this one had to be checked rather than read off the site. It
|
|
113
|
+
// substitutes a new READ operand, it does not retire a value: the extension's source keeps
|
|
114
|
+
// its use in the `zext` that `zx` is the result of, so a home stamped on it is still carried
|
|
115
|
+
// by a value the graph reaches, and no function-wide replacement is owed the propagation
|
|
116
|
+
// `replaceAllUsesWith` does.
|
|
117
|
+
op.opcode = 'sext';
|
|
118
|
+
op.operands = [zx];
|
|
119
|
+
op.attrs = { width: sgn.width };
|
|
120
|
+
rewritten++;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return rewritten;
|
|
124
|
+
}
|