@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
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
// L3: a POINTER CELL — a global the body stores a pointer into, and the one place that spelling is
|
|
2
|
+
// recognised.
|
|
3
|
+
//
|
|
4
|
+
// structure() spells a pointer value stored into a word global as `g = (void *)X` (structure.ts
|
|
5
|
+
// `intoPtrCell`), the store every pointer declaration of `g` takes. Three readers act on it, and
|
|
6
|
+
// all three ask `pointerCells` rather than re-reading the cast:
|
|
7
|
+
//
|
|
8
|
+
// • l3/symbol-refs.ts declares a name-only cell `void *` in the candidate's own world, the type
|
|
9
|
+
// that store needs, and so each global the cell meets bare that nothing reads as an integer
|
|
10
|
+
// (`pointerPartners`);
|
|
11
|
+
// • `legalizePointerCells` below converts the cell's other uses to match that declaration;
|
|
12
|
+
// • l3/intcell.ts spells the cell as the integer a project may have declared instead.
|
|
13
|
+
//
|
|
14
|
+
// The fact is derived from the final tree at each reader rather than carried on SFn, for the reason
|
|
15
|
+
// l3/symbol-refs.ts's header gives: a rewrite that drops the store must drop the fact with it.
|
|
16
|
+
import { type IrType, T } from '../ir/types';
|
|
17
|
+
import { declaredWidth } from '../proto';
|
|
18
|
+
import { type Expr, type SFn, type Stmt, exprChildren, mapExprChildren, stmtChildren, walkExprs } from './ast';
|
|
19
|
+
import { declaredTypes, exprCType } from './typing';
|
|
20
|
+
|
|
21
|
+
/** The bare name an `assign` stores a `(void *)` value into: a pointer cell, when no local binds
|
|
22
|
+
* it. */
|
|
23
|
+
export function pointerCellStore(s: Stmt): string | undefined {
|
|
24
|
+
return s.k === 'assign' && s.value.k === 'cast' && s.value.to.kind === 'ptr' && s.value.to.to.kind === 'void'
|
|
25
|
+
? s.name
|
|
26
|
+
: undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const COMPARE = new Set(['<', '<=', '>', '>=', '==', '!=']);
|
|
30
|
+
|
|
31
|
+
const assigns = (body: Stmt[]): Extract<Stmt, { k: 'assign' }>[] => {
|
|
32
|
+
const out: Extract<Stmt, { k: 'assign' }>[] = [];
|
|
33
|
+
const visit = (s: Stmt): void => {
|
|
34
|
+
if (s.k === 'assign') {
|
|
35
|
+
out.push(s);
|
|
36
|
+
}
|
|
37
|
+
stmtChildren(s).forEach(visit);
|
|
38
|
+
};
|
|
39
|
+
body.forEach(visit);
|
|
40
|
+
return out;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** Whether `e` reads a variable `pick` takes. */
|
|
44
|
+
const readsVar = (e: Expr, pick: (name: string) => boolean): boolean =>
|
|
45
|
+
(e.k === 'var' && pick(e.name)) || exprChildren(e).some((c) => readsVar(c, pick));
|
|
46
|
+
|
|
47
|
+
/** A cell's value moved by integers (`g + 4`, `g + a0 - 8`): pointer arithmetic, a pointer as the
|
|
48
|
+
* cell is. Whatever is added is an integer, as no pointer is; what is subtracted is a constant,
|
|
49
|
+
* since a pointer's difference is no pointer. */
|
|
50
|
+
export function cellOffset(e: Expr, cells: ReadonlySet<string>): boolean {
|
|
51
|
+
if (e.k !== 'bin' || (e.op !== '+' && e.op !== '-')) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
const base = (x: Expr): boolean => (x.k === 'var' && cells.has(x.name)) || cellOffset(x, cells);
|
|
55
|
+
const offset = (x: Expr): boolean => !readsVar(x, (n) => e.op === '-' || cells.has(n));
|
|
56
|
+
return (base(e.l) && offset(e.r)) || (e.op === '+' && base(e.r) && offset(e.l));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The globals `body` stores a pointer into: a `(void *)` store, or the bare value of another one,
|
|
60
|
+
* or that value moved by integers (`cellOffset`). */
|
|
61
|
+
export function pointerCells(body: Stmt[], isGlobal: (name: string) => boolean): Set<string> {
|
|
62
|
+
const stores = assigns(body).filter((s) => isGlobal(s.name));
|
|
63
|
+
const out = new Set(stores.filter((s) => pointerCellStore(s) !== undefined).map((s) => s.name));
|
|
64
|
+
for (let grew = true; grew;) {
|
|
65
|
+
grew = false;
|
|
66
|
+
for (const s of stores) {
|
|
67
|
+
if (!out.has(s.name) && ((s.value.k === 'var' && out.has(s.value.name)) || cellOffset(s.value, out))) {
|
|
68
|
+
out.add(s.name);
|
|
69
|
+
grew = true;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The bare globals a pointer cell meets bare: compared with one or with its `cellOffset`, or
|
|
77
|
+
* stored into one. */
|
|
78
|
+
function meetsBare(body: Stmt[], isGlobal: (name: string) => boolean, cells: ReadonlySet<string>): Set<string> {
|
|
79
|
+
const out = new Set<string>();
|
|
80
|
+
const meets = (e: Expr): void => {
|
|
81
|
+
if (e.k === 'var' && isGlobal(e.name) && !cells.has(e.name)) {
|
|
82
|
+
out.add(e.name);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
const isCell = (e: Expr): boolean => (e.k === 'var' && cells.has(e.name)) || cellOffset(e, cells);
|
|
86
|
+
for (const s of assigns(body)) {
|
|
87
|
+
if (cells.has(s.name)) {
|
|
88
|
+
meets(s.value);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
for (const e of walkExprs(body)) {
|
|
92
|
+
if (e.k === 'bin' && COMPARE.has(e.op)) {
|
|
93
|
+
if (isCell(e.l)) {
|
|
94
|
+
meets(e.r);
|
|
95
|
+
}
|
|
96
|
+
if (isCell(e.r)) {
|
|
97
|
+
meets(e.l);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Whether a name `sfn` reads is a global: no param or local binds it. */
|
|
105
|
+
const globalOf = (sfn: SFn): ((name: string) => boolean) => {
|
|
106
|
+
const bound = new Set([...sfn.params.map((p) => p.name), ...sfn.locals.map((l) => l.name)]);
|
|
107
|
+
return (name) => !bound.has(name);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
/** The globals `sfn` stores a pointer into (`pointerCells`). */
|
|
111
|
+
export function pointerCellsOf(sfn: SFn): Set<string> {
|
|
112
|
+
return pointerCells(sfn.body, globalOf(sfn));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Whether a declared parameter spelling is an integer of at most a word: the conversion a pointer
|
|
116
|
+
* argument needs to reach it. A spelling this cannot read is no opinion. */
|
|
117
|
+
const integerParam = (t: string | undefined): boolean => {
|
|
118
|
+
if (t === undefined) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
const w = declaredWidth(t);
|
|
122
|
+
return w !== undefined && w <= 32 && !t.includes('*') && !t.includes('(');
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/** What `mapIntegerSlots` does at each place a pointer cell meets an integer. */
|
|
126
|
+
interface SlotRules {
|
|
127
|
+
/** An operand a cell meets bare, which no conversion takes. */
|
|
128
|
+
pointerTyped: (e: Expr) => boolean;
|
|
129
|
+
/** An operand the context reads as an integer. */
|
|
130
|
+
int: (e: Expr) => Expr;
|
|
131
|
+
/** An argument an integer parameter takes. */
|
|
132
|
+
intArg: (e: Expr) => Expr;
|
|
133
|
+
/** A bare global compared with a cell that is no partner. */
|
|
134
|
+
comparedGlobal: (e: Expr) => Expr;
|
|
135
|
+
/** A value stored into a cell. */
|
|
136
|
+
intoCell: (e: Expr) => Expr;
|
|
137
|
+
/** A `cellOffset` a pointer meets. */
|
|
138
|
+
pointerOffset: (e: Expr) => Expr;
|
|
139
|
+
/** A global stored into that is no cell, which is an integer slot. */
|
|
140
|
+
storedGlobal: (name: string) => void;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** `sfn` with `rules` applied at every place its context states an integer type: an operand of a
|
|
144
|
+
* non-additive operator, of `-`/`~`, an index, a switch, a comparison with no pointer, a call
|
|
145
|
+
* argument whose callee's declared parameter (`SFn.declaredArgs`) is an integer, and a store, an
|
|
146
|
+
* assignment or a return into an integer slot. A global stored into that is no cell is an integer
|
|
147
|
+
* slot: the candidate's own world declares it one. */
|
|
148
|
+
function mapIntegerSlots(sfn: SFn, cells: ReadonlySet<string>, rules: SlotRules): SFn {
|
|
149
|
+
const isGlobal = globalOf(sfn);
|
|
150
|
+
const vt = declaredTypes(sfn);
|
|
151
|
+
const isCell = (e: Expr): boolean => (e.k === 'var' && cells.has(e.name)) || cellOffset(e, cells);
|
|
152
|
+
const bareGlobal = (e: Expr): boolean => e.k === 'var' && isGlobal(e.name) && !cells.has(e.name);
|
|
153
|
+
const compare = (x: Expr, other: Expr): Expr =>
|
|
154
|
+
isCell(other) && bareGlobal(x) && !rules.pointerTyped(x)
|
|
155
|
+
? rules.comparedGlobal(x)
|
|
156
|
+
: rules.pointerTyped(other) || cellOffset(other, cells)
|
|
157
|
+
? cellOffset(x, cells)
|
|
158
|
+
? rules.pointerOffset(x)
|
|
159
|
+
: x
|
|
160
|
+
: rules.int(x);
|
|
161
|
+
const expr = (e0: Expr): Expr => {
|
|
162
|
+
const e = mapExprChildren(e0, expr);
|
|
163
|
+
switch (e.k) {
|
|
164
|
+
case 'bin':
|
|
165
|
+
if (e.op === '&&' || e.op === '||' || e.op === '+' || e.op === '-') {
|
|
166
|
+
return e;
|
|
167
|
+
}
|
|
168
|
+
if (COMPARE.has(e.op)) {
|
|
169
|
+
return { ...e, l: compare(e.l, e.r), r: compare(e.r, e.l) };
|
|
170
|
+
}
|
|
171
|
+
return { ...e, l: rules.int(e.l), r: rules.int(e.r) };
|
|
172
|
+
case 'un':
|
|
173
|
+
return e.op === '-' || e.op === '~' ? { ...e, e: rules.int(e.e) } : e;
|
|
174
|
+
case 'call': {
|
|
175
|
+
const params =
|
|
176
|
+
typeof e.fn === 'string' && sfn.declaredArgs && Object.hasOwn(sfn.declaredArgs, e.fn)
|
|
177
|
+
? sfn.declaredArgs[e.fn]
|
|
178
|
+
: undefined;
|
|
179
|
+
if (params === undefined) {
|
|
180
|
+
return e;
|
|
181
|
+
}
|
|
182
|
+
return { ...e, args: e.args.map((a, i) => (integerParam(params[i]) ? rules.intArg(a) : a)) };
|
|
183
|
+
}
|
|
184
|
+
case 'index':
|
|
185
|
+
return { ...e, idx: rules.int(e.idx), ...(e.lead ? { lead: e.lead.map(rules.int) } : {}) };
|
|
186
|
+
default:
|
|
187
|
+
return e;
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const stmt = (s: Stmt): Stmt => {
|
|
192
|
+
switch (s.k) {
|
|
193
|
+
case 'assign': {
|
|
194
|
+
const value = expr(s.value);
|
|
195
|
+
if (cells.has(s.name)) {
|
|
196
|
+
return { ...s, value: rules.pointerTyped(value) ? value : rules.intoCell(value) };
|
|
197
|
+
}
|
|
198
|
+
const t = vt(s.name);
|
|
199
|
+
if (t === undefined && isGlobal(s.name)) {
|
|
200
|
+
rules.storedGlobal(s.name);
|
|
201
|
+
return { ...s, value: rules.int(value) };
|
|
202
|
+
}
|
|
203
|
+
return { ...s, value: t?.kind === 'int' ? rules.int(value) : value };
|
|
204
|
+
}
|
|
205
|
+
case 'store': {
|
|
206
|
+
const value = expr(s.value);
|
|
207
|
+
return {
|
|
208
|
+
...s,
|
|
209
|
+
lval: expr(s.lval),
|
|
210
|
+
value: exprCType(s.lval, vt)?.kind === 'int' ? rules.int(value) : value,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
case 'exprstmt':
|
|
214
|
+
return { ...s, value: expr(s.value) };
|
|
215
|
+
case 'return':
|
|
216
|
+
return s.value ? { ...s, value: sfn.retType.kind === 'int' ? rules.int(expr(s.value)) : expr(s.value) } : s;
|
|
217
|
+
case 'if':
|
|
218
|
+
return { ...s, cond: expr(s.cond), then: s.then.map(stmt), else: s.else.map(stmt) };
|
|
219
|
+
case 'while':
|
|
220
|
+
case 'dowhile':
|
|
221
|
+
return { ...s, cond: expr(s.cond), body: s.body.map(stmt) };
|
|
222
|
+
case 'for':
|
|
223
|
+
return { ...s, init: stmt(s.init), cond: expr(s.cond), inc: stmt(s.inc), body: s.body.map(stmt) };
|
|
224
|
+
case 'switch':
|
|
225
|
+
return {
|
|
226
|
+
...s,
|
|
227
|
+
scrutinee: rules.int(expr(s.scrutinee)),
|
|
228
|
+
cases: s.cases.map((c) => ({ ...c, body: c.body.map(stmt) })),
|
|
229
|
+
default: s.default?.map(stmt),
|
|
230
|
+
};
|
|
231
|
+
case 'break':
|
|
232
|
+
case 'continue':
|
|
233
|
+
return s;
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
return { ...sfn, body: sfn.body.map(stmt) };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** A sum no operand of which renders a pointer: read as an integer, each bare name in it is. */
|
|
240
|
+
const integerSum = (e: Expr, vt: (name: string) => IrType | undefined): e is Extract<Expr, { k: 'bin' }> =>
|
|
241
|
+
e.k === 'bin' &&
|
|
242
|
+
(e.op === '+' || e.op === '-') &&
|
|
243
|
+
exprCType(e.l, vt)?.kind !== 'ptr' &&
|
|
244
|
+
exprCType(e.r, vt)?.kind !== 'ptr';
|
|
245
|
+
|
|
246
|
+
/** The globals a pointer cell meets bare (compared with it or its offset, or stored into it) that hold a pointer
|
|
247
|
+
* too: the candidate's own world declares each `void *` beside the cells, and they are left as
|
|
248
|
+
* spelled, since converting one would convert whatever the project declares it, where bare a
|
|
249
|
+
* float, an array or a function meets the cell as a constraint violation.
|
|
250
|
+
*
|
|
251
|
+
* A global the body also reads as an integer is not one: `void *` takes none of those reads, so its
|
|
252
|
+
* own world declares it the integer it is read as, and the cell converts to meet it. */
|
|
253
|
+
export function pointerPartners(sfn: SFn): Set<string> {
|
|
254
|
+
const isGlobal = globalOf(sfn);
|
|
255
|
+
const cells = pointerCells(sfn.body, isGlobal);
|
|
256
|
+
const meets = meetsBare(sfn.body, isGlobal, cells);
|
|
257
|
+
if (meets.size === 0) {
|
|
258
|
+
return meets;
|
|
259
|
+
}
|
|
260
|
+
const vt = declaredTypes(sfn);
|
|
261
|
+
const read = new Set<string>();
|
|
262
|
+
const int = (e: Expr): Expr => {
|
|
263
|
+
if (e.k === 'var' && meets.has(e.name)) {
|
|
264
|
+
read.add(e.name);
|
|
265
|
+
} else if (integerSum(e, vt)) {
|
|
266
|
+
int(e.l);
|
|
267
|
+
int(e.r);
|
|
268
|
+
}
|
|
269
|
+
return e;
|
|
270
|
+
};
|
|
271
|
+
mapIntegerSlots(sfn, cells, {
|
|
272
|
+
pointerTyped: (e) => e.k === 'var' && (cells.has(e.name) || meets.has(e.name)),
|
|
273
|
+
int,
|
|
274
|
+
intArg: int,
|
|
275
|
+
comparedGlobal: int,
|
|
276
|
+
intoCell: (e) => e,
|
|
277
|
+
pointerOffset: (e) => e,
|
|
278
|
+
storedGlobal: (name) => {
|
|
279
|
+
if (meets.has(name)) {
|
|
280
|
+
read.add(name);
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
return new Set([...meets].filter((n) => !read.has(n)));
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** `sfn` with every integer use of a pointer cell converted to the integer it reads as, and every
|
|
288
|
+
* integer stored into one converted to a pointer.
|
|
289
|
+
*
|
|
290
|
+
* A cell the body stores a pointer into is a pointer under every declaration that store compiles
|
|
291
|
+
* against, so the cell's integer readers (`return g;`, `if (g >= a0)`, `v0 = g;`, `g + 4` returned
|
|
292
|
+
* as an integer, an integer parameter's argument) and its integer writers (`g = a0;`) need the
|
|
293
|
+
* conversion C leaves implicit and CodeWarrior and IDO reject: `(u32)g`, `g = (void *)a0`. The
|
|
294
|
+
* conversion of a word is no instruction, so the bytes are the implicit one's. A cell's offset
|
|
295
|
+
* (`cellOffset`) stored into a cell or compared with a pointer is the pointer it is, spelled in
|
|
296
|
+
* bytes: `gOut = (void *)((u8 *)g + 4)`, `(u8 *)g + 4 == gP`.
|
|
297
|
+
*
|
|
298
|
+
* SCOPE: a use whose context states an integer type (`mapIntegerSlots`); elsewhere the use is left
|
|
299
|
+
* as the structurer spelled it. A truth test, a sum that renders a pointer (the structurer's own
|
|
300
|
+
* byte sum, which the backend converts at an integer slot: `legalizePointerWrites`), and a
|
|
301
|
+
* comparison with a pointer (a call by its declared return, `SFn.declaredReturns`), with 0 or with
|
|
302
|
+
* a partner (`pointerPartners`) are left alone. A global compared with the cell that is no partner
|
|
303
|
+
* goes `(u32)(u8 *)g` beside the cell's `(u32)g`: under a float declaration that is no C, as the
|
|
304
|
+
* bare comparison was not. KNOWN GAP: under an array or a function declaration that is the
|
|
305
|
+
* global's address, compared with no diagnostic, and IDO takes the integer read that made it no
|
|
306
|
+
* partner (`v1 = g`) with none either; structure.ts `globalWord` says why no spelling reads the
|
|
307
|
+
* word there.
|
|
308
|
+
*
|
|
309
|
+
* Run by the C-family backend's `emit` (backend/cfamily.ts), after every respell variation, so a
|
|
310
|
+
* respell reads the uses as the structurer spelled them and a store it removes takes the
|
|
311
|
+
* conversions with it. */
|
|
312
|
+
export function legalizePointerCells(sfn: SFn): SFn {
|
|
313
|
+
const cells = pointerCellsOf(sfn);
|
|
314
|
+
if (cells.size === 0) {
|
|
315
|
+
return sfn;
|
|
316
|
+
}
|
|
317
|
+
const partners = pointerPartners(sfn);
|
|
318
|
+
const vt = declaredTypes(sfn);
|
|
319
|
+
const isCell = (e: Expr): boolean => e.k === 'var' && cells.has(e.name);
|
|
320
|
+
const returnsPointer = (fn: string | Expr): boolean =>
|
|
321
|
+
typeof fn === 'string' &&
|
|
322
|
+
sfn.declaredReturns !== undefined &&
|
|
323
|
+
Object.hasOwn(sfn.declaredReturns, fn) &&
|
|
324
|
+
sfn.declaredReturns[fn].includes('*');
|
|
325
|
+
const asInt = (e: Expr): Expr =>
|
|
326
|
+
isCell(e) ? { k: 'cast', to: T.u(32), e } : integerSum(e, vt) ? { ...e, l: asInt(e.l), r: asInt(e.r) } : e;
|
|
327
|
+
// A cell's offset as the pointer it is: byte arithmetic on the cell, `(u8 *)g + 4`, which no
|
|
328
|
+
// pointee scales and which a `void *` takes.
|
|
329
|
+
const asBytes = (e: Expr): Expr =>
|
|
330
|
+
isCell(e)
|
|
331
|
+
? { k: 'cast', to: T.ptr(T.u(8)), e }
|
|
332
|
+
: e.k === 'bin' && cellOffset(e, cells)
|
|
333
|
+
? { ...e, l: asBytes(e.l), r: e.op === '+' ? asBytes(e.r) : e.r }
|
|
334
|
+
: e;
|
|
335
|
+
// The structurer's byte sum on a cell's value (`(u8 *)g + 8`) is a pointer too, which an integer
|
|
336
|
+
// parameter takes converted as a bare cell does; the backend converts it at every other integer
|
|
337
|
+
// slot (`legalizePointerWrites`).
|
|
338
|
+
const cellSum = (e: Expr): boolean =>
|
|
339
|
+
(e.k === 'cast' && e.to.kind === 'ptr' && isCell(e.e)) ||
|
|
340
|
+
(e.k === 'bin' && (e.op === '+' || e.op === '-') && (cellSum(e.l) || cellSum(e.r)));
|
|
341
|
+
return mapIntegerSlots(sfn, cells, {
|
|
342
|
+
pointerTyped: (e) =>
|
|
343
|
+
isCell(e) ||
|
|
344
|
+
(e.k === 'var' && partners.has(e.name)) ||
|
|
345
|
+
e.k === 'addr' ||
|
|
346
|
+
(e.k === 'call' && returnsPointer(e.fn)) ||
|
|
347
|
+
(e.k === 'const' && e.value === 0) ||
|
|
348
|
+
exprCType(e, vt)?.kind === 'ptr',
|
|
349
|
+
int: asInt,
|
|
350
|
+
intArg: (e) =>
|
|
351
|
+
e.k === 'bin' && cellSum(e) && exprCType(e, vt)?.kind === 'ptr' ? { k: 'cast', to: T.u(32), e } : asInt(e),
|
|
352
|
+
comparedGlobal: (e) => ({ k: 'cast', to: T.u(32), e: { k: 'cast', to: T.ptr(T.u(8)), e } }),
|
|
353
|
+
intoCell: (e) => ({ k: 'cast', to: T.ptr(T.void()), e: asBytes(e) }),
|
|
354
|
+
pointerOffset: asBytes,
|
|
355
|
+
storedGlobal: () => {},
|
|
356
|
+
});
|
|
357
|
+
}
|
package/src/l3/regspell.ts
CHANGED
|
@@ -40,6 +40,7 @@ function isPure(e: Expr): boolean {
|
|
|
40
40
|
switch (e.k) {
|
|
41
41
|
case 'var':
|
|
42
42
|
case 'const':
|
|
43
|
+
case 'fconst':
|
|
43
44
|
return true;
|
|
44
45
|
case 'un':
|
|
45
46
|
case 'cast':
|
|
@@ -56,6 +57,9 @@ function renameVar(e: Expr, from: string, to: string): Expr {
|
|
|
56
57
|
if (e.k === 'var') {
|
|
57
58
|
return e.name === from ? { k: 'var', name: to } : e;
|
|
58
59
|
}
|
|
60
|
+
if (e.k === 'postincr') {
|
|
61
|
+
return e.name === from ? { ...e, name: to } : e;
|
|
62
|
+
}
|
|
59
63
|
return mapExprChildren(e, (c) => renameVar(c, from, to));
|
|
60
64
|
}
|
|
61
65
|
|
|
@@ -251,12 +255,22 @@ export function registerishSpellings(sfn: SFn): RegcopySpelling[] {
|
|
|
251
255
|
// • E and the cond pure; the cond's non-E operand must NOT mention v (a clamp's
|
|
252
256
|
// `if (a < v)` would compare against the POST-assignment v — reproduced);
|
|
253
257
|
// • the copy w carries E's RENDERED type, not v's declared one (retyping a u32
|
|
254
|
-
// comparison signed flipped its sense — reproduced)
|
|
255
|
-
|
|
258
|
+
// comparison signed flipped its sense — reproduced), PROMOTED: the update assigns
|
|
259
|
+
// back into w, and a narrow w would truncate what the update computed;
|
|
260
|
+
// • v not narrow, because then v's declaration IS the update's truncation, which a
|
|
261
|
+
// word-wide w would drop.
|
|
262
|
+
const vType = typeOf(v);
|
|
263
|
+
if (
|
|
264
|
+
!isPure(E) ||
|
|
265
|
+
!isPure(cond) ||
|
|
266
|
+
condOtherMentions(cond, E, v) ||
|
|
267
|
+
(vType.kind === 'int' && vType.width < 32)
|
|
268
|
+
) {
|
|
256
269
|
out.push(s);
|
|
257
270
|
continue;
|
|
258
271
|
}
|
|
259
|
-
const
|
|
272
|
+
const rendered = exprCType(E, declaredTypes({ ...sfn, locals })) ?? vType;
|
|
273
|
+
const wType = rendered.kind === 'int' && rendered.width < 32 ? T.s(32) : rendered;
|
|
260
274
|
const w = freshVar(wType);
|
|
261
275
|
const condOnW = rewriteCond(cond, E, w);
|
|
262
276
|
if (condOnW) {
|
package/src/l3/reindex.ts
CHANGED
|
@@ -95,6 +95,7 @@ import {
|
|
|
95
95
|
Stmt,
|
|
96
96
|
mapExprChildren,
|
|
97
97
|
mapStmtExprs,
|
|
98
|
+
mentionedName,
|
|
98
99
|
rematerializableAddress,
|
|
99
100
|
stmtChildren,
|
|
100
101
|
stmtExprs,
|
|
@@ -109,14 +110,15 @@ interface WalkLoop {
|
|
|
109
110
|
base: string; // the var `p` was initialised from
|
|
110
111
|
}
|
|
111
112
|
|
|
112
|
-
/** Total mentions of `name` across a statement list (reads, writes, everywhere)
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
113
|
+
/** Total mentions of `name` across a statement list (reads, writes, everywhere) — `mentionedName`'s
|
|
114
|
+
* whole vocabulary, because the accounting these recognizers rest on is "nothing else in the
|
|
115
|
+
* function touches this name", which a var-only count cannot say. `&x` is how a frame local's
|
|
116
|
+
* address renders (structure.ts laddrName), and an escaped address is a read of the object by
|
|
117
|
+
* whatever holds it. */
|
|
116
118
|
function countMentions(stmts: Stmt[], name: string): number {
|
|
117
119
|
let n = 0;
|
|
118
120
|
const inExpr = (e: Expr): void => {
|
|
119
|
-
if ((e
|
|
121
|
+
if (mentionedName(e) === name) {
|
|
120
122
|
n++;
|
|
121
123
|
}
|
|
122
124
|
mapExprChildren(e, (c) => {
|
|
@@ -170,8 +172,9 @@ function derefWidths(stmts: Stmt[], p: string): number[] {
|
|
|
170
172
|
|
|
171
173
|
/** Does `e` mention `name` anywhere — as a value or as an escaped address (see countMentions)? */
|
|
172
174
|
function mentionsVar(e: Expr, name: string): boolean {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
+
const own = mentionedName(e);
|
|
176
|
+
if (own !== undefined) {
|
|
177
|
+
return own === name;
|
|
175
178
|
}
|
|
176
179
|
let found = false;
|
|
177
180
|
mapExprChildren(e, (c) => {
|
package/src/l3/symbol-refs.ts
CHANGED
|
@@ -16,8 +16,12 @@
|
|
|
16
16
|
// to recompute it (a dead-store DCE that drops a tree's only reference would otherwise leave a
|
|
17
17
|
// stale ref, transitively reintroducing the hazards the collector excludes). Deriving at the
|
|
18
18
|
// consumption point makes staleness impossible by construction.
|
|
19
|
+
import type { IrType } from '../ir/types';
|
|
20
|
+
import { type AggregateLayout, type ParamType, type Prototypes, spellableProto } from '../proto';
|
|
19
21
|
import type { SymbolInfo } from '../symbols';
|
|
20
|
-
import {
|
|
22
|
+
import type { TargetDescription } from '../target';
|
|
23
|
+
import { Expr, SFn, Stmt, exprChildren, mentionedName, stmtChildren, stmtExprs } from './ast';
|
|
24
|
+
import { pointerCellsOf, pointerPartners } from './ptrcell';
|
|
21
25
|
|
|
22
26
|
/** One recorded VALUE reference — a name the tree references plus the facts to declare it. */
|
|
23
27
|
export interface SymbolRef {
|
|
@@ -27,43 +31,88 @@ export interface SymbolRef {
|
|
|
27
31
|
* own IR — attached by the enumeration (rank.ts bareGlobalAccessFacts), consumed by the
|
|
28
32
|
* declaration synthesis (declare.ts) as the width/signedness authority for `extern T name;`. */
|
|
29
33
|
access?: { width: number; signed: boolean };
|
|
30
|
-
/** NO SYMBOL MAP
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
34
|
+
/** NO SYMBOL MAP ACCOUNTS FOR THIS NAME — that is the whole predicate, and it is one fact with
|
|
35
|
+
* two provenances the consumer must be able to tell apart.
|
|
36
|
+
*
|
|
37
|
+
* WITHOUT `proto`, the declaration is a HYPOTHESIS: the name and (through `access`) its width
|
|
38
|
+
* and signedness were read out of the candidate's own asm — the very bytes the candidate is
|
|
39
|
+
* then scored against. A fitted declaration cannot LOSE score, so it is not covered by
|
|
40
|
+
* declare.ts's only-loses-score argument; it can manufacture agreement instead. Which is
|
|
41
|
+
* legitimate (the artifact is decls + source, and it does compile to those bytes) exactly as
|
|
42
|
+
* long as the consumer SHOWS the declarations with the verdict.
|
|
43
|
+
*
|
|
44
|
+
* WITH `proto`, the declaration's TEXT is the project's own — a call target asmlift has a
|
|
45
|
+
* prototype for — and asmlift invented nothing about it. It is still marked, because the
|
|
46
|
+
* predicate this flag states is about the symbol MAP and not about who wrote the type, and
|
|
47
|
+
* because the score rests on that line being in the translation unit either way. A consumer
|
|
48
|
+
* wording the block reads `proto` to say which it is looking at; dropping the mark instead
|
|
49
|
+
* would under-report a declaration the verdict depends on, which is the direction
|
|
50
|
+
* `synthesizedRefs` exists to avoid. */
|
|
37
51
|
synthesized?: true;
|
|
52
|
+
/** A CALL TARGET's own declaration, re-spelled from the prototype the project supplied — the
|
|
53
|
+
* parameter type texts and the return type text, verbatim as the user wrote them. Present only
|
|
54
|
+
* where {@link spellableProto} could read a COMPLETE C prototype out of the `FnProto`, and it
|
|
55
|
+
* is what `declare.ts` prints instead of an `extern`: a call target is a function, and
|
|
56
|
+
* `extern u32 llsrc;` is not a declaration of it. Absent where the symbol map calls the name
|
|
57
|
+
* something other than code — two facts that disagree are not one fact — and that subtraction
|
|
58
|
+
* happens at the table (`proto.ts` `prototypesFromSymbols`), so the frontend loses the same
|
|
59
|
+
* fact in the same place rather than acting on one this withheld. */
|
|
60
|
+
proto?: { readonly params: readonly ParamType[]; readonly returns: ParamType };
|
|
61
|
+
/** The struct a call target returns through memory — a type the project's headers own
|
|
62
|
+
* (ir/types.ts `declared`), so `declare.ts` defines it beside `proto`, whose `returns` spells it:
|
|
63
|
+
* by its tag (`name`), from the members the declaration lists (`layout`, which the lift laid out
|
|
64
|
+
* to type the call). */
|
|
65
|
+
returned?: { readonly name: string; readonly declared: string; readonly layout: AggregateLayout };
|
|
66
|
+
/** The bare name holds a pointer (l3/ptrcell.ts): the body stores one into it, or it meets such a
|
|
67
|
+
* cell bare and nothing reads it as an integer (`pointerPartners`). A name-only declaration of it
|
|
68
|
+
* is a pointer, which the store needs and an integer cell rejects on CodeWarrior. */
|
|
69
|
+
holdsPointer?: true;
|
|
38
70
|
}
|
|
39
71
|
|
|
40
72
|
/** The declarable symbols a structured body references in a VALUE context — the input to the
|
|
41
73
|
* scoring layer's declaration synthesis. A name counts when it appears as a `var`/`addr` leaf
|
|
42
74
|
* and the caller's dictionary knows it (bare `gSym`, `&gSym`, `(u32)Func`, a `field` base — all reduce to
|
|
43
|
-
* those leaves).
|
|
44
|
-
*
|
|
45
|
-
* when the call passes args, while leaving it undeclared keeps today's implicit-declaration
|
|
46
|
-
* behavior (the one honest option without arity knowledge). The function's OWN name
|
|
47
|
-
* (`selfName`) is excluded too — the candidate's definition IS its declaration, and a
|
|
75
|
+
* those leaves). The function's OWN name
|
|
76
|
+
* (`tree.name`) is excluded too — the candidate's definition IS its declaration, and a
|
|
48
77
|
* synthesized `void F(void);` above `s32 F(...)` is a conflicting-types hard error (a
|
|
49
78
|
* self-address reference resolves against the definition itself).
|
|
50
79
|
*
|
|
80
|
+
* A CALL TARGET IS DECLARED WHEN, AND ONLY WHEN, THE PROJECT DECLARED IT COMPLETELY. `void
|
|
81
|
+
* F(void);` hard-errors under gcc-2.9 the moment the call passes an argument, so with no arity
|
|
82
|
+
* knowledge the honest option is to leave the name undeclared and keep C's implicit-declaration
|
|
83
|
+
* behaviour — which is what this did for every call target and still does for every one the
|
|
84
|
+
* prototype table cannot spell. Where it CAN (`spellableProto`), that knowledge is exactly what
|
|
85
|
+
* the refusal said it lacked, and the declaration is the project's own text rather than a guess.
|
|
86
|
+
* It is needed: an implicit declaration is `int`, so `(s64)llsrc()` sign-extends a word and the
|
|
87
|
+
* candidate carries an `asr` the target does not have.
|
|
88
|
+
*
|
|
51
89
|
* Both exclusions are REPORTED through `onRefused`, on the caller's own refusal channel: they
|
|
52
90
|
* leave a name undeclared for asmlift's own reason, and a consumer's list of those reasons is
|
|
53
91
|
* incomplete without them. */
|
|
54
92
|
export function collectSymbolRefs(
|
|
55
|
-
|
|
93
|
+
tree: SFn,
|
|
56
94
|
symbols: Map<string, SymbolInfo>,
|
|
57
|
-
|
|
95
|
+
/** the project's prototype table — the only thing that can turn a call target into a
|
|
96
|
+
* declaration. REQUIRED, not optional: a caller that forgot it would silently get the old
|
|
97
|
+
* blanket refusal back, which is the shape of an optional refusal a second caller switches off. */
|
|
98
|
+
prototypes: Prototypes,
|
|
99
|
+
/** which declared types the frontend lays out, so which a printed prototype may carry */
|
|
100
|
+
target: Pick<TargetDescription, 'doubleArgWords'>,
|
|
58
101
|
onRefused?: (name: string, reason: 'call-target' | 'self-name') => void,
|
|
59
102
|
): SymbolRef[] {
|
|
60
103
|
const called = new Set<string>();
|
|
104
|
+
// the struct a callee returns through memory, as the lift typed it
|
|
105
|
+
const returned = new Map<string, Extract<IrType, { kind: 'struct' }>>();
|
|
61
106
|
const valueRefs = new Set<string>();
|
|
62
107
|
const visitExpr = (e: Expr): void => {
|
|
63
|
-
|
|
108
|
+
const named = mentionedName(e);
|
|
109
|
+
if (e.k === 'call' && typeof e.fn === 'string') {
|
|
64
110
|
called.add(e.fn);
|
|
65
|
-
|
|
66
|
-
|
|
111
|
+
if (e.sret?.kind === 'struct') {
|
|
112
|
+
returned.set(e.fn, e.sret);
|
|
113
|
+
}
|
|
114
|
+
} else if (named !== undefined && symbols.has(named)) {
|
|
115
|
+
valueRefs.add(named);
|
|
67
116
|
}
|
|
68
117
|
exprChildren(e).forEach(visitExpr);
|
|
69
118
|
};
|
|
@@ -76,19 +125,51 @@ export function collectSymbolRefs(
|
|
|
76
125
|
stmtExprs(s).forEach(visitExpr);
|
|
77
126
|
stmtChildren(s).forEach(visitStmt);
|
|
78
127
|
};
|
|
79
|
-
body.forEach(visitStmt);
|
|
80
|
-
|
|
128
|
+
tree.body.forEach(visitStmt);
|
|
129
|
+
const holdsPointer = new Set([...pointerCellsOf(tree), ...pointerPartners(tree)]);
|
|
130
|
+
// A call target is a ref in its own right, whether or not the body also names it as a value —
|
|
131
|
+
// `Object.hasOwn`, because `prototypes` is caller-supplied JSON and a callee may be named
|
|
132
|
+
// `toString`. The union is sorted as one list so the rendered block stays deterministic.
|
|
133
|
+
const proto = new Map<string, NonNullable<SymbolRef['proto']>>();
|
|
134
|
+
for (const n of called) {
|
|
135
|
+
// TWO FACTS ABOUT ONE NAME, AND A DISAGREEMENT IS SILENCE — decided at the TABLE, not here. A
|
|
136
|
+
// map entry that states a data shape for this name contradicts the prototype that calls it a
|
|
137
|
+
// function, and `proto.ts` `prototypesFromSymbols` has already taken the `returns` off such an
|
|
138
|
+
// entry, so what arrives here states no printable prototype and the frontend read the call the
|
|
139
|
+
// same way. Testing it a second time at this reader is what let the two answer differently.
|
|
140
|
+
const p = Object.hasOwn(prototypes, n) ? spellableProto(prototypes[n], target, returned.get(n)) : undefined;
|
|
141
|
+
if (p !== undefined) {
|
|
142
|
+
proto.set(n, p);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return [...new Set([...valueRefs, ...proto.keys()])]
|
|
81
146
|
.sort()
|
|
82
147
|
.filter((n) => {
|
|
83
|
-
if (called.has(n)) {
|
|
148
|
+
if (called.has(n) && !proto.has(n)) {
|
|
84
149
|
onRefused?.(n, 'call-target');
|
|
85
150
|
return false;
|
|
86
151
|
}
|
|
87
|
-
if (n ===
|
|
152
|
+
if (n === tree.name) {
|
|
88
153
|
onRefused?.(n, 'self-name');
|
|
89
154
|
return false;
|
|
90
155
|
}
|
|
91
156
|
return true;
|
|
92
157
|
})
|
|
93
|
-
.map((n) =>
|
|
158
|
+
.map((n) => {
|
|
159
|
+
const p = proto.get(n);
|
|
160
|
+
// A call target the map never mentioned still has to carry an `info` — it is a code symbol
|
|
161
|
+
// and that is all this knows about it. `declare.ts` prints the prototype and never reaches
|
|
162
|
+
// the shape, so nothing here is guessed about storage that does not exist.
|
|
163
|
+
const info = symbols.get(n) ?? { name: n, kind: 'code' as const };
|
|
164
|
+
const r = p ? returned.get(n) : undefined;
|
|
165
|
+
const layout = r?.declared !== undefined ? prototypes[n].returnLayout : undefined;
|
|
166
|
+
const definition = layout?.members !== undefined ? { name: r!.name, declared: r!.declared!, layout } : undefined;
|
|
167
|
+
return {
|
|
168
|
+
name: n,
|
|
169
|
+
info,
|
|
170
|
+
...(p ? { proto: p } : {}),
|
|
171
|
+
...(definition ? { returned: definition } : {}),
|
|
172
|
+
...(holdsPointer.has(n) ? { holdsPointer: true as const } : {}),
|
|
173
|
+
};
|
|
174
|
+
});
|
|
94
175
|
}
|