@asmlift/core 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -30
- package/package.json +1 -1
- package/src/aggregate.ts +248 -0
- package/src/backend/c.ts +5 -2
- package/src/backend/cfamily.ts +236 -37
- package/src/backend/cpp.ts +114 -9
- package/src/backend/pascal.ts +39 -0
- package/src/cdecl/constant.ts +59 -0
- package/src/cdecl/lex.ts +209 -0
- package/src/cdecl/parse.ts +975 -0
- package/src/cdecl/spell.ts +69 -0
- package/src/codegen-flags.ts +38 -6
- package/src/compiler-diagnostics.ts +194 -0
- package/src/contracts.ts +615 -79
- package/src/declare.ts +75 -5
- package/src/frontend/asmdata.ts +19 -5
- package/src/frontend/call-plan.ts +686 -0
- package/src/frontend/device-pins.ts +277 -0
- package/src/frontend/errors.ts +5 -0
- package/src/frontend/flags-edge.ts +101 -0
- package/src/frontend/fpu.ts +167 -0
- package/src/frontend/frame-objects.ts +2131 -0
- package/src/frontend/high-half.ts +2 -1
- package/src/frontend/local-object.ts +490 -0
- package/src/frontend/mips.ts +363 -40
- package/src/frontend/opaque.ts +97 -18
- package/src/frontend/ppc.ts +520 -129
- package/src/frontend/reloc-symbol.ts +94 -13
- package/src/frontend/splat.ts +108 -5
- package/src/frontend/ssa.ts +669 -76
- package/src/frontend/stackargs.ts +120 -39
- package/src/frontend/thumb.ts +2764 -2018
- package/src/ir/alias.ts +29 -0
- package/src/ir/core.ts +101 -3
- package/src/ir/discipline.ts +248 -0
- package/src/ir/float-bits.ts +47 -0
- package/src/ir/opcodes.ts +84 -112
- package/src/ir/parse.ts +1 -0
- package/src/ir/types.ts +118 -2
- package/src/ir/verify.ts +150 -3
- package/src/l3/address.ts +5 -0
- package/src/l3/argcopy.ts +38 -6
- package/src/l3/ast.ts +186 -14
- package/src/l3/coalesce.ts +21 -13
- package/src/l3/dce.ts +32 -10
- package/src/l3/gates.ts +9 -3
- package/src/l3/hoist.ts +8 -7
- package/src/l3/initfirst.ts +27 -9
- package/src/l3/intcell.ts +97 -0
- package/src/l3/mentions.ts +10 -4
- package/src/l3/narrowdecl.ts +218 -0
- package/src/l3/nearbase.ts +10 -5
- package/src/l3/parkfirst.ts +8 -5
- package/src/l3/pollguard.ts +3 -2
- package/src/l3/ptrcell.ts +357 -0
- package/src/l3/regspell.ts +17 -3
- package/src/l3/reindex.ts +10 -7
- package/src/l3/symbol-refs.ts +104 -23
- package/src/l3/tailmerge.ts +6 -19
- package/src/l3/typing.ts +137 -25
- package/src/l3/unreduce.ts +4 -4
- package/src/l3/volstore.ts +96 -7
- package/src/mangle.ts +11 -1
- package/src/pattern/engine.ts +19 -16
- package/src/pipeline.ts +57 -12
- package/src/proto-context.ts +788 -0
- package/src/proto.ts +689 -50
- package/src/raise/arrays.ts +4 -3
- package/src/raise/declared-volatile.ts +131 -0
- package/src/raise/divpow2.ts +2 -2
- package/src/raise/errors.ts +17 -0
- package/src/raise/floathelpers.ts +243 -0
- package/src/raise/globalshape.ts +11 -5
- package/src/raise/gvn.ts +26 -5
- package/src/raise/narrow.ts +29 -2
- package/src/raise/narrowlocal.ts +11 -6
- package/src/raise/offsetnames.ts +510 -0
- package/src/raise/pairparams.ts +81 -0
- package/src/raise/paramwidth.ts +7 -0
- package/src/raise/pre-recovery.ts +78 -4
- package/src/raise/recover.ts +14 -10
- package/src/raise/retsink.ts +1 -1
- package/src/raise/shortcircuit.ts +41 -19
- package/src/raise/softdiv.ts +21 -23
- package/src/raise/struct-arrays.ts +5 -4
- package/src/raise/structs.ts +311 -98
- package/src/raise/truncload.ts +366 -0
- package/src/raise/widehelpers.ts +106 -0
- package/src/rank-declare.ts +30 -5
- package/src/rank-variations.ts +36 -8
- package/src/rank.ts +276 -80
- package/src/runtime-helpers.ts +287 -0
- package/src/stillborn.ts +289 -0
- package/src/structure/analysis.ts +1285 -448
- package/src/structure/hazards.ts +633 -58
- package/src/structure/local-statics.ts +381 -0
- package/src/structure/namecoalesce.ts +1 -1
- package/src/structure/redundant-test.ts +99 -0
- package/src/structure/structure.ts +1772 -395
- package/src/structure/switch-recover.ts +159 -78
- package/src/symbols.ts +38 -3
- package/src/target.ts +662 -59
- package/src/trace.ts +44 -24
- package/src/variation-definitions.ts +119 -1
- package/src/variation-tokens.ts +20 -6
package/src/backend/cfamily.ts
CHANGED
|
@@ -10,8 +10,10 @@
|
|
|
10
10
|
// below the signature is this file's: declarations, statements, expressions, precedence, the
|
|
11
11
|
// legalizing casts, and the recovered-struct declaration spelling (which is why that lives here
|
|
12
12
|
// too, shared with the scoring layer's synthesized declarations so the two cannot drift).
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
13
|
+
import { doubleLiteral } from '../ir/float-bits';
|
|
14
|
+
import { IrType, T, scalarTypeForAccess, typeEquals, typeToString } from '../ir/types';
|
|
15
|
+
import { BinOp, Expr, SFn, SStatic, Stmt, dotBase } from '../l3/ast';
|
|
16
|
+
import { legalizePointerCells } from '../l3/ptrcell';
|
|
15
17
|
import { orderSlotLocals } from '../l3/slotorder';
|
|
16
18
|
import {
|
|
17
19
|
type PrintEnv,
|
|
@@ -20,6 +22,7 @@ import {
|
|
|
20
22
|
declaredTypes,
|
|
21
23
|
derefStrideOk,
|
|
22
24
|
exprCType,
|
|
25
|
+
exprIntWidth,
|
|
23
26
|
printEnv,
|
|
24
27
|
renderedIntSignedness,
|
|
25
28
|
writesNonPointerIntoPointer,
|
|
@@ -28,6 +31,10 @@ import {
|
|
|
28
31
|
// C operator precedence (lower binds tighter). Used to emit MINIMAL parentheses. Shared: C++ has
|
|
29
32
|
// the same precedence for these operators.
|
|
30
33
|
const PREC: Record<BinOp, number> = {
|
|
34
|
+
'f*': 3,
|
|
35
|
+
'f/': 3,
|
|
36
|
+
'f+': 4,
|
|
37
|
+
'f-': 4,
|
|
31
38
|
'*': 3,
|
|
32
39
|
'/': 3,
|
|
33
40
|
'/u': 3, // the unsigned twins spell as C's `/` and `%` (see C_SPELLING) — same precedence
|
|
@@ -64,17 +71,32 @@ const C_SPELLING: Partial<Record<BinOp, { token: string; signed: boolean }>> = {
|
|
|
64
71
|
'%u': { token: '%', signed: false },
|
|
65
72
|
};
|
|
66
73
|
|
|
74
|
+
/** The float operators (l3/ast.ts BinOp) spell as C's own tokens and take no operand pin: C's
|
|
75
|
+
* arithmetic over two floats is the IEEE operation the machine did. */
|
|
76
|
+
const FLOAT_TOKEN: Partial<Record<BinOp, string>> = { 'f+': '+', 'f-': '-', 'f*': '*', 'f/': '/' };
|
|
77
|
+
|
|
67
78
|
/** Spell a recovered type in the decomp C-family typedef vocabulary (`s32`/`u32`/`u8`/`T *`). */
|
|
68
79
|
export function cType(t: IrType): string {
|
|
69
80
|
if (t.kind === 'ptr') {
|
|
70
81
|
return `${cType(t.to)} *`;
|
|
71
82
|
}
|
|
72
83
|
if (t.kind === 'struct') {
|
|
73
|
-
return `struct ${t.name}`;
|
|
84
|
+
return t.declared ?? `struct ${t.name}`;
|
|
85
|
+
}
|
|
86
|
+
// Declared inline, where the struct member that holds it is declared (ir/types.ts).
|
|
87
|
+
if (t.kind === 'union') {
|
|
88
|
+
return `union { ${t.members.map((m) => `${cDeclare(m.type, m.name)};`).join(' ')} }`;
|
|
74
89
|
}
|
|
75
90
|
if (t.kind === 'array') {
|
|
76
91
|
return `${cType(t.elem)}[${t.count}]`;
|
|
77
92
|
} // ill-formed as a prefix; use cDeclare
|
|
93
|
+
// The C89 KEYWORDS, not the `f32`/`f64` typedefs a decomp project declares: the candidate prelude
|
|
94
|
+
// (`C_TYPEDEFS`) holds the integer family only, and a keyword needs no declaration in any
|
|
95
|
+
// translation unit — so no project context that already typedefs `f32` can collide with it.
|
|
96
|
+
// A float of no stated precision compiles the same under either keyword; `float` is its spelling.
|
|
97
|
+
if (t.kind === 'float') {
|
|
98
|
+
return t.width === 64 ? 'double' : 'float';
|
|
99
|
+
}
|
|
78
100
|
return typeToString(t); // s32 / u32 / u8 / unk32 (treated as s32 upstream)
|
|
79
101
|
}
|
|
80
102
|
|
|
@@ -83,7 +105,7 @@ export function cType(t: IrType): string {
|
|
|
83
105
|
* the prefix `cType name`. A NESTED array spells every extent after the name in declaration
|
|
84
106
|
* order (`u8 unk8[6][8]`) — one declarator, not an element type that is itself an array, which
|
|
85
107
|
* C has no syntax for and `cType` marks ill-formed as a prefix. */
|
|
86
|
-
function cDeclare(t: IrType, name: string): string {
|
|
108
|
+
export function cDeclare(t: IrType, name: string): string {
|
|
87
109
|
if (t.kind === 'array') {
|
|
88
110
|
const extents: number[] = [];
|
|
89
111
|
let e: IrType = t;
|
|
@@ -113,10 +135,11 @@ export interface StructFieldDecl {
|
|
|
113
135
|
bits?: number;
|
|
114
136
|
}
|
|
115
137
|
|
|
116
|
-
/** THE struct-declaration spelling — every `struct N { ... };` asmlift
|
|
117
|
-
* so the backend's recovered-struct decls and the scoring layer's synthesized
|
|
118
|
-
* drift apart.
|
|
119
|
-
*
|
|
138
|
+
/** THE struct-declaration spelling — every `struct N { ... };` asmlift recovers or synthesizes
|
|
139
|
+
* comes from here, so the backend's recovered-struct decls and the scoring layer's synthesized
|
|
140
|
+
* decls cannot drift apart. A struct the headers own is transcribed from their declaration
|
|
141
|
+
* instead (declare.ts `declaredStructDecl`). One line, fields in caller order (the type is
|
|
142
|
+
* self-describing: padding is the caller's discipline, already present as real fields). */
|
|
120
143
|
export function renderStructDecl(name: string, fields: StructFieldDecl[]): string {
|
|
121
144
|
const one = (f: StructFieldDecl) =>
|
|
122
145
|
`${f.volatile ? 'volatile ' : ''}${cDeclare(f.type, f.name)}${f.bits !== undefined ? ` : ${f.bits}` : ''};`;
|
|
@@ -166,18 +189,23 @@ function legalizedIndexBase(ix: Extract<Expr, { k: 'index' }>, vt: PrintEnv): Ex
|
|
|
166
189
|
};
|
|
167
190
|
}
|
|
168
191
|
|
|
169
|
-
/** The
|
|
192
|
+
/** The integer cast that PINS an operand's signedness, AT THE OPERAND'S OWN RANK.
|
|
170
193
|
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
|
|
177
|
-
|
|
194
|
+
* The width is not a constant: `(s32)a / (s32)b` over two `s64`s does not pin a signedness, it
|
|
195
|
+
* truncates the operation to 32 bits and calls a different helper than the machine did. So the
|
|
196
|
+
* rank comes from `exprIntWidth`, which is total and answers 64 only where it can prove it.
|
|
197
|
+
*
|
|
198
|
+
* An existing integer cast AT THE SAME WIDTH is REPLACED rather than wrapped — `(u32)(s32)&g` and
|
|
199
|
+
* `(u32)&g` are the same bytes, and the arithmetic rules upstream do emit that inner cast
|
|
200
|
+
* (intifyAddr). A cast at a DIFFERENT width is a narrowing the source wrote and must be kept. The
|
|
201
|
+
* replacement CARRIES the qualifier: re-typing a `volatile` cast without it drops an assertion the
|
|
202
|
+
* differ cannot referee the loss of, which is why l3/initfirst.ts's `stripWideIntCast` refuses the
|
|
203
|
+
* same peel one pass over. */
|
|
204
|
+
function recastInt(x: Expr, signed: boolean, width: 32 | 64): Expr {
|
|
205
|
+
const replaced = x.k === 'cast' && x.to.kind === 'int' && x.to.width === width ? x : undefined;
|
|
178
206
|
return {
|
|
179
207
|
k: 'cast',
|
|
180
|
-
to: T.int(
|
|
208
|
+
to: T.int(width, signed),
|
|
181
209
|
...(replaced?.volatile === true ? { volatile: true as const } : {}),
|
|
182
210
|
e: replaced ? replaced.e : x,
|
|
183
211
|
};
|
|
@@ -205,14 +233,65 @@ function recast32(x: Expr, signed: boolean): Expr {
|
|
|
205
233
|
* the division unsigned. Verified by compiling: `((u32)a / b) / 7` calls `__udivsi3` twice,
|
|
206
234
|
* `(s32)((u32)a / b) / 7` calls `__udivsi3` then `__divsi3`. */
|
|
207
235
|
function pinnedOperands(e0: Extract<Expr, { k: 'bin' }>, wantSigned: boolean, vt: PrintEnv): [Expr, Expr] {
|
|
236
|
+
// A shift reads its LEFT operand's rank; a divide reads the wider of the two, which is what the
|
|
237
|
+
// usual arithmetic conversions make the operation's own rank.
|
|
208
238
|
if (e0.op === '>>' || e0.op === '>>>') {
|
|
209
|
-
|
|
239
|
+
const w = exprIntWidth(e0.l, vt.type);
|
|
240
|
+
return [renderedIntSignedness(e0.l, vt.type) === wantSigned ? e0.l : recastInt(e0.l, wantSigned, w), e0.r];
|
|
210
241
|
}
|
|
211
242
|
if (arithConversionSignedness(e0.l, e0.r, vt.type) === wantSigned) {
|
|
212
243
|
return [e0.l, e0.r];
|
|
213
244
|
}
|
|
214
|
-
const
|
|
215
|
-
|
|
245
|
+
const w = exprIntWidth(e0.l, vt.type) === 64 || exprIntWidth(e0.r, vt.type) === 64 ? 64 : 32;
|
|
246
|
+
const pinSigned = (x: Expr): Expr => (renderedIntSignedness(x, vt.type) === true ? x : recastInt(x, true, w));
|
|
247
|
+
return wantSigned ? [pinSigned(e0.l), pinSigned(e0.r)] : [recastInt(e0.l, false, w), e0.r];
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** Does passing `a` to a parameter declared `to` take a conversion C++ will not make implicitly?
|
|
251
|
+
* Three do, and C makes each of them implicitly, so the cast spells the conversion C would have
|
|
252
|
+
* made and the compiled code is C's: an object pointer to a pointer of another pointee (a conversion
|
|
253
|
+
* to `void *`, or one that only adds a qualifier, is implicit in C++ too), an integer to a pointer
|
|
254
|
+
* (the literal `0` is the one integer that converts), and a pointer to an integer. Only reached on a
|
|
255
|
+
* function compiled as C++ (`SFn.dialect`). */
|
|
256
|
+
function argConversion(a: Expr, to: string, vt: PrintEnv): boolean {
|
|
257
|
+
const from = exprCType(a, vt.type);
|
|
258
|
+
// qualifiers never decide it: a `T * const` parameter is a pointer parameter
|
|
259
|
+
const bare = to
|
|
260
|
+
.replace(/\b(?:const|volatile)\b/g, ' ')
|
|
261
|
+
.replace(/\s+/g, ' ')
|
|
262
|
+
.trim();
|
|
263
|
+
if (bare.endsWith('*')) {
|
|
264
|
+
if (a.k === 'const' && a.value === 0) {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
if (from?.kind !== 'ptr') {
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
return bare !== 'void *' && bare !== cType(from).replace(/\s+/g, ' ').trim();
|
|
271
|
+
}
|
|
272
|
+
return from?.kind === 'ptr';
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** The unqualified pointer a `const` static's address is spelled as where it reaches a place that
|
|
276
|
+
* converts it implicitly — an argument, an assigned or stored value, a returned one — or null for
|
|
277
|
+
* any other expression. The static is `const` because the target holds it in read-only data, not
|
|
278
|
+
* because the source's pointer types say so: mwcc rejects the implicit `const u8[4]` → `u8 *` a
|
|
279
|
+
* bare `g(tbl)` makes (compiled), and a pointer cast changes no instruction. */
|
|
280
|
+
function constEscape(e: Expr, vt: PrintEnv): IrType | null {
|
|
281
|
+
if (e.k === 'addr' && vt.constStatic(e.name)) {
|
|
282
|
+
const t = vt.type(e.name);
|
|
283
|
+
return t === undefined ? null : T.ptr(t.kind === 'array' ? t.elem : t);
|
|
284
|
+
}
|
|
285
|
+
if (e.k === 'bin' && (e.op === '+' || e.op === '-')) {
|
|
286
|
+
return constEscape(e.l, vt) ?? (e.op === '+' ? constEscape(e.r, vt) : null);
|
|
287
|
+
}
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** `e` printed where C converts it implicitly, with a `const` static's address cast (constEscape). */
|
|
292
|
+
function printConverted(e: Expr, vt: PrintEnv, leaf?: LeafHook): string {
|
|
293
|
+
const to = constEscape(e, vt);
|
|
294
|
+
return to === null ? printExpr(e, 99, vt, leaf) : `(${cType(to)})${printExpr(e, 2, vt, leaf)}`;
|
|
216
295
|
}
|
|
217
296
|
|
|
218
297
|
function printExpr(e: Expr, parentPrec: number, vt: PrintEnv, leaf?: LeafHook): string {
|
|
@@ -228,14 +307,51 @@ function printExpr(e: Expr, parentPrec: number, vt: PrintEnv, leaf?: LeafHook):
|
|
|
228
307
|
return e.name;
|
|
229
308
|
case 'const':
|
|
230
309
|
return String(e.value);
|
|
310
|
+
// a negative literal is a prefix `-`, and parenthesizes where one would (`un` below)
|
|
311
|
+
case 'fconst': {
|
|
312
|
+
const s = doubleLiteral(e.bits);
|
|
313
|
+
return parentPrec < 2 && s.startsWith('-') ? `(${s})` : s;
|
|
314
|
+
}
|
|
231
315
|
case 'addr': {
|
|
232
|
-
// `&gSym` — the address of a named global. A prefix operator;
|
|
233
|
-
// parent like the other prefix forms.
|
|
316
|
+
// `&gSym` — the address of a named global or of a frame-local object. A prefix operator;
|
|
317
|
+
// parenthesizes under a POSTFIX parent like the other prefix forms.
|
|
318
|
+
//
|
|
319
|
+
// AN ARRAY DECAYS INSTEAD, and the declared type is what says so, which is why the decision
|
|
320
|
+
// is here rather than on the node: `&` on `u8 sp0[16]` spells a `u8 (*)[16]` — a different
|
|
321
|
+
// type for the same byte, which every typed pointer parameter rejects (`passing arg 1 from
|
|
322
|
+
// incompatible pointer type`) — where the bare name is the `u8 *` the machine produced.
|
|
323
|
+
// Compiled both ways, the object is identical, so the `&` buys a diagnostic and nothing
|
|
324
|
+
// else. An identifier binds tighter than every parent, so the decayed form never
|
|
325
|
+
// parenthesizes. A name this env cannot type keeps the `&`: an unknown shape is not an
|
|
326
|
+
// array, and dropping the operator on a scalar would spell its value.
|
|
327
|
+
if (vt.type(e.name)?.kind === 'array') {
|
|
328
|
+
return e.name;
|
|
329
|
+
}
|
|
234
330
|
const g = `&${e.name}`;
|
|
235
331
|
return parentPrec < 2 ? `(${g})` : g;
|
|
236
332
|
}
|
|
237
|
-
case 'call':
|
|
238
|
-
|
|
333
|
+
case 'call': {
|
|
334
|
+
if (typeof e.fn !== 'string') {
|
|
335
|
+
// An unprototyped pointer to a function returning the call's own rank: C passes each
|
|
336
|
+
// argument as its promoted self, which is what a call with no declaration passes.
|
|
337
|
+
const ret = cType(T.int(e.wide64 ? 64 : 32, true));
|
|
338
|
+
return `((${ret} (*)())${rec(e.fn, 2)})(${e.args.map((a) => printConverted(a, vt, leaf)).join(', ')})`;
|
|
339
|
+
}
|
|
340
|
+
if (e.sret !== undefined) {
|
|
341
|
+
throw new Error(`the call to '${e.fn}' returns a struct through memory and is spelled only as a statement`);
|
|
342
|
+
}
|
|
343
|
+
const declared = vt.declaredArgs(e.fn);
|
|
344
|
+
return `${e.fn}(${e.args
|
|
345
|
+
.map((a, i) => {
|
|
346
|
+
const to = declared?.[i];
|
|
347
|
+
return to !== undefined && argConversion(a, to, vt) ? `(${to})${rec(a, 2)}` : printConverted(a, vt, leaf);
|
|
348
|
+
})
|
|
349
|
+
.join(', ')})`;
|
|
350
|
+
}
|
|
351
|
+
// `v++` / `v--`. Postfix over a bare identifier, so it binds tighter than every parent and
|
|
352
|
+
// never self-parenthesizes.
|
|
353
|
+
case 'postincr':
|
|
354
|
+
return `${e.name}${e.by === 1 ? '++' : '--'}`;
|
|
239
355
|
case 'index': {
|
|
240
356
|
// `*base` for the zero offset (a PREFIX operator, so a prefix-shaped base like a cast needs
|
|
241
357
|
// no parens: `*(u8 *)p` — but the whole form must self-parenthesize under a POSTFIX parent:
|
|
@@ -306,7 +422,8 @@ function printExpr(e: Expr, parentPrec: number, vt: PrintEnv, leaf?: LeafHook):
|
|
|
306
422
|
// A prefix operator: parenthesize under a POSTFIX parent (`(-a)[1]`), and parenthesize a
|
|
307
423
|
// same-op nested `-` (`-(-a)`, never `--a` — C lexes that as predecrement).
|
|
308
424
|
const inner = rec(e.e, 2);
|
|
309
|
-
const
|
|
425
|
+
const op = e.op === 'f-' ? '-' : e.op;
|
|
426
|
+
const s = `${op}${op === '-' && inner.startsWith('-') ? `(${inner})` : inner}`;
|
|
310
427
|
return parentPrec < 2 ? `(${s})` : s;
|
|
311
428
|
}
|
|
312
429
|
// A gap marker spells as a call to the UNDEFINED macro ASMLIFT_ERROR("reason", args…) — the
|
|
@@ -331,25 +448,41 @@ function printExpr(e: Expr, parentPrec: number, vt: PrintEnv, leaf?: LeafHook):
|
|
|
331
448
|
// operand cast that says which of the pair it is.
|
|
332
449
|
const pair = C_SPELLING[e.op];
|
|
333
450
|
const [l, r] = pair ? pinnedOperands(e, pair.signed, vt) : [e.l, e.r];
|
|
334
|
-
const s = `${rec(l, p)} ${pair ? pair.token : e.op} ${rec(r, p - 1)}`;
|
|
451
|
+
const s = `${rec(l, p)} ${pair ? pair.token : (FLOAT_TOKEN[e.op] ?? e.op)} ${rec(r, p - 1)}`;
|
|
335
452
|
return p > parentPrec ? `(${s})` : s;
|
|
336
453
|
}
|
|
337
454
|
}
|
|
338
455
|
}
|
|
339
456
|
|
|
457
|
+
/** `dest = fn(args)` for a call whose `args[0]` is the address of the struct it returns: a local
|
|
458
|
+
* declared with that struct type, named by its address. Any other destination has no spelling
|
|
459
|
+
* that hands the callee the same pointer — compiled, `*(struct Blob64 *)&gDst = makeblob(gBlob);`
|
|
460
|
+
* is a frame temp and a `memcpy`, where `gDst = makeblob(gBlob);` hands over `&gDst` itself. */
|
|
461
|
+
function structReturnCall(call: Extract<Expr, { k: 'call' }>, vt: PrintEnv, leaf?: LeafHook): string {
|
|
462
|
+
const [dest, ...rest] = call.args;
|
|
463
|
+
const declared = dest?.k === 'addr' ? vt.type(dest.name) : undefined;
|
|
464
|
+
if (dest?.k !== 'addr' || declared === undefined || !typeEquals(declared, call.sret!)) {
|
|
465
|
+
throw new Error(`the call to '${call.fn}' returns a struct into something other than a local of its type`);
|
|
466
|
+
}
|
|
467
|
+
return `${dest.name} = ${printExpr({ k: 'call', fn: call.fn, args: rest }, 99, vt, leaf)}`;
|
|
468
|
+
}
|
|
469
|
+
|
|
340
470
|
function printStmt(s: Stmt, indent: string, vt: PrintEnv, leaf?: LeafHook): string[] {
|
|
341
471
|
const pe = (e: Expr, p: number) => printExpr(e, p, vt, leaf);
|
|
342
472
|
switch (s.k) {
|
|
343
473
|
case 'assign':
|
|
344
|
-
return [`${indent}${s.name} = ${
|
|
474
|
+
return [`${indent}${s.name} = ${printConverted(s.value, vt, leaf)};`];
|
|
345
475
|
case 'store':
|
|
346
476
|
// The lvalue is a full Expr (`index` or `field`), so the leaf hook spells a member write
|
|
347
477
|
// (`this->x = …`) exactly as it spells a member read.
|
|
348
|
-
return [`${indent}${pe(s.lval, 2)} = ${
|
|
478
|
+
return [`${indent}${pe(s.lval, 2)} = ${printConverted(s.value, vt, leaf)};`];
|
|
349
479
|
case 'exprstmt':
|
|
480
|
+
if (s.value.k === 'call' && s.value.sret !== undefined) {
|
|
481
|
+
return [`${indent}${structReturnCall(s.value, vt, leaf)};`];
|
|
482
|
+
}
|
|
350
483
|
return [`${indent}${pe(s.value, 99)};`];
|
|
351
484
|
case 'return':
|
|
352
|
-
return [`${indent}return${s.value ? ' ' +
|
|
485
|
+
return [`${indent}return${s.value ? ' ' + printConverted(s.value, vt, leaf) : ''};`];
|
|
353
486
|
case 'if': {
|
|
354
487
|
const cond = pe(s.cond, 99);
|
|
355
488
|
if (s.then.length === 1 && s.else.length === 0 && s.then[0].k !== 'if') {
|
|
@@ -519,8 +652,12 @@ function endsTerminated(body: Stmt[]): boolean {
|
|
|
519
652
|
// traversal-vocabulary exemption.)
|
|
520
653
|
function legalizePointerWrites(fn: SFn): SFn {
|
|
521
654
|
const vt = declaredTypes(fn);
|
|
655
|
+
// …and the mirror: a rendered pointer into an INTEGER slot takes the slot's type. A cast keeps
|
|
656
|
+
// the sum it wraps, so the bytes are the uncast write's; CodeWarrior rejects that write outright.
|
|
522
657
|
const castTo = (t: IrType | undefined, e: Expr): Expr =>
|
|
523
|
-
writesNonPointerIntoPointer(t, e, vt)
|
|
658
|
+
writesNonPointerIntoPointer(t, e, vt) || (t?.kind === 'int' && exprCType(e, vt)?.kind === 'ptr')
|
|
659
|
+
? { k: 'cast', to: t, e }
|
|
660
|
+
: e;
|
|
524
661
|
const fix = (s: Stmt): Stmt => {
|
|
525
662
|
switch (s.k) {
|
|
526
663
|
case 'assign':
|
|
@@ -562,13 +699,18 @@ function cFamilyBody(fn0: SFn, leaf?: LeafHook): string[] {
|
|
|
562
699
|
// reader will see.
|
|
563
700
|
const vt: PrintEnv = printEnv(fn);
|
|
564
701
|
const lines: string[] = [];
|
|
702
|
+
for (const st of fn.statics ?? []) {
|
|
703
|
+
lines.push(...staticDefinition(st));
|
|
704
|
+
}
|
|
565
705
|
for (const l of fn.locals) {
|
|
566
706
|
// Both facts render at the PREFIX position, where C's declarator grammar reads them
|
|
567
707
|
// differently: on a scalar the qualifier binds to the object (`volatile u16 sp0`), on a
|
|
568
708
|
// pointer declarator to the pointee — the INNERMOST one for a multi-level pointer
|
|
569
|
-
// (`volatile u16 **
|
|
570
|
-
// no variation or recognizer produces one.
|
|
571
|
-
|
|
709
|
+
// (`volatile u16 **p`). An object-volatile POINTER (`u16 *volatile p`) has no inhabitant —
|
|
710
|
+
// no variation or recognizer produces one. The NAME is placed by `cDeclare`, the one
|
|
711
|
+
// placement every declaration position in this backend uses: a local can carry an array type
|
|
712
|
+
// — the storage extent a frame object pins — and C puts those extents after the name.
|
|
713
|
+
lines.push(` ${l.volatile || l.pointeeVolatile ? 'volatile ' : ''}${cDeclare(l.type, l.name)};`);
|
|
572
714
|
}
|
|
573
715
|
for (const s of fn.body) {
|
|
574
716
|
lines.push(...printStmt(s, ' ', vt, leaf));
|
|
@@ -576,6 +718,62 @@ function cFamilyBody(fn0: SFn, leaf?: LeafHook): string[] {
|
|
|
576
718
|
return lines;
|
|
577
719
|
}
|
|
578
720
|
|
|
721
|
+
/** One initial element: hex from 10 up, a negative one with its sign, so a byte table reads as the
|
|
722
|
+
* bytes and a signed one as its values. */
|
|
723
|
+
const initElement = (v: number): string =>
|
|
724
|
+
Math.abs(v) < 10 ? `${v}` : `${v < 0 ? '-' : ''}0x${Math.abs(v).toString(16)}`;
|
|
725
|
+
|
|
726
|
+
/** A byte array's initial elements as a string literal: printable characters as themselves, every
|
|
727
|
+
* other byte in three-digit octal (which no following digit can extend), and a `?` after a `?`
|
|
728
|
+
* escaped so no trigraph forms. The trailing zeros are left to the declared size, which C fills
|
|
729
|
+
* with zeros. */
|
|
730
|
+
function stringLiteral(init: readonly number[]): string {
|
|
731
|
+
let end = init.length;
|
|
732
|
+
while (end > 0 && (init[end - 1] & 0xff) === 0) {
|
|
733
|
+
end--;
|
|
734
|
+
}
|
|
735
|
+
let out = '';
|
|
736
|
+
for (const v of init.slice(0, end)) {
|
|
737
|
+
const b = v & 0xff;
|
|
738
|
+
const c = String.fromCharCode(b);
|
|
739
|
+
out +=
|
|
740
|
+
b < 0x20 || b > 0x7e || c === '"' || c === '\\' || (c === '?' && out.endsWith('?'))
|
|
741
|
+
? `\\${b.toString(8).padStart(3, '0')}`
|
|
742
|
+
: c;
|
|
743
|
+
}
|
|
744
|
+
return `"${out}"`;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
/** A function-scope static's definition, as the first lines of the body. An array's elements are
|
|
748
|
+
* laid out eight to a line; an alignment the type does not give is an attribute, which gcc and
|
|
749
|
+
* mwcc both read after the declarator. */
|
|
750
|
+
function staticDefinition(st: SStatic): string[] {
|
|
751
|
+
const aligned = st.align === undefined ? '' : ` __attribute__((aligned(${st.align})))`;
|
|
752
|
+
const head = ` static ${st.const ? 'const ' : ''}${cDeclare(st.type, st.name)}${aligned}`;
|
|
753
|
+
if (st.init === undefined) {
|
|
754
|
+
return [`${head};`];
|
|
755
|
+
}
|
|
756
|
+
if (st.string) {
|
|
757
|
+
return [`${head} = ${stringLiteral(st.init)};`];
|
|
758
|
+
}
|
|
759
|
+
if (st.type.kind !== 'array') {
|
|
760
|
+
return [`${head} = ${initElement(st.init[0])};`];
|
|
761
|
+
}
|
|
762
|
+
if (st.init.length <= 8) {
|
|
763
|
+
return [`${head} = { ${st.init.map(initElement).join(', ')} };`];
|
|
764
|
+
}
|
|
765
|
+
const rows: string[] = [];
|
|
766
|
+
for (let i = 0; i < st.init.length; i += 8) {
|
|
767
|
+
rows.push(
|
|
768
|
+
` ${st.init
|
|
769
|
+
.slice(i, i + 8)
|
|
770
|
+
.map(initElement)
|
|
771
|
+
.join(', ')},`,
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
return [`${head} = {`, ...rows, ' };'];
|
|
775
|
+
}
|
|
776
|
+
|
|
579
777
|
/** Struct declarations this function references, one `struct N { ... };` per recovered aggregate.
|
|
580
778
|
* The struct type is SELF-DESCRIBING: any padding needed to seat fields at their exact offsets is
|
|
581
779
|
* already present as real `u8[N]` pad fields — both raise/struct-arrays.ts (element strides) and
|
|
@@ -587,10 +785,11 @@ function structDecls(fn: SFn): string[] {
|
|
|
587
785
|
|
|
588
786
|
/** Assemble a full C-family function from a caller-supplied signature line and the shared body. */
|
|
589
787
|
export function emitCFamily(signature: string, fn0: SFn, leaf?: LeafHook): string {
|
|
590
|
-
// The declaration list is put into the target's own frame order
|
|
591
|
-
//
|
|
592
|
-
//
|
|
593
|
-
|
|
788
|
+
// The declaration list is put into the target's own frame order, and a pointer cell's integer
|
|
789
|
+
// uses converted (l3/ptrcell.ts), HERE — in the shared C-family assembler, reached only from
|
|
790
|
+
// `cBackend.emit` and `cppBackend(...).emit`, so both C-family backends print them and no `.emit(`
|
|
791
|
+
// call site does (l3/slotorder.ts says why `emit` owns it).
|
|
792
|
+
const fn = orderSlotLocals(legalizePointerCells(fn0));
|
|
594
793
|
const decls = structDecls(fn);
|
|
595
794
|
const preamble = decls.length ? decls.join('\n') + '\n' : '';
|
|
596
795
|
return preamble + [`${signature} {`, ...cFamilyBody(fn, leaf), '}'].join('\n') + '\n';
|
package/src/backend/cpp.ts
CHANGED
|
@@ -13,10 +13,14 @@
|
|
|
13
13
|
// Scope: free functions and non-virtual member functions with scalar/pointer params and named
|
|
14
14
|
// field access. Virtual dispatch, references, and constructors/destructors are deliberately not
|
|
15
15
|
// built ahead of an inhabitant.
|
|
16
|
-
import { Expr, LanguageBackend, SFn } from '../l3/ast';
|
|
17
|
-
import { type CppType, mangle, spellType } from '../mangle';
|
|
16
|
+
import { Expr, LanguageBackend, SFn, walkExprs } from '../l3/ast';
|
|
17
|
+
import { type CppType, declareCpp, mangle, spellType } from '../mangle';
|
|
18
|
+
import type { TargetDescription } from '../target';
|
|
18
19
|
import { LeafHook, cComment, emitCFamily } from './cfamily';
|
|
19
20
|
|
|
21
|
+
/** How the target's argument slots hold floats (`TargetDescription.fpu`). */
|
|
22
|
+
type FloatSlots = NonNullable<TargetDescription['fpu']>['slots'];
|
|
23
|
+
|
|
20
24
|
export interface CppClass {
|
|
21
25
|
fields: { name: string; type: CppType }[];
|
|
22
26
|
} // field i at word offset i
|
|
@@ -33,9 +37,104 @@ export function cppSymbol(spec: CppFnSpec): string {
|
|
|
33
37
|
return mangle({ name: spec.method, cls: spec.cls, params: spec.params.map((p) => p.type) });
|
|
34
38
|
}
|
|
35
39
|
|
|
36
|
-
/**
|
|
37
|
-
*
|
|
38
|
-
|
|
40
|
+
/** The lifted parameter each EXPLICIT spec parameter is (its SFn name), for a member function after
|
|
41
|
+
* `this` (SFn.params[0]). How depends on the target's float slot model (`TargetDescription.fpu`),
|
|
42
|
+
* because the lifted order is the ABI sort's (frontend/fpu.ts `fpuArgSlots`):
|
|
43
|
+
* - `'separate'` (PowerPC EABI): by register FILE, then by position within it. The files count
|
|
44
|
+
* independently and every float argument sorts after the integers, so `float g(float x, int n)`
|
|
45
|
+
* lifts as `(s32 a0, float a1)`. A float the body never reads leaves no hole, so the spec may
|
|
46
|
+
* have MORE floats than the lift (`int m(int n, float x)` lifts as `(s32 a0)`), never fewer.
|
|
47
|
+
* - `'leading'` (MIPS o32), and a target with no float file: by POSITION. The lifted order is the
|
|
48
|
+
* slot order, which is the source order, and an unread leading float is minted as an integer
|
|
49
|
+
* hole — so binding by file would hand `int k(float x, int n)`'s `n` the hole `a0`.
|
|
50
|
+
* NULL when the two contradict: a lifted float the spec has no float for, or has one of the other
|
|
51
|
+
* precision for. No binding of the rest is then trustworthy. The precision is part of the
|
|
52
|
+
* contradiction because the body is spelled over the spec's types: a lifted `double` bound to a
|
|
53
|
+
* `float a, float b` prints `a + b`, which is a single-precision add where the machine did a
|
|
54
|
+
* double one.
|
|
55
|
+
*
|
|
56
|
+
* THE PRECISION IS AN OPERATION'S, so only a parameter the body reads has one to contradict. A
|
|
57
|
+
* frontend types every float register of a function at the function's one precision
|
|
58
|
+
* (frontend/fpu.ts `fpPrecision`), and that includes an argument slot the body never reads, which
|
|
59
|
+
* it mints only to hold the later arguments' places: `float f1(double a, float b){ return b + b; }`
|
|
60
|
+
* is `fadds f1,f2,f2`, and its `a` is a single-precision hole that nothing states the width of. So
|
|
61
|
+
* is a lifted float of no stated precision (`T.fUnstated`) anywhere. Either binds a spec float of
|
|
62
|
+
* both widths, and the spec's is the one to print.
|
|
63
|
+
*
|
|
64
|
+
* EXCEPT WHERE THE HOLE'S WIDTH PLACED THE PARAMETERS AFTER IT. Under `'leading'` a float takes one
|
|
65
|
+
* integer slot for a single and two for a double, counted at the function's precision
|
|
66
|
+
* (`fpuArgSlots`), so an unread hole of the other width than the spec's moves every integer after
|
|
67
|
+
* it: `float m2(double a, float b, int *p, int *q){ *p = 0; return b + b; }` (gcc2.7.2kmc) is
|
|
68
|
+
* `sw zero,0(a3)` with `a` laid out as ONE slot, and binding by position would name `a3` `q`. So
|
|
69
|
+
* there an unread float followed by an integer parameter keeps its precision.
|
|
70
|
+
*
|
|
71
|
+
* A spec `double` bound to a lifted NON-float is the same misplacement. A double takes two integer
|
|
72
|
+
* slots under o32 and on a target with no float file (agbcc passes one in r0:r1), so a body that
|
|
73
|
+
* reads no FPU register mints it as two 32-bit holes: `void n1(double d, int *p, int *q){ *p = 0; }`
|
|
74
|
+
* (gcc2.7.2kmc) is `sw zero,0(a2)` and lifts as `(s32 a0, s32 a1, s32 *a2)`, where binding by
|
|
75
|
+
* position would name `a2` `q`. Only a lifted 64-bit parameter holds both slots, so a spec double
|
|
76
|
+
* over a narrower one refuses when a lifted parameter follows it, and when the body reads it — a
|
|
77
|
+
* read half would print as the whole double (agbcc `int u1(int a, double d)` returning the word
|
|
78
|
+
* `((int *)&d)[0]` is `add r0,r1,#0`). A lifted parameter after it is not a refusal when the double
|
|
79
|
+
* is the spec's last and the body reads none of the slots from it on, which is IDO homing a
|
|
80
|
+
* trailing double it never uses (`int o1(int a, double d){ return a; }` spills `a2`/`a3`): nothing
|
|
81
|
+
* then binds past it. */
|
|
82
|
+
export function bindSpecParams(
|
|
83
|
+
spec: Pick<CppFnSpec, 'cls' | 'params'>,
|
|
84
|
+
lifted: Pick<SFn, 'params' | 'body'>,
|
|
85
|
+
floatSlots: FloatSlots | undefined,
|
|
86
|
+
): (string | undefined)[] | null {
|
|
87
|
+
const floatBits = (t: CppType) => (t.ptr !== 0 ? null : t.base === 'float' ? 32 : t.base === 'double' ? 64 : null);
|
|
88
|
+
const isFloat = (t: CppType) => floatBits(t) !== null;
|
|
89
|
+
const read = new Set<string>();
|
|
90
|
+
for (const e of walkExprs(lifted.body)) {
|
|
91
|
+
if (e.k === 'var') {
|
|
92
|
+
read.add(e.name);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const clashes = (p: SFn['params'][number], t: CppType | undefined, laysOut: boolean) =>
|
|
96
|
+
p.type.kind === 'float' &&
|
|
97
|
+
(t === undefined ||
|
|
98
|
+
floatBits(t) === null ||
|
|
99
|
+
(p.type.width !== null && (laysOut || read.has(p.name)) && floatBits(t) !== p.type.width));
|
|
100
|
+
const explicit = lifted.params.slice(spec.cls ? 1 : 0);
|
|
101
|
+
const lastAndUnread = (i: number) =>
|
|
102
|
+
i === spec.params.length - 1 && explicit.slice(i).every((p) => !read.has(p.name));
|
|
103
|
+
if (floatSlots !== 'separate') {
|
|
104
|
+
const clash = explicit.some((p, i) => {
|
|
105
|
+
const t = spec.params[i]?.type;
|
|
106
|
+
const later = explicit.slice(i + 1);
|
|
107
|
+
const oneSlot = p.type.kind !== 'float' && !('width' in p.type && p.type.width === 64);
|
|
108
|
+
return (
|
|
109
|
+
clashes(
|
|
110
|
+
p,
|
|
111
|
+
t,
|
|
112
|
+
later.some((q) => q.type.kind !== 'float'),
|
|
113
|
+
) ||
|
|
114
|
+
(t !== undefined &&
|
|
115
|
+
floatBits(t) === 64 &&
|
|
116
|
+
oneSlot &&
|
|
117
|
+
(later.length > 0 || read.has(p.name)) &&
|
|
118
|
+
!lastAndUnread(i))
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
return clash ? null : spec.params.map((_, i) => explicit[i]?.name);
|
|
122
|
+
}
|
|
123
|
+
const floats = explicit.filter((p) => p.type.kind === 'float');
|
|
124
|
+
const others = explicit.filter((p) => p.type.kind !== 'float');
|
|
125
|
+
const specFloats = spec.params.filter((p) => isFloat(p.type));
|
|
126
|
+
if (specFloats.length < floats.length || floats.some((p, i) => clashes(p, specFloats[i].type, false))) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
let f = 0;
|
|
130
|
+
let o = 0;
|
|
131
|
+
return spec.params.map((p) => (isFloat(p.type) ? floats[f++] : others[o++])?.name);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Build a C++ backend for one function, parameterized by its recovered C++ signature and the float
|
|
135
|
+
* slot model of the target it was lifted for. For a member function SFn.params[0] is `this`; the
|
|
136
|
+
* rest bind to `params` by `bindSpecParams`. */
|
|
137
|
+
export function cppBackend(spec: CppFnSpec, floatSlots: FloatSlots | undefined): LanguageBackend {
|
|
39
138
|
return {
|
|
40
139
|
id: 'cpp',
|
|
41
140
|
spellsSwitchFallthrough: true,
|
|
@@ -43,15 +142,21 @@ export function cppBackend(spec: CppFnSpec): LanguageBackend {
|
|
|
43
142
|
// Map each lifted param var → its C++ meaning: `this` (bare member access) or a named param
|
|
44
143
|
// (a pointer-to-class param uses `->`). A pointer-to-known-class param is a member receiver.
|
|
45
144
|
const thisVar = spec.cls ? fn.params[0]?.name : undefined;
|
|
46
|
-
const explicitStart = spec.cls ? 1 : 0;
|
|
47
145
|
const rename = new Map<string, string>(); // lifted var → C++ name
|
|
48
146
|
const recv = new Map<string, { cls: string; via: 'this' | string }>(); // var → member receiver
|
|
49
147
|
if (thisVar) {
|
|
50
148
|
rename.set(thisVar, 'this');
|
|
51
149
|
recv.set(thisVar, { cls: spec.cls!, via: 'this' });
|
|
52
150
|
}
|
|
151
|
+
const bound = bindSpecParams(spec, fn, floatSlots);
|
|
152
|
+
if (!bound) {
|
|
153
|
+
throw new Error(
|
|
154
|
+
`cpp backend: the spec's floating-point parameters do not match the lifted function's — ` +
|
|
155
|
+
`supply a signature whose float and integer parameters are the ones it takes`,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
53
158
|
spec.params.forEach((p, i) => {
|
|
54
|
-
const v =
|
|
159
|
+
const v = bound[i];
|
|
55
160
|
if (!v) {
|
|
56
161
|
return;
|
|
57
162
|
}
|
|
@@ -115,7 +220,7 @@ export function cppBackend(spec: CppFnSpec): LanguageBackend {
|
|
|
115
220
|
return null;
|
|
116
221
|
};
|
|
117
222
|
|
|
118
|
-
const paramList = spec.params.map((p) =>
|
|
223
|
+
const paramList = spec.params.map((p) => declareCpp(p.type, p.name)).join(', ');
|
|
119
224
|
const decls = classDecls(spec, paramList);
|
|
120
225
|
const signature = `${spellType(spec.retType)} ${spec.cls ? spec.cls + '::' : ''}${spec.method}(${paramList})`;
|
|
121
226
|
return (decls ? decls + '\n' : '') + emitCFamily(signature, fn, leaf);
|
|
@@ -140,7 +245,7 @@ function typeWidth(t: CppType): number {
|
|
|
140
245
|
function classDecls(spec: CppFnSpec, paramList: string): string {
|
|
141
246
|
const out: string[] = [];
|
|
142
247
|
for (const [cname, cdef] of Object.entries(spec.classes ?? {})) {
|
|
143
|
-
const fields = cdef.fields.map((f) => `${
|
|
248
|
+
const fields = cdef.fields.map((f) => `${declareCpp(f.type, f.name)};`).join(' ');
|
|
144
249
|
const method = cname === spec.cls ? ` ${spellType(spec.retType)} ${spec.method}(${paramList});` : '';
|
|
145
250
|
out.push(`struct ${cname} { ${fields}${method} };`);
|
|
146
251
|
}
|