@asmlift/core 0.5.0 → 0.7.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 +270 -171
- package/src/backend/cpp.ts +1 -0
- package/src/backend/pascal.ts +26 -12
- package/src/contracts.ts +243 -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 +78 -0
- package/src/ir/bits.ts +75 -0
- package/src/ir/core.ts +345 -2
- package/src/ir/opcodes.ts +176 -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/advance.ts +373 -0
- package/src/l3/argbase.ts +6 -5
- package/src/l3/ast.ts +510 -59
- package/src/l3/basecse.ts +686 -78
- package/src/l3/coalesce.ts +432 -46
- package/src/l3/dce.ts +31 -9
- package/src/l3/gates.ts +96 -1
- 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 +176 -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 +114 -89
- package/src/l3/reindex.ts +722 -80
- package/src/l3/scopebase.ts +649 -220
- 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 +16 -1
- package/src/l3/typing.ts +198 -9
- package/src/l3/unmerge.ts +687 -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 +239 -16
- package/src/pipeline.ts +173 -60
- package/src/proto.ts +112 -14
- package/src/raise/arrays.ts +6 -1
- package/src/raise/const.ts +203 -3
- package/src/raise/divpow2.ts +4 -4
- package/src/raise/extscale.ts +342 -0
- package/src/raise/globalshape.ts +1058 -0
- package/src/raise/gvn.ts +33 -18
- package/src/raise/latch.ts +126 -0
- package/src/raise/magicdiv.ts +2 -2
- package/src/raise/memberarrays.ts +594 -0
- package/src/raise/narrow.ts +124 -0
- package/src/raise/narrowlocal.ts +572 -0
- package/src/raise/paramwidth.ts +201 -0
- package/src/raise/pre-recovery.ts +169 -21
- package/src/raise/recover.ts +56 -23
- package/src/raise/retsink.ts +585 -19
- package/src/raise/shortcircuit.ts +1050 -89
- package/src/raise/struct-arrays.ts +19 -2
- package/src/raise/structs.ts +34 -4
- package/src/raise/tailsink.ts +126 -0
- package/src/rank-declare.ts +256 -0
- package/src/rank-variations.ts +760 -0
- package/src/rank.ts +2122 -326
- package/src/structure/analysis.ts +1398 -150
- package/src/structure/bitfields.ts +432 -0
- package/src/structure/globalaccess.ts +300 -0
- package/src/structure/hazards.ts +411 -20
- package/src/structure/loops.ts +2 -49
- package/src/structure/namecoalesce.ts +454 -0
- package/src/structure/structure.ts +3979 -612
- package/src/structure/switch-recover.ts +710 -145
- package/src/symbols.ts +188 -6
- package/src/target.ts +495 -32
- package/src/trace.ts +112 -33
- package/src/variation-definitions.ts +1540 -0
- package/src/variation-gates.ts +89 -0
- package/src/variation-tokens.ts +355 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
// THE GLOBAL-ACCESS ADDRESS DECOMPOSITION — the pure half of the symbol-map access spellings.
|
|
2
|
+
//
|
|
3
|
+
// Everything here is a function of an `Expr`, a `SymbolInfo` and a width: no structurer state, no
|
|
4
|
+
// naming walk, no loop context. It answers three questions the access spellings in structure.ts
|
|
5
|
+
// ask over and over — is this address `&gSym` plus something; does that something divide into
|
|
6
|
+
// whole elements; and do the terms of it name the DECLARED subscripts of a multidimensional array
|
|
7
|
+
// — and it answers them the same way for every caller, which is the point of the split: the
|
|
8
|
+
// rank-pinning fallback and the declared-subscript recovery share `bareArrayElement`, so the two
|
|
9
|
+
// cannot disagree about what a bare element spelling is.
|
|
10
|
+
import type { Expr } from '../l3/ast';
|
|
11
|
+
import { type SymbolInfo, arrayInnerExtents } from '../symbols';
|
|
12
|
+
|
|
13
|
+
// `&gSym`, possibly wearing the value-context integer cast the additive lowering adds
|
|
14
|
+
// (`(u32)&gSym` — see `intifyAddr` in structure.ts's lowerDef): both spell the same link-time
|
|
15
|
+
// constant, so the fold rules match through the cast and every access that CAN spell a named
|
|
16
|
+
// element still does.
|
|
17
|
+
// WIDTH 32 ONLY — a NARROWING cast (`(u8)&gSym`, from a zext/sext lowering) is a different
|
|
18
|
+
// VALUE (`addr & 0xFF`), and folding through it would read the named global at a wrong address
|
|
19
|
+
// (the adversarial round's probe: `*(u8*)(u8)&gSym` must keep its truncation, never become
|
|
20
|
+
// `*(u8*)&gSym` — let alone a confidently-named `gSym.field`).
|
|
21
|
+
export function addrIn(e: Expr): Extract<Expr, { k: 'addr' }> | null {
|
|
22
|
+
if (e.k === 'addr') {
|
|
23
|
+
return e;
|
|
24
|
+
}
|
|
25
|
+
if (e.k === 'cast' && e.to.kind === 'int' && e.to.width === 32 && e.e.k === 'addr') {
|
|
26
|
+
return e.e;
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// If `e` is a global address `&gSym` (optionally `+ index`), return the global name and the
|
|
32
|
+
// element index (byte residual divided by the access width). `&gSym` alone → idx const 0;
|
|
33
|
+
// `&gSym + i` → idx `i / width` (exact division only — a non-multiple residual is a mid-element
|
|
34
|
+
// access this whole-global spelling can't express, so it declines to null and the caller casts).
|
|
35
|
+
export function globalOf(e: Expr, width: number): { name: string; idx: Expr } | null {
|
|
36
|
+
const gb = globalByteBase(e);
|
|
37
|
+
if (gb === null) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const idx = elementIndex(gb.residual, width);
|
|
41
|
+
return idx ? { name: gb.name, idx } : null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The same split ONE step earlier: `&gSym` and the raw BYTE residual added to it, before the
|
|
45
|
+
* division into elements throws the individual terms away. The multidimensional recovery needs
|
|
46
|
+
* the terms — a row index is a term at the row's byte stride. */
|
|
47
|
+
export function globalByteBase(e: Expr): { name: string; residual: Expr } | null {
|
|
48
|
+
const top = addrIn(e);
|
|
49
|
+
if (top) {
|
|
50
|
+
return { name: top.name, residual: { k: 'const', value: 0 } };
|
|
51
|
+
}
|
|
52
|
+
if (e.k === 'bin' && e.op === '+') {
|
|
53
|
+
for (const [side, other] of [
|
|
54
|
+
[e.l, e.r],
|
|
55
|
+
[e.r, e.l],
|
|
56
|
+
] as const) {
|
|
57
|
+
const addrSide = addrIn(side);
|
|
58
|
+
if (addrSide) {
|
|
59
|
+
return { name: addrSide.name, residual: other };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// THE one gate on the BARE-NAME array-global spelling (`gSym[i]` rather than `((T *)&gSym)[i]`),
|
|
67
|
+
// shared by the constant-offset and variable-index access paths so the two cannot disagree.
|
|
68
|
+
// Returns the `index` node's `lead` fragment when the bare form is spellable, or null to fall
|
|
69
|
+
// through to the always-valid `&gSym` cast form.
|
|
70
|
+
//
|
|
71
|
+
// Two facts are required, not one. The element WIDTH must match, as it always has. And the RANK
|
|
72
|
+
// must be SPELLABLE, because one subscript reaches an element only on a rank-1 array: on `u16
|
|
73
|
+
// g[4][0x400]`, `g[i]` is a ROW. Against the project's own header that is usually a type error,
|
|
74
|
+
// but where the row address flows into an integer context it is merely a warning and the emitted C
|
|
75
|
+
// then addresses a different object than the asm did — silently.
|
|
76
|
+
//
|
|
77
|
+
// A rank > 1 pins the leading dimensions at 0 and puts the whole flat element index in the last
|
|
78
|
+
// subscript (`g[0][i]`) — the same address arithmetic, and the idiom decomp sources themselves use
|
|
79
|
+
// when the split is not observable in the asm either (`gBgTilemapBufs[0][…]` in kleod,
|
|
80
|
+
// `gNatureStatTable[nature][…]` in pokeemerald). A rank the map states but cannot spell (an unknown
|
|
81
|
+
// inner extent) gets no bare form at all; `((T *)&gSym)[i]` is byte-identical and valid under ANY
|
|
82
|
+
// declaration, which is why it is the safe fallback. See symbols.ts arrayInnerExtents for why an
|
|
83
|
+
// ABSENT rank is read as 1 rather than as unknown.
|
|
84
|
+
export function bareArrayLead(si: SymbolInfo, width: number, signed: boolean): { lead?: Expr[] } | null {
|
|
85
|
+
if (!bareArrayElement(si, width, signed)) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
const inner = arrayInnerExtents(si);
|
|
89
|
+
return inner === null ? null : inner.length === 0 ? {} : { lead: inner.map((): Expr => ({ k: 'const', value: 0 })) };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** The element half of the bare-name rule: the global is an ARRAY whose declared element the
|
|
93
|
+
* access reads WHOLE and with the declared extension. Shared by the rank-pinning fallback above
|
|
94
|
+
* and the declared-subscript recovery below, so the two cannot disagree about what they spell. */
|
|
95
|
+
export function bareArrayElement(si: SymbolInfo, width: number, signed: boolean): boolean {
|
|
96
|
+
if (si.shape !== 'array' || si.elemSize !== width) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
// …and the element must EXTEND the way the access does, for the same reason the width must
|
|
100
|
+
// match: the bare spelling carries no cast, so the declared element type is the only thing in
|
|
101
|
+
// the emitted C that says whether a sub-word read sign- or zero-fills. Against the DECLARED
|
|
102
|
+
// signedness, defaulted exactly as the element type registered for the env is (noteGlobal, just
|
|
103
|
+
// below) — a disagreement there makes the deref legalization wrap the base, and a leading
|
|
104
|
+
// subscript has no room for that wrapping.
|
|
105
|
+
//
|
|
106
|
+
// The caller then falls through to `((T *)&gSym)[i]`, byte-identical under any declaration.
|
|
107
|
+
return !(width < 4 && (si.elemSigned ?? false) !== signed);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ── the DECLARED subscripts, recovered from the address arithmetic ───────────────────────────
|
|
111
|
+
//
|
|
112
|
+
// A rank-2 access the source wrote as `g[r][i]` reaches the element through `&g + r*rowBytes +
|
|
113
|
+
// i*elemSize`, and agbcc leaves those two terms SEPARATE (`lsl #0xb` and `lsl #0x1`, added). The
|
|
114
|
+
// flat spelling `((u16 *)&g)[r*1024 + i]` does not: it scales once (`lsl #0xa` then `lsl #0x1`),
|
|
115
|
+
// so the two are distinguishable in the asm and the term at the declared ROW stride is the
|
|
116
|
+
// evidence that the source named the row. bareArrayLead cannot use it — one subscript is all it
|
|
117
|
+
// spells, so a residual with a row term falls through to `*(T *)(… + (u32)&g)` — and that cast
|
|
118
|
+
// form is what this recovers the subscripts out of.
|
|
119
|
+
//
|
|
120
|
+
// THE EVIDENCE IS THE BYTE RESIDUAL, AND ONLY THE BYTE RESIDUAL — the refusal that keeps the
|
|
121
|
+
// paragraph above from being read backwards. An index ALREADY DIVIDED into elements (what
|
|
122
|
+
// arrayAccess holds, when the asm scaled the whole sum once at the end) is what BOTH spellings
|
|
123
|
+
// reduce to: `(r<<11) + (i<<1)` and `((r<<10) + i) << 1` differ only in where the element scale
|
|
124
|
+
// sits, which is exactly what the division removes. So this runs on the byte residual and
|
|
125
|
+
// arrayAccess does not call it — see the note at that site for what the two spellings measure.
|
|
126
|
+
// `packages/cli/test/matching/array-rank-variation.test.ts` compiles both halves of that.
|
|
127
|
+
//
|
|
128
|
+
// The recovered address is the SAME address either way (C scales `[r]` by the declared row size,
|
|
129
|
+
// which is the constant the arithmetic multiplied by), so this is a spelling, not a re-addressing.
|
|
130
|
+
//
|
|
131
|
+
// AND IT IS A VARIATION, NOT A DEFAULT — the evidence above says the residual carries a ROW, and it
|
|
132
|
+
// does not say which of the two spellings that reach it wrote one. The cast form this replaces
|
|
133
|
+
// (`*(T *)((r<<11) + (i<<1) + (u32)&g)`) compiles to the SAME shift structure and differs only in
|
|
134
|
+
// scheduling on agbcc, kmc and mwcc, and is byte-identical on IDO — where the flat sum is
|
|
135
|
+
// distributed into those same separate scales, so the premise above is a per-compiler fact and not
|
|
136
|
+
// a universal one. `spellDeclaredSubscripts` is the switch and `/flat-rank` the arm; see that
|
|
137
|
+
// option for the compiled table.
|
|
138
|
+
|
|
139
|
+
/** `residual` as a list of additively combined terms with their sign — `a + (b - c)` is
|
|
140
|
+
* `[+a, +b, -c]`. Only `+`/`-` are opened; anything else is one opaque term. */
|
|
141
|
+
function addTerms(e: Expr, sign: 1 | -1, into: { e: Expr; sign: 1 | -1 }[]): void {
|
|
142
|
+
if (e.k === 'bin' && (e.op === '+' || e.op === '-')) {
|
|
143
|
+
addTerms(e.l, sign, into);
|
|
144
|
+
addTerms(e.r, e.op === '+' ? sign : (-sign as 1 | -1), into);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
into.push({ e, sign });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** `x` when `t` is the NON-CONSTANT value `x` scaled by exactly `stride` (`x * stride` or
|
|
151
|
+
* `x << log2(stride)`), else null. A constant term is never a recovered subscript: both
|
|
152
|
+
* spellings of a constant row index compile identically, so nothing referees the question. */
|
|
153
|
+
function scaledBy(t: Expr, stride: number): Expr | null {
|
|
154
|
+
if (t.k !== 'bin') {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
if (t.op === '<<' && t.r.k === 'const' && t.r.value < 31 && 1 << t.r.value === stride && t.l.k !== 'const') {
|
|
158
|
+
return t.l;
|
|
159
|
+
}
|
|
160
|
+
if (t.op === '*' && t.r.k === 'const' && t.r.value === stride && t.l.k !== 'const') {
|
|
161
|
+
return t.l;
|
|
162
|
+
}
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** The leading subscripts of `si` recovered from the BYTE `residual`, plus what is left for the
|
|
167
|
+
* last one. The parameter is a byte residual by contract, not by convention: a residual already
|
|
168
|
+
* divided into elements carries no evidence about the row (see the header), so there is no unit
|
|
169
|
+
* to pass and no caller that could pass one.
|
|
170
|
+
*
|
|
171
|
+
* REFUSES (falling back to the caller's existing spelling, which is byte-identical) when: the
|
|
172
|
+
* symbol is not an array of exactly this element; the declared rank is 1 or unspellable; any
|
|
173
|
+
* leading stride is not strictly larger than the one below it (an extent of 1 makes two
|
|
174
|
+
* positions indistinguishable, so the split would be a guess); NO term is a non-constant
|
|
175
|
+
* multiple of a leading stride (there is nothing to recover and today's answer already spells
|
|
176
|
+
* the same address); or what remains does not divide into whole elements.
|
|
177
|
+
*
|
|
178
|
+
* It does NOT refuse an unknown OUTERMOST extent (`dims: [null, 0x400]` — 8 symbols across the
|
|
179
|
+
* six vendored maps), and that is deliberate rather than an omission: only the INNER extents
|
|
180
|
+
* enter a stride, and declare.ts leaves the outermost dimension unsized in the emitted
|
|
181
|
+
* declaration either way (`extern u16 gRows[][1024];` for `[4,1024]` and for `[null,1024]`
|
|
182
|
+
* alike), so the recovered subscripts and the declaration they are read against agree and stride
|
|
183
|
+
* identically. There is therefore no guard on the outermost extent anywhere below, and its
|
|
184
|
+
* absence is the rule rather than a gap in it. */
|
|
185
|
+
export function declaredSubscripts(
|
|
186
|
+
si: SymbolInfo,
|
|
187
|
+
residual: Expr,
|
|
188
|
+
width: number,
|
|
189
|
+
signed: boolean,
|
|
190
|
+
): { lead: Expr[]; idx: Expr } | null {
|
|
191
|
+
if (!bareArrayElement(si, width, signed)) {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
const inner = arrayInnerExtents(si);
|
|
195
|
+
if (inner === null || inner.length === 0) {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
return subscriptsFromExtents(inner, residual, width, true);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** THE split of a BYTE residual into a multidimensional array's subscripts, shared by the two
|
|
202
|
+
* declarations that can state a rank: a GLOBAL's ({@link declaredSubscripts}) and an array
|
|
203
|
+
* MEMBER's (structure.ts `pointeeElement`). One copy, because the two must not come to disagree
|
|
204
|
+
* about which term is a row.
|
|
205
|
+
*
|
|
206
|
+
* `needRecovered` is the one place they legitimately differ, and it is a fact about the
|
|
207
|
+
* FALLBACK rather than about the arithmetic. For a global, a residual holding no term at a
|
|
208
|
+
* leading stride means there is nothing to recover, and today's flat `gSym[i]` already spells
|
|
209
|
+
* the very same address — so the split is refused and nothing is lost. An array MEMBER of a rank
|
|
210
|
+
* the project's own header declares has NO such fallback: `->x[i]` on a `u8 x[6][8]` is a ROW,
|
|
211
|
+
* which does not type-check, so the leading subscripts have to be spelled even when every one of
|
|
212
|
+
* them comes out the literal 0 (`->x[0][i]`, the same address the byte arithmetic reached).
|
|
213
|
+
*
|
|
214
|
+
* REFUSES (null) when: any leading stride is not strictly larger than the one below it (an
|
|
215
|
+
* extent of 1 makes two positions indistinguishable, so the split would be a guess); a stride is
|
|
216
|
+
* not a safe integer; `needRecovered` and no term is a non-constant multiple of a leading stride;
|
|
217
|
+
* or what remains does not divide into whole elements. */
|
|
218
|
+
export function subscriptsFromExtents(
|
|
219
|
+
inner: number[],
|
|
220
|
+
residual: Expr,
|
|
221
|
+
width: number,
|
|
222
|
+
needRecovered: boolean,
|
|
223
|
+
): { lead: Expr[]; idx: Expr } | null {
|
|
224
|
+
const strides: number[] = [];
|
|
225
|
+
for (let p = 0; p < inner.length; p++) {
|
|
226
|
+
strides.push(inner.slice(p).reduce((a, b) => a * b, width));
|
|
227
|
+
}
|
|
228
|
+
for (let p = 0; p < strides.length; p++) {
|
|
229
|
+
if (!Number.isSafeInteger(strides[p]) || strides[p] <= (p + 1 < strides.length ? strides[p + 1] : width)) {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const terms: { e: Expr; sign: 1 | -1 }[] = [];
|
|
234
|
+
addTerms(residual, 1, terms);
|
|
235
|
+
const taken = new Set<number>();
|
|
236
|
+
const lead = strides.map((stride): Expr => {
|
|
237
|
+
for (let i = 0; i < terms.length; i++) {
|
|
238
|
+
const x = taken.has(i) || terms[i].sign !== 1 ? null : scaledBy(terms[i].e, stride);
|
|
239
|
+
if (x) {
|
|
240
|
+
taken.add(i);
|
|
241
|
+
return x;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return { k: 'const', value: 0 };
|
|
245
|
+
});
|
|
246
|
+
if (needRecovered && taken.size === 0) {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
let sum: Expr = { k: 'const', value: 0 };
|
|
250
|
+
for (const [i, t] of terms.entries()) {
|
|
251
|
+
sum = taken.has(i)
|
|
252
|
+
? sum
|
|
253
|
+
: sum.k === 'const' && sum.value === 0 && t.sign === 1
|
|
254
|
+
? t.e
|
|
255
|
+
: { k: 'bin', op: t.sign === 1 ? '+' : '-', l: sum, r: t.e };
|
|
256
|
+
}
|
|
257
|
+
const idx = elementIndex(sum, width);
|
|
258
|
+
return idx === null ? null : { lead, idx };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// A BYTE residual read as an ELEMENT index of `elemSize`-wide elements, or null when it is not one
|
|
262
|
+
// — the residual then addresses mid-element and no whole-element spelling can express it, so the
|
|
263
|
+
// caller falls through to the honest cast forms. THE one copy of the rule, indexing the
|
|
264
|
+
// `&gSym`-based array spelling: width 1 → the byte residual IS the index; wider → a constant
|
|
265
|
+
// residual must divide exactly, and a non-constant one must already be element-scaled
|
|
266
|
+
// (`i * elemSize` / `i << log2(elemSize)`), which is exactly what the asm's own index scaling
|
|
267
|
+
// produced.
|
|
268
|
+
export function elementIndex(residual: Expr, elemSize: number): Expr | null {
|
|
269
|
+
if (elemSize === 1) {
|
|
270
|
+
return residual;
|
|
271
|
+
}
|
|
272
|
+
if (residual.k === 'const') {
|
|
273
|
+
return residual.value % elemSize === 0 ? { k: 'const', value: residual.value / elemSize } : null;
|
|
274
|
+
}
|
|
275
|
+
if (residual.k === 'bin' && (residual.op === '*' || residual.op === '<<')) {
|
|
276
|
+
const factor =
|
|
277
|
+
residual.op === '<<'
|
|
278
|
+
? residual.r.k === 'const'
|
|
279
|
+
? 1 << residual.r.value
|
|
280
|
+
: 0
|
|
281
|
+
: residual.r.k === 'const'
|
|
282
|
+
? residual.r.value
|
|
283
|
+
: 0;
|
|
284
|
+
if (factor === elemSize) {
|
|
285
|
+
return residual.l;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** `idx + n` with a constant fold, `idx` itself for n 0 — the one place an access's memory-operand
|
|
292
|
+
* displacement joins the subscript it was always part of. */
|
|
293
|
+
export function addOffset(idx: Expr, n: number): Expr {
|
|
294
|
+
if (n === 0) {
|
|
295
|
+
return idx;
|
|
296
|
+
}
|
|
297
|
+
return idx.k === 'const'
|
|
298
|
+
? { k: 'const', value: idx.value + n }
|
|
299
|
+
: { k: 'bin', op: '+', l: idx, r: { k: 'const', value: n } };
|
|
300
|
+
}
|