@asmlift/core 0.8.0 → 0.9.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 +34 -30
- package/package.json +1 -1
- package/src/aggregate.ts +248 -0
- package/src/backend/c.ts +5 -2
- package/src/backend/cfamily.ts +236 -37
- package/src/backend/cpp.ts +114 -9
- package/src/backend/pascal.ts +39 -0
- 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 +615 -79
- package/src/declare.ts +75 -5
- package/src/frontend/asmdata.ts +19 -5
- package/src/frontend/call-plan.ts +686 -0
- package/src/frontend/device-pins.ts +277 -0
- package/src/frontend/errors.ts +5 -0
- package/src/frontend/flags-edge.ts +101 -0
- package/src/frontend/fpu.ts +167 -0
- package/src/frontend/frame-objects.ts +2131 -0
- package/src/frontend/high-half.ts +2 -1
- package/src/frontend/local-object.ts +490 -0
- package/src/frontend/mips.ts +363 -40
- package/src/frontend/opaque.ts +97 -18
- package/src/frontend/ppc.ts +520 -129
- package/src/frontend/reloc-symbol.ts +94 -13
- package/src/frontend/splat.ts +108 -5
- package/src/frontend/ssa.ts +669 -76
- package/src/frontend/stackargs.ts +120 -39
- package/src/frontend/thumb.ts +2764 -2018
- package/src/ir/alias.ts +29 -0
- package/src/ir/core.ts +101 -3
- package/src/ir/discipline.ts +248 -0
- package/src/ir/float-bits.ts +47 -0
- package/src/ir/opcodes.ts +84 -112
- package/src/ir/parse.ts +1 -0
- package/src/ir/types.ts +118 -2
- package/src/ir/verify.ts +150 -3
- package/src/l3/address.ts +5 -0
- package/src/l3/argcopy.ts +38 -6
- package/src/l3/ast.ts +186 -14
- package/src/l3/coalesce.ts +21 -13
- package/src/l3/dce.ts +32 -10
- package/src/l3/gates.ts +9 -3
- package/src/l3/hoist.ts +8 -7
- package/src/l3/initfirst.ts +27 -9
- package/src/l3/intcell.ts +97 -0
- package/src/l3/mentions.ts +10 -4
- package/src/l3/narrowdecl.ts +218 -0
- package/src/l3/nearbase.ts +10 -5
- package/src/l3/parkfirst.ts +8 -5
- package/src/l3/pollguard.ts +3 -2
- package/src/l3/ptrcell.ts +357 -0
- package/src/l3/regspell.ts +17 -3
- package/src/l3/reindex.ts +10 -7
- package/src/l3/symbol-refs.ts +104 -23
- package/src/l3/tailmerge.ts +6 -19
- package/src/l3/typing.ts +137 -25
- package/src/l3/unreduce.ts +4 -4
- package/src/l3/volstore.ts +96 -7
- package/src/mangle.ts +11 -1
- package/src/pattern/engine.ts +19 -16
- package/src/pipeline.ts +57 -12
- package/src/proto-context.ts +788 -0
- package/src/proto.ts +689 -50
- package/src/raise/arrays.ts +4 -3
- package/src/raise/declared-volatile.ts +131 -0
- package/src/raise/divpow2.ts +2 -2
- package/src/raise/errors.ts +17 -0
- package/src/raise/floathelpers.ts +243 -0
- package/src/raise/globalshape.ts +11 -5
- package/src/raise/gvn.ts +26 -5
- package/src/raise/narrow.ts +29 -2
- package/src/raise/narrowlocal.ts +11 -6
- package/src/raise/offsetnames.ts +510 -0
- package/src/raise/pairparams.ts +81 -0
- package/src/raise/paramwidth.ts +7 -0
- package/src/raise/pre-recovery.ts +78 -4
- package/src/raise/recover.ts +14 -10
- package/src/raise/retsink.ts +1 -1
- package/src/raise/shortcircuit.ts +41 -19
- package/src/raise/softdiv.ts +21 -23
- package/src/raise/struct-arrays.ts +5 -4
- package/src/raise/structs.ts +311 -98
- package/src/raise/truncload.ts +366 -0
- package/src/raise/widehelpers.ts +106 -0
- package/src/rank-declare.ts +30 -5
- package/src/rank-variations.ts +36 -8
- package/src/rank.ts +276 -80
- package/src/runtime-helpers.ts +287 -0
- package/src/stillborn.ts +289 -0
- package/src/structure/analysis.ts +1285 -448
- package/src/structure/hazards.ts +633 -58
- package/src/structure/local-statics.ts +381 -0
- package/src/structure/namecoalesce.ts +1 -1
- package/src/structure/redundant-test.ts +99 -0
- package/src/structure/structure.ts +1772 -395
- package/src/structure/switch-recover.ts +159 -78
- package/src/symbols.ts +38 -3
- package/src/target.ts +662 -59
- package/src/trace.ts +44 -24
- package/src/variation-definitions.ts +119 -1
- package/src/variation-tokens.ts +20 -6
package/src/l3/tailmerge.ts
CHANGED
|
@@ -53,37 +53,24 @@
|
|
|
53
53
|
// lifts alike. What no arm of the fan spells is the third form, the hidden statement merged
|
|
54
54
|
// while KEEPING the record's order.
|
|
55
55
|
//
|
|
56
|
-
// SCOPE. Only `assign`/`store`/`exprstmt` merge, compared structurally through `
|
|
56
|
+
// SCOPE. Only `assign`/`store`/`exprstmt` merge, compared structurally through `stmtEquals`.
|
|
57
57
|
// Control flow (`break`/`continue`/`return`) is excluded: moving one out of an arm changes which
|
|
58
|
-
// statements the arm can still reach. Nested `if`/loop/`switch` statements are excluded
|
|
59
|
-
//
|
|
60
|
-
//
|
|
58
|
+
// statements the arm can still reach. Nested `if`/loop/`switch` statements are excluded: merging
|
|
59
|
+
// one needs a congruence over loops and `switch` too, which `stmtEquals` does not have, and there
|
|
60
|
+
// is no second inhabitant for one.
|
|
61
61
|
//
|
|
62
62
|
// An `ASMLIFT_ERROR` marker ending both arms merges like anything else. The gap stays loud (the
|
|
63
63
|
// artifact still refuses to compile) but `collectMarkers` then reports it once rather than twice,
|
|
64
64
|
// which is accurate — it is one gap that ran on both paths.
|
|
65
65
|
import type { SFn, Stmt } from './ast';
|
|
66
|
-
import {
|
|
66
|
+
import { stmtEquals } from './ast';
|
|
67
67
|
|
|
68
68
|
/** Statements this pass may move. Deliberately narrow — see SCOPE. */
|
|
69
69
|
type Mergeable = Extract<Stmt, { k: 'assign' } | { k: 'store' } | { k: 'exprstmt' }>;
|
|
70
70
|
const isMergeable = (s: Stmt): s is Mergeable => s.k === 'assign' || s.k === 'store' || s.k === 'exprstmt';
|
|
71
71
|
|
|
72
72
|
/** Do these two statements write the same thing from the same expression? */
|
|
73
|
-
|
|
74
|
-
if (!isMergeable(a) || !isMergeable(b) || a.k !== b.k) {
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
if (a.k === 'assign' && b.k === 'assign') {
|
|
78
|
-
return a.name === b.name && exprEquals(a.value, b.value);
|
|
79
|
-
}
|
|
80
|
-
if (a.k === 'store' && b.k === 'store') {
|
|
81
|
-
return exprEquals(a.lval, b.lval) && exprEquals(a.value, b.value);
|
|
82
|
-
}
|
|
83
|
-
const av = (a as Extract<Stmt, { k: 'exprstmt' }>).value;
|
|
84
|
-
const bv = (b as Extract<Stmt, { k: 'exprstmt' }>).value;
|
|
85
|
-
return exprEquals(av, bv);
|
|
86
|
-
}
|
|
73
|
+
const sameStmt = (a: Stmt, b: Stmt): boolean => isMergeable(a) && isMergeable(b) && stmtEquals(a, b);
|
|
87
74
|
|
|
88
75
|
/** Rewrite one statement, then the list it lives in. */
|
|
89
76
|
function rewrite(s: Stmt): Stmt[] {
|
package/src/l3/typing.ts
CHANGED
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
// Callers choose their conservative direction: the emission guard treats `undefined` as "not
|
|
18
18
|
// provably a pointer" (adds a cast — valid C either way); the deref contract treats `undefined`
|
|
19
19
|
// as "not provably wrong" (no error).
|
|
20
|
-
import { IrType, T, scalarTypeForAccess } from '../ir/types';
|
|
21
|
-
import { type Expr, type SFn, exprChildren } from './ast';
|
|
20
|
+
import { IrType, T, memberOf, scalarTypeForAccess } from '../ir/types';
|
|
21
|
+
import { type BinOp, type Expr, type SFn, exprChildren } from './ast';
|
|
22
22
|
|
|
23
23
|
/** The declared type of a printed variable — the env `exprCType` judges rendered C against.
|
|
24
24
|
* THE one copy of the SFn→env derivation — printers, contracts and L3 respell variations alike: each
|
|
@@ -31,6 +31,9 @@ export function declaredTypes(fn: SFn): VarTypes {
|
|
|
31
31
|
for (const g of fn.globals ?? []) {
|
|
32
32
|
m.set(g.name, g.type);
|
|
33
33
|
}
|
|
34
|
+
for (const st of fn.statics ?? []) {
|
|
35
|
+
m.set(st.name, st.type);
|
|
36
|
+
}
|
|
34
37
|
for (const p of fn.params) {
|
|
35
38
|
m.set(p.name, p.type);
|
|
36
39
|
}
|
|
@@ -55,6 +58,11 @@ export function ptrElemBytes(to: IrType): number {
|
|
|
55
58
|
export interface PrintEnv {
|
|
56
59
|
readonly type: VarTypes;
|
|
57
60
|
readonly volatilePointee: (name: string) => boolean;
|
|
61
|
+
/** a callee's declared parameter types, on a function compiled as C++ (`SFn.declaredArgs`,
|
|
62
|
+
* `SFn.dialect`) */
|
|
63
|
+
readonly declaredArgs: (callee: string) => readonly (string | undefined)[] | undefined;
|
|
64
|
+
/** a function-scope static this function defines `const` (`SFn.statics`) */
|
|
65
|
+
readonly constStatic: (name: string) => boolean;
|
|
58
66
|
}
|
|
59
67
|
|
|
60
68
|
export function printEnv(fn: SFn): PrintEnv {
|
|
@@ -63,7 +71,16 @@ export function printEnv(fn: SFn): PrintEnv {
|
|
|
63
71
|
const vol = new Set(
|
|
64
72
|
fn.locals.filter((l) => l.type.kind === 'ptr' && (l.pointeeVolatile || l.volatile)).map((l) => l.name),
|
|
65
73
|
);
|
|
66
|
-
|
|
74
|
+
const constStatics = new Set((fn.statics ?? []).filter((s) => s.const).map((s) => s.name));
|
|
75
|
+
return {
|
|
76
|
+
type: declaredTypes(fn),
|
|
77
|
+
volatilePointee: (n) => vol.has(n),
|
|
78
|
+
declaredArgs: (callee) =>
|
|
79
|
+
fn.dialect === 'c++' && fn.declaredArgs && Object.hasOwn(fn.declaredArgs, callee)
|
|
80
|
+
? fn.declaredArgs[callee]
|
|
81
|
+
: undefined,
|
|
82
|
+
constStatic: (n) => constStatics.has(n),
|
|
83
|
+
};
|
|
67
84
|
}
|
|
68
85
|
|
|
69
86
|
/** Does anything under `e` assert a VOLATILE POINTEE? The two spellings that carry the qualifier
|
|
@@ -103,15 +120,81 @@ export function derefStrideOk(rt: IrType | undefined, width: number, signed: boo
|
|
|
103
120
|
return false;
|
|
104
121
|
}
|
|
105
122
|
|
|
106
|
-
/** The
|
|
107
|
-
*
|
|
108
|
-
*
|
|
123
|
+
/** The INTEGER RANK a rendered expression carries, as a width in bits: 64 only where it can be
|
|
124
|
+
* PROVEN, 32 everywhere else. The complement of `renderedIntSignedness` — that one models the
|
|
125
|
+
* signedness `exprCType` reports uniformly as `s32`, this one models the width it reports the same
|
|
126
|
+
* way — and the two together are what the usual arithmetic conversions need.
|
|
127
|
+
*
|
|
128
|
+
* THE COMPLEMENT PARTS COMPANY WITH `exprCType` AT ONE NODE, AND IT IS `call`. There `exprCType`
|
|
129
|
+
* is UNINFORMED by design and answers `undefined`, while this reads the `wide64` stamp the
|
|
130
|
+
* structurer put on the node and can answer 64. A reader who reaches for `exprCType` on a call
|
|
131
|
+
* will be told the information does not exist; it does, and it is here.
|
|
132
|
+
*
|
|
133
|
+
* ITS SOUNDNESS IS AN ENUMERATION OF THE PRODUCERS, so here is the enumeration. A 64-bit value
|
|
134
|
+
* reaches a rendered expression in exactly four ways:
|
|
135
|
+
* 1. a `var`/`postincr` whose DECLARED type is 64 bits wide — read off `varType`;
|
|
136
|
+
* 2. a `cast` to a 64-bit type — read off the node;
|
|
137
|
+
* 3. a `call` the structurer STAMPED `wide64` — read off the node, and it is the one case where
|
|
138
|
+
* the rank cannot be derived from anything visible here: a callee's return type comes from a
|
|
139
|
+
* prototype outside the emitted function, which is why `exprCType` answers `undefined` for a
|
|
140
|
+
* call and why the stamp exists (l3/ast.ts carries what it means);
|
|
141
|
+
* 4. an arithmetic node over one of those — the recursion below.
|
|
142
|
+
* It does NOT arrive through MEMORY, because `contracts.ts` `SCALAR_WIDTHS` is {1,2,4} and there
|
|
143
|
+
* is no 64-bit global; nor through a `const`, because a 64-bit literal only folds inside C `int`
|
|
144
|
+
* range, where 32 is the right answer for the literal's own type.
|
|
145
|
+
*
|
|
146
|
+
* AN UNSTAMPED CALL IS 32 AND THAT IS THE SAFE ANSWER, not a claim: it is what C makes of a callee
|
|
147
|
+
* nobody declared, and every consumer of this rank takes a cast on a 32 it doubts and takes none on
|
|
148
|
+
* a 64 — so the direction that would be wrong silently is the one this never guesses.
|
|
149
|
+
*
|
|
150
|
+
* A SHIFT takes the rank of its left operand alone; every other ARITHMETIC node takes the wider of
|
|
151
|
+
* the two. That is C, and it is also why this is not simply `exprCType(e).width`.
|
|
152
|
+
*
|
|
153
|
+
* THE NODES THAT ARE NOT ARITHMETIC ONES are the same list `renderedIntSignedness` enumerates, and
|
|
154
|
+
* they yield `int` however wide their operands: a comparison, a logical connective, and `!`. `-`
|
|
155
|
+
* and `~` are not in it, because each carries the promoted type of its operand. */
|
|
156
|
+
export function exprIntWidth(e: Expr, varType: VarTypes): 32 | 64 {
|
|
157
|
+
const wide = (t: IrType | undefined): boolean => t?.kind === 'int' && t.width === 64;
|
|
158
|
+
switch (e.k) {
|
|
159
|
+
case 'var':
|
|
160
|
+
case 'postincr':
|
|
161
|
+
case 'cast':
|
|
162
|
+
case 'index':
|
|
163
|
+
case 'field':
|
|
164
|
+
return wide(exprCType(e, varType)) ? 64 : 32;
|
|
165
|
+
case 'call':
|
|
166
|
+
return e.wide64 === true ? 64 : 32;
|
|
167
|
+
case 'un':
|
|
168
|
+
return e.op === '!' ? 32 : exprIntWidth(e.e, varType);
|
|
169
|
+
case 'bin':
|
|
170
|
+
if (INT_RESULT_BINOPS.has(e.op)) {
|
|
171
|
+
return 32;
|
|
172
|
+
}
|
|
173
|
+
if (e.op === '<<' || e.op === '>>' || e.op === '>>>') {
|
|
174
|
+
return exprIntWidth(e.l, varType);
|
|
175
|
+
}
|
|
176
|
+
return exprIntWidth(e.l, varType) === 64 || exprIntWidth(e.r, varType) === 64 ? 64 : 32;
|
|
177
|
+
default:
|
|
178
|
+
return 32;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** The float operators (l3/ast.ts BinOp): their result is the float their operands are, never an
|
|
183
|
+
* integer, so none of the integer conversion rules below applies to one. */
|
|
184
|
+
export const FLOAT_BINOPS: ReadonlySet<BinOp> = new Set<BinOp>(['f+', 'f-', 'f*', 'f/']);
|
|
185
|
+
|
|
186
|
+
/** The binary operators whose RESULT is a C `int` whatever their operands are — read by both
|
|
187
|
+
* halves of the conversion model, which is the point of naming them once. */
|
|
188
|
+
const INT_RESULT_BINOPS = new Set<BinOp>(['<', '<=', '>', '>=', '==', '!=', '&&', '||']);
|
|
189
|
+
|
|
190
|
+
/** The USUAL ARITHMETIC CONVERSIONS over two rendered operands.
|
|
109
191
|
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* type first, so `unsigned int
|
|
113
|
-
*
|
|
114
|
-
*
|
|
192
|
+
* AT EQUAL RANK, unsigned wins, and either side unknown leaves the result unknown — EXCEPT when
|
|
193
|
+
* the known side is unsigned, which already decides it. AT UNEQUAL RANK C converts to the wider
|
|
194
|
+
* type first, so the WIDER side's signedness decides outright: `unsigned int / long long` is
|
|
195
|
+
* SIGNED, and taking the equal-rank shortcut there would spell an unsigned divide over a signed
|
|
196
|
+
* one. `exprIntWidth` is what tells the two cases apart, and it is TOTAL, so the rank is always
|
|
197
|
+
* known and only the signedness can be undetermined.
|
|
115
198
|
*
|
|
116
199
|
* Exported because two consumers ask it about an operator whose own rendering they are deciding,
|
|
117
200
|
* so they cannot ask `renderedIntSignedness` about the node: the C-family backend's operand pin,
|
|
@@ -119,6 +202,11 @@ export function derefStrideOk(rt: IrType | undefined, width: number, signed: boo
|
|
|
119
202
|
export function arithConversionSignedness(l: Expr, r: Expr, varType: VarTypes): boolean | undefined {
|
|
120
203
|
const ls = renderedIntSignedness(l, varType);
|
|
121
204
|
const rs = renderedIntSignedness(r, varType);
|
|
205
|
+
const lw = exprIntWidth(l, varType);
|
|
206
|
+
const rw = exprIntWidth(r, varType);
|
|
207
|
+
if (lw !== rw) {
|
|
208
|
+
return lw > rw ? ls : rs;
|
|
209
|
+
}
|
|
122
210
|
if (ls === false || rs === false) {
|
|
123
211
|
return false;
|
|
124
212
|
}
|
|
@@ -158,10 +246,16 @@ export function arithConversionSignedness(l: Expr, r: Expr, varType: VarTypes):
|
|
|
158
246
|
export function renderedIntSignedness(e: Expr, varType: VarTypes): boolean | undefined {
|
|
159
247
|
const rec = (x: Expr): boolean | undefined => renderedIntSignedness(x, varType);
|
|
160
248
|
// an lvalue-ish leaf: its C type is a declaration / an explicit cast / a carried access width
|
|
249
|
+
// Anything narrower than `int` promotes and comes out SIGNED whatever it was declared. `int` and
|
|
250
|
+
// `long long` keep their own signedness, because no promotion applies to either. A width that is
|
|
251
|
+
// NEITHER is not a C type at all — an `index` node carries a struct STRIDE as its width — and
|
|
252
|
+
// stays undetermined, which is what every consumer already takes a cast on.
|
|
161
253
|
const promoted = (t: IrType | undefined): boolean | undefined =>
|
|
162
|
-
t?.kind !== 'int' ? undefined : t.width < 32 ? true : t.width === 32 ? t.signed : undefined;
|
|
254
|
+
t?.kind !== 'int' ? undefined : t.width < 32 ? true : t.width === 32 || t.width === 64 ? t.signed : undefined;
|
|
163
255
|
switch (e.k) {
|
|
164
256
|
case 'var':
|
|
257
|
+
// `v++` renders the value the local held, so it promotes exactly as the bare name does.
|
|
258
|
+
case 'postincr':
|
|
165
259
|
case 'cast':
|
|
166
260
|
case 'index':
|
|
167
261
|
case 'field':
|
|
@@ -174,10 +268,16 @@ export function renderedIntSignedness(e: Expr, varType: VarTypes): boolean | und
|
|
|
174
268
|
// folded to a shift — where `a / (s32)-2147483648` calls `__divsi3`.
|
|
175
269
|
case 'const':
|
|
176
270
|
return e.value > -2147483648 && e.value <= 2147483647;
|
|
177
|
-
|
|
271
|
+
case 'fconst':
|
|
272
|
+
return undefined;
|
|
273
|
+
// `-x` / `~x` carry the PROMOTED type of the operand; `!x` is `int`. A float has no integer
|
|
274
|
+
// signedness at all.
|
|
178
275
|
case 'un':
|
|
179
|
-
return e.op === '!' ? true : rec(e.e);
|
|
276
|
+
return e.op === 'f-' ? undefined : e.op === '!' ? true : rec(e.e);
|
|
180
277
|
case 'bin': {
|
|
278
|
+
if (FLOAT_BINOPS.has(e.op)) {
|
|
279
|
+
return undefined;
|
|
280
|
+
}
|
|
181
281
|
// The SIGNEDNESS-CARRYING pairs — the ops the tower keeps apart because C spells each pair
|
|
182
282
|
// with one token and picks between them from the operand types (l3/ast.ts BinOp). The
|
|
183
283
|
// C-family backend PINS their operands, so what gets printed renders as the op says
|
|
@@ -194,8 +294,8 @@ export function renderedIntSignedness(e: Expr, varType: VarTypes): boolean | und
|
|
|
194
294
|
if (e.op === '<<') {
|
|
195
295
|
return rec(e.l);
|
|
196
296
|
}
|
|
197
|
-
// Comparisons and the logical connectives yield `int
|
|
198
|
-
if (
|
|
297
|
+
// Comparisons and the logical connectives yield `int`, which is signed.
|
|
298
|
+
if (INT_RESULT_BINOPS.has(e.op)) {
|
|
199
299
|
return true;
|
|
200
300
|
}
|
|
201
301
|
return arithConversionSignedness(e.l, e.r, varType);
|
|
@@ -257,17 +357,25 @@ export function exprCType(e: Expr, varType: (name: string) => IrType | undefined
|
|
|
257
357
|
const rec = (x: Expr): IrType | undefined => exprCType(x, varType);
|
|
258
358
|
switch (e.k) {
|
|
259
359
|
case 'var':
|
|
360
|
+
case 'postincr':
|
|
260
361
|
return varType(e.name);
|
|
261
362
|
// An integer literal spells as a plain C `int` — NEVER a pointer, whatever the value's
|
|
262
363
|
// recovered type was. This is the exact gap the emission guard exists to bridge.
|
|
263
364
|
case 'const':
|
|
264
365
|
return T.s(32);
|
|
366
|
+
case 'fconst':
|
|
367
|
+
return T.f64();
|
|
265
368
|
case 'cast':
|
|
266
369
|
return e.to;
|
|
267
|
-
// `-`/`~` yield the promoted integer; `!` yields int. None yields a pointer.
|
|
370
|
+
// `-`/`~` yield the promoted integer; `!` yields int. None yields a pointer. A float negation
|
|
371
|
+
// yields the float it negates.
|
|
268
372
|
case 'un':
|
|
269
|
-
return T.s(32);
|
|
373
|
+
return e.op === 'f-' ? rec(e.e) : T.s(32);
|
|
270
374
|
case 'bin': {
|
|
375
|
+
// Both operands are one float type (ir/verify.ts holds the IR op to that), so the left names it.
|
|
376
|
+
if (FLOAT_BINOPS.has(e.op)) {
|
|
377
|
+
return rec(e.l);
|
|
378
|
+
}
|
|
271
379
|
if (e.op === '+' || e.op === '-') {
|
|
272
380
|
const l = rec(e.l);
|
|
273
381
|
const r = rec(e.r);
|
|
@@ -291,7 +399,14 @@ export function exprCType(e: Expr, varType: (name: string) => IrType | undefined
|
|
|
291
399
|
return T.s(32);
|
|
292
400
|
}
|
|
293
401
|
// A callee's C return type comes from a prototype OUTSIDE the emitted function (the
|
|
294
|
-
// project ctx / C89 implicit int) — not
|
|
402
|
+
// project ctx / C89 implicit int) — not derivable from the tree here.
|
|
403
|
+
//
|
|
404
|
+
// THE NODE MAY STILL CARRY ITS RANK, and that is a different question from its TYPE:
|
|
405
|
+
// `exprIntWidth` reads the `wide64` stamp and answers 64 for a call whose callee the frontend
|
|
406
|
+
// was told returns a pair. Nothing is read off it here, because the stamp is a width and this
|
|
407
|
+
// owes its callers a full `IrType` — signedness included, which no source states. Widening
|
|
408
|
+
// this to answer `s64` from the stamp would be a signedness claim nobody made, over the one
|
|
409
|
+
// node every rendered function has; the rank-only reader is the honest half.
|
|
295
410
|
case 'call':
|
|
296
411
|
return undefined;
|
|
297
412
|
case 'index': {
|
|
@@ -319,13 +434,10 @@ export function exprCType(e: Expr, varType: (name: string) => IrType | undefined
|
|
|
319
434
|
return scalarTypeForAccess(e.width, e.signed);
|
|
320
435
|
}
|
|
321
436
|
case 'field': {
|
|
322
|
-
// `base->name` (base: ptr-to-struct) or `base.name` (base: struct value, an array element
|
|
437
|
+
// `base->name` (base: ptr-to-struct) or `base.name` (base: struct value, an array element,
|
|
438
|
+
// or a union member's view).
|
|
323
439
|
const bt = rec(e.base);
|
|
324
|
-
|
|
325
|
-
if (st?.kind !== 'struct') {
|
|
326
|
-
return undefined;
|
|
327
|
-
}
|
|
328
|
-
return st.fields.find((f) => f.name === e.name)?.type;
|
|
440
|
+
return memberOf(bt?.kind === 'ptr' ? bt.to : bt, e.name)?.type;
|
|
329
441
|
}
|
|
330
442
|
case 'marker':
|
|
331
443
|
return undefined;
|
package/src/l3/unreduce.ts
CHANGED
|
@@ -440,10 +440,10 @@ function arithScale(t: IrType | undefined): number | null {
|
|
|
440
440
|
* refusal.
|
|
441
441
|
*
|
|
442
442
|
* MEASURED, so this is a stated zero and not an assumption: instrumented at the deletion site, it
|
|
443
|
-
* fires on none of the
|
|
444
|
-
* `uninit_spill` — so the clause costs no candidate today. (
|
|
445
|
-
*
|
|
446
|
-
*
|
|
443
|
+
* fires on none of the agbcc rows that spill AND lift — `spillorder`, `dma_fill_uninit`,
|
|
444
|
+
* `uninit_spill`, `spill10`, `spillarg` — so the clause costs no candidate today. (The last two
|
|
445
|
+
* bring no local to these gates at all, so their zero says nothing.) It is here for the day one
|
|
446
|
+
* does. */
|
|
447
447
|
function declarationPins(l: SFn['locals'][number]): boolean {
|
|
448
448
|
return (
|
|
449
449
|
l.volatile !== undefined ||
|
package/src/l3/volstore.ts
CHANGED
|
@@ -92,9 +92,10 @@
|
|
|
92
92
|
// both configurations and with any window. No row demands the read spelling, and a variation with no
|
|
93
93
|
// inhabitant is what "earn the level" forbids.
|
|
94
94
|
import { type IrType, T, scalarTypeForAccess } from '../ir/types';
|
|
95
|
-
import { cellAddress, inRange } from './address';
|
|
96
|
-
import { type Expr, type SFn, type Stmt, stmtChildren } from './ast';
|
|
95
|
+
import { cellAddress, inRange, qualifiedBase } from './address';
|
|
96
|
+
import { type Expr, type SFn, type Stmt, dotBase, stmtChildren } from './ast';
|
|
97
97
|
import { type Gate, firstRejection } from './gates';
|
|
98
|
+
import { derefStrideOk } from './typing';
|
|
98
99
|
|
|
99
100
|
/** One STORE lvalue as the gates read it. */
|
|
100
101
|
interface AccessCtx {
|
|
@@ -141,9 +142,6 @@ export const VOL_STORE_GATES: readonly Gate<AccessCtx>[] = [
|
|
|
141
142
|
},
|
|
142
143
|
];
|
|
143
144
|
|
|
144
|
-
/** Does this base already assert volatility — a `volatile` cast at any depth of the cast chain? */
|
|
145
|
-
const qualifiedBase = (e: Expr): boolean => e.k === 'cast' && (e.volatile === true || qualifiedBase(e.e));
|
|
146
|
-
|
|
147
145
|
/** The pointee the deref cast carries: the access's own scalar type. */
|
|
148
146
|
const pointee = (ix: Extract<Expr, { k: 'index' }>): IrType => scalarTypeForAccess(ix.width, ix.signed);
|
|
149
147
|
|
|
@@ -169,8 +167,17 @@ function qualify(lval: Expr, window: readonly [number, number] | undefined): Exp
|
|
|
169
167
|
if (firstRejection(VOL_STORE_GATES, ctx) !== null) {
|
|
170
168
|
return lval;
|
|
171
169
|
}
|
|
172
|
-
|
|
173
|
-
|
|
170
|
+
return qualifiedAccess(lval);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** An indexed access with `volatile` on the pointee of its deref cast. An existing scalar pointer
|
|
174
|
+
* cast takes the qualifier in place; a bare base gets one minted, exactly the node
|
|
175
|
+
* backend/cfamily.ts's own deref legalization would have synthesized. Also how
|
|
176
|
+
* {@link qualifiedMemoryAccess} spells an indexed access the lift marked `volatile`. */
|
|
177
|
+
export function qualifiedAccess(lval: Extract<Expr, { k: 'index' }>): Extract<Expr, { k: 'index' }> {
|
|
178
|
+
if (qualifiedBase(lval.base)) {
|
|
179
|
+
return lval;
|
|
180
|
+
}
|
|
174
181
|
const base: Expr =
|
|
175
182
|
lval.base.k === 'cast' && lval.base.to.kind === 'ptr'
|
|
176
183
|
? { ...lval.base, volatile: true }
|
|
@@ -178,6 +185,88 @@ function qualify(lval: Expr, window: readonly [number, number] | undefined): Exp
|
|
|
178
185
|
return { ...lval, base };
|
|
179
186
|
}
|
|
180
187
|
|
|
188
|
+
/** A memory access the lift marked `volatile` (frontend/device-pins.ts), spelled through a
|
|
189
|
+
* `volatile` pointee: an indexed access by {@link qualifiedAccess}; a recovered struct member
|
|
190
|
+
* through the struct pointer it is reached by — `((volatile struct S *)0x40000B0)[ch].field_0`,
|
|
191
|
+
* `((volatile struct S *)p)->field_0` — whether that pointer is already a cast or a variable
|
|
192
|
+
* `typeOf` types as one; and a union view through the member it views. A named global — `gSym`,
|
|
193
|
+
* `gSym.field` — is returned unchanged, since its declaration owns its qualifiers. Null when the
|
|
194
|
+
* pointer the access is reached by has no type to cast it to, for the caller to refuse rather
|
|
195
|
+
* than drop the qualifier. */
|
|
196
|
+
export function qualifiedMemoryAccess(e: Expr, typeOf: (x: Expr) => IrType | undefined): Expr | null {
|
|
197
|
+
if (e.k === 'index') {
|
|
198
|
+
return qualifiedAccess(e);
|
|
199
|
+
}
|
|
200
|
+
if (e.k !== 'field') {
|
|
201
|
+
return e;
|
|
202
|
+
}
|
|
203
|
+
const element = dotBase(e);
|
|
204
|
+
if (element !== undefined) {
|
|
205
|
+
const base = qualifiedPointer(element.base, typeOf);
|
|
206
|
+
return base === null ? null : { ...e, base: { ...element, base } };
|
|
207
|
+
}
|
|
208
|
+
if (e.dot === true) {
|
|
209
|
+
if (e.base.k !== 'field') {
|
|
210
|
+
return e;
|
|
211
|
+
}
|
|
212
|
+
const member = qualifiedMemoryAccess(e.base, typeOf);
|
|
213
|
+
return member === null ? null : { ...e, base: member };
|
|
214
|
+
}
|
|
215
|
+
const base = qualifiedPointer(e.base, typeOf);
|
|
216
|
+
return base === null ? null : { ...e, base };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Does this access reach the object it names through a pointer cast — one the tree spells,
|
|
220
|
+
* `((struct T *)&gSym)->f`, or one the C-family printer puts on a base that does not stride the
|
|
221
|
+
* access as spelled (backend/cfamily.ts `legalizedIndexBase`), `((u16 *)&gSym)[3]` — rather than
|
|
222
|
+
* through the object's own name, element or member? The pointee of such a cast carries none of the
|
|
223
|
+
* object's declared qualifiers unless it says so itself. `typeOf` is the rendered C type the
|
|
224
|
+
* printer judges a base by. */
|
|
225
|
+
export function castOffDeclaration(e: Expr, typeOf: (x: Expr) => IrType | undefined): boolean {
|
|
226
|
+
if (e.k === 'field') {
|
|
227
|
+
// A dot member over an element prints that element's base as spelled.
|
|
228
|
+
const element = dotBase(e);
|
|
229
|
+
if (element !== undefined) {
|
|
230
|
+
return addressCast(element.base, typeOf);
|
|
231
|
+
}
|
|
232
|
+
return e.dot === true ? castOffDeclaration(e.base, typeOf) : addressCast(e.base, typeOf);
|
|
233
|
+
}
|
|
234
|
+
if (e.k !== 'index') {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
if (addressCast(e.base, typeOf)) {
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
if (e.lead !== undefined && e.lead.length > 0) {
|
|
241
|
+
return false; // a multidimensional global's subscript prints its base as spelled
|
|
242
|
+
}
|
|
243
|
+
return !derefStrideOk(
|
|
244
|
+
typeOf(e.base) ?? (e.baseElem !== undefined ? T.ptr(e.baseElem) : undefined),
|
|
245
|
+
e.width,
|
|
246
|
+
e.signed,
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** Is there a cast between this address and the object it is taken from: one on it, under its
|
|
251
|
+
* arithmetic, or on the access whose element or member it is? */
|
|
252
|
+
const addressCast = (p: Expr, typeOf: (x: Expr) => IrType | undefined): boolean =>
|
|
253
|
+
p.k === 'cast' ||
|
|
254
|
+
(p.k === 'bin' && (addressCast(p.l, typeOf) || addressCast(p.r, typeOf))) ||
|
|
255
|
+
((p.k === 'index' || p.k === 'field') && castOffDeclaration(p, typeOf));
|
|
256
|
+
|
|
257
|
+
/** A pointer expression carrying `volatile` on its pointee: a pointer cast takes it in place, and
|
|
258
|
+
* any other pointer is cast to its own type with it. */
|
|
259
|
+
function qualifiedPointer(p: Expr, typeOf: (x: Expr) => IrType | undefined): Expr | null {
|
|
260
|
+
if (qualifiedBase(p)) {
|
|
261
|
+
return p;
|
|
262
|
+
}
|
|
263
|
+
if (p.k === 'cast' && p.to.kind === 'ptr') {
|
|
264
|
+
return { ...p, volatile: true };
|
|
265
|
+
}
|
|
266
|
+
const t = typeOf(p);
|
|
267
|
+
return t?.kind === 'ptr' ? { k: 'cast', to: t, volatile: true, e: p } : null;
|
|
268
|
+
}
|
|
269
|
+
|
|
181
270
|
/** How many stores this tree would qualify — the enumeration gate, so a function with no device
|
|
182
271
|
* store costs one walk and no candidate.
|
|
183
272
|
*
|
package/src/mangle.ts
CHANGED
|
@@ -178,7 +178,17 @@ export function demangledName(sym: string): string | null {
|
|
|
178
178
|
return null;
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
-
/** Spell a CppType as C++ source
|
|
181
|
+
/** Spell a CppType as C++ source where no declarator is involved — a return type, a cast
|
|
182
|
+
* (`Vec *`, `unsigned int`). */
|
|
182
183
|
export function spellType(t: CppType): string {
|
|
183
184
|
return t.base + (t.ptr ? ' ' + '*'.repeat(t.ptr) : '');
|
|
184
185
|
}
|
|
186
|
+
|
|
187
|
+
/** Declare a name of a CppType, C declarator rules: a pointer binds its `*` to the declarator
|
|
188
|
+
* (`Vec *p`), everything else is the prefix `spellType name`. The C-family backend's `cDeclare`
|
|
189
|
+
* says the same of an `IrType`; C++ has its own type vocabulary (`CppType`, from the mangled
|
|
190
|
+
* name) and so needs its own declarator, or one function spells its pointers two ways — the type
|
|
191
|
+
* prefix in the parameter list and the declarator form in the body. */
|
|
192
|
+
export function declareCpp(t: CppType, name: string): string {
|
|
193
|
+
return t.ptr > 0 ? `${t.base} ${'*'.repeat(t.ptr)}${name}` : `${t.base} ${name}`;
|
|
194
|
+
}
|
package/src/pattern/engine.ts
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
// Crucially, rewrites go through replaceAllUsesWith + DCE — never in-place opcode
|
|
7
7
|
// mutation of a live value.
|
|
8
8
|
import { Block, Fn, Op, Value, defOpMap, mkOp, mkValue, replaceAllUsesWith } from '../ir/core';
|
|
9
|
-
import {
|
|
9
|
+
import { deletableWhenDead, effectful, orderSensitive } from '../ir/discipline';
|
|
10
|
+
import { NEGATED_ICMP, type Opcode } from '../ir/opcodes';
|
|
10
11
|
import type { IrType } from '../ir/types';
|
|
11
12
|
import { T } from '../ir/types';
|
|
12
13
|
|
|
@@ -589,17 +590,18 @@ function validatePattern(pat: RewritePattern): void {
|
|
|
589
590
|
* just its def, and the question has to be asked over the cones: one pure `+ 1` between an effect
|
|
590
591
|
* and the fold's operand is the difference between `f() % g()` and `(f() + 1) % g()`, and the
|
|
591
592
|
* second one reorders exactly as the first does. Both cones become operands of ONE expression,
|
|
592
|
-
* where C leaves their order unspecified. asmlift's inline-at-use model (structure/analysis.ts
|
|
593
|
-
*
|
|
594
|
-
*
|
|
595
|
-
*
|
|
596
|
-
*
|
|
593
|
+
* where C leaves their order unspecified. asmlift's inline-at-use model (structure/analysis.ts,
|
|
594
|
+
* the barrier scan) keeps two operands of one statement inline only in the order every corpus
|
|
595
|
+
* compiler gives back — a call before the reads beside it — but it judges the program the fold
|
|
596
|
+
* leaves, after the idiom's own ops are gone. The fold INVENTS the expression, so it checks the
|
|
597
|
+
* cones it joins itself.
|
|
597
598
|
*
|
|
598
|
-
* A cone's members are weighed by
|
|
599
|
+
* A cone's members are weighed by `orderSensitive`, not `effectful`: a memory read answers
|
|
599
600
|
* whichever stores ran before it, so hoisting a `load` over a `call` — one asmlift may itself be
|
|
600
601
|
* passing the loaded pointer to — changes the answer as surely as swapping two calls. Two READS
|
|
601
|
-
* are the exception and commute, the same fact the structurer states as "a load never bars
|
|
602
|
-
* load", so a hazard needs an EFFECT on at least one side; `*p % *q` is admitted.
|
|
602
|
+
* are the exception and commute, the same fact the structurer states as "a plain load never bars
|
|
603
|
+
* a load", so a hazard needs an EFFECT on at least one side; `*p % *q` is admitted. Two `volatile`
|
|
604
|
+
* device reads do not commute, and the structurer names the first of them itself.
|
|
603
605
|
*
|
|
604
606
|
* Refuses only what it must — the alternative spelling (the idiom written out) names the operands
|
|
605
607
|
* and states the order, so a refusal is a loud, correct, slightly-worse-scoring answer. */
|
|
@@ -654,20 +656,18 @@ function reordersUnsequenced(
|
|
|
654
656
|
return cone;
|
|
655
657
|
};
|
|
656
658
|
const [lc, rc] = names.map(coneOf);
|
|
657
|
-
const sensitive = (c: Set<Op>) => [...c].filter(
|
|
659
|
+
const sensitive = (c: Set<Op>) => [...c].filter(orderSensitive);
|
|
658
660
|
for (const l of sensitive(lc)) {
|
|
659
661
|
for (const r of sensitive(rc)) {
|
|
660
662
|
// One op standing in BOTH cones is one evaluation and cannot be sequenced against itself; two
|
|
661
663
|
// READS commute; and a right-cone op the machine ALREADY runs first loses nothing.
|
|
662
|
-
if (l === r || at.get(l)! >= at.get(r)! || !(
|
|
664
|
+
if (l === r || at.get(l)! >= at.get(r)! || !(effectful(l) || effectful(r))) {
|
|
663
665
|
continue;
|
|
664
666
|
}
|
|
665
667
|
// The left one runs first, so an inlined `A op B` would swap them — UNLESS a sibling effect
|
|
666
668
|
// stands between, which the inline-at-use model refuses to cross, forcing a named temp at the
|
|
667
669
|
// def's own position. A cone member is no sibling: it is inlined into this very expression.
|
|
668
|
-
if (
|
|
669
|
-
!blk.ops.slice(at.get(l)! + 1, at.get(r)!).some((o) => EFFECTFUL_OPS.has(o.opcode) && !lc.has(o) && !rc.has(o))
|
|
670
|
-
) {
|
|
670
|
+
if (!blk.ops.slice(at.get(l)! + 1, at.get(r)!).some((o) => effectful(o) && !lc.has(o) && !rc.has(o))) {
|
|
671
671
|
return true;
|
|
672
672
|
}
|
|
673
673
|
}
|
|
@@ -794,7 +794,8 @@ export function applyPattern(fn: Fn, pat: RewritePattern, target: PatternTarget)
|
|
|
794
794
|
}
|
|
795
795
|
|
|
796
796
|
/** Remove effect-free ops whose single result is unused, to a fixed point. Deletability is
|
|
797
|
-
*
|
|
797
|
+
* ir/discipline.ts `deletableWhenDead`, which keeps a read the lift pinned `volatile`: the machine
|
|
798
|
+
* made it, so the recompile has to. */
|
|
798
799
|
export function dce(fn: Fn): void {
|
|
799
800
|
let changed = true;
|
|
800
801
|
while (changed) {
|
|
@@ -813,7 +814,9 @@ export function dce(fn: Fn): void {
|
|
|
813
814
|
}
|
|
814
815
|
}
|
|
815
816
|
for (const b of fn.blocks) {
|
|
816
|
-
const kept = b.ops.filter(
|
|
817
|
+
const kept = b.ops.filter(
|
|
818
|
+
(op) => !(deletableWhenDead(op) && op.results.length === 1 && !used.has(op.results[0])),
|
|
819
|
+
);
|
|
817
820
|
if (kept.length !== b.ops.length) {
|
|
818
821
|
b.ops = kept;
|
|
819
822
|
changed = true;
|