@asmlift/core 0.8.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -31
- package/package.json +1 -1
- package/src/aggregate.ts +397 -0
- package/src/backend/c.ts +6 -2
- package/src/backend/cfamily.ts +370 -56
- package/src/backend/cpp.ts +116 -10
- package/src/backend/pascal.ts +69 -11
- package/src/cdecl/constant.ts +59 -0
- package/src/cdecl/lex.ts +209 -0
- package/src/cdecl/parse.ts +975 -0
- package/src/cdecl/spell.ts +69 -0
- package/src/codegen-flags.ts +38 -6
- package/src/compiler-diagnostics.ts +194 -0
- package/src/contracts.ts +777 -84
- package/src/declare.ts +132 -13
- package/src/frontend/asmdata.ts +26 -6
- package/src/frontend/call-plan.ts +1590 -0
- package/src/frontend/dead-reads.ts +628 -0
- package/src/frontend/device-pins.ts +296 -0
- package/src/frontend/emit.ts +4 -3
- package/src/frontend/errors.ts +5 -0
- package/src/frontend/flags-edge.ts +101 -0
- package/src/frontend/fpu.ts +374 -0
- package/src/frontend/frame-objects.ts +3171 -0
- package/src/frontend/frontend.ts +64 -3
- package/src/frontend/high-half.ts +55 -5
- package/src/frontend/jump-table.ts +530 -0
- package/src/frontend/local-object.ts +616 -0
- package/src/frontend/mips.ts +2392 -208
- package/src/frontend/opaque.ts +97 -18
- package/src/frontend/own-struct-return.ts +500 -0
- package/src/frontend/ppc.ts +3137 -267
- package/src/frontend/register-struct-call.ts +484 -0
- package/src/frontend/reloc-symbol.ts +109 -13
- package/src/frontend/splat.ts +108 -5
- package/src/frontend/ssa.ts +2098 -89
- package/src/frontend/stackargs.ts +172 -42
- package/src/frontend/thumb.ts +4563 -2091
- package/src/ir/alias.ts +71 -0
- package/src/ir/bits.ts +681 -1
- package/src/ir/core.ts +199 -6
- package/src/ir/discipline.ts +313 -0
- package/src/ir/entry.ts +159 -0
- package/src/ir/float-bits.ts +47 -0
- package/src/{structure → ir}/loops.ts +6 -2
- package/src/ir/opcodes.ts +193 -121
- package/src/ir/parse.ts +1 -0
- package/src/ir/types.ts +202 -2
- package/src/ir/verify.ts +219 -3
- package/src/l3/address.ts +5 -0
- package/src/l3/argcopy.ts +38 -6
- package/src/l3/ast.ts +467 -23
- package/src/l3/basecse.ts +33 -5
- package/src/l3/coalesce.ts +21 -13
- package/src/l3/constinit.ts +382 -0
- package/src/l3/dce.ts +34 -12
- package/src/l3/gates.ts +9 -3
- package/src/l3/hoist.ts +8 -7
- package/src/l3/initfirst.ts +68 -19
- package/src/l3/intcell.ts +98 -0
- package/src/l3/mentions.ts +10 -4
- package/src/l3/narrowdecl.ts +259 -0
- package/src/l3/nearbase.ts +10 -5
- package/src/l3/parkfirst.ts +8 -5
- package/src/l3/pollguard.ts +11 -4
- package/src/l3/ptrcell.ts +354 -0
- package/src/l3/ptrfield.ts +123 -8
- package/src/l3/regspell.ts +26 -7
- package/src/l3/reindex.ts +293 -103
- package/src/l3/storage.ts +98 -1
- package/src/l3/symbol-refs.ts +182 -23
- package/src/l3/tailmerge.ts +9 -20
- package/src/l3/typing.ts +237 -32
- package/src/l3/unreduce.ts +10 -9
- package/src/l3/volatileptr.ts +17 -54
- package/src/l3/volstore.ts +96 -7
- package/src/mangle.ts +41 -1
- package/src/pattern/engine.ts +373 -82
- package/src/pipeline.ts +589 -27
- package/src/proto-context.ts +1648 -0
- package/src/proto.ts +1203 -49
- package/src/raise/aggmembers.ts +290 -0
- package/src/raise/arrays.ts +8 -3
- package/src/raise/declared-volatile.ts +307 -0
- package/src/raise/divpow2.ts +2 -2
- package/src/raise/errors.ts +17 -0
- package/src/raise/floathelpers.ts +464 -0
- package/src/raise/globalshape.ts +11 -5
- package/src/raise/gvn.ts +29 -8
- package/src/raise/latch.ts +162 -6
- package/src/raise/memberarrays.ts +10 -4
- package/src/raise/narrow.ts +29 -2
- package/src/raise/narrowlocal.ts +11 -6
- package/src/raise/offsetnames.ts +494 -0
- package/src/raise/pairparams.ts +114 -0
- package/src/raise/paramwidth.ts +203 -6
- package/src/raise/pre-recovery.ts +174 -17
- package/src/raise/pre-structure.ts +54 -0
- package/src/raise/recover.ts +26 -15
- package/src/raise/reroll.ts +694 -0
- package/src/raise/retsink.ts +1 -1
- package/src/raise/shortcircuit.ts +66 -30
- package/src/raise/softdiv.ts +21 -23
- package/src/raise/storesign.ts +153 -0
- package/src/raise/struct-arrays.ts +34 -14
- package/src/raise/structs.ts +674 -98
- package/src/raise/truncload.ts +367 -0
- package/src/raise/unthread.ts +400 -0
- package/src/raise/widehelpers.ts +106 -0
- package/src/rank-declare.ts +143 -12
- package/src/rank-variations.ts +117 -31
- package/src/rank.ts +563 -122
- package/src/runtime-helpers.ts +405 -0
- package/src/stillborn.ts +289 -0
- package/src/structure/analysis.ts +1449 -449
- package/src/structure/arith-ops.ts +23 -0
- package/src/structure/globalaccess.ts +111 -9
- package/src/structure/hazards.ts +641 -114
- package/src/structure/local-statics.ts +517 -0
- package/src/structure/namecoalesce.ts +2 -2
- package/src/structure/pointer-spelling.ts +1106 -0
- package/src/structure/redundant-test.ts +99 -0
- package/src/structure/structure.ts +3478 -975
- package/src/structure/switch-recover.ts +403 -178
- package/src/symbols.ts +44 -3
- package/src/target.ts +1387 -71
- package/src/trace.ts +66 -28
- package/src/variation-definitions.ts +243 -13
- package/src/variation-gates.ts +2 -0
- package/src/variation-tokens.ts +25 -7
package/src/backend/cfamily.ts
CHANGED
|
@@ -10,8 +10,11 @@
|
|
|
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, InitAddress, SFn, SStatic, Stmt, dotBase } from '../l3/ast';
|
|
16
|
+
import { foldConstantImages } from '../l3/constinit';
|
|
17
|
+
import { legalizePointerCells } from '../l3/ptrcell';
|
|
15
18
|
import { orderSlotLocals } from '../l3/slotorder';
|
|
16
19
|
import {
|
|
17
20
|
type PrintEnv,
|
|
@@ -20,14 +23,26 @@ import {
|
|
|
20
23
|
declaredTypes,
|
|
21
24
|
derefStrideOk,
|
|
22
25
|
exprCType,
|
|
26
|
+
exprIntWidth,
|
|
23
27
|
printEnv,
|
|
24
28
|
renderedIntSignedness,
|
|
25
29
|
writesNonPointerIntoPointer,
|
|
26
30
|
} from '../l3/typing';
|
|
31
|
+
import { spellableType } from '../proto';
|
|
27
32
|
|
|
28
33
|
// C operator precedence (lower binds tighter). Used to emit MINIMAL parentheses. Shared: C++ has
|
|
29
34
|
// the same precedence for these operators.
|
|
30
35
|
const PREC: Record<BinOp, number> = {
|
|
36
|
+
'f*': 3,
|
|
37
|
+
'f/': 3,
|
|
38
|
+
'f+': 4,
|
|
39
|
+
'f-': 4,
|
|
40
|
+
'f<': 6,
|
|
41
|
+
'f<=': 6,
|
|
42
|
+
'f>': 6,
|
|
43
|
+
'f>=': 6,
|
|
44
|
+
'f==': 7,
|
|
45
|
+
'f!=': 7,
|
|
31
46
|
'*': 3,
|
|
32
47
|
'/': 3,
|
|
33
48
|
'/u': 3, // the unsigned twins spell as C's `/` and `%` (see C_SPELLING) — same precedence
|
|
@@ -64,17 +79,44 @@ const C_SPELLING: Partial<Record<BinOp, { token: string; signed: boolean }>> = {
|
|
|
64
79
|
'%u': { token: '%', signed: false },
|
|
65
80
|
};
|
|
66
81
|
|
|
82
|
+
/** The float operators (l3/ast.ts BinOp) spell as C's own tokens and take no operand pin: C's
|
|
83
|
+
* arithmetic over two floats is the IEEE operation the machine did, and its relations over two
|
|
84
|
+
* floats are the IEEE compares — false on a NaN, `!=` true. */
|
|
85
|
+
const FLOAT_TOKEN: Partial<Record<BinOp, string>> = {
|
|
86
|
+
'f+': '+',
|
|
87
|
+
'f-': '-',
|
|
88
|
+
'f*': '*',
|
|
89
|
+
'f/': '/',
|
|
90
|
+
'f<': '<',
|
|
91
|
+
'f<=': '<=',
|
|
92
|
+
'f>': '>',
|
|
93
|
+
'f>=': '>=',
|
|
94
|
+
'f==': '==',
|
|
95
|
+
'f!=': '!=',
|
|
96
|
+
};
|
|
97
|
+
|
|
67
98
|
/** Spell a recovered type in the decomp C-family typedef vocabulary (`s32`/`u32`/`u8`/`T *`). */
|
|
68
99
|
export function cType(t: IrType): string {
|
|
69
100
|
if (t.kind === 'ptr') {
|
|
70
101
|
return `${cType(t.to)} *`;
|
|
71
102
|
}
|
|
72
103
|
if (t.kind === 'struct') {
|
|
73
|
-
return `struct ${t.name}`;
|
|
104
|
+
return t.declared ?? `struct ${t.name}`;
|
|
105
|
+
}
|
|
106
|
+
// Declared inline, where the struct member that holds it is declared (ir/types.ts).
|
|
107
|
+
if (t.kind === 'union') {
|
|
108
|
+
return `union { ${t.members.map((m) => `${cDeclare(m.type, m.name)};`).join(' ')} }`;
|
|
74
109
|
}
|
|
75
110
|
if (t.kind === 'array') {
|
|
76
111
|
return `${cType(t.elem)}[${t.count}]`;
|
|
77
112
|
} // ill-formed as a prefix; use cDeclare
|
|
113
|
+
// The C89 KEYWORDS, not the `f32`/`f64` typedefs a decomp project declares: the candidate prelude
|
|
114
|
+
// (`C_TYPEDEFS`) holds the integer family only, and a keyword needs no declaration in any
|
|
115
|
+
// translation unit — so no project context that already typedefs `f32` can collide with it.
|
|
116
|
+
// A float of no stated precision compiles the same under either keyword; `float` is its spelling.
|
|
117
|
+
if (t.kind === 'float') {
|
|
118
|
+
return t.width === 64 ? 'double' : 'float';
|
|
119
|
+
}
|
|
78
120
|
return typeToString(t); // s32 / u32 / u8 / unk32 (treated as s32 upstream)
|
|
79
121
|
}
|
|
80
122
|
|
|
@@ -83,7 +125,7 @@ export function cType(t: IrType): string {
|
|
|
83
125
|
* the prefix `cType name`. A NESTED array spells every extent after the name in declaration
|
|
84
126
|
* order (`u8 unk8[6][8]`) — one declarator, not an element type that is itself an array, which
|
|
85
127
|
* C has no syntax for and `cType` marks ill-formed as a prefix. */
|
|
86
|
-
function cDeclare(t: IrType, name: string): string {
|
|
128
|
+
export function cDeclare(t: IrType, name: string): string {
|
|
87
129
|
if (t.kind === 'array') {
|
|
88
130
|
const extents: number[] = [];
|
|
89
131
|
let e: IrType = t;
|
|
@@ -113,10 +155,11 @@ export interface StructFieldDecl {
|
|
|
113
155
|
bits?: number;
|
|
114
156
|
}
|
|
115
157
|
|
|
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
|
-
*
|
|
158
|
+
/** THE struct-declaration spelling — every `struct N { ... };` asmlift recovers or synthesizes
|
|
159
|
+
* comes from here, so the backend's recovered-struct decls and the scoring layer's synthesized
|
|
160
|
+
* decls cannot drift apart. A struct the headers own is transcribed from their declaration
|
|
161
|
+
* instead (declare.ts `declaredStructDecl`). One line, fields in caller order (the type is
|
|
162
|
+
* self-describing: padding is the caller's discipline, already present as real fields). */
|
|
120
163
|
export function renderStructDecl(name: string, fields: StructFieldDecl[]): string {
|
|
121
164
|
const one = (f: StructFieldDecl) =>
|
|
122
165
|
`${f.volatile ? 'volatile ' : ''}${cDeclare(f.type, f.name)}${f.bits !== undefined ? ` : ${f.bits}` : ''};`;
|
|
@@ -166,18 +209,23 @@ function legalizedIndexBase(ix: Extract<Expr, { k: 'index' }>, vt: PrintEnv): Ex
|
|
|
166
209
|
};
|
|
167
210
|
}
|
|
168
211
|
|
|
169
|
-
/** The
|
|
212
|
+
/** The integer cast that PINS an operand's signedness, AT THE OPERAND'S OWN RANK.
|
|
170
213
|
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
|
|
177
|
-
|
|
214
|
+
* The width is not a constant: `(s32)a / (s32)b` over two `s64`s does not pin a signedness, it
|
|
215
|
+
* truncates the operation to 32 bits and calls a different helper than the machine did. So the
|
|
216
|
+
* rank comes from `exprIntWidth`, which is total and answers 64 only where it can prove it.
|
|
217
|
+
*
|
|
218
|
+
* An existing integer cast AT THE SAME WIDTH is REPLACED rather than wrapped — `(u32)(s32)&g` and
|
|
219
|
+
* `(u32)&g` are the same bytes, and the arithmetic rules upstream do emit that inner cast
|
|
220
|
+
* (structure/pointer-spelling.ts ARITH_ROWS). A cast at a DIFFERENT width is a narrowing the
|
|
221
|
+
* source wrote and must be kept. The replacement CARRIES the qualifier: re-typing a `volatile`
|
|
222
|
+
* cast without it drops an assertion the differ cannot referee the loss of, which is why
|
|
223
|
+
* l3/initfirst.ts's `stripWideIntCast` refuses the same peel one pass over. */
|
|
224
|
+
function recastInt(x: Expr, signed: boolean, width: 32 | 64): Expr {
|
|
225
|
+
const replaced = x.k === 'cast' && x.to.kind === 'int' && x.to.width === width ? x : undefined;
|
|
178
226
|
return {
|
|
179
227
|
k: 'cast',
|
|
180
|
-
to: T.int(
|
|
228
|
+
to: T.int(width, signed),
|
|
181
229
|
...(replaced?.volatile === true ? { volatile: true as const } : {}),
|
|
182
230
|
e: replaced ? replaced.e : x,
|
|
183
231
|
};
|
|
@@ -205,14 +253,66 @@ function recast32(x: Expr, signed: boolean): Expr {
|
|
|
205
253
|
* the division unsigned. Verified by compiling: `((u32)a / b) / 7` calls `__udivsi3` twice,
|
|
206
254
|
* `(s32)((u32)a / b) / 7` calls `__udivsi3` then `__divsi3`. */
|
|
207
255
|
function pinnedOperands(e0: Extract<Expr, { k: 'bin' }>, wantSigned: boolean, vt: PrintEnv): [Expr, Expr] {
|
|
256
|
+
// A shift reads its LEFT operand's rank; a divide reads the wider of the two, which is what the
|
|
257
|
+
// usual arithmetic conversions make the operation's own rank.
|
|
208
258
|
if (e0.op === '>>' || e0.op === '>>>') {
|
|
209
|
-
|
|
259
|
+
const w = exprIntWidth(e0.l, vt.type);
|
|
260
|
+
return [renderedIntSignedness(e0.l, vt.type) === wantSigned ? e0.l : recastInt(e0.l, wantSigned, w), e0.r];
|
|
210
261
|
}
|
|
211
262
|
if (arithConversionSignedness(e0.l, e0.r, vt.type) === wantSigned) {
|
|
212
263
|
return [e0.l, e0.r];
|
|
213
264
|
}
|
|
214
|
-
const
|
|
215
|
-
|
|
265
|
+
const w = exprIntWidth(e0.l, vt.type) === 64 || exprIntWidth(e0.r, vt.type) === 64 ? 64 : 32;
|
|
266
|
+
const pinSigned = (x: Expr): Expr => (renderedIntSignedness(x, vt.type) === true ? x : recastInt(x, true, w));
|
|
267
|
+
return wantSigned ? [pinSigned(e0.l), pinSigned(e0.r)] : [recastInt(e0.l, false, w), e0.r];
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** Does passing `a` to a parameter declared `to` take a conversion C++ will not make implicitly?
|
|
271
|
+
* Three do, and C makes each of them implicitly, so the cast spells the conversion C would have
|
|
272
|
+
* made and the compiled code is C's: an object pointer to a pointer of another pointee (a conversion
|
|
273
|
+
* to `void *`, or one that only adds a qualifier, is implicit in C++ too), an integer to a pointer
|
|
274
|
+
* (the literal `0` is the one integer that converts), and a pointer to an integer. Only reached on a
|
|
275
|
+
* function compiled as C++ (`SFn.dialect`), or by a C front end that refuses the same three
|
|
276
|
+
* (`SFn.strictConversions` — mwcc). */
|
|
277
|
+
function argConversion(a: Expr, to: string, vt: PrintEnv): boolean {
|
|
278
|
+
const from = exprCType(a, vt.type);
|
|
279
|
+
// qualifiers never decide it: a `T * const` parameter is a pointer parameter
|
|
280
|
+
const bare = to
|
|
281
|
+
.replace(/\b(?:const|volatile)\b/g, ' ')
|
|
282
|
+
.replace(/\s+/g, ' ')
|
|
283
|
+
.trim();
|
|
284
|
+
if (bare.endsWith('*')) {
|
|
285
|
+
if (a.k === 'const' && a.value === 0) {
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
if (from?.kind !== 'ptr') {
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
return bare !== 'void *' && bare !== cType(from).replace(/\s+/g, ' ').trim();
|
|
292
|
+
}
|
|
293
|
+
return from?.kind === 'ptr';
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** The unqualified pointer a `const` static's address is spelled as where it reaches a place that
|
|
297
|
+
* converts it implicitly — an argument, an assigned or stored value, a returned one — or null for
|
|
298
|
+
* any other expression. The static is `const` because the target holds it in read-only data, not
|
|
299
|
+
* because the source's pointer types say so: mwcc rejects the implicit `const u8[4]` → `u8 *` a
|
|
300
|
+
* bare `g(tbl)` makes (compiled), and a pointer cast changes no instruction. */
|
|
301
|
+
function constEscape(e: Expr, vt: PrintEnv): IrType | null {
|
|
302
|
+
if (e.k === 'addr' && vt.constStatic(e.name)) {
|
|
303
|
+
const t = vt.type(e.name);
|
|
304
|
+
return t === undefined ? null : T.ptr(t.kind === 'array' ? t.elem : t);
|
|
305
|
+
}
|
|
306
|
+
if (e.k === 'bin' && (e.op === '+' || e.op === '-')) {
|
|
307
|
+
return constEscape(e.l, vt) ?? (e.op === '+' ? constEscape(e.r, vt) : null);
|
|
308
|
+
}
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** `e` printed where C converts it implicitly, with a `const` static's address cast (constEscape). */
|
|
313
|
+
function printConverted(e: Expr, vt: PrintEnv, leaf?: LeafHook): string {
|
|
314
|
+
const to = constEscape(e, vt);
|
|
315
|
+
return to === null ? printExpr(e, 99, vt, leaf) : `(${cType(to)})${printExpr(e, 2, vt, leaf)}`;
|
|
216
316
|
}
|
|
217
317
|
|
|
218
318
|
function printExpr(e: Expr, parentPrec: number, vt: PrintEnv, leaf?: LeafHook): string {
|
|
@@ -228,14 +328,59 @@ function printExpr(e: Expr, parentPrec: number, vt: PrintEnv, leaf?: LeafHook):
|
|
|
228
328
|
return e.name;
|
|
229
329
|
case 'const':
|
|
230
330
|
return String(e.value);
|
|
331
|
+
// a negative literal is a prefix `-`, and parenthesizes where one would (`un` below)
|
|
332
|
+
case 'fconst': {
|
|
333
|
+
const s = doubleLiteral(e.bits);
|
|
334
|
+
return parentPrec < 2 && s.startsWith('-') ? `(${s})` : s;
|
|
335
|
+
}
|
|
231
336
|
case 'addr': {
|
|
232
|
-
// `&gSym` — the address of a named global. A prefix operator;
|
|
233
|
-
// parent like the other prefix forms.
|
|
337
|
+
// `&gSym` — the address of a named global or of a frame-local object. A prefix operator;
|
|
338
|
+
// parenthesizes under a POSTFIX parent like the other prefix forms.
|
|
339
|
+
//
|
|
340
|
+
// AN ARRAY DECAYS INSTEAD, and the declared type is what says so, which is why the decision
|
|
341
|
+
// is here rather than on the node: `&` on `u8 sp0[16]` spells a `u8 (*)[16]` — a different
|
|
342
|
+
// type for the same byte, which every typed pointer parameter rejects (`passing arg 1 from
|
|
343
|
+
// incompatible pointer type`) — where the bare name is the `u8 *` the machine produced.
|
|
344
|
+
// Compiled both ways, the object is identical, so the `&` buys a diagnostic and nothing
|
|
345
|
+
// else. An identifier binds tighter than every parent, so the decayed form never
|
|
346
|
+
// parenthesizes. A name this env cannot type keeps the `&`: an unknown shape is not an
|
|
347
|
+
// array, and dropping the operator on a scalar would spell its value.
|
|
348
|
+
if (vt.type(e.name)?.kind === 'array') {
|
|
349
|
+
return e.name;
|
|
350
|
+
}
|
|
234
351
|
const g = `&${e.name}`;
|
|
235
352
|
return parentPrec < 2 ? `(${g})` : g;
|
|
236
353
|
}
|
|
237
|
-
case 'call':
|
|
238
|
-
|
|
354
|
+
case 'call': {
|
|
355
|
+
if (typeof e.fn !== 'string') {
|
|
356
|
+
// An unprototyped pointer to a function returning the call's own rank: C passes each
|
|
357
|
+
// argument as its promoted self, which is what a call with no declaration passes.
|
|
358
|
+
const ret = cType(T.int(e.wide64 ? 64 : 32, true));
|
|
359
|
+
return `((${ret} (*)())${rec(e.fn, 2)})(${e.args.map((a) => printConverted(a, vt, leaf)).join(', ')})`;
|
|
360
|
+
}
|
|
361
|
+
if (e.sret !== undefined) {
|
|
362
|
+
throw new Error(`the call to '${e.fn}' returns a struct through memory and is spelled only as a statement`);
|
|
363
|
+
}
|
|
364
|
+
const declared = vt.declaredArgs(e.fn);
|
|
365
|
+
return `${e.fn}(${e.args
|
|
366
|
+
.map((a, i) => {
|
|
367
|
+
const to = declared?.[i];
|
|
368
|
+
// A parameter type this cannot spell is an object pointer (target.ts `declaredArgTypes`),
|
|
369
|
+
// reached through `void *` from an argument whose own type is known to differ — a value
|
|
370
|
+
// this function types, or the address of an object it declares (a static) — never from
|
|
371
|
+
// `&gW` of a global only the project declares, which may already be the parameter's type.
|
|
372
|
+
const opaque = to !== undefined && !spellableType(to);
|
|
373
|
+
const typed = exprCType(a, vt.type) !== undefined || (a.k === 'addr' && vt.type(a.name) !== undefined);
|
|
374
|
+
return to !== undefined && (!opaque || typed) && argConversion(a, to, vt)
|
|
375
|
+
? `(${opaque ? 'void *' : to})${rec(a, 2)}`
|
|
376
|
+
: printConverted(a, vt, leaf);
|
|
377
|
+
})
|
|
378
|
+
.join(', ')})`;
|
|
379
|
+
}
|
|
380
|
+
// `v++` / `v--`. Postfix over a bare identifier, so it binds tighter than every parent and
|
|
381
|
+
// never self-parenthesizes.
|
|
382
|
+
case 'postincr':
|
|
383
|
+
return `${e.name}${e.by === 1 ? '++' : '--'}`;
|
|
239
384
|
case 'index': {
|
|
240
385
|
// `*base` for the zero offset (a PREFIX operator, so a prefix-shaped base like a cast needs
|
|
241
386
|
// no parens: `*(u8 *)p` — but the whole form must self-parenthesize under a POSTFIX parent:
|
|
@@ -267,7 +412,7 @@ function printExpr(e: Expr, parentPrec: number, vt: PrintEnv, leaf?: LeafHook):
|
|
|
267
412
|
}
|
|
268
413
|
return `${rec(base, 1)}${e.lead.map((l) => `[${rec(l, 99)}]`).join('')}[${rec(e.idx, 99)}]`;
|
|
269
414
|
}
|
|
270
|
-
if (e.idx.k === 'const' && e.idx.value === 0) {
|
|
415
|
+
if (e.idx.k === 'const' && e.idx.value === 0 && e.subscript !== true) {
|
|
271
416
|
const s = `*${rec(base, 2)}`;
|
|
272
417
|
return parentPrec < 2 ? `(${s})` : s;
|
|
273
418
|
}
|
|
@@ -306,7 +451,8 @@ function printExpr(e: Expr, parentPrec: number, vt: PrintEnv, leaf?: LeafHook):
|
|
|
306
451
|
// A prefix operator: parenthesize under a POSTFIX parent (`(-a)[1]`), and parenthesize a
|
|
307
452
|
// same-op nested `-` (`-(-a)`, never `--a` — C lexes that as predecrement).
|
|
308
453
|
const inner = rec(e.e, 2);
|
|
309
|
-
const
|
|
454
|
+
const op = e.op === 'f-' ? '-' : e.op;
|
|
455
|
+
const s = `${op}${op === '-' && inner.startsWith('-') ? `(${inner})` : inner}`;
|
|
310
456
|
return parentPrec < 2 ? `(${s})` : s;
|
|
311
457
|
}
|
|
312
458
|
// A gap marker spells as a call to the UNDEFINED macro ASMLIFT_ERROR("reason", args…) — the
|
|
@@ -331,25 +477,41 @@ function printExpr(e: Expr, parentPrec: number, vt: PrintEnv, leaf?: LeafHook):
|
|
|
331
477
|
// operand cast that says which of the pair it is.
|
|
332
478
|
const pair = C_SPELLING[e.op];
|
|
333
479
|
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)}`;
|
|
480
|
+
const s = `${rec(l, p)} ${pair ? pair.token : (FLOAT_TOKEN[e.op] ?? e.op)} ${rec(r, p - 1)}`;
|
|
335
481
|
return p > parentPrec ? `(${s})` : s;
|
|
336
482
|
}
|
|
337
483
|
}
|
|
338
484
|
}
|
|
339
485
|
|
|
486
|
+
/** `dest = fn(args)` for a call whose `args[0]` is the address of the struct it returns: a local
|
|
487
|
+
* declared with that struct type, named by its address. Any other destination has no spelling
|
|
488
|
+
* that hands the callee the same pointer — compiled, `*(struct Blob64 *)&gDst = makeblob(gBlob);`
|
|
489
|
+
* is a frame temp and a `memcpy`, where `gDst = makeblob(gBlob);` hands over `&gDst` itself. */
|
|
490
|
+
function structReturnCall(call: Extract<Expr, { k: 'call' }>, vt: PrintEnv, leaf?: LeafHook): string {
|
|
491
|
+
const [dest, ...rest] = call.args;
|
|
492
|
+
const declared = dest?.k === 'addr' ? vt.type(dest.name) : undefined;
|
|
493
|
+
if (dest?.k !== 'addr' || declared === undefined || !typeEquals(declared, call.sret!)) {
|
|
494
|
+
throw new Error(`the call to '${call.fn}' returns a struct into something other than a local of its type`);
|
|
495
|
+
}
|
|
496
|
+
return `${dest.name} = ${printExpr({ k: 'call', fn: call.fn, args: rest }, 99, vt, leaf)}`;
|
|
497
|
+
}
|
|
498
|
+
|
|
340
499
|
function printStmt(s: Stmt, indent: string, vt: PrintEnv, leaf?: LeafHook): string[] {
|
|
341
500
|
const pe = (e: Expr, p: number) => printExpr(e, p, vt, leaf);
|
|
342
501
|
switch (s.k) {
|
|
343
502
|
case 'assign':
|
|
344
|
-
return [`${indent}${s.name} = ${
|
|
503
|
+
return [`${indent}${s.name} = ${printConverted(s.value, vt, leaf)};`];
|
|
345
504
|
case 'store':
|
|
346
505
|
// The lvalue is a full Expr (`index` or `field`), so the leaf hook spells a member write
|
|
347
506
|
// (`this->x = …`) exactly as it spells a member read.
|
|
348
|
-
return [`${indent}${pe(s.lval, 2)} = ${
|
|
507
|
+
return [`${indent}${pe(s.lval, 2)} = ${printConverted(s.value, vt, leaf)};`];
|
|
349
508
|
case 'exprstmt':
|
|
509
|
+
if (s.value.k === 'call' && s.value.sret !== undefined) {
|
|
510
|
+
return [`${indent}${structReturnCall(s.value, vt, leaf)};`];
|
|
511
|
+
}
|
|
350
512
|
return [`${indent}${pe(s.value, 99)};`];
|
|
351
513
|
case 'return':
|
|
352
|
-
return [`${indent}return${s.value ? ' ' +
|
|
514
|
+
return [`${indent}return${s.value ? ' ' + printConverted(s.value, vt, leaf) : ''};`];
|
|
353
515
|
case 'if': {
|
|
354
516
|
const cond = pe(s.cond, 99);
|
|
355
517
|
if (s.then.length === 1 && s.else.length === 0 && s.then[0].k !== 'if') {
|
|
@@ -374,6 +536,7 @@ function printStmt(s: Stmt, indent: string, vt: PrintEnv, leaf?: LeafHook): stri
|
|
|
374
536
|
return out;
|
|
375
537
|
}
|
|
376
538
|
case 'while': {
|
|
539
|
+
refuseMisboundBreak(s.body, 'loop');
|
|
377
540
|
const out = [`${indent}while (${pe(s.cond, 99)}) {`];
|
|
378
541
|
for (const t of s.body) {
|
|
379
542
|
out.push(...printStmt(t, indent + ' ', vt, leaf));
|
|
@@ -382,6 +545,7 @@ function printStmt(s: Stmt, indent: string, vt: PrintEnv, leaf?: LeafHook): stri
|
|
|
382
545
|
return out;
|
|
383
546
|
}
|
|
384
547
|
case 'dowhile': {
|
|
548
|
+
refuseMisboundBreak(s.body, 'loop');
|
|
385
549
|
const out = [`${indent}do {`];
|
|
386
550
|
for (const t of s.body) {
|
|
387
551
|
out.push(...printStmt(t, indent + ' ', vt, leaf));
|
|
@@ -394,6 +558,7 @@ function printStmt(s: Stmt, indent: string, vt: PrintEnv, leaf?: LeafHook): stri
|
|
|
394
558
|
// `recognizeForLoops` and l3/reindex.ts): init/inc are each a SINGLE-LINE `assign` statement. `clause` renders one
|
|
395
559
|
// and strips its trailing `;` so it sits inside the header (`i = 0; c; i = i + 1`). A multi-line
|
|
396
560
|
// statement (an `if`/nested loop) would render mangled — but the recognizer never builds one here.
|
|
561
|
+
refuseMisboundBreak(s.body, 'loop');
|
|
397
562
|
const clause = (st: Stmt) => printStmt(st, '', vt, leaf).join(' ').replace(/;\s*$/, '').trim();
|
|
398
563
|
const out = [`${indent}for (${clause(s.init)}; ${pe(s.cond, 99)}; ${clause(s.inc)}) {`];
|
|
399
564
|
for (const t of s.body) {
|
|
@@ -417,11 +582,12 @@ function printStmt(s: Stmt, indent: string, vt: PrintEnv, leaf?: LeafHook): stri
|
|
|
417
582
|
* is one call. */
|
|
418
583
|
function printSwitchStmt(s: Extract<Stmt, { k: 'switch' }>, indent: string, vt: PrintEnv, leaf?: LeafHook): string[] {
|
|
419
584
|
const pe = (e: Expr, p: number) => printExpr(e, p, vt, leaf);
|
|
420
|
-
// A `break;` in an arm BODY is the innermost LOOP's in L3 (l3/ast.ts) and the SWITCH's in
|
|
421
|
-
// so printing one between `case` labels rebinds it — a changed program that reads as ordinary
|
|
585
|
+
// A plain `break;` in an arm BODY is the innermost LOOP's in L3 (l3/ast.ts) and the SWITCH's in
|
|
586
|
+
// C, so printing one between `case` labels rebinds it — a changed program that reads as ordinary
|
|
422
587
|
// C. Refused HERE because the rebinding is the printer's, for every producer rather than for
|
|
423
|
-
// the two switch regimes
|
|
424
|
-
//
|
|
588
|
+
// the two switch regimes (`refuseMisboundBreak`, which checks a loop body the same way for a
|
|
589
|
+
// `break` marked `ofSwitch`). The structurer spells a loop exit inside an arm as the switch's
|
|
590
|
+
// own `break` or refuses it, so no recovery reaches either today: a contract on the next one.
|
|
425
591
|
//
|
|
426
592
|
// `continue;` is deliberately NOT refused: C binds it to the smallest enclosing ITERATION
|
|
427
593
|
// statement, which a `switch` is not, so it already means what L3 means. What a pass that re-spells
|
|
@@ -430,9 +596,7 @@ function printSwitchStmt(s: Extract<Stmt, { k: 'switch' }>, indent: string, vt:
|
|
|
430
596
|
// `respellCountdown` in l3/reindex.ts, and l3/unreduce.ts) each scan switch arms for the node
|
|
431
597
|
// before firing, and a fourth must too.
|
|
432
598
|
for (const body of [...s.cases.map((c) => c.body), s.default ?? []]) {
|
|
433
|
-
|
|
434
|
-
throw new Error('c backend: a switch arm carries a loop-scoped `break;`, which C would bind to the switch');
|
|
435
|
-
}
|
|
599
|
+
refuseMisboundBreak(body, 'switch');
|
|
436
600
|
}
|
|
437
601
|
const out = [`${indent}switch (${pe(s.scrutinee, 99)}) {`];
|
|
438
602
|
const ci = indent + ' '; // case-label indent
|
|
@@ -491,13 +655,26 @@ function printSwitchStmt(s: Extract<Stmt, { k: 'switch' }>, indent: string, vt:
|
|
|
491
655
|
return out;
|
|
492
656
|
}
|
|
493
657
|
|
|
494
|
-
/**
|
|
495
|
-
*
|
|
496
|
-
* `
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
658
|
+
/** L3 has two `break`s (l3/ast.ts) and C one: C binds `break;` to the innermost enclosing loop OR
|
|
659
|
+
* switch, so a `break` prints as what L3 meant only where its own kind is that innermost
|
|
660
|
+
* statement. `body` is a loop body or a switch arm (`owner`); a loop or switch opened inside it
|
|
661
|
+
* captures its own breaks and is checked when it is printed, so it is not walked. Refused loud on
|
|
662
|
+
* either mismatch — a plain (loop) `break` in an arm, a switch's `break` in a loop body — since
|
|
663
|
+
* each prints as ordinary C that leaves a different statement. */
|
|
664
|
+
function refuseMisboundBreak(body: Stmt[], owner: 'loop' | 'switch'): void {
|
|
665
|
+
const misbound = (ss: Stmt[]): boolean =>
|
|
666
|
+
ss.some(
|
|
667
|
+
(s) =>
|
|
668
|
+
(s.k === 'break' && (s.ofSwitch === true) !== (owner === 'switch')) ||
|
|
669
|
+
(s.k === 'if' && (misbound(s.then) || misbound(s.else))),
|
|
670
|
+
);
|
|
671
|
+
if (misbound(body)) {
|
|
672
|
+
throw new Error(
|
|
673
|
+
owner === 'switch'
|
|
674
|
+
? 'c backend: a switch arm carries a loop-scoped `break;`, which C would bind to the switch'
|
|
675
|
+
: 'c backend: a loop body carries a switch-scoped `break;`, which C would bind to the loop',
|
|
676
|
+
);
|
|
677
|
+
}
|
|
501
678
|
}
|
|
502
679
|
|
|
503
680
|
// Does a statement list end in a control-flow terminator (so a trailing `break;` would be dead)?
|
|
@@ -512,6 +689,22 @@ function endsTerminated(body: Stmt[]): boolean {
|
|
|
512
689
|
);
|
|
513
690
|
}
|
|
514
691
|
|
|
692
|
+
/** Is `e` a pointer to another pointee than the pointer slot `t` declares, as rendered? Its pointer
|
|
693
|
+
* type is its C type, or, for the address of a declared object (which `exprCType` leaves
|
|
694
|
+
* unknown), a pointer to that object or to an array's element, the way it decays. A `void *` slot
|
|
695
|
+
* takes every object pointer implicitly. A `void *` value converts into any pointer slot in C
|
|
696
|
+
* (mwcc's C takes `gp = (void *)x` into an `s32 *gp`, compiled) but not in C++ (`cxx`). An unknown
|
|
697
|
+
* type is no opinion. */
|
|
698
|
+
function otherPointee(t: IrType | undefined, e: Expr, vt: (name: string) => IrType | undefined, cxx: boolean): boolean {
|
|
699
|
+
if (t?.kind !== 'ptr' || t.to.kind === 'void') {
|
|
700
|
+
return false;
|
|
701
|
+
}
|
|
702
|
+
const of = e.k === 'addr' ? vt(e.name) : undefined;
|
|
703
|
+
const ct = of !== undefined ? T.ptr(of.kind === 'array' ? of.elem : of) : exprCType(e, vt);
|
|
704
|
+
const p = ct?.kind === 'array' ? T.ptr(ct.elem) : ct;
|
|
705
|
+
return p?.kind === 'ptr' && (cxx || p.to.kind !== 'void') && !typeEquals(p, t);
|
|
706
|
+
}
|
|
707
|
+
|
|
515
708
|
// C-FAMILY WRITE LEGALIZATION (the assign-side sibling of the deref legalization in printExpr):
|
|
516
709
|
// the C family's answer to `writesNonPointerIntoPointer` is the reinterpret cast to the DECLARED
|
|
517
710
|
// type — exactly what the machine's register move does — where Pascal, having no such cast,
|
|
@@ -519,8 +712,21 @@ function endsTerminated(body: Stmt[]): boolean {
|
|
|
519
712
|
// traversal-vocabulary exemption.)
|
|
520
713
|
function legalizePointerWrites(fn: SFn): SFn {
|
|
521
714
|
const vt = declaredTypes(fn);
|
|
715
|
+
// …and the mirror: a rendered pointer into an INTEGER slot takes the slot's type. A cast keeps
|
|
716
|
+
// the sum it wraps, so the bytes are the uncast write's; CodeWarrior rejects that write outright.
|
|
717
|
+
//
|
|
718
|
+
// …and, where the compiler refuses the third implicit conversion too (C++, or mwcc's C:
|
|
719
|
+
// `SFn.strictConversions`), a pointer of ANOTHER pointee into a pointer slot: mwcc rejects
|
|
720
|
+
// `return t;` from an `s32 *` function when `t` is a `const u32[2]` static (mwcc: "illegal implicit
|
|
721
|
+
// conversion" of an `unsigned int *` to an `int *`, compiled), and takes it cast (`otherPointee`
|
|
722
|
+
// says which `void *` converts implicitly).
|
|
723
|
+
const strict = fn.dialect === 'c++' || fn.strictConversions === true;
|
|
522
724
|
const castTo = (t: IrType | undefined, e: Expr): Expr =>
|
|
523
|
-
writesNonPointerIntoPointer(t, e, vt)
|
|
725
|
+
writesNonPointerIntoPointer(t, e, vt) ||
|
|
726
|
+
(t?.kind === 'int' && exprCType(e, vt)?.kind === 'ptr') ||
|
|
727
|
+
(strict && otherPointee(t, e, vt, fn.dialect === 'c++'))
|
|
728
|
+
? { k: 'cast', to: t!, e }
|
|
729
|
+
: e;
|
|
524
730
|
const fix = (s: Stmt): Stmt => {
|
|
525
731
|
switch (s.k) {
|
|
526
732
|
case 'assign':
|
|
@@ -555,20 +761,28 @@ function legalizePointerWrites(fn: SFn): SFn {
|
|
|
555
761
|
/** The body of a C-family function: local declarations + statements, one string per line. The
|
|
556
762
|
* SIGNATURE (return type + name + params, plus any C++ scope/`this`/mangling) is the caller's —
|
|
557
763
|
* that is the language-divergent part each backend owns. */
|
|
558
|
-
function cFamilyBody(fn0: SFn, leaf
|
|
764
|
+
function cFamilyBody(fn0: SFn, leaf: LeafHook | undefined, designators: boolean): string[] {
|
|
559
765
|
const fn = legalizePointerWrites(fn0);
|
|
560
766
|
// The legalization env: every printed var's declared type and pointee volatility, from the SAME
|
|
561
767
|
// params/locals the emitted declarations come from — so the printer judges exactly the C the
|
|
562
768
|
// reader will see.
|
|
563
769
|
const vt: PrintEnv = printEnv(fn);
|
|
564
770
|
const lines: string[] = [];
|
|
771
|
+
for (const st of fn.statics ?? []) {
|
|
772
|
+
lines.push(...staticDefinition(st));
|
|
773
|
+
}
|
|
565
774
|
for (const l of fn.locals) {
|
|
566
775
|
// Both facts render at the PREFIX position, where C's declarator grammar reads them
|
|
567
776
|
// differently: on a scalar the qualifier binds to the object (`volatile u16 sp0`), on a
|
|
568
777
|
// pointer declarator to the pointee — the INNERMOST one for a multi-level pointer
|
|
569
|
-
// (`volatile u16 **
|
|
570
|
-
// no variation or recognizer produces one.
|
|
571
|
-
|
|
778
|
+
// (`volatile u16 **p`). An object-volatile POINTER (`u16 *volatile p`) has no inhabitant —
|
|
779
|
+
// no variation or recognizer produces one. The NAME is placed by `cDeclare`, the one
|
|
780
|
+
// placement every declaration position in this backend uses: a local can carry an array type
|
|
781
|
+
// — the storage extent a frame object pins — and C puts those extents after the name.
|
|
782
|
+
const head = ` ${l.volatile || l.pointeeVolatile ? 'volatile ' : ''}${cDeclare(l.type, l.name)}`;
|
|
783
|
+
lines.push(
|
|
784
|
+
...(l.init === undefined ? [`${head};`] : initialized(head, l.type, l.init, l.initString === true, designators)),
|
|
785
|
+
);
|
|
572
786
|
}
|
|
573
787
|
for (const s of fn.body) {
|
|
574
788
|
lines.push(...printStmt(s, ' ', vt, leaf));
|
|
@@ -576,6 +790,96 @@ function cFamilyBody(fn0: SFn, leaf?: LeafHook): string[] {
|
|
|
576
790
|
return lines;
|
|
577
791
|
}
|
|
578
792
|
|
|
793
|
+
/** One initial number: hex from 10 up, a negative one with its sign, so a byte table reads as the
|
|
794
|
+
* bytes and a signed one as its values. */
|
|
795
|
+
const initNumber = (v: number): string =>
|
|
796
|
+
Math.abs(v) < 10 ? `${v}` : `${v < 0 ? '-' : ''}0x${Math.abs(v).toString(16)}`;
|
|
797
|
+
|
|
798
|
+
/** One initial element of type `elem`: a number, or the address constant a relocated one holds,
|
|
799
|
+
* converted to the element's integer type — `(u32)&sym`, and with an addend `(u32)((u8 *)&sym + k)`:
|
|
800
|
+
* the offset in bytes, as the relocation counts it. Not `(u32)&sym + k`, which agbcc folds alike but
|
|
801
|
+
* mwcc refuses as an illegal constant expression (compiled); through a byte pointer both write the
|
|
802
|
+
* pointer-typed source's word and relocation (`&garr[2]`, `&garr[-1]`, compiled on both). */
|
|
803
|
+
function initElement(v: number | InitAddress, elem: IrType): string {
|
|
804
|
+
if (typeof v === 'number') {
|
|
805
|
+
return initNumber(v);
|
|
806
|
+
}
|
|
807
|
+
const t = cType(elem);
|
|
808
|
+
return v.addend === 0
|
|
809
|
+
? `(${t})&${v.symbol}`
|
|
810
|
+
: `(${t})((u8 *)&${v.symbol} ${v.addend < 0 ? '-' : '+'} ${initNumber(Math.abs(v.addend))})`;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
/** A byte array's initial elements as a string literal: printable characters as themselves, every
|
|
814
|
+
* other byte in three-digit octal (which no following digit can extend), and a `?` after a `?`
|
|
815
|
+
* escaped so no trigraph forms. The trailing zeros are left to the declared size, which C fills
|
|
816
|
+
* with zeros. */
|
|
817
|
+
function stringLiteral(init: readonly number[]): string {
|
|
818
|
+
let end = init.length;
|
|
819
|
+
while (end > 0 && (init[end - 1] & 0xff) === 0) {
|
|
820
|
+
end--;
|
|
821
|
+
}
|
|
822
|
+
let out = '';
|
|
823
|
+
for (const v of init.slice(0, end)) {
|
|
824
|
+
const b = v & 0xff;
|
|
825
|
+
const c = String.fromCharCode(b);
|
|
826
|
+
out +=
|
|
827
|
+
b < 0x20 || b > 0x7e || c === '"' || c === '\\' || (c === '?' && out.endsWith('?'))
|
|
828
|
+
? `\\${b.toString(8).padStart(3, '0')}`
|
|
829
|
+
: c;
|
|
830
|
+
}
|
|
831
|
+
return `"${out}"`;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/** A function-scope static's definition, as the first lines of the body. An array's elements are
|
|
835
|
+
* laid out eight to a line; an alignment the type does not give is an attribute, which gcc and
|
|
836
|
+
* mwcc both read after the declarator. */
|
|
837
|
+
function staticDefinition(st: SStatic): string[] {
|
|
838
|
+
const aligned = st.align === undefined ? '' : ` __attribute__((aligned(${st.align})))`;
|
|
839
|
+
const head = ` static ${st.const ? 'const ' : ''}${cDeclare(st.type, st.name)}${aligned}`;
|
|
840
|
+
return st.init === undefined ? [`${head};`] : initialized(head, st.type, st.init, st.string === true, false);
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
/** A declaration `head` of `type` with its initializer: a string literal, one scalar, or an
|
|
844
|
+
* array's elements laid out eight to a line. An element left out (null) is skipped and the next
|
|
845
|
+
* one spelled by designator, `[k] = v` — or, where the compiler cannot parse one (`SFn.designators`
|
|
846
|
+
* absent: IDO, mwcc) or the language has none (C++), spelled as the zero it is. */
|
|
847
|
+
function initialized(
|
|
848
|
+
head: string,
|
|
849
|
+
type: IrType,
|
|
850
|
+
init: readonly (number | InitAddress | null)[],
|
|
851
|
+
string: boolean,
|
|
852
|
+
designators: boolean,
|
|
853
|
+
): string[] {
|
|
854
|
+
const whole = init.map((v) => v ?? 0);
|
|
855
|
+
if (string) {
|
|
856
|
+
// a string's elements are bytes, and no byte holds an address (structure/local-statics.ts)
|
|
857
|
+
const bytes = whole.filter((v): v is number => typeof v === 'number');
|
|
858
|
+
if (bytes.length !== whole.length) {
|
|
859
|
+
throw new Error(`internal: a string initializer of '${head.trim()}' holds an address`);
|
|
860
|
+
}
|
|
861
|
+
return [`${head} = ${stringLiteral(bytes)};`];
|
|
862
|
+
}
|
|
863
|
+
if (type.kind !== 'array') {
|
|
864
|
+
return [`${head} = ${initElement(whole[0], type)};`];
|
|
865
|
+
}
|
|
866
|
+
const items: string[] = [];
|
|
867
|
+
init.forEach((v, i) => {
|
|
868
|
+
if (v !== null || !designators) {
|
|
869
|
+
const e = initElement(v ?? 0, type.elem);
|
|
870
|
+
items.push(init[i - 1] === null && designators ? `[${i}] = ${e}` : e);
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
if (items.length <= 8) {
|
|
874
|
+
return [`${head} = { ${items.join(', ')} };`];
|
|
875
|
+
}
|
|
876
|
+
const rows: string[] = [];
|
|
877
|
+
for (let i = 0; i < items.length; i += 8) {
|
|
878
|
+
rows.push(` ${items.slice(i, i + 8).join(', ')},`);
|
|
879
|
+
}
|
|
880
|
+
return [`${head} = {`, ...rows, ' };'];
|
|
881
|
+
}
|
|
882
|
+
|
|
579
883
|
/** Struct declarations this function references, one `struct N { ... };` per recovered aggregate.
|
|
580
884
|
* The struct type is SELF-DESCRIBING: any padding needed to seat fields at their exact offsets is
|
|
581
885
|
* already present as real `u8[N]` pad fields — both raise/struct-arrays.ts (element strides) and
|
|
@@ -585,15 +889,25 @@ function structDecls(fn: SFn): string[] {
|
|
|
585
889
|
return (fn.structs ?? []).map((s) => renderStructDecl(s.name, s.fields));
|
|
586
890
|
}
|
|
587
891
|
|
|
588
|
-
/** Assemble a full C-family function from a caller-supplied signature line and the shared body.
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
892
|
+
/** Assemble a full C-family function from a caller-supplied signature line and the shared body.
|
|
893
|
+
* `designators`: the LANGUAGE spells an array element by index in an initializer (C, not C++).
|
|
894
|
+
* Whether the compiler's dialect parses one is the function's own fact (`SFn.designators`); an
|
|
895
|
+
* initializer is spelled by index only where both say yes. */
|
|
896
|
+
export function emitCFamily(signature: string, fn0: SFn, leaf?: LeafHook, designators = true): string {
|
|
897
|
+
// The declaration list is put into the target's own frame order, and a pointer cell's integer
|
|
898
|
+
// uses converted (l3/ptrcell.ts), HERE — in the shared C-family assembler, reached only from
|
|
899
|
+
// `cBackend.emit` and `cppBackend(...).emit`, so both C-family backends print them and no `.emit(`
|
|
900
|
+
// call site does (l3/slotorder.ts says why `emit` owns it).
|
|
901
|
+
// …and the compiler's anonymous constant images folded into the initializers of the locals
|
|
902
|
+
// their copies fill (l3/constinit.ts), which the tree keeps as copies until here.
|
|
903
|
+
const fn = orderSlotLocals(legalizePointerCells(foldConstantImages(fn0)));
|
|
594
904
|
const decls = structDecls(fn);
|
|
595
905
|
const preamble = decls.length ? decls.join('\n') + '\n' : '';
|
|
596
|
-
return
|
|
906
|
+
return (
|
|
907
|
+
preamble +
|
|
908
|
+
[`${signature} {`, ...cFamilyBody(fn, leaf, designators && fn.designators === true), '}'].join('\n') +
|
|
909
|
+
'\n'
|
|
910
|
+
);
|
|
597
911
|
}
|
|
598
912
|
|
|
599
913
|
// One C-family comment spelling, shared by the C and C++ backends. A `*` followed by `/`
|