@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/ir/parse.ts
CHANGED
package/src/ir/types.ts
CHANGED
|
@@ -7,6 +7,11 @@ export interface StructField {
|
|
|
7
7
|
off: number;
|
|
8
8
|
type: IrType;
|
|
9
9
|
name: string;
|
|
10
|
+
/** A cell the function also reads or writes at another width or extension, declared at its
|
|
11
|
+
* widest view because the union of its views would not fit the compiler's layout
|
|
12
|
+
* (raise/structs.ts `buildUnionStruct`). An access of any other type is a cast of the base
|
|
13
|
+
* (structure.ts `memAccess`). */
|
|
14
|
+
castViews?: true;
|
|
10
15
|
}
|
|
11
16
|
|
|
12
17
|
export type IrType =
|
|
@@ -17,13 +22,36 @@ export type IrType =
|
|
|
17
22
|
// from `ptr(int)`+array-index because its access pattern is inconsistent with a homogeneous
|
|
18
23
|
// array (mixed widths / non-uniform offsets). `name` is synthetic today (`Struct0`); a later
|
|
19
24
|
// DWARF pass supplies real names. Fields are sorted by `off`.
|
|
20
|
-
|
|
25
|
+
//
|
|
26
|
+
// `declared` is a struct the project's HEADERS own — one a callee is declared to return
|
|
27
|
+
// (aggregate.ts `aggregateType`) — holding the headers' own spelling of it (`struct Blob64`,
|
|
28
|
+
// `BlobT`). No source this lift prints defines it: the declarations block does, which a candidate
|
|
29
|
+
// compiled inside the project's headers drops, since the headers define it there.
|
|
30
|
+
| { kind: 'struct'; name: string; fields: StructField[]; size?: number; declared?: string }
|
|
21
31
|
// A fixed-length array `elem[count]`. Today its sole inhabitant is struct padding (a `u8[N]`
|
|
22
32
|
// pad member seats fields at their exact offsets, raise/struct-arrays.ts) — a REAL type,
|
|
23
33
|
// not a printed string. Array-typed fields declare with the length AFTER the name in C
|
|
24
34
|
// (`u8 _pad[4]`), so the backend routes them through a declarator-aware `cDeclare`, not the
|
|
25
35
|
// prefix `cType`.
|
|
26
36
|
| { kind: 'array'; elem: IrType; count: number }
|
|
37
|
+
// Several VIEWS of one storage cell (raise/structs.ts): a base read or written at more than one
|
|
38
|
+
// width or extension over the same bytes. Every member sits at offset 0. It carries no name
|
|
39
|
+
// because it is only ever declared INLINE, as the type of the struct member that holds it, and its
|
|
40
|
+
// `size` is the one the target's compiler gives it — which is not always its widest view
|
|
41
|
+
// (`T.union` computes it from the compiler's aggregate boundary).
|
|
42
|
+
| { kind: 'union'; members: StructField[]; size: number }
|
|
43
|
+
// An IEEE floating-point value, single (32) or double (64). A KIND and not an `int` width, which is
|
|
44
|
+
// the one exception to the 64-bit rule at `ir/opcodes.ts` `concat` ("widen the number that is already
|
|
45
|
+
// there"): a float is not an integer of any width, so every pass that tests `kind === 'int'` must
|
|
46
|
+
// SKIP it rather than compute on it, and skipping is what a new kind buys. Its values live in the
|
|
47
|
+
// FPU's own register file, or in a soft-float runtime's register pair, and are computed only by
|
|
48
|
+
// the float opcodes (`fadd` …); `docs/floating-point.md` says which frontends mint one and what
|
|
49
|
+
// they still refuse. Two floats of different widths are different types (`typeEquals`), so a
|
|
50
|
+
// float op mixing them fails `ir/verify.ts`. A null width is a float whose code states no precision:
|
|
51
|
+
// a PowerPC FPR holds a double whichever the source declared, and `fneg`/`fmr` are one instruction
|
|
52
|
+
// for both, so `double dneg(double a){ return -a; }` and its `float` twin compile to one object.
|
|
53
|
+
// Either spelling reproduces it, and a declaration the caller supplies decides between them.
|
|
54
|
+
| { kind: 'float'; width: 32 | 64 | null }
|
|
27
55
|
| { kind: 'void' }; // a function that returns nothing
|
|
28
56
|
|
|
29
57
|
/** The scalar type of a memory access of `width` bytes: word ⇒ the s32 integer default;
|
|
@@ -34,6 +62,23 @@ export function scalarTypeForAccess(width: number, signed: boolean): IrType {
|
|
|
34
62
|
return width === 4 ? T.s(32) : T.int(width * 8, signed);
|
|
35
63
|
}
|
|
36
64
|
|
|
65
|
+
/** The width in bits of the integer a type carries, or null where a type carries no integer. A
|
|
66
|
+
* pointer's own width is the machine's and says nothing about what it addresses; an aggregate and
|
|
67
|
+
* `void` have no single width at all. `unknown` DOES carry one, which is the point — before type
|
|
68
|
+
* recovery every value is `unknown`, so a rule that skipped that kind would be vacuous exactly
|
|
69
|
+
* where the frontends build these.
|
|
70
|
+
*
|
|
71
|
+
* THE one copy, for the reason `scalarTypeForAccess` above is: its two consumers read the same
|
|
72
|
+
* question under OPPOSITE policies, and a per-consumer copy is how they drift apart. `ir/verify.ts`
|
|
73
|
+
* reads null as "this type does not take part in the width rule, so pass"; `runtime-helpers.ts`
|
|
74
|
+
* reads null as "this operand carries no integer width, so refuse to fold a 64-bit operation over
|
|
75
|
+
* it". Both want exactly this predicate — a pointer handed to `__muldi3` has no width either of
|
|
76
|
+
* them may claim — so an `IrType` kind added here reaches the verifier and the recogniser together
|
|
77
|
+
* or neither. */
|
|
78
|
+
export function intWidth(t: IrType): number | null {
|
|
79
|
+
return t.kind === 'int' || t.kind === 'unknown' ? t.width : null;
|
|
80
|
+
}
|
|
81
|
+
|
|
37
82
|
export const T = {
|
|
38
83
|
unk: (width = 32): IrType => ({ kind: 'unknown', width }),
|
|
39
84
|
int: (width: number, signed: boolean): IrType => ({ kind: 'int', width, signed }),
|
|
@@ -42,9 +87,147 @@ export const T = {
|
|
|
42
87
|
ptr: (to: IrType): IrType => ({ kind: 'ptr', to }),
|
|
43
88
|
struct: (name: string, fields: StructField[], size?: number): IrType => ({ kind: 'struct', name, fields, size }),
|
|
44
89
|
array: (elem: IrType, count: number): IrType => ({ kind: 'array', elem, count }),
|
|
90
|
+
// Its size is its widest view rounded up to the compiler's aggregate boundary (target.ts).
|
|
91
|
+
union: (members: StructField[], boundary: number): IrType => {
|
|
92
|
+
const extent = Math.max(...members.map((m) => (m.type.kind === 'array' ? m.type.count : 1) * viewBytes(m.type)));
|
|
93
|
+
return { kind: 'union', members, size: Math.ceil(extent / boundary) * boundary };
|
|
94
|
+
},
|
|
95
|
+
f32: (): IrType => ({ kind: 'float', width: 32 }),
|
|
96
|
+
f64: (): IrType => ({ kind: 'float', width: 64 }),
|
|
97
|
+
fUnstated: (): IrType => ({ kind: 'float', width: null }),
|
|
45
98
|
void: (): IrType => ({ kind: 'void' }),
|
|
46
99
|
};
|
|
47
100
|
|
|
101
|
+
/** The member name of each view a union holds, by access width. A width with no entry has no view
|
|
102
|
+
* (the deref contract admits no other scalar width either), and its overlap still declines. A
|
|
103
|
+
* narrow width loaded with both extensions has a view per extension, `u`/`s` prefixed. */
|
|
104
|
+
export const UNION_VIEW_NAMES: Readonly<Record<number, string>> = { 1: 'byte', 2: 'half', 4: 'word' };
|
|
105
|
+
|
|
106
|
+
/** The extensions a union declares a view of `width` bytes in, from the accesses of that width:
|
|
107
|
+
* THE one rule raise/structs.ts `buildUnionStruct` and the frame audit (frontend/frame-objects.ts
|
|
108
|
+
* `unionViews`) both declare by, so a struct member's union and a frame object's cannot disagree
|
|
109
|
+
* on their views. A word has one view whatever its loads say (`scalarTypeForAccess`); a narrower
|
|
110
|
+
* width a view per extension it is LOADED with, unsigned first; a width only stored the signed
|
|
111
|
+
* view where a stamped store says signed (raise/storesign.ts) and the unsigned one otherwise. */
|
|
112
|
+
export function unionViewSigns(
|
|
113
|
+
width: number,
|
|
114
|
+
accesses: readonly { readonly isLoad: boolean; readonly signed: boolean; readonly stamped: boolean }[],
|
|
115
|
+
): boolean[] {
|
|
116
|
+
if (width === 4) {
|
|
117
|
+
return [true];
|
|
118
|
+
}
|
|
119
|
+
const loaded = new Set(accesses.filter((a) => a.isLoad).map((a) => a.signed));
|
|
120
|
+
if (loaded.size === 0) {
|
|
121
|
+
return [accesses.some((a) => a.stamped && a.signed)];
|
|
122
|
+
}
|
|
123
|
+
return [false, true].filter((sg) => loaded.has(sg));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** The byte width of a union view's element (an array view's element, or the scalar itself). */
|
|
127
|
+
function viewBytes(t: IrType): number {
|
|
128
|
+
const e = t.kind === 'array' ? t.elem : t;
|
|
129
|
+
if (e.kind !== 'int') {
|
|
130
|
+
throw new Error(`a union view is an integer or an array of one, not '${typeToString(t)}'`);
|
|
131
|
+
}
|
|
132
|
+
return e.width / 8;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** A struct as its declaration spells it (`declared`), else by its tag; any other type as printed. */
|
|
136
|
+
export function structSpelling(t: IrType): string {
|
|
137
|
+
return t.kind === 'struct' ? (t.declared ?? `struct ${t.name}`) : typeToString(t);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** The member `name` of an aggregate — a struct's field or a union's view — or undefined when `t`
|
|
141
|
+
* is not an aggregate or declares no such member. THE one lookup the typing walk and the deref
|
|
142
|
+
* contract share, so the two cannot disagree on which member access is well-typed. */
|
|
143
|
+
export function memberOf(t: IrType | undefined, name: string): StructField | undefined {
|
|
144
|
+
if (t?.kind === 'struct') {
|
|
145
|
+
return t.fields.find((f) => f.name === name);
|
|
146
|
+
}
|
|
147
|
+
if (t?.kind === 'union') {
|
|
148
|
+
return t.members.find((m) => m.name === name);
|
|
149
|
+
}
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** The union member of a struct that holds byte `off`, and the view of it an access of `width`
|
|
154
|
+
* bytes reads — the view of that width and, for a narrow LOAD, of that extension; a store, which
|
|
155
|
+
* carries none, takes the width's only view or the unsigned one of two — with the element index
|
|
156
|
+
* inside an ARRAY view (null for a scalar one). Undefined when no union member holds `off`; `view`
|
|
157
|
+
* undefined when the member has no view for the access. */
|
|
158
|
+
export function unionViewAt(
|
|
159
|
+
st: Extract<IrType, { kind: 'struct' }>,
|
|
160
|
+
off: number,
|
|
161
|
+
width: number,
|
|
162
|
+
signed: boolean,
|
|
163
|
+
isStore: boolean,
|
|
164
|
+
): { member: StructField; view: StructField | undefined; index: number | null } | undefined {
|
|
165
|
+
const member = st.fields.find((f) => f.type.kind === 'union' && f.off <= off && off < f.off + f.type.size);
|
|
166
|
+
if (member === undefined || member.type.kind !== 'union') {
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
169
|
+
return { member, ...unionView(member.type, off - member.off, width, signed, isStore) };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Does a member declared at signedness `declared` read as EXACTLY the type the cast spelling this
|
|
173
|
+
* replaces would have produced — `scalarTypeForAccess(width, signed)`? That is the whole
|
|
174
|
+
* byte-exactness argument for naming a member instead of casting: same address, same read width,
|
|
175
|
+
* same C type, so every operator downstream compiles identically. A 4-byte access renders s32
|
|
176
|
+
* whatever the load said (the ISA has one word load), so only a SIGNED member may take its place;
|
|
177
|
+
* narrower accesses carry their own signedness and must match it. An undeclared signedness (the
|
|
178
|
+
* member is not a base type — a nested struct, an enum, a pointer) is never assumed to match.
|
|
179
|
+
* THE one copy of "an access names the member": structure.ts's member spellings and `isCastView`
|
|
180
|
+
* below all ask it, so they cannot drift apart. */
|
|
181
|
+
export function spellsAccessType(declared: boolean | undefined, width: number, signed: boolean): boolean {
|
|
182
|
+
if (declared === undefined) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
return typeEquals(T.int(width * 8, declared), scalarTypeForAccess(width, signed));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Does an access of `width` bytes at byte `off`, inside a `castViews` field of the struct, read it
|
|
189
|
+
* at a view other than the field's own type — another width or offset, or a load whose type the
|
|
190
|
+
* field does not spell (`spellsAccessType`)? A store carries no extension (it truncates to the
|
|
191
|
+
* field's bytes whatever their type), so one of the field's width at its offset is the field. */
|
|
192
|
+
export function isCastView(
|
|
193
|
+
st: Extract<IrType, { kind: 'struct' }>,
|
|
194
|
+
off: number,
|
|
195
|
+
width: number,
|
|
196
|
+
signed: boolean,
|
|
197
|
+
isStore: boolean,
|
|
198
|
+
): boolean {
|
|
199
|
+
const f = st.fields.find(
|
|
200
|
+
(m) => m.castViews === true && m.off <= off && off + width <= m.off + (intWidth(m.type) ?? 0) / 8,
|
|
201
|
+
);
|
|
202
|
+
if (f === undefined || f.type.kind !== 'int') {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
return off !== f.off || f.type.width !== width * 8 || (!isStore && !spellsAccessType(f.type.signed, width, signed));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** The view of a union an access of `width` bytes at byte `at` of it reads — the rule
|
|
209
|
+
* `unionViewAt` states, asked of a union that is not a struct's member (a frame object declared
|
|
210
|
+
* as one, structure.ts `laddrType`). */
|
|
211
|
+
export function unionView(
|
|
212
|
+
u: Extract<IrType, { kind: 'union' }>,
|
|
213
|
+
at: number,
|
|
214
|
+
width: number,
|
|
215
|
+
signed: boolean,
|
|
216
|
+
isStore: boolean,
|
|
217
|
+
): { view: StructField | undefined; index: number | null } {
|
|
218
|
+
const elemOf = (t: IrType): IrType => (t.kind === 'array' ? t.elem : t);
|
|
219
|
+
const wide = u.members.filter((m) => intWidth(elemOf(m.type)) === width * 8);
|
|
220
|
+
const signOf = (m: StructField): boolean | undefined => {
|
|
221
|
+
const e = elemOf(m.type);
|
|
222
|
+
return e.kind === 'int' ? e.signed : undefined;
|
|
223
|
+
};
|
|
224
|
+
const view =
|
|
225
|
+
width === 4 || (isStore && wide.length === 1)
|
|
226
|
+
? wide[0]
|
|
227
|
+
: wide.find((m) => signOf(m) === (isStore ? false : signed));
|
|
228
|
+
return { view, index: view?.type.kind === 'array' ? at / width : null };
|
|
229
|
+
}
|
|
230
|
+
|
|
48
231
|
export function typeToString(t: IrType): string {
|
|
49
232
|
switch (t.kind) {
|
|
50
233
|
case 'unknown':
|
|
@@ -57,6 +240,10 @@ export function typeToString(t: IrType): string {
|
|
|
57
240
|
return t.name;
|
|
58
241
|
case 'array':
|
|
59
242
|
return `${typeToString(t.elem)}[${t.count}]`;
|
|
243
|
+
case 'union':
|
|
244
|
+
return `union{${t.members.map((m) => `${typeToString(m.type)} ${m.name}`).join(';')}}`;
|
|
245
|
+
case 'float':
|
|
246
|
+
return `f${t.width ?? '?'}`;
|
|
60
247
|
case 'void':
|
|
61
248
|
return 'void';
|
|
62
249
|
}
|
|
@@ -67,6 +254,9 @@ export function parseType(s: string): IrType {
|
|
|
67
254
|
if (s.endsWith('*')) {
|
|
68
255
|
return T.ptr(parseType(s.slice(0, -1)));
|
|
69
256
|
}
|
|
257
|
+
if (s === 'f32' || s === 'f64' || s === 'f?') {
|
|
258
|
+
return s === 'f32' ? T.f32() : s === 'f64' ? T.f64() : T.fUnstated();
|
|
259
|
+
}
|
|
70
260
|
const m = s.match(/^(unk|s|u)(\d+)$/);
|
|
71
261
|
if (!m) {
|
|
72
262
|
throw new Error(`bad type '${s}'`);
|
|
@@ -85,12 +275,22 @@ export function typeEquals(a: IrType, b: IrType): boolean {
|
|
|
85
275
|
if (a.kind === 'int' && b.kind === 'int') {
|
|
86
276
|
return a.width === b.width && a.signed === b.signed;
|
|
87
277
|
}
|
|
88
|
-
if (a.kind === '
|
|
278
|
+
if (a.kind === 'void' && b.kind === 'void') {
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
if ((a.kind === 'unknown' && b.kind === 'unknown') || (a.kind === 'float' && b.kind === 'float')) {
|
|
89
282
|
return a.width === b.width;
|
|
90
283
|
}
|
|
91
284
|
if (a.kind === 'array' && b.kind === 'array') {
|
|
92
285
|
return a.count === b.count && typeEquals(a.elem, b.elem);
|
|
93
286
|
}
|
|
287
|
+
if (a.kind === 'union' && b.kind === 'union') {
|
|
288
|
+
return (
|
|
289
|
+
a.size === b.size &&
|
|
290
|
+
a.members.length === b.members.length &&
|
|
291
|
+
a.members.every((m, i) => m.name === b.members[i].name && typeEquals(m.type, b.members[i].type))
|
|
292
|
+
);
|
|
293
|
+
}
|
|
94
294
|
// Two structs are equal when their name + field layout match (recovered structs are named
|
|
95
295
|
// by layout-discovery order, so equal name ⇒ equal layout in practice).
|
|
96
296
|
if (a.kind === 'struct' && b.kind === 'struct') {
|
package/src/ir/verify.ts
CHANGED
|
@@ -3,16 +3,116 @@
|
|
|
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
|
-
// 4.
|
|
6
|
+
// 4. a float value is an operand or result of a float op, a conversion or a compare, or `ret` only
|
|
7
|
+
// — or a `double` operand of a call whose callee declares one there, or the result of a call
|
|
8
|
+
// whose callee is declared to return a `float` — and crosses an edge only
|
|
9
|
+
// into a block parameter of its own type; an `fconst` is a double, sixteen hex digits into an f64
|
|
10
|
+
// 5. side data: a fn that carries a write-order record carries one for EVERY block, with every
|
|
7
11
|
// ordinal inside that block's own write count (ir/core.ts `WriteOrder`)
|
|
8
|
-
import { Block, Fn, Value, dominators } from './core';
|
|
9
|
-
import {
|
|
12
|
+
import { Block, Fn, Op, Value, dominators } from './core';
|
|
13
|
+
import { isDoubleBits } from './float-bits';
|
|
14
|
+
import { FLOAT_COMPUTING, FLOAT_OPS, FLOAT_RESIZE, FLOAT_TO_INT, INT_TO_FLOAT, WIDE_BITS, opSig } from './opcodes';
|
|
15
|
+
import { type IrType, T, intWidth, typeEquals, typeToString } from './types';
|
|
10
16
|
|
|
11
17
|
export class VerifyError extends Error {}
|
|
12
18
|
|
|
19
|
+
/** A call hands a float only to a parameter its callee is declared to take a `double` in — the
|
|
20
|
+
* operands the frontend lists in `doubles` — and returns one only where its callee is declared to
|
|
21
|
+
* return a `float` (`floatReturn`), as that single. */
|
|
22
|
+
function floatsDeclared(op: Op): boolean {
|
|
23
|
+
const at = Array.isArray(op.attrs.doubles) ? op.attrs.doubles : [];
|
|
24
|
+
return (
|
|
25
|
+
op.opcode === 'call' &&
|
|
26
|
+
op.results.every(
|
|
27
|
+
(r) => r.type.kind !== 'float' || (op.attrs.floatReturn === 'float' && typeEquals(r.type, T.f32())),
|
|
28
|
+
) &&
|
|
29
|
+
op.operands.every((o, i) => o.type.kind !== 'float' || (at.includes(i) && typeEquals(o.type, T.f64())))
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The ops where a float meets an integer or the other precision, each held to the one shape it
|
|
34
|
+
* has: a conversion takes a float of either stated precision or none and gives an integer, or the
|
|
35
|
+
* reverse; a resize takes its own source width to its own destination width; a compare takes two
|
|
36
|
+
* floats of one type and gives a 32-bit integer. The integer side may be narrower than a word once
|
|
37
|
+
* recovery has typed it (`(float)x` of a `short` parameter), which converts the value it promotes
|
|
38
|
+
* to; the C spelling casts through `s32`/`s64`. */
|
|
39
|
+
function verifyFloatCrossing(op: Op): void {
|
|
40
|
+
const ins = op.operands.map((o) => o.type);
|
|
41
|
+
const out = op.results[0].type;
|
|
42
|
+
const isInt = (t: IrType) => intWidth(t) !== null;
|
|
43
|
+
const resize = FLOAT_RESIZE[op.opcode];
|
|
44
|
+
const ok = FLOAT_TO_INT.has(op.opcode)
|
|
45
|
+
? ins[0].kind === 'float' && isInt(out)
|
|
46
|
+
: INT_TO_FLOAT.has(op.opcode)
|
|
47
|
+
? isInt(ins[0]) && out.kind === 'float'
|
|
48
|
+
: resize !== undefined
|
|
49
|
+
? typeEquals(ins[0], resize.from === 64 ? T.f64() : T.f32()) &&
|
|
50
|
+
typeEquals(out, resize.to === 64 ? T.f64() : T.f32())
|
|
51
|
+
: ins[0].kind === 'float' && typeEquals(ins[0], ins[1]) && intWidth(out) === 32;
|
|
52
|
+
if (!ok) {
|
|
53
|
+
throw new VerifyError(`'${op.opcode}' does not take ${ins.map(typeToString).join(', ')} to ${typeToString(out)}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
13
57
|
// Opcodes admitting EITHER a 2-operand register form OR a 1-operand + `imm` attr form.
|
|
14
58
|
const TWO_OR_IMM = new Set(['sdiv', 'shl', 'shr_u', 'shr_s']);
|
|
15
59
|
|
|
60
|
+
// ── 64 DOES NOT MIX ────────────────────────────────────────────────────────────────────────────
|
|
61
|
+
// A 64-bit value is one `Value` whose type carries width 64 (ir/opcodes.ts, `concat`), so every
|
|
62
|
+
// pass that reads `t.width` and is wrong about it is wrong HERE rather than three stages later. The
|
|
63
|
+
// rule below is a QUARANTINE and not full width agreement, and the difference is measured rather
|
|
64
|
+
// than stylistic: full agreement is already false on 32-bit IR, because `raise/paramwidth.ts`
|
|
65
|
+
// narrows a parameter to 8 or 16 bits and `add(p_u8, x_s32)` is an ordinary correct shape. What
|
|
66
|
+
// must hold is that a 64-bit operand never meets a 32-bit one in an op that computes on both.
|
|
67
|
+
//
|
|
68
|
+
// `unknown` PARTICIPATES, and that is the whole point: at L1 every value is `unknown`, so a rule
|
|
69
|
+
// that skipped it would be vacuous exactly where this is meant to be red. The shifts count only
|
|
70
|
+
// their SHIFTED operand — a 64-bit shift by a 32-bit count is what the machine does.
|
|
71
|
+
const WIDTH_UNIFORM = new Set([
|
|
72
|
+
'add',
|
|
73
|
+
'sub',
|
|
74
|
+
'mul',
|
|
75
|
+
'and',
|
|
76
|
+
'or',
|
|
77
|
+
'xor',
|
|
78
|
+
'neg',
|
|
79
|
+
'neg_s',
|
|
80
|
+
'not',
|
|
81
|
+
'sdiv',
|
|
82
|
+
'udiv',
|
|
83
|
+
'smod',
|
|
84
|
+
'umod',
|
|
85
|
+
]);
|
|
86
|
+
// The shifts count only their SHIFTED operand: a 64-bit value shifted by a 32-bit count is what
|
|
87
|
+
// every one of these machines does. The comparisons count only their OPERANDS: the result of a C
|
|
88
|
+
// comparison is an `int` whatever it compared, which is what `raise/recover.ts` already types it.
|
|
89
|
+
const SHIFTS = new Set(['shl', 'shr_u', 'shr_s']);
|
|
90
|
+
const COMPARES = new Set([
|
|
91
|
+
'icmp_slt',
|
|
92
|
+
'icmp_sle',
|
|
93
|
+
'icmp_sgt',
|
|
94
|
+
'icmp_sge',
|
|
95
|
+
'icmp_ult',
|
|
96
|
+
'icmp_ule',
|
|
97
|
+
'icmp_ugt',
|
|
98
|
+
'icmp_uge',
|
|
99
|
+
'icmp_eq',
|
|
100
|
+
'icmp_ne',
|
|
101
|
+
]);
|
|
102
|
+
/** Whether `t` takes part in the width rule at all: it does exactly when it carries an integer
|
|
103
|
+
* width. `ir/types.ts` owns that question — `raise/widehelpers.ts` asks it too, of the same kinds,
|
|
104
|
+
* and reads the null with the opposite polarity. */
|
|
105
|
+
const widthOf = intWidth;
|
|
106
|
+
/** The types an op's width rule quantifies over. */
|
|
107
|
+
const widthParticipants = (op: Op): IrType[] => {
|
|
108
|
+
const operands = op.operands.map((o) => o.type);
|
|
109
|
+
if (COMPARES.has(op.opcode)) {
|
|
110
|
+
return operands;
|
|
111
|
+
}
|
|
112
|
+
const results = op.results.map((r) => r.type);
|
|
113
|
+
return SHIFTS.has(op.opcode) ? [...results, ...operands.slice(0, 1)] : [...results, ...operands];
|
|
114
|
+
};
|
|
115
|
+
|
|
16
116
|
export function verify(fn: Fn): void {
|
|
17
117
|
if (fn.blocks.length === 0) {
|
|
18
118
|
throw new VerifyError(`fn '${fn.name}' has no blocks`);
|
|
@@ -104,11 +204,118 @@ export function verify(fn: Fn): void {
|
|
|
104
204
|
if (!isTerm && op.successors.length) {
|
|
105
205
|
throw new VerifyError(`non-terminator '${op.opcode}' has successors`);
|
|
106
206
|
}
|
|
207
|
+
// `laddr` carries two shapes in one set of attrs and `count` is the only thing telling
|
|
208
|
+
// them apart: one element is a SCALAR typed by the accesses the frame-object audit read
|
|
209
|
+
// off the machine, and more than one is STORAGE that audit sized without typing, whose
|
|
210
|
+
// width and signedness are therefore not an access at all. The structurer declares the
|
|
211
|
+
// second as `u8 name[count]`, so a typed element there would be a type nothing pinned —
|
|
212
|
+
// the exact answer the audit refuses to invent. (One BYTE encodes like a real `u8`
|
|
213
|
+
// scalar either way; the declaration is the same one, so nothing has to tell them apart.)
|
|
214
|
+
if (op.opcode === 'laddr') {
|
|
215
|
+
const count = op.attrs.count;
|
|
216
|
+
if (typeof count !== 'number' || !Number.isInteger(count) || count < 1) {
|
|
217
|
+
throw new VerifyError(`'laddr' count must be a positive integer, got ${String(count)}`);
|
|
218
|
+
}
|
|
219
|
+
if (count > 1 && (op.attrs.width !== 1 || op.attrs.signed !== false)) {
|
|
220
|
+
throw new VerifyError(
|
|
221
|
+
`'laddr' of ${count} elements is storage no access typed, so it must be unsigned bytes — ` +
|
|
222
|
+
`got width ${String(op.attrs.width)}, signed ${String(op.attrs.signed)}`,
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
// …and a PART of the one object (frame-objects.ts `provenParts`) is such storage too
|
|
226
|
+
if (op.attrs.part !== undefined && op.attrs.part !== true) {
|
|
227
|
+
throw new VerifyError(`'laddr' part must be true, got ${String(op.attrs.part)}`);
|
|
228
|
+
}
|
|
229
|
+
if (op.attrs.part === true && count < 2) {
|
|
230
|
+
throw new VerifyError(`'laddr' part must be storage of two or more bytes, got ${count}`);
|
|
231
|
+
}
|
|
232
|
+
// …and storage accessed at two widths over one byte names the VIEWS a union of it
|
|
233
|
+
// declares (frame-objects.ts `unionViews`): one per element width in bytes, negative
|
|
234
|
+
// for a signed narrow one, each tiling the storage — so at least two, distinct, of a
|
|
235
|
+
// width a view is named for, and a word never signed-marked (it has one view).
|
|
236
|
+
const views = op.attrs.views;
|
|
237
|
+
if (views !== undefined) {
|
|
238
|
+
const ok =
|
|
239
|
+
Array.isArray(views) &&
|
|
240
|
+
count > 1 &&
|
|
241
|
+
views.length >= 2 &&
|
|
242
|
+
new Set(views).size === views.length &&
|
|
243
|
+
views.every((v) => [1, 2, 4, -1, -2].includes(v) && count % Math.abs(v) === 0);
|
|
244
|
+
if (!ok) {
|
|
245
|
+
throw new VerifyError(
|
|
246
|
+
`'laddr' views must be two or more distinct element widths (1, 2, 4; negative for a signed ` +
|
|
247
|
+
`narrow view) tiling its ${String(count)} bytes, got ${JSON.stringify(views)}`,
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
// A 64-bit value is BUILT and TAKEN APART by exactly these three, so their shape is
|
|
253
|
+
// checked here rather than left to whoever reads a half. `concat` is the only producer.
|
|
254
|
+
if (op.opcode === 'concat' || op.opcode === 'lo32' || op.opcode === 'hi32') {
|
|
255
|
+
const wide = op.opcode === 'concat' ? op.results[0].type : op.operands[0].type;
|
|
256
|
+
const narrow = op.opcode === 'concat' ? op.operands.map((o) => o.type) : op.results.map((r) => r.type);
|
|
257
|
+
const wideW = widthOf(wide);
|
|
258
|
+
if (wideW !== WIDE_BITS) {
|
|
259
|
+
throw new VerifyError(
|
|
260
|
+
`'${op.opcode}' ${op.opcode === 'concat' ? 'result' : 'operand'} must be an integer of ` +
|
|
261
|
+
`width ${WIDE_BITS}, got ${String(wideW ?? wide.kind)}`,
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
for (const t of narrow) {
|
|
265
|
+
if (widthOf(t) !== 32) {
|
|
266
|
+
throw new VerifyError(
|
|
267
|
+
`'${op.opcode}' half must be an integer of width 32, got ${String(widthOf(t) ?? t.kind)}`,
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
// …and everywhere else, 64 does not mix (see the header above this file's checks).
|
|
273
|
+
if (WIDTH_UNIFORM.has(op.opcode) || SHIFTS.has(op.opcode) || COMPARES.has(op.opcode)) {
|
|
274
|
+
const ws = widthParticipants(op)
|
|
275
|
+
.map(widthOf)
|
|
276
|
+
.filter((w): w is number => w !== null);
|
|
277
|
+
if (ws.some((w) => w === WIDE_BITS) && ws.some((w) => w !== WIDE_BITS)) {
|
|
278
|
+
throw new VerifyError(
|
|
279
|
+
`'${op.opcode}' mixes a ${WIDE_BITS}-bit operand with a narrower one (widths ${ws.join(', ')})`,
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
// A FLOAT IS COMPUTED ON BY THE FLOAT OPS AND RETURNED, AND NOTHING ELSE TOUCHES IT. Both
|
|
284
|
+
// halves are checked, because each is a silent wrong the other cannot see: a float op over
|
|
285
|
+
// an integer is a frontend that lost track of a register file, and an integer op over a
|
|
286
|
+
// float is a pass that matched an opcode without asking what it computes on.
|
|
287
|
+
const floats = [...op.operands, ...op.results].map((v) => v.type).filter((t) => t.kind === 'float');
|
|
288
|
+
if (FLOAT_OPS.has(op.opcode)) {
|
|
289
|
+
const all = [...op.operands, ...op.results].map((v) => v.type);
|
|
290
|
+
if (floats.length !== all.length || all.some((t) => !typeEquals(t, all[0]))) {
|
|
291
|
+
throw new VerifyError(`'${op.opcode}' computes on floats only, got ${all.map(typeToString).join(', ')}`);
|
|
292
|
+
}
|
|
293
|
+
} else if (FLOAT_COMPUTING.has(op.opcode)) {
|
|
294
|
+
verifyFloatCrossing(op);
|
|
295
|
+
} else if (floats.length > 0 && op.opcode !== 'ret' && !floatsDeclared(op)) {
|
|
296
|
+
throw new VerifyError(`a float value reaches '${op.opcode}', which does not compute on floats`);
|
|
297
|
+
}
|
|
298
|
+
// A literal's width is its pattern's, so the pattern and the result must name one width.
|
|
299
|
+
if (op.opcode === 'fconst' && (!isDoubleBits(op.attrs.bits) || !typeEquals(op.results[0].type, T.f64()))) {
|
|
300
|
+
throw new VerifyError(
|
|
301
|
+
`'fconst' is a double literal: bits ${JSON.stringify(op.attrs.bits)} into ${typeToString(op.results[0].type)}`,
|
|
302
|
+
);
|
|
303
|
+
}
|
|
107
304
|
for (const k of sig.requiredAttrs ?? []) {
|
|
108
305
|
if (!(k in op.attrs)) {
|
|
109
306
|
throw new VerifyError(`'${op.opcode}' missing required attr '${k}'`);
|
|
110
307
|
}
|
|
111
308
|
}
|
|
309
|
+
if (op.opcode === 'call') {
|
|
310
|
+
const named = typeof op.attrs.target === 'string';
|
|
311
|
+
const indirect = op.attrs.indirect === true;
|
|
312
|
+
if (named === indirect || 'target' in op.attrs !== named || (indirect && op.operands.length === 0)) {
|
|
313
|
+
throw new VerifyError(
|
|
314
|
+
"'call' names its callee in a string 'target' or calls the value in its last operand under " +
|
|
315
|
+
"'indirect', and does exactly one of the two",
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
112
319
|
for (const u of op.operands) {
|
|
113
320
|
if (!defined.has(u)) {
|
|
114
321
|
throw new VerifyError(`use of undefined value in '${op.opcode}'`);
|
|
@@ -128,6 +335,15 @@ export function verify(fn: Fn): void {
|
|
|
128
335
|
throw new VerifyError(`use of undefined value in successor args of '${op.opcode}'`);
|
|
129
336
|
}
|
|
130
337
|
}
|
|
338
|
+
// …and a float crosses an edge only into a block parameter of its own type.
|
|
339
|
+
s.args.forEach((u, i) => {
|
|
340
|
+
const p = s.block.params[i].type;
|
|
341
|
+
if ((u.type.kind === 'float' || p.kind === 'float') && !typeEquals(u.type, p)) {
|
|
342
|
+
throw new VerifyError(
|
|
343
|
+
`successor of '${op.opcode}' passes a ${typeToString(u.type)} to a ${typeToString(p)} block parameter`,
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
});
|
|
131
347
|
}
|
|
132
348
|
},
|
|
133
349
|
() => at(b, idx),
|
package/src/l3/address.ts
CHANGED
|
@@ -33,6 +33,11 @@ export const baseConst = (e: Expr): number | null =>
|
|
|
33
33
|
? baseConst(e.e)
|
|
34
34
|
: null;
|
|
35
35
|
|
|
36
|
+
/** Does this base already assert volatility — a `volatile` cast at any depth of the cast chain? A
|
|
37
|
+
* respell variation that replaces such a base drops the qualifier with it, and every access
|
|
38
|
+
* through it becomes a plain one agbcc may delete or hoist. */
|
|
39
|
+
export const qualifiedBase = (e: Expr): boolean => e.k === 'cast' && (e.volatile === true || qualifiedBase(e.e));
|
|
40
|
+
|
|
36
41
|
/** the numeric address an expression IS, through any number of pointer casts */
|
|
37
42
|
export const addrConst = (e: Expr): number | null =>
|
|
38
43
|
e.k === 'const' ? e.value : e.k === 'cast' && e.to.kind === 'ptr' ? addrConst(e.e) : null;
|
package/src/l3/argcopy.ts
CHANGED
|
@@ -101,9 +101,12 @@ export interface ArgCopyRegionCtx {
|
|
|
101
101
|
readonly reads: number;
|
|
102
102
|
/** a loop encloses the region — its own body, or any list nested below one */
|
|
103
103
|
readonly underLoop: boolean;
|
|
104
|
+
/** the region is ONE basic block: no statement opens a nested list, and no expression holds a
|
|
105
|
+
* short-circuit connective */
|
|
106
|
+
readonly straightLine: boolean;
|
|
104
107
|
}
|
|
105
108
|
|
|
106
|
-
/** Which regions are worth offering.
|
|
109
|
+
/** Which regions are worth offering. NONE is sound — each refuses a spelling that is correct
|
|
107
110
|
* but models nothing, and what they buy is the candidate count: every one of these is a COMPILE,
|
|
108
111
|
* and this variation multiplies with `/coalesce`. */
|
|
109
112
|
export const ARGCOPY_REGION_GATES: readonly Gate<ArgCopyRegionCtx>[] = [
|
|
@@ -117,13 +120,37 @@ export const ARGCOPY_REGION_GATES: readonly Gate<ArgCopyRegionCtx>[] = [
|
|
|
117
120
|
id: 'loop-region',
|
|
118
121
|
why: 'a copy anywhere inside a loop re-runs every iteration, so it belongs to a region that holds the loop instead — and no region does when the loop is at function top level, where this variation offers nothing at all',
|
|
119
122
|
sound: false,
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
+
guardedBy: [
|
|
124
|
+
'argcopy.test.ts: an arm NESTED inside a loop body is refused too',
|
|
125
|
+
'argcopy.test.ts: a TOP-LEVEL loop is offered NOTHING',
|
|
126
|
+
],
|
|
123
127
|
rejects: (c) => c.underLoop,
|
|
124
128
|
},
|
|
129
|
+
{
|
|
130
|
+
id: 'straight-line',
|
|
131
|
+
why: 'a copy whose every read sits in one basic block is forward-propagated away by every compiler in the matrix, so the copy is erased — a `&&` or `||` in the region is an edge the copy can live across, and such a region is offered',
|
|
132
|
+
sound: false,
|
|
133
|
+
guardedBy: 'argcopy.test.ts: a STRAIGHT-LINE region is refused',
|
|
134
|
+
rejects: (c) => c.straightLine,
|
|
135
|
+
},
|
|
125
136
|
];
|
|
126
137
|
|
|
138
|
+
/** Whether `list` is one basic block. A nested list is a branch or a loop; a short-circuit
|
|
139
|
+
* connective is a branch too — `p[3] = p[0] > v && p[4] < v` is two blocks, and agbcc keeps a
|
|
140
|
+
* copy live across that edge (the synthetic `leafand` row matches only through it). `walkExprs` for the
|
|
141
|
+
* reason `countReads` gives: it is the walk `repoint` rewrites by. */
|
|
142
|
+
function isStraightLine(list: Stmt[]): boolean {
|
|
143
|
+
if (list.some((st) => stmtLists(st).length > 0)) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
for (const e of walkExprs(list)) {
|
|
147
|
+
if (e.k === 'bin' && (e.op === '&&' || e.op === '||')) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
|
|
127
154
|
/** How many `var n` leaves the region holds, nested statements included — the region rules'
|
|
128
155
|
* yardstick for whether a copy has a range to shorten.
|
|
129
156
|
*
|
|
@@ -176,7 +203,8 @@ function regions(body: Stmt[]): { at: number[]; list: Stmt[]; underLoop: boolean
|
|
|
176
203
|
* for > do-while > if > store). This runs once per region per parameter per candidate, and each
|
|
177
204
|
* candidate is a compile. */
|
|
178
205
|
function repoint(list: Stmt[], n: string, to: string): Stmt[] {
|
|
179
|
-
const inExpr = (e: Expr): Expr =>
|
|
206
|
+
const inExpr = (e: Expr): Expr =>
|
|
207
|
+
(e.k === 'var' || e.k === 'postincr') && e.name === n ? { ...e, name: to } : mapExprChildren(e, inExpr);
|
|
180
208
|
return list.map((s) => mapStmtExprs(s, inExpr));
|
|
181
209
|
}
|
|
182
210
|
|
|
@@ -238,7 +266,11 @@ export function argCopyUnder(
|
|
|
238
266
|
if (reads === 0) {
|
|
239
267
|
continue;
|
|
240
268
|
}
|
|
241
|
-
const regionRefused = firstRejection(regionGates, {
|
|
269
|
+
const regionRefused = firstRejection(regionGates, {
|
|
270
|
+
reads,
|
|
271
|
+
underLoop: r.underLoop,
|
|
272
|
+
straightLine: isStraightLine(r.list),
|
|
273
|
+
});
|
|
242
274
|
if (regionRefused !== null) {
|
|
243
275
|
refusals.set(regionRefused, (refusals.get(regionRefused) ?? 0) + 1);
|
|
244
276
|
continue;
|