@asmlift/core 0.8.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -31
- package/package.json +1 -1
- package/src/aggregate.ts +397 -0
- package/src/backend/c.ts +6 -2
- package/src/backend/cfamily.ts +370 -56
- package/src/backend/cpp.ts +116 -10
- package/src/backend/pascal.ts +69 -11
- package/src/cdecl/constant.ts +59 -0
- package/src/cdecl/lex.ts +209 -0
- package/src/cdecl/parse.ts +975 -0
- package/src/cdecl/spell.ts +69 -0
- package/src/codegen-flags.ts +38 -6
- package/src/compiler-diagnostics.ts +194 -0
- package/src/contracts.ts +777 -84
- package/src/declare.ts +132 -13
- package/src/frontend/asmdata.ts +26 -6
- package/src/frontend/call-plan.ts +1590 -0
- package/src/frontend/dead-reads.ts +628 -0
- package/src/frontend/device-pins.ts +296 -0
- package/src/frontend/emit.ts +4 -3
- package/src/frontend/errors.ts +5 -0
- package/src/frontend/flags-edge.ts +101 -0
- package/src/frontend/fpu.ts +374 -0
- package/src/frontend/frame-objects.ts +3171 -0
- package/src/frontend/frontend.ts +64 -3
- package/src/frontend/high-half.ts +55 -5
- package/src/frontend/jump-table.ts +530 -0
- package/src/frontend/local-object.ts +616 -0
- package/src/frontend/mips.ts +2392 -208
- package/src/frontend/opaque.ts +97 -18
- package/src/frontend/own-struct-return.ts +500 -0
- package/src/frontend/ppc.ts +3137 -267
- package/src/frontend/register-struct-call.ts +484 -0
- package/src/frontend/reloc-symbol.ts +109 -13
- package/src/frontend/splat.ts +108 -5
- package/src/frontend/ssa.ts +2098 -89
- package/src/frontend/stackargs.ts +172 -42
- package/src/frontend/thumb.ts +4563 -2091
- package/src/ir/alias.ts +71 -0
- package/src/ir/bits.ts +681 -1
- package/src/ir/core.ts +199 -6
- package/src/ir/discipline.ts +313 -0
- package/src/ir/entry.ts +159 -0
- package/src/ir/float-bits.ts +47 -0
- package/src/{structure → ir}/loops.ts +6 -2
- package/src/ir/opcodes.ts +193 -121
- package/src/ir/parse.ts +1 -0
- package/src/ir/types.ts +202 -2
- package/src/ir/verify.ts +219 -3
- package/src/l3/address.ts +5 -0
- package/src/l3/argcopy.ts +38 -6
- package/src/l3/ast.ts +467 -23
- package/src/l3/basecse.ts +33 -5
- package/src/l3/coalesce.ts +21 -13
- package/src/l3/constinit.ts +382 -0
- package/src/l3/dce.ts +34 -12
- package/src/l3/gates.ts +9 -3
- package/src/l3/hoist.ts +8 -7
- package/src/l3/initfirst.ts +68 -19
- package/src/l3/intcell.ts +98 -0
- package/src/l3/mentions.ts +10 -4
- package/src/l3/narrowdecl.ts +259 -0
- package/src/l3/nearbase.ts +10 -5
- package/src/l3/parkfirst.ts +8 -5
- package/src/l3/pollguard.ts +11 -4
- package/src/l3/ptrcell.ts +354 -0
- package/src/l3/ptrfield.ts +123 -8
- package/src/l3/regspell.ts +26 -7
- package/src/l3/reindex.ts +293 -103
- package/src/l3/storage.ts +98 -1
- package/src/l3/symbol-refs.ts +182 -23
- package/src/l3/tailmerge.ts +9 -20
- package/src/l3/typing.ts +237 -32
- package/src/l3/unreduce.ts +10 -9
- package/src/l3/volatileptr.ts +17 -54
- package/src/l3/volstore.ts +96 -7
- package/src/mangle.ts +41 -1
- package/src/pattern/engine.ts +373 -82
- package/src/pipeline.ts +589 -27
- package/src/proto-context.ts +1648 -0
- package/src/proto.ts +1203 -49
- package/src/raise/aggmembers.ts +290 -0
- package/src/raise/arrays.ts +8 -3
- package/src/raise/declared-volatile.ts +307 -0
- package/src/raise/divpow2.ts +2 -2
- package/src/raise/errors.ts +17 -0
- package/src/raise/floathelpers.ts +464 -0
- package/src/raise/globalshape.ts +11 -5
- package/src/raise/gvn.ts +29 -8
- package/src/raise/latch.ts +162 -6
- package/src/raise/memberarrays.ts +10 -4
- package/src/raise/narrow.ts +29 -2
- package/src/raise/narrowlocal.ts +11 -6
- package/src/raise/offsetnames.ts +494 -0
- package/src/raise/pairparams.ts +114 -0
- package/src/raise/paramwidth.ts +203 -6
- package/src/raise/pre-recovery.ts +174 -17
- package/src/raise/pre-structure.ts +54 -0
- package/src/raise/recover.ts +26 -15
- package/src/raise/reroll.ts +694 -0
- package/src/raise/retsink.ts +1 -1
- package/src/raise/shortcircuit.ts +66 -30
- package/src/raise/softdiv.ts +21 -23
- package/src/raise/storesign.ts +153 -0
- package/src/raise/struct-arrays.ts +34 -14
- package/src/raise/structs.ts +674 -98
- package/src/raise/truncload.ts +367 -0
- package/src/raise/unthread.ts +400 -0
- package/src/raise/widehelpers.ts +106 -0
- package/src/rank-declare.ts +143 -12
- package/src/rank-variations.ts +117 -31
- package/src/rank.ts +563 -122
- package/src/runtime-helpers.ts +405 -0
- package/src/stillborn.ts +289 -0
- package/src/structure/analysis.ts +1449 -449
- package/src/structure/arith-ops.ts +23 -0
- package/src/structure/globalaccess.ts +111 -9
- package/src/structure/hazards.ts +641 -114
- package/src/structure/local-statics.ts +517 -0
- package/src/structure/namecoalesce.ts +2 -2
- package/src/structure/pointer-spelling.ts +1106 -0
- package/src/structure/redundant-test.ts +99 -0
- package/src/structure/structure.ts +3478 -975
- package/src/structure/switch-recover.ts +403 -178
- package/src/symbols.ts +44 -3
- package/src/target.ts +1387 -71
- package/src/trace.ts +66 -28
- package/src/variation-definitions.ts +243 -13
- package/src/variation-gates.ts +2 -0
- package/src/variation-tokens.ts +25 -7
package/src/l3/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, isCastView, memberOf, scalarTypeForAccess } from '../ir/types';
|
|
21
|
+
import { type BinOp, type Expr, type SFn, exprChildren, walkExprs } 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 ?? []), ...(fn.images ?? [])]) {
|
|
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,12 @@ 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 whose compiler refuses C's implicit pointer
|
|
62
|
+
* conversions: compiled as C++ (`SFn.dialect`), or by a C front end that refuses them too
|
|
63
|
+
* (`SFn.strictConversions`) (`SFn.declaredArgs`) */
|
|
64
|
+
readonly declaredArgs: (callee: string) => readonly (string | undefined)[] | undefined;
|
|
65
|
+
/** a function-scope static this function defines `const` (`SFn.statics`) */
|
|
66
|
+
readonly constStatic: (name: string) => boolean;
|
|
58
67
|
}
|
|
59
68
|
|
|
60
69
|
export function printEnv(fn: SFn): PrintEnv {
|
|
@@ -63,7 +72,18 @@ export function printEnv(fn: SFn): PrintEnv {
|
|
|
63
72
|
const vol = new Set(
|
|
64
73
|
fn.locals.filter((l) => l.type.kind === 'ptr' && (l.pointeeVolatile || l.volatile)).map((l) => l.name),
|
|
65
74
|
);
|
|
66
|
-
|
|
75
|
+
const constStatics = new Set((fn.statics ?? []).filter((s) => s.const).map((s) => s.name));
|
|
76
|
+
return {
|
|
77
|
+
type: declaredTypes(fn),
|
|
78
|
+
volatilePointee: (n) => vol.has(n),
|
|
79
|
+
declaredArgs: (callee) =>
|
|
80
|
+
(fn.dialect === 'c++' || fn.strictConversions === true) &&
|
|
81
|
+
fn.declaredArgs &&
|
|
82
|
+
Object.hasOwn(fn.declaredArgs, callee)
|
|
83
|
+
? fn.declaredArgs[callee]
|
|
84
|
+
: undefined,
|
|
85
|
+
constStatic: (n) => constStatics.has(n),
|
|
86
|
+
};
|
|
67
87
|
}
|
|
68
88
|
|
|
69
89
|
/** Does anything under `e` assert a VOLATILE POINTEE? The two spellings that carry the qualifier
|
|
@@ -103,15 +123,97 @@ export function derefStrideOk(rt: IrType | undefined, width: number, signed: boo
|
|
|
103
123
|
return false;
|
|
104
124
|
}
|
|
105
125
|
|
|
106
|
-
/** The
|
|
107
|
-
*
|
|
108
|
-
*
|
|
126
|
+
/** The INTEGER RANK a rendered expression carries, as a width in bits: 64 only where it can be
|
|
127
|
+
* PROVEN, 32 everywhere else. The complement of `renderedIntSignedness` — that one models the
|
|
128
|
+
* signedness `exprCType` reports uniformly as `s32`, this one models the width it reports the same
|
|
129
|
+
* way — and the two together are what the usual arithmetic conversions need.
|
|
130
|
+
*
|
|
131
|
+
* THE COMPLEMENT PARTS COMPANY WITH `exprCType` AT ONE NODE, AND IT IS `call`. There `exprCType`
|
|
132
|
+
* is UNINFORMED by design and answers `undefined`, while this reads the `wide64` stamp the
|
|
133
|
+
* structurer put on the node and can answer 64. A reader who reaches for `exprCType` on a call
|
|
134
|
+
* will be told the information does not exist; it does, and it is here.
|
|
135
|
+
*
|
|
136
|
+
* ITS SOUNDNESS IS AN ENUMERATION OF THE PRODUCERS, so here is the enumeration. A 64-bit value
|
|
137
|
+
* reaches a rendered expression in exactly four ways:
|
|
138
|
+
* 1. a `var`/`postincr` whose DECLARED type is 64 bits wide — read off `varType`;
|
|
139
|
+
* 2. a `cast` to a 64-bit type — read off the node;
|
|
140
|
+
* 3. a `call` the structurer STAMPED `wide64` — read off the node, and it is the one case where
|
|
141
|
+
* the rank cannot be derived from anything visible here: a callee's return type comes from a
|
|
142
|
+
* prototype outside the emitted function, which is why `exprCType` answers `undefined` for a
|
|
143
|
+
* call and why the stamp exists (l3/ast.ts carries what it means);
|
|
144
|
+
* 4. an arithmetic node over one of those — the recursion below.
|
|
145
|
+
* It does NOT arrive through MEMORY, because `contracts.ts` `SCALAR_WIDTHS` is {1,2,4} and there
|
|
146
|
+
* is no 64-bit global; nor through a `const`, because a 64-bit literal only folds inside C `int`
|
|
147
|
+
* range, where 32 is the right answer for the literal's own type.
|
|
109
148
|
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
149
|
+
* AN UNSTAMPED CALL IS 32 AND THAT IS THE SAFE ANSWER, not a claim: it is what C makes of a callee
|
|
150
|
+
* nobody declared, and every consumer of this rank takes a cast on a 32 it doubts and takes none on
|
|
151
|
+
* a 64 — so the direction that would be wrong silently is the one this never guesses.
|
|
152
|
+
*
|
|
153
|
+
* A SHIFT takes the rank of its left operand alone; every other ARITHMETIC node takes the wider of
|
|
154
|
+
* the two. That is C, and it is also why this is not simply `exprCType(e).width`.
|
|
155
|
+
*
|
|
156
|
+
* THE NODES THAT ARE NOT ARITHMETIC ONES are the same list `renderedIntSignedness` enumerates, and
|
|
157
|
+
* they yield `int` however wide their operands: a comparison, a logical connective, and `!`. `-`
|
|
158
|
+
* and `~` are not in it, because each carries the promoted type of its operand. */
|
|
159
|
+
export function exprIntWidth(e: Expr, varType: VarTypes): 32 | 64 {
|
|
160
|
+
const wide = (t: IrType | undefined): boolean => t?.kind === 'int' && t.width === 64;
|
|
161
|
+
switch (e.k) {
|
|
162
|
+
case 'var':
|
|
163
|
+
case 'postincr':
|
|
164
|
+
case 'cast':
|
|
165
|
+
case 'index':
|
|
166
|
+
case 'field':
|
|
167
|
+
return wide(exprCType(e, varType)) ? 64 : 32;
|
|
168
|
+
case 'call':
|
|
169
|
+
return e.wide64 === true ? 64 : 32;
|
|
170
|
+
case 'un':
|
|
171
|
+
return e.op === '!' ? 32 : exprIntWidth(e.e, varType);
|
|
172
|
+
case 'bin':
|
|
173
|
+
if (INT_RESULT_BINOPS.has(e.op)) {
|
|
174
|
+
return 32;
|
|
175
|
+
}
|
|
176
|
+
if (e.op === '<<' || e.op === '>>' || e.op === '>>>') {
|
|
177
|
+
return exprIntWidth(e.l, varType);
|
|
178
|
+
}
|
|
179
|
+
return exprIntWidth(e.l, varType) === 64 || exprIntWidth(e.r, varType) === 64 ? 64 : 32;
|
|
180
|
+
default:
|
|
181
|
+
return 32;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** The float operators (l3/ast.ts BinOp): their result is the float their operands are, never an
|
|
186
|
+
* integer, so none of the integer conversion rules below applies to one. */
|
|
187
|
+
export const FLOAT_BINOPS: ReadonlySet<BinOp> = new Set<BinOp>(['f+', 'f-', 'f*', 'f/']);
|
|
188
|
+
|
|
189
|
+
/** The binary operators whose RESULT is a C `int` whatever their operands are — read by both
|
|
190
|
+
* halves of the conversion model, which is the point of naming them once. A float compare is one:
|
|
191
|
+
* its operands are floats and its result is C's `int`. */
|
|
192
|
+
const INT_RESULT_BINOPS = new Set<BinOp>([
|
|
193
|
+
'<',
|
|
194
|
+
'<=',
|
|
195
|
+
'>',
|
|
196
|
+
'>=',
|
|
197
|
+
'==',
|
|
198
|
+
'!=',
|
|
199
|
+
'&&',
|
|
200
|
+
'||',
|
|
201
|
+
'f<',
|
|
202
|
+
'f<=',
|
|
203
|
+
'f>',
|
|
204
|
+
'f>=',
|
|
205
|
+
'f==',
|
|
206
|
+
'f!=',
|
|
207
|
+
]);
|
|
208
|
+
|
|
209
|
+
/** The USUAL ARITHMETIC CONVERSIONS over two rendered operands.
|
|
210
|
+
*
|
|
211
|
+
* AT EQUAL RANK, unsigned wins, and either side unknown leaves the result unknown — EXCEPT when
|
|
212
|
+
* the known side is unsigned, which already decides it. AT UNEQUAL RANK C converts to the wider
|
|
213
|
+
* type first, so the WIDER side's signedness decides outright: `unsigned int / long long` is
|
|
214
|
+
* SIGNED, and taking the equal-rank shortcut there would spell an unsigned divide over a signed
|
|
215
|
+
* one. `exprIntWidth` is what tells the two cases apart, and it is TOTAL, so the rank is always
|
|
216
|
+
* known and only the signedness can be undetermined.
|
|
115
217
|
*
|
|
116
218
|
* Exported because two consumers ask it about an operator whose own rendering they are deciding,
|
|
117
219
|
* so they cannot ask `renderedIntSignedness` about the node: the C-family backend's operand pin,
|
|
@@ -119,6 +221,11 @@ export function derefStrideOk(rt: IrType | undefined, width: number, signed: boo
|
|
|
119
221
|
export function arithConversionSignedness(l: Expr, r: Expr, varType: VarTypes): boolean | undefined {
|
|
120
222
|
const ls = renderedIntSignedness(l, varType);
|
|
121
223
|
const rs = renderedIntSignedness(r, varType);
|
|
224
|
+
const lw = exprIntWidth(l, varType);
|
|
225
|
+
const rw = exprIntWidth(r, varType);
|
|
226
|
+
if (lw !== rw) {
|
|
227
|
+
return lw > rw ? ls : rs;
|
|
228
|
+
}
|
|
122
229
|
if (ls === false || rs === false) {
|
|
123
230
|
return false;
|
|
124
231
|
}
|
|
@@ -133,7 +240,7 @@ export function arithConversionSignedness(l: Expr, r: Expr, varType: VarTypes):
|
|
|
133
240
|
*
|
|
134
241
|
* THE one rendered-signedness judgment, and it lives beside the declarations it judges against
|
|
135
242
|
* because every consumer shares it: the C-family backend's operand pin, structure.ts's
|
|
136
|
-
* unsigned-compare gate (
|
|
243
|
+
* unsigned-compare gate (unsignedCompareSpelling) and its signed-compare pin, and — through
|
|
137
244
|
* `arithConversionSignedness` above — initfirst's compare-meaning gate. Two of those models
|
|
138
245
|
* disagreeing about one expression is the drift this placement prevents.
|
|
139
246
|
*
|
|
@@ -149,8 +256,8 @@ export function arithConversionSignedness(l: Expr, r: Expr, varType: VarTypes):
|
|
|
149
256
|
* instruction-for-instruction the same where `f` really returns `int`); a missing one is a
|
|
150
257
|
* miscompile — with `u32 f(void);` in scope `f() / a` calls `__udivsi3` where the machine called
|
|
151
258
|
* `__divsi3`, and `if (f() >= 0)` compiles to `bl f; mov r0, #0`, comparison and both arms gone.
|
|
152
|
-
* The one consumer that reads `undefined` the other way is the
|
|
153
|
-
* asking whether an operand is ALREADY unsigned.
|
|
259
|
+
* The one consumer that reads `undefined` the other way is the unsigned-compare spelling's own
|
|
260
|
+
* gate, which is asking whether an operand is ALREADY unsigned.
|
|
154
261
|
*
|
|
155
262
|
* Anything narrower than 32 bits promotes to `int` and is therefore SIGNED, whatever it was
|
|
156
263
|
* declared. Pointers, calls and markers are `undefined`.
|
|
@@ -158,10 +265,16 @@ export function arithConversionSignedness(l: Expr, r: Expr, varType: VarTypes):
|
|
|
158
265
|
export function renderedIntSignedness(e: Expr, varType: VarTypes): boolean | undefined {
|
|
159
266
|
const rec = (x: Expr): boolean | undefined => renderedIntSignedness(x, varType);
|
|
160
267
|
// an lvalue-ish leaf: its C type is a declaration / an explicit cast / a carried access width
|
|
268
|
+
// Anything narrower than `int` promotes and comes out SIGNED whatever it was declared. `int` and
|
|
269
|
+
// `long long` keep their own signedness, because no promotion applies to either. A width that is
|
|
270
|
+
// NEITHER is not a C type at all — an `index` node carries a struct STRIDE as its width — and
|
|
271
|
+
// stays undetermined, which is what every consumer already takes a cast on.
|
|
161
272
|
const promoted = (t: IrType | undefined): boolean | undefined =>
|
|
162
|
-
t?.kind !== 'int' ? undefined : t.width < 32 ? true : t.width === 32 ? t.signed : undefined;
|
|
273
|
+
t?.kind !== 'int' ? undefined : t.width < 32 ? true : t.width === 32 || t.width === 64 ? t.signed : undefined;
|
|
163
274
|
switch (e.k) {
|
|
164
275
|
case 'var':
|
|
276
|
+
// `v++` renders the value the local held, so it promotes exactly as the bare name does.
|
|
277
|
+
case 'postincr':
|
|
165
278
|
case 'cast':
|
|
166
279
|
case 'index':
|
|
167
280
|
case 'field':
|
|
@@ -174,10 +287,16 @@ export function renderedIntSignedness(e: Expr, varType: VarTypes): boolean | und
|
|
|
174
287
|
// folded to a shift — where `a / (s32)-2147483648` calls `__divsi3`.
|
|
175
288
|
case 'const':
|
|
176
289
|
return e.value > -2147483648 && e.value <= 2147483647;
|
|
177
|
-
|
|
290
|
+
case 'fconst':
|
|
291
|
+
return undefined;
|
|
292
|
+
// `-x` / `~x` carry the PROMOTED type of the operand; `!x` is `int`. A float has no integer
|
|
293
|
+
// signedness at all.
|
|
178
294
|
case 'un':
|
|
179
|
-
return e.op === '!' ? true : rec(e.e);
|
|
295
|
+
return e.op === 'f-' ? undefined : e.op === '!' ? true : rec(e.e);
|
|
180
296
|
case 'bin': {
|
|
297
|
+
if (FLOAT_BINOPS.has(e.op)) {
|
|
298
|
+
return undefined;
|
|
299
|
+
}
|
|
181
300
|
// The SIGNEDNESS-CARRYING pairs — the ops the tower keeps apart because C spells each pair
|
|
182
301
|
// with one token and picks between them from the operand types (l3/ast.ts BinOp). The
|
|
183
302
|
// C-family backend PINS their operands, so what gets printed renders as the op says
|
|
@@ -194,8 +313,8 @@ export function renderedIntSignedness(e: Expr, varType: VarTypes): boolean | und
|
|
|
194
313
|
if (e.op === '<<') {
|
|
195
314
|
return rec(e.l);
|
|
196
315
|
}
|
|
197
|
-
// Comparisons and the logical connectives yield `int
|
|
198
|
-
if (
|
|
316
|
+
// Comparisons and the logical connectives yield `int`, which is signed.
|
|
317
|
+
if (INT_RESULT_BINOPS.has(e.op)) {
|
|
199
318
|
return true;
|
|
200
319
|
}
|
|
201
320
|
return arithConversionSignedness(e.l, e.r, varType);
|
|
@@ -208,16 +327,41 @@ export function renderedIntSignedness(e: Expr, varType: VarTypes): boolean | und
|
|
|
208
327
|
}
|
|
209
328
|
|
|
210
329
|
/** Is the rendered expression provably in [0, 2^31) — the range where a signed and an unsigned
|
|
211
|
-
* compare agree on every input
|
|
212
|
-
*
|
|
213
|
-
*
|
|
330
|
+
* compare agree on every input? Narrow unsigned values are the everyday case: a `(u8)x` cast or a
|
|
331
|
+
* `u8`/`u16` deref promotes to a non-negative `int`, and so is a mask, `x & 0xFFFF`, whose result
|
|
332
|
+
* has no bit its non-negative side lacks; `|` and `^` set a bit only where a side has it, so they
|
|
333
|
+
* are non-negative when both sides are. Conservative false elsewhere. A VALUE fact: it says the
|
|
334
|
+
* two compares mean the same, not that they compile the same (`spelledNonNegative`). */
|
|
214
335
|
export function provablyNonNegative(e: Expr, varType: VarTypes): boolean {
|
|
336
|
+
return nonNegative(e, varType, true);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/** The subset of `provablyNonNegative` over which the DEFAULT leaves an unsigned compare uncast
|
|
340
|
+
* (structure.ts): everything but an `|`/`^`. Those are non-negative too, so their uncast
|
|
341
|
+
* spelling owes no proof, but the pin stays in the default because it is codegen-visible where
|
|
342
|
+
* the compiler does not track the bits: mwcc_242_81 compiles
|
|
343
|
+
* `if ((unsigned)((a & 0xf0) | (b & 3)) < 0x31u)` beside a `!= 0` test to `cmplwi`, and the lift
|
|
344
|
+
* scores 9/15 cast against 13/16 uncast over `s32` parameters (10/15 for the `u32` twin). Without
|
|
345
|
+
* the pin in the default that spelling is in no candidate at all, since `/signed-cmp` is the
|
|
346
|
+
* uncast one. A mask alone (gcc's own pick of the unsigned branch, kmc `selconst`) keeps the
|
|
347
|
+
* uncast default. */
|
|
348
|
+
export function spelledNonNegative(e: Expr, varType: VarTypes): boolean {
|
|
349
|
+
return nonNegative(e, varType, false);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function nonNegative(e: Expr, varType: VarTypes, orXor: boolean): boolean {
|
|
215
353
|
switch (e.k) {
|
|
216
354
|
case 'const':
|
|
217
355
|
return e.value >= 0 && e.value < 0x80000000;
|
|
218
356
|
case 'un':
|
|
219
357
|
return e.op === '!';
|
|
220
358
|
case 'bin':
|
|
359
|
+
if (e.op === '&') {
|
|
360
|
+
return nonNegative(e.l, varType, orXor) || nonNegative(e.r, varType, orXor);
|
|
361
|
+
}
|
|
362
|
+
if (orXor && (e.op === '|' || e.op === '^')) {
|
|
363
|
+
return nonNegative(e.l, varType, orXor) && nonNegative(e.r, varType, orXor);
|
|
364
|
+
}
|
|
221
365
|
return ['<', '<=', '>', '>=', '==', '!=', '&&', '||'].includes(e.op);
|
|
222
366
|
case 'cast':
|
|
223
367
|
return e.to.kind === 'int' && e.to.width < 32 && !e.to.signed;
|
|
@@ -228,6 +372,47 @@ export function provablyNonNegative(e: Expr, varType: VarTypes): boolean {
|
|
|
228
372
|
}
|
|
229
373
|
}
|
|
230
374
|
|
|
375
|
+
/** Would C evaluate `l OP r` SIGNED, for an ordering compare the machine did unsigned? Both sides
|
|
376
|
+
* render as signed-promoting C (or as a type this env cannot see, which may be), neither is a
|
|
377
|
+
* pointer (C compares an address unsigned), and they are not both `nonNeg`. With the default,
|
|
378
|
+
* `provablyNonNegative`, a true answer means the C compares DIFFERENT values from the machine:
|
|
379
|
+
* the proof obligation, which `signedOverUnsignedCompare` asks of the finished tree.
|
|
380
|
+
* structure.ts also asks it with `spelledNonNegative` to place the `(u32)` pin. */
|
|
381
|
+
export function rendersSignedOverUnsigned(
|
|
382
|
+
l: Expr,
|
|
383
|
+
r: Expr,
|
|
384
|
+
varType: VarTypes,
|
|
385
|
+
nonNeg: (e: Expr, varType: VarTypes) => boolean = provablyNonNegative,
|
|
386
|
+
): boolean {
|
|
387
|
+
const ptrSide = (x: Expr): boolean => {
|
|
388
|
+
const t = exprCType(x, varType);
|
|
389
|
+
return t?.kind === 'ptr' || t?.kind === 'array';
|
|
390
|
+
};
|
|
391
|
+
return (
|
|
392
|
+
!ptrSide(l) &&
|
|
393
|
+
!ptrSide(r) &&
|
|
394
|
+
renderedIntSignedness(l, varType) !== false &&
|
|
395
|
+
renderedIntSignedness(r, varType) !== false &&
|
|
396
|
+
!(nonNeg(l, varType) && nonNeg(r, varType))
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/** Does the finished tree spell some unsigned compare (`Expr.unsignedCmp`) so that C evaluates it
|
|
401
|
+
* signed? Judged over the tree's own declarations, AFTER every pass that rewrites it: emission
|
|
402
|
+
* pins each compare it lowers, but a respell that substitutes an arm's value into an emitted
|
|
403
|
+
* compare (l3/unmerge.ts puts an `s16` global where a `u32` local stood) re-renders it without
|
|
404
|
+
* asking. A name the declarations do not hold — a global the symbol declarations type outside
|
|
405
|
+
* the tree — is not provably unsigned, so it counts: this errs toward withholding. */
|
|
406
|
+
export function signedOverUnsignedCompare(sfn: SFn): boolean {
|
|
407
|
+
const env = declaredTypes(sfn);
|
|
408
|
+
for (const e of walkExprs(sfn.body)) {
|
|
409
|
+
if (e.k === 'bin' && e.unsignedCmp === true && rendersSignedOverUnsigned(e.l, e.r, env)) {
|
|
410
|
+
return true;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
|
|
231
416
|
/** Does this write put a value that renders as a DEFINITELY-NON-POINTER C expression into a
|
|
232
417
|
* pointer-declared slot? — `v2 = a1 + v0` with `v2: u8 *`, a `return a0 + v0` from a
|
|
233
418
|
* pointer-returning function, a store through a pointer-element lvalue. The machine's register
|
|
@@ -257,17 +442,25 @@ export function exprCType(e: Expr, varType: (name: string) => IrType | undefined
|
|
|
257
442
|
const rec = (x: Expr): IrType | undefined => exprCType(x, varType);
|
|
258
443
|
switch (e.k) {
|
|
259
444
|
case 'var':
|
|
445
|
+
case 'postincr':
|
|
260
446
|
return varType(e.name);
|
|
261
447
|
// An integer literal spells as a plain C `int` — NEVER a pointer, whatever the value's
|
|
262
448
|
// recovered type was. This is the exact gap the emission guard exists to bridge.
|
|
263
449
|
case 'const':
|
|
264
450
|
return T.s(32);
|
|
451
|
+
case 'fconst':
|
|
452
|
+
return T.f64();
|
|
265
453
|
case 'cast':
|
|
266
454
|
return e.to;
|
|
267
|
-
// `-`/`~` yield the promoted integer; `!` yields int. None yields a pointer.
|
|
455
|
+
// `-`/`~` yield the promoted integer; `!` yields int. None yields a pointer. A float negation
|
|
456
|
+
// yields the float it negates.
|
|
268
457
|
case 'un':
|
|
269
|
-
return T.s(32);
|
|
458
|
+
return e.op === 'f-' ? rec(e.e) : T.s(32);
|
|
270
459
|
case 'bin': {
|
|
460
|
+
// Both operands are one float type (ir/verify.ts holds the IR op to that), so the left names it.
|
|
461
|
+
if (FLOAT_BINOPS.has(e.op)) {
|
|
462
|
+
return rec(e.l);
|
|
463
|
+
}
|
|
271
464
|
if (e.op === '+' || e.op === '-') {
|
|
272
465
|
const l = rec(e.l);
|
|
273
466
|
const r = rec(e.r);
|
|
@@ -291,7 +484,14 @@ export function exprCType(e: Expr, varType: (name: string) => IrType | undefined
|
|
|
291
484
|
return T.s(32);
|
|
292
485
|
}
|
|
293
486
|
// A callee's C return type comes from a prototype OUTSIDE the emitted function (the
|
|
294
|
-
// project ctx / C89 implicit int) — not
|
|
487
|
+
// project ctx / C89 implicit int) — not derivable from the tree here.
|
|
488
|
+
//
|
|
489
|
+
// THE NODE MAY STILL CARRY ITS RANK, and that is a different question from its TYPE:
|
|
490
|
+
// `exprIntWidth` reads the `wide64` stamp and answers 64 for a call whose callee the frontend
|
|
491
|
+
// was told returns a pair. Nothing is read off it here, because the stamp is a width and this
|
|
492
|
+
// owes its callers a full `IrType` — signedness included, which no source states. Widening
|
|
493
|
+
// this to answer `s64` from the stamp would be a signedness claim nobody made, over the one
|
|
494
|
+
// node every rendered function has; the rank-only reader is the honest half.
|
|
295
495
|
case 'call':
|
|
296
496
|
return undefined;
|
|
297
497
|
case 'index': {
|
|
@@ -306,8 +506,16 @@ export function exprCType(e: Expr, varType: (name: string) => IrType | undefined
|
|
|
306
506
|
// would type it `s96`-style garbage and make the field contract reject valid trees. The
|
|
307
507
|
// node width must AGREE with the element size (when the struct declares one): a mismatch
|
|
308
508
|
// means the stride channel is corrupt, so it types scalar and the field contract flags it.
|
|
509
|
+
// A recovered struct declares no size, so the one other index on its pointer — a CAST VIEW of
|
|
510
|
+
// a `castViews` field, `((u8 *)p)[1]` (structure.ts `memAccess`) — is told apart by the
|
|
511
|
+
// same rule that minted it, and reads its scalar.
|
|
309
512
|
const bt = rec(e.base);
|
|
310
|
-
if (
|
|
513
|
+
if (
|
|
514
|
+
bt?.kind === 'ptr' &&
|
|
515
|
+
bt.to.kind === 'struct' &&
|
|
516
|
+
(bt.to.size === undefined || bt.to.size === e.width) &&
|
|
517
|
+
!(e.idx.k === 'const' && isCastView(bt.to, e.idx.value * e.width, e.width, e.signed, false))
|
|
518
|
+
) {
|
|
311
519
|
return bt.to;
|
|
312
520
|
}
|
|
313
521
|
if (bt?.kind === 'ptr' && derefStrideOk(bt, e.width, e.signed)) {
|
|
@@ -319,13 +527,10 @@ export function exprCType(e: Expr, varType: (name: string) => IrType | undefined
|
|
|
319
527
|
return scalarTypeForAccess(e.width, e.signed);
|
|
320
528
|
}
|
|
321
529
|
case 'field': {
|
|
322
|
-
// `base->name` (base: ptr-to-struct) or `base.name` (base: struct value, an array element
|
|
530
|
+
// `base->name` (base: ptr-to-struct) or `base.name` (base: struct value, an array element,
|
|
531
|
+
// or a union member's view).
|
|
323
532
|
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;
|
|
533
|
+
return memberOf(bt?.kind === 'ptr' ? bt.to : bt, e.name)?.type;
|
|
329
534
|
}
|
|
330
535
|
case 'marker':
|
|
331
536
|
return undefined;
|
package/src/l3/unreduce.ts
CHANGED
|
@@ -137,11 +137,12 @@
|
|
|
137
137
|
// read off `acc = acc + K`, so it counts in the units of the ACCUMULATOR's declared type: on a
|
|
138
138
|
// `u16 *` a step of 32 advances 64 BYTES. The closed form spells that stride onto the INIT, whose
|
|
139
139
|
// `+` scales by whatever the INIT's own C type says. Where the two disagree the candidate
|
|
140
|
-
// addresses the wrong byte, compiles clean, and carries no marker —
|
|
141
|
-
// states the same rule from the other end
|
|
142
|
-
//
|
|
143
|
-
// both scales are KNOWN and equal; a narrow integer accumulator is
|
|
144
|
-
// direction, since `u16 acc` wraps at 65536 where
|
|
140
|
+
// addresses the wrong byte, compiles clean, and carries no marker — the POINTER STRIDE rule of
|
|
141
|
+
// `ARITH_ROWS` in structure/pointer-spelling.ts states the same rule from the other end: a
|
|
142
|
+
// runtime offset on a pointer whose pointee is not one byte walks `(u8 *)p`.
|
|
143
|
+
// `stride-units` refuses unless both scales are KNOWN and equal; a narrow integer accumulator is
|
|
144
|
+
// the same question in the other direction, since `u16 acc` wraps at 65536 where
|
|
145
|
+
// `init + (i << 6)` does not.
|
|
145
146
|
//
|
|
146
147
|
// THAT GATE HAS NO BENCHMARK REACH AT ALL, and neither tier can see it. Censused at the
|
|
147
148
|
// `firstRejection` call site over both — 750 synthetic trees and all 252 real-tier rows, the real
|
|
@@ -440,10 +441,10 @@ function arithScale(t: IrType | undefined): number | null {
|
|
|
440
441
|
* refusal.
|
|
441
442
|
*
|
|
442
443
|
* 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
|
-
*
|
|
444
|
+
* fires on none of the agbcc rows that spill AND lift — `spillorder`, `dma_fill_uninit`,
|
|
445
|
+
* `uninit_spill`, `spill10`, `spillarg` — so the clause costs no candidate today. (The last two
|
|
446
|
+
* bring no local to these gates at all, so their zero says nothing.) It is here for the day one
|
|
447
|
+
* does. */
|
|
447
448
|
function declarationPins(l: SFn['locals'][number]): boolean {
|
|
448
449
|
return (
|
|
449
450
|
l.volatile !== undefined ||
|
package/src/l3/volatileptr.ts
CHANGED
|
@@ -27,7 +27,15 @@
|
|
|
27
27
|
// mapped global through a volatile view the map never granted. No qualifying local ⇒ decline
|
|
28
28
|
// (null), so the variation never emits a duplicate of the default.
|
|
29
29
|
import { addrConst, cellAddress, inRange } from './address';
|
|
30
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
type Expr,
|
|
32
|
+
type SFn,
|
|
33
|
+
assignsIn,
|
|
34
|
+
mapExprChildren,
|
|
35
|
+
rematerializableAddress,
|
|
36
|
+
taintOverAssigns,
|
|
37
|
+
walkExprs,
|
|
38
|
+
} from './ast';
|
|
31
39
|
|
|
32
40
|
const exprHas = (e: Expr, pred: (x: Expr) => boolean): boolean => {
|
|
33
41
|
if (pred(e)) {
|
|
@@ -41,36 +49,6 @@ const exprHas = (e: Expr, pred: (x: Expr) => boolean): boolean => {
|
|
|
41
49
|
return hit;
|
|
42
50
|
};
|
|
43
51
|
|
|
44
|
-
function collectAssigns(stmts: Stmt[], out: { name: string; value: Expr }[]): void {
|
|
45
|
-
for (const s of stmts) {
|
|
46
|
-
switch (s.k) {
|
|
47
|
-
case 'assign':
|
|
48
|
-
out.push({ name: s.name, value: s.value });
|
|
49
|
-
break;
|
|
50
|
-
case 'if':
|
|
51
|
-
collectAssigns(s.then, out);
|
|
52
|
-
collectAssigns(s.else, out);
|
|
53
|
-
break;
|
|
54
|
-
case 'while':
|
|
55
|
-
case 'dowhile':
|
|
56
|
-
collectAssigns(s.body, out);
|
|
57
|
-
break;
|
|
58
|
-
case 'for':
|
|
59
|
-
collectAssigns([s.init, s.inc], out);
|
|
60
|
-
collectAssigns(s.body, out);
|
|
61
|
-
break;
|
|
62
|
-
case 'switch':
|
|
63
|
-
for (const c of s.cases) {
|
|
64
|
-
collectAssigns(c.body, out);
|
|
65
|
-
}
|
|
66
|
-
collectAssigns(s.default ?? [], out);
|
|
67
|
-
break;
|
|
68
|
-
default:
|
|
69
|
-
break;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
52
|
/** How many of this tree's `volatile` claims land on a DEVICE REGISTER — the gate on rank.ts's
|
|
75
53
|
* volatility tie-break, so it is counted here beside the variation that mints the claims.
|
|
76
54
|
*
|
|
@@ -100,8 +78,7 @@ export function deviceVolatileClaims(sfn: SFn, window?: readonly [number, number
|
|
|
100
78
|
underSubscript.add(e.base);
|
|
101
79
|
}
|
|
102
80
|
}
|
|
103
|
-
const assigns
|
|
104
|
-
collectAssigns(sfn.body, assigns);
|
|
81
|
+
const assigns = assignsIn(sfn.body);
|
|
105
82
|
for (const l of sfn.locals) {
|
|
106
83
|
if (l.type.kind === 'ptr' && (l.pointeeVolatile !== undefined || l.volatile !== undefined)) {
|
|
107
84
|
n += assigns.filter((a) => a.name === l.name && inWindow(a.value)).length;
|
|
@@ -143,27 +120,13 @@ export const volatileEligibleValue = (e: Expr): boolean => rematerializableAddre
|
|
|
143
120
|
|
|
144
121
|
/** the shared eligibility predicate (the GATE in the header) for one function's locals */
|
|
145
122
|
function eligibility(sfn: SFn): (l: SFn['locals'][number]) => boolean {
|
|
146
|
-
const assigns
|
|
147
|
-
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
if (feedsSymbolAddress(a.value)) {
|
|
155
|
-
tainted.add(a.name);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
for (let grew = true; grew;) {
|
|
159
|
-
grew = false;
|
|
160
|
-
for (const a of assigns) {
|
|
161
|
-
if (!tainted.has(a.name) && exprHas(a.value, (x) => x.k === 'var' && tainted.has(x.name))) {
|
|
162
|
-
tainted.add(a.name);
|
|
163
|
-
grew = true;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
123
|
+
const assigns = assignsIn(sfn.body);
|
|
124
|
+
const numericFed = new Set(assigns.filter((a) => rematerializableAddress(a.value)).map((a) => a.name));
|
|
125
|
+
const tainted = taintOverAssigns(
|
|
126
|
+
assigns,
|
|
127
|
+
assigns.filter((a) => feedsSymbolAddress(a.value)).map((a) => a.name),
|
|
128
|
+
(a, t) => exprHas(a.value, (x) => x.k === 'var' && t.has(x.name)),
|
|
129
|
+
);
|
|
167
130
|
return (l) =>
|
|
168
131
|
l.type.kind === 'ptr' &&
|
|
169
132
|
l.volatile === undefined &&
|