@asmlift/core 0.9.0 → 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 +25 -24
- package/package.json +1 -1
- package/src/aggregate.ts +149 -0
- package/src/backend/c.ts +1 -0
- package/src/backend/cfamily.ts +165 -50
- package/src/backend/cpp.ts +2 -1
- package/src/backend/pascal.ts +31 -12
- package/src/contracts.ts +173 -16
- package/src/declare.ts +69 -20
- package/src/frontend/asmdata.ts +7 -1
- package/src/frontend/call-plan.ts +964 -60
- package/src/frontend/dead-reads.ts +628 -0
- package/src/frontend/device-pins.ts +23 -4
- package/src/frontend/emit.ts +4 -3
- package/src/frontend/fpu.ts +224 -17
- package/src/frontend/frame-objects.ts +1232 -192
- package/src/frontend/frontend.ts +64 -3
- package/src/frontend/high-half.ts +53 -4
- package/src/frontend/jump-table.ts +530 -0
- package/src/frontend/local-object.ts +158 -32
- package/src/frontend/mips.ts +2076 -215
- package/src/frontend/own-struct-return.ts +500 -0
- package/src/frontend/ppc.ts +2711 -232
- package/src/frontend/register-struct-call.ts +484 -0
- package/src/frontend/reloc-symbol.ts +22 -7
- package/src/frontend/ssa.ts +1460 -44
- package/src/frontend/stackargs.ts +106 -57
- package/src/frontend/thumb.ts +2044 -318
- package/src/ir/alias.ts +42 -0
- package/src/ir/bits.ts +681 -1
- package/src/ir/core.ts +100 -5
- package/src/ir/discipline.ts +86 -21
- package/src/ir/entry.ts +159 -0
- package/src/{structure → ir}/loops.ts +6 -2
- package/src/ir/opcodes.ts +109 -9
- package/src/ir/types.ts +86 -2
- package/src/ir/verify.ts +80 -11
- package/src/l3/ast.ts +290 -18
- package/src/l3/basecse.ts +33 -5
- package/src/l3/constinit.ts +382 -0
- package/src/l3/dce.ts +2 -2
- package/src/l3/initfirst.ts +42 -11
- package/src/l3/intcell.ts +2 -1
- package/src/l3/narrowdecl.ts +77 -36
- package/src/l3/pollguard.ts +8 -2
- package/src/l3/ptrcell.ts +31 -34
- package/src/l3/ptrfield.ts +123 -8
- package/src/l3/regspell.ts +9 -4
- package/src/l3/reindex.ts +283 -96
- package/src/l3/storage.ts +98 -1
- package/src/l3/symbol-refs.ts +84 -6
- package/src/l3/tailmerge.ts +4 -2
- package/src/l3/typing.ts +108 -15
- package/src/l3/unreduce.ts +6 -5
- package/src/l3/volatileptr.ts +17 -54
- package/src/mangle.ts +30 -0
- package/src/pattern/engine.ts +361 -73
- package/src/pipeline.ts +541 -24
- package/src/proto-context.ts +905 -45
- package/src/proto.ts +555 -40
- package/src/raise/aggmembers.ts +290 -0
- package/src/raise/arrays.ts +5 -1
- package/src/raise/declared-volatile.ts +207 -31
- package/src/raise/floathelpers.ts +254 -33
- package/src/raise/gvn.ts +3 -3
- package/src/raise/latch.ts +162 -6
- package/src/raise/memberarrays.ts +10 -4
- package/src/raise/offsetnames.ts +10 -26
- package/src/raise/pairparams.ts +33 -0
- package/src/raise/paramwidth.ts +196 -6
- package/src/raise/pre-recovery.ts +102 -19
- package/src/raise/pre-structure.ts +54 -0
- package/src/raise/recover.ts +12 -5
- package/src/raise/reroll.ts +694 -0
- package/src/raise/shortcircuit.ts +26 -12
- package/src/raise/softdiv.ts +1 -1
- package/src/raise/storesign.ts +153 -0
- package/src/raise/struct-arrays.ts +30 -11
- package/src/raise/structs.ts +396 -33
- package/src/raise/truncload.ts +4 -3
- package/src/raise/unthread.ts +400 -0
- package/src/rank-declare.ts +118 -12
- package/src/rank-variations.ts +89 -31
- package/src/rank.ts +307 -54
- package/src/runtime-helpers.ts +140 -22
- package/src/structure/analysis.ts +165 -2
- package/src/structure/arith-ops.ts +23 -0
- package/src/structure/globalaccess.ts +111 -9
- package/src/structure/hazards.ts +16 -64
- package/src/structure/local-statics.ts +145 -9
- package/src/structure/namecoalesce.ts +1 -1
- package/src/structure/pointer-spelling.ts +1106 -0
- package/src/structure/redundant-test.ts +1 -1
- package/src/structure/structure.ts +2180 -1054
- package/src/structure/switch-recover.ts +244 -100
- package/src/symbols.ts +6 -0
- package/src/target.ts +765 -52
- package/src/trace.ts +26 -8
- package/src/variation-definitions.ts +126 -14
- package/src/variation-gates.ts +2 -0
- package/src/variation-tokens.ts +5 -1
package/src/l3/symbol-refs.ts
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
// stale ref, transitively reintroducing the hazards the collector excludes). Deriving at the
|
|
18
18
|
// consumption point makes staleness impossible by construction.
|
|
19
19
|
import type { IrType } from '../ir/types';
|
|
20
|
-
import { type AggregateLayout, type ParamType, type Prototypes, spellableProto } from '../proto';
|
|
20
|
+
import { type AggregateLayout, type FnProto, type ParamType, type Prototypes, spellableProto } from '../proto';
|
|
21
21
|
import type { SymbolInfo } from '../symbols';
|
|
22
22
|
import type { TargetDescription } from '../target';
|
|
23
23
|
import { Expr, SFn, Stmt, exprChildren, mentionedName, stmtChildren, stmtExprs } from './ast';
|
|
@@ -63,10 +63,43 @@ export interface SymbolRef {
|
|
|
63
63
|
* by its tag (`name`), from the members the declaration lists (`layout`, which the lift laid out
|
|
64
64
|
* to type the call). */
|
|
65
65
|
returned?: { readonly name: string; readonly declared: string; readonly layout: AggregateLayout };
|
|
66
|
+
/** The structs a call target is passed BY VALUE (`byValue` on the call), defined the same way
|
|
67
|
+
* from the declaration's `byValueLayouts`. */
|
|
68
|
+
passed?: readonly { readonly name: string; readonly declared: string; readonly layout: AggregateLayout }[];
|
|
69
|
+
/** A global the body passes BARE as a struct by value (`take(gS)`), as `struct <tag>`: a
|
|
70
|
+
* name-only declaration of it is that struct, which the call needs and an integer cell rejects. */
|
|
71
|
+
passedAs?: string;
|
|
66
72
|
/** The bare name holds a pointer (l3/ptrcell.ts): the body stores one into it, or it meets such a
|
|
67
73
|
* cell bare and nothing reads it as an integer (`pointerPartners`). A name-only declaration of it
|
|
68
74
|
* is a pointer, which the store needs and an integer cell rejects on CodeWarrior. */
|
|
69
75
|
holdsPointer?: true;
|
|
76
|
+
/** A call target with NO spellable prototype whose address a function-scope static's (or an
|
|
77
|
+
* anonymous image's) initializer holds. The static's `(u32)&F` precedes every call, so the
|
|
78
|
+
* implicit declaration a call makes comes too late for it; `declare.ts` prints that same
|
|
79
|
+
* declaration first, `int F();`, which states no parameter and so binds the call as before. */
|
|
80
|
+
implicitCall?: true;
|
|
81
|
+
/** THE STRUCT THE FUNCTION ITSELF RETURNS ({@link ownReturnDefinition}): a ref under the
|
|
82
|
+
* function's own name that declares nothing of it — its definition is its declaration — and only
|
|
83
|
+
* defines `returned`, the type its own signature spells. */
|
|
84
|
+
definesReturn?: true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** The ref that defines the struct `tree` itself returns by value — a type the project's headers
|
|
88
|
+
* own (ir/types.ts `declared`), so the candidate's declarations define it as they define a
|
|
89
|
+
* callee's (`SymbolRef.returned`) — from the members its own declaration `own` lists. Undefined
|
|
90
|
+
* where it returns no such struct, or its declaration lists no members. */
|
|
91
|
+
export function ownReturnDefinition(tree: SFn, own: FnProto | undefined): SymbolRef | undefined {
|
|
92
|
+
const t = tree.retType;
|
|
93
|
+
const layout = own?.returnLayout;
|
|
94
|
+
if (t.kind !== 'struct' || t.declared === undefined || layout?.members === undefined) {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
name: tree.name,
|
|
99
|
+
info: { name: tree.name, kind: 'code' },
|
|
100
|
+
returned: { name: t.name, declared: t.declared, layout },
|
|
101
|
+
definesReturn: true,
|
|
102
|
+
};
|
|
70
103
|
}
|
|
71
104
|
|
|
72
105
|
/** The declarable symbols a structured body references in a VALUE context — the input to the
|
|
@@ -101,15 +134,31 @@ export function collectSymbolRefs(
|
|
|
101
134
|
onRefused?: (name: string, reason: 'call-target' | 'self-name') => void,
|
|
102
135
|
): SymbolRef[] {
|
|
103
136
|
const called = new Set<string>();
|
|
104
|
-
// the struct a callee returns through memory, as the lift typed it
|
|
137
|
+
// the struct a callee returns through memory or by value in registers, as the lift typed it
|
|
105
138
|
const returned = new Map<string, Extract<IrType, { kind: 'struct' }>>();
|
|
139
|
+
// …and the structs it is passed by value, by declared parameter index
|
|
140
|
+
const passedTo = new Map<string, Map<number, Extract<IrType, { kind: 'struct' }>>>();
|
|
141
|
+
// …and the globals passed bare as one, by its spelling
|
|
142
|
+
const passedAs = new Map<string, string>();
|
|
106
143
|
const valueRefs = new Set<string>();
|
|
107
144
|
const visitExpr = (e: Expr): void => {
|
|
108
145
|
const named = mentionedName(e);
|
|
109
146
|
if (e.k === 'call' && typeof e.fn === 'string') {
|
|
110
147
|
called.add(e.fn);
|
|
111
|
-
|
|
112
|
-
|
|
148
|
+
const struct = e.sret ?? e.returns;
|
|
149
|
+
if (struct?.kind === 'struct') {
|
|
150
|
+
returned.set(e.fn, struct);
|
|
151
|
+
}
|
|
152
|
+
for (const b of e.byValue ?? []) {
|
|
153
|
+
if (b.type.kind === 'struct') {
|
|
154
|
+
const params = passedTo.get(e.fn) ?? passedTo.set(e.fn, new Map()).get(e.fn)!;
|
|
155
|
+
params.set(b.param, b.type);
|
|
156
|
+
const arg = e.args[b.at];
|
|
157
|
+
if (arg?.k === 'var' && symbols.has(arg.name)) {
|
|
158
|
+
// by its tag, which declares even ahead of the definition and of a typedef of it
|
|
159
|
+
passedAs.set(arg.name, `struct ${b.type.name}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
113
162
|
}
|
|
114
163
|
} else if (named !== undefined && symbols.has(named)) {
|
|
115
164
|
valueRefs.add(named);
|
|
@@ -126,6 +175,18 @@ export function collectSymbolRefs(
|
|
|
126
175
|
stmtChildren(s).forEach(visitStmt);
|
|
127
176
|
};
|
|
128
177
|
tree.body.forEach(visitStmt);
|
|
178
|
+
// A function-scope static's initializer names the globals whose addresses it holds
|
|
179
|
+
// (`(u32)&MetatileBehavior_IsTallGrass`), which the candidate must declare as much as the body's —
|
|
180
|
+
// and so does an anonymous image, which the emitter folds into a local's initializer.
|
|
181
|
+
const initAddresses = new Set<string>();
|
|
182
|
+
for (const st of [...(tree.statics ?? []), ...(tree.images ?? [])]) {
|
|
183
|
+
for (const v of st.init ?? []) {
|
|
184
|
+
if (v !== null && typeof v === 'object' && symbols.has(v.symbol)) {
|
|
185
|
+
valueRefs.add(v.symbol);
|
|
186
|
+
initAddresses.add(v.symbol);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
129
190
|
const holdsPointer = new Set([...pointerCellsOf(tree), ...pointerPartners(tree)]);
|
|
130
191
|
// A call target is a ref in its own right, whether or not the body also names it as a value —
|
|
131
192
|
// `Object.hasOwn`, because `prototypes` is caller-supplied JSON and a callee may be named
|
|
@@ -137,7 +198,9 @@ export function collectSymbolRefs(
|
|
|
137
198
|
// function, and `proto.ts` `prototypesFromSymbols` has already taken the `returns` off such an
|
|
138
199
|
// entry, so what arrives here states no printable prototype and the frontend read the call the
|
|
139
200
|
// same way. Testing it a second time at this reader is what let the two answer differently.
|
|
140
|
-
const p = Object.hasOwn(prototypes, n)
|
|
201
|
+
const p = Object.hasOwn(prototypes, n)
|
|
202
|
+
? spellableProto(prototypes[n], target, returned.get(n), new Set(passedTo.get(n)?.keys()))
|
|
203
|
+
: undefined;
|
|
141
204
|
if (p !== undefined) {
|
|
142
205
|
proto.set(n, p);
|
|
143
206
|
}
|
|
@@ -145,7 +208,11 @@ export function collectSymbolRefs(
|
|
|
145
208
|
return [...new Set([...valueRefs, ...proto.keys()])]
|
|
146
209
|
.sort()
|
|
147
210
|
.filter((n) => {
|
|
148
|
-
|
|
211
|
+
// …except where an initializer holds its address: there the name is spelled ahead of every
|
|
212
|
+
// call, so leaving it to the call's implicit declaration leaves the initializer naming an
|
|
213
|
+
// undeclared identifier, and the implicit declaration itself is the one that is printed
|
|
214
|
+
// (`SymbolRef.implicitCall`).
|
|
215
|
+
if (called.has(n) && !proto.has(n) && !initAddresses.has(n)) {
|
|
149
216
|
onRefused?.(n, 'call-target');
|
|
150
217
|
return false;
|
|
151
218
|
}
|
|
@@ -164,12 +231,23 @@ export function collectSymbolRefs(
|
|
|
164
231
|
const r = p ? returned.get(n) : undefined;
|
|
165
232
|
const layout = r?.declared !== undefined ? prototypes[n].returnLayout : undefined;
|
|
166
233
|
const definition = layout?.members !== undefined ? { name: r!.name, declared: r!.declared!, layout } : undefined;
|
|
234
|
+
const passed = p
|
|
235
|
+
? [...(passedTo.get(n) ?? [])].flatMap(([k, t]) => {
|
|
236
|
+
const l = prototypes[n].byValueLayouts?.[k];
|
|
237
|
+
return l?.members !== undefined && t.declared !== undefined
|
|
238
|
+
? [{ name: t.name, declared: t.declared, layout: l }]
|
|
239
|
+
: [];
|
|
240
|
+
})
|
|
241
|
+
: [];
|
|
167
242
|
return {
|
|
168
243
|
name: n,
|
|
169
244
|
info,
|
|
170
245
|
...(p ? { proto: p } : {}),
|
|
171
246
|
...(definition ? { returned: definition } : {}),
|
|
247
|
+
...(passed.length > 0 ? { passed } : {}),
|
|
248
|
+
...(passedAs.has(n) ? { passedAs: passedAs.get(n)! } : {}),
|
|
172
249
|
...(holdsPointer.has(n) ? { holdsPointer: true as const } : {}),
|
|
250
|
+
...(called.has(n) && !p ? { implicitCall: true as const } : {}),
|
|
173
251
|
};
|
|
174
252
|
});
|
|
175
253
|
}
|
package/src/l3/tailmerge.ts
CHANGED
|
@@ -63,7 +63,7 @@
|
|
|
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 { stmtEquals } from './ast';
|
|
66
|
+
import { stmtEquals, unionStmtEvidence } 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' }>;
|
|
@@ -89,7 +89,9 @@ function rewrite(s: Stmt): Stmt[] {
|
|
|
89
89
|
// it, but it is the one shape where a zero-arm merge is qualitatively unlike a partial one.
|
|
90
90
|
const tail: Stmt[] = [];
|
|
91
91
|
while (then.length > 0 && els.length > 0 && sameStmt(then[then.length - 1], els[els.length - 1])) {
|
|
92
|
-
|
|
92
|
+
// The two print alike, but an unsigned compare in one prints like the signed one in the
|
|
93
|
+
// other: the moved statement carries the evidence of both (ast.ts `unionEvidence`).
|
|
94
|
+
tail.unshift(unionStmtEvidence(then[then.length - 1], els[els.length - 1]));
|
|
93
95
|
then.pop();
|
|
94
96
|
els.pop();
|
|
95
97
|
}
|
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, memberOf, scalarTypeForAccess } from '../ir/types';
|
|
21
|
-
import { type BinOp, 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,7 +31,7 @@ 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 ?? []) {
|
|
34
|
+
for (const st of [...(fn.statics ?? []), ...(fn.images ?? [])]) {
|
|
35
35
|
m.set(st.name, st.type);
|
|
36
36
|
}
|
|
37
37
|
for (const p of fn.params) {
|
|
@@ -58,8 +58,9 @@ export function ptrElemBytes(to: IrType): number {
|
|
|
58
58
|
export interface PrintEnv {
|
|
59
59
|
readonly type: VarTypes;
|
|
60
60
|
readonly volatilePointee: (name: string) => boolean;
|
|
61
|
-
/** a callee's declared parameter types, on a function
|
|
62
|
-
* `SFn.dialect`)
|
|
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`) */
|
|
63
64
|
readonly declaredArgs: (callee: string) => readonly (string | undefined)[] | undefined;
|
|
64
65
|
/** a function-scope static this function defines `const` (`SFn.statics`) */
|
|
65
66
|
readonly constStatic: (name: string) => boolean;
|
|
@@ -76,7 +77,9 @@ export function printEnv(fn: SFn): PrintEnv {
|
|
|
76
77
|
type: declaredTypes(fn),
|
|
77
78
|
volatilePointee: (n) => vol.has(n),
|
|
78
79
|
declaredArgs: (callee) =>
|
|
79
|
-
fn.dialect === 'c++'
|
|
80
|
+
(fn.dialect === 'c++' || fn.strictConversions === true) &&
|
|
81
|
+
fn.declaredArgs &&
|
|
82
|
+
Object.hasOwn(fn.declaredArgs, callee)
|
|
80
83
|
? fn.declaredArgs[callee]
|
|
81
84
|
: undefined,
|
|
82
85
|
constStatic: (n) => constStatics.has(n),
|
|
@@ -184,8 +187,24 @@ export function exprIntWidth(e: Expr, varType: VarTypes): 32 | 64 {
|
|
|
184
187
|
export const FLOAT_BINOPS: ReadonlySet<BinOp> = new Set<BinOp>(['f+', 'f-', 'f*', 'f/']);
|
|
185
188
|
|
|
186
189
|
/** 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
|
-
|
|
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
|
+
]);
|
|
189
208
|
|
|
190
209
|
/** The USUAL ARITHMETIC CONVERSIONS over two rendered operands.
|
|
191
210
|
*
|
|
@@ -221,7 +240,7 @@ export function arithConversionSignedness(l: Expr, r: Expr, varType: VarTypes):
|
|
|
221
240
|
*
|
|
222
241
|
* THE one rendered-signedness judgment, and it lives beside the declarations it judges against
|
|
223
242
|
* because every consumer shares it: the C-family backend's operand pin, structure.ts's
|
|
224
|
-
* unsigned-compare gate (
|
|
243
|
+
* unsigned-compare gate (unsignedCompareSpelling) and its signed-compare pin, and — through
|
|
225
244
|
* `arithConversionSignedness` above — initfirst's compare-meaning gate. Two of those models
|
|
226
245
|
* disagreeing about one expression is the drift this placement prevents.
|
|
227
246
|
*
|
|
@@ -237,8 +256,8 @@ export function arithConversionSignedness(l: Expr, r: Expr, varType: VarTypes):
|
|
|
237
256
|
* instruction-for-instruction the same where `f` really returns `int`); a missing one is a
|
|
238
257
|
* miscompile — with `u32 f(void);` in scope `f() / a` calls `__udivsi3` where the machine called
|
|
239
258
|
* `__divsi3`, and `if (f() >= 0)` compiles to `bl f; mov r0, #0`, comparison and both arms gone.
|
|
240
|
-
* The one consumer that reads `undefined` the other way is the
|
|
241
|
-
* 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.
|
|
242
261
|
*
|
|
243
262
|
* Anything narrower than 32 bits promotes to `int` and is therefore SIGNED, whatever it was
|
|
244
263
|
* declared. Pointers, calls and markers are `undefined`.
|
|
@@ -308,16 +327,41 @@ export function renderedIntSignedness(e: Expr, varType: VarTypes): boolean | und
|
|
|
308
327
|
}
|
|
309
328
|
|
|
310
329
|
/** Is the rendered expression provably in [0, 2^31) — the range where a signed and an unsigned
|
|
311
|
-
* compare agree on every input
|
|
312
|
-
*
|
|
313
|
-
*
|
|
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`). */
|
|
314
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 {
|
|
315
353
|
switch (e.k) {
|
|
316
354
|
case 'const':
|
|
317
355
|
return e.value >= 0 && e.value < 0x80000000;
|
|
318
356
|
case 'un':
|
|
319
357
|
return e.op === '!';
|
|
320
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
|
+
}
|
|
321
365
|
return ['<', '<=', '>', '>=', '==', '!=', '&&', '||'].includes(e.op);
|
|
322
366
|
case 'cast':
|
|
323
367
|
return e.to.kind === 'int' && e.to.width < 32 && !e.to.signed;
|
|
@@ -328,6 +372,47 @@ export function provablyNonNegative(e: Expr, varType: VarTypes): boolean {
|
|
|
328
372
|
}
|
|
329
373
|
}
|
|
330
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
|
+
|
|
331
416
|
/** Does this write put a value that renders as a DEFINITELY-NON-POINTER C expression into a
|
|
332
417
|
* pointer-declared slot? — `v2 = a1 + v0` with `v2: u8 *`, a `return a0 + v0` from a
|
|
333
418
|
* pointer-returning function, a store through a pointer-element lvalue. The machine's register
|
|
@@ -421,8 +506,16 @@ export function exprCType(e: Expr, varType: (name: string) => IrType | undefined
|
|
|
421
506
|
// would type it `s96`-style garbage and make the field contract reject valid trees. The
|
|
422
507
|
// node width must AGREE with the element size (when the struct declares one): a mismatch
|
|
423
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.
|
|
424
512
|
const bt = rec(e.base);
|
|
425
|
-
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
|
+
) {
|
|
426
519
|
return bt.to;
|
|
427
520
|
}
|
|
428
521
|
if (bt?.kind === 'ptr' && derefStrideOk(bt, e.width, e.signed)) {
|
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
|
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 &&
|
package/src/mangle.ts
CHANGED
|
@@ -129,6 +129,36 @@ export function demangle(sym: string): CppSig | null {
|
|
|
129
129
|
return { name, cls, params };
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
/** How a class opens where gcc 2 names one: a length (`3Foo`), a nesting count (`Q21A1B`, and
|
|
133
|
+
* `Q_12_` past nine levels) or a template (`t3Vec1Zi`). A member function's const and volatile
|
|
134
|
+
* marks come before it (`get__C3Fooi`, `get__V3Fooi`, `get__CQ21A1Bi`), and CodeWarrior's class
|
|
135
|
+
* opens the same way, its marks following the class (`getStartHour__11PlayerStateCFv`). */
|
|
136
|
+
const GCC2_CLASS = String.raw`(?:\d|Q(?:\d|_\d+_)|t\d)`;
|
|
137
|
+
|
|
138
|
+
/** How many argument words a C++ symbol may be handed ahead of its declared parameters: 1 where it
|
|
139
|
+
* may name a MEMBER function, whose `this` the declared list leaves out; 0 where it cannot; and
|
|
140
|
+
* `undefined` for a constructor or destructor, which may also take a flag no declaration states
|
|
141
|
+
* (an in-charge or delete flag, beside `this`). Either mangling scheme a target here compiles with
|
|
142
|
+
* puts the class right after the `__`, length-prefixed or `Q<n>`-nested: CodeWarrior's
|
|
143
|
+
* `pick__3FooFiPi` (`__ct__3FooFv`, `__dt__3FooFv`), and gcc 2's `pick__3FooiPi` (a const method
|
|
144
|
+
* `pick__C3FooiPi`, a volatile one `pick__V3FooiPi`, a constructor `__3Foo`, a destructor `_$_3Foo`
|
|
145
|
+
* or `_._3Foo`, every one of which this abstains on, nested `_._Q21A1B` included), where a
|
|
146
|
+
* TEMPLATE class is spelled `t<n><name><args>` in the class's place (`get__t3Vec1Zii`, a
|
|
147
|
+
* constructor `__t3Vec1Zii`, `operator=` `__as__t3Vec1ZiRCt3Vec1Zi`). A free function (`f__Fii`)
|
|
148
|
+
* and a plain C name answer 0. A STATIC member mangles as a method does and takes no
|
|
149
|
+
* `this`, so 1 is "may", and a reader that needs the exact count must not take it as one.
|
|
150
|
+
*
|
|
151
|
+
* THE PATTERNS ERR TOWARDS A MEMBER, because the errors cost differently: a method read as free
|
|
152
|
+
* loses its `this` word, and the own-declaration bound (`proto.ts` `ownArgumentWords`) then turns
|
|
153
|
+
* its last parameter into an uninitialised local, where a C name read as a method only leaves one
|
|
154
|
+
* more register a parameter. */
|
|
155
|
+
export function implicitArgumentWords(sym: string): 0 | 1 | undefined {
|
|
156
|
+
if (/^__(?:ct|dt)__/.test(sym) || new RegExp(`^__${GCC2_CLASS}`).test(sym) || /^_[$.]_/.test(sym)) {
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
return new RegExp(`__[CV]*${GCC2_CLASS}`).test(sym) ? 1 : 0;
|
|
160
|
+
}
|
|
161
|
+
|
|
132
162
|
/** A class qualifier off the front of `s` — `12RefCountable`, or `Q23zen17particleGenerator` for a
|
|
133
163
|
* nested one — as its scope names and the remaining string; null when `s` does not open with one. */
|
|
134
164
|
function parseQualifier(s: string): { scopes: string[]; rest: string } | null {
|