@asmlift/core 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -24
- package/package.json +1 -1
- package/src/aggregate.ts +149 -0
- package/src/backend/c.ts +1 -0
- package/src/backend/cfamily.ts +165 -50
- package/src/backend/cpp.ts +2 -1
- package/src/backend/pascal.ts +31 -12
- package/src/contracts.ts +173 -16
- package/src/declare.ts +69 -20
- package/src/frontend/asmdata.ts +7 -1
- package/src/frontend/call-plan.ts +964 -60
- package/src/frontend/dead-reads.ts +628 -0
- package/src/frontend/device-pins.ts +23 -4
- package/src/frontend/emit.ts +4 -3
- package/src/frontend/fpu.ts +224 -17
- package/src/frontend/frame-objects.ts +1232 -192
- package/src/frontend/frontend.ts +64 -3
- package/src/frontend/high-half.ts +53 -4
- package/src/frontend/jump-table.ts +530 -0
- package/src/frontend/local-object.ts +158 -32
- package/src/frontend/mips.ts +2076 -215
- package/src/frontend/own-struct-return.ts +500 -0
- package/src/frontend/ppc.ts +2711 -232
- package/src/frontend/register-struct-call.ts +484 -0
- package/src/frontend/reloc-symbol.ts +22 -7
- package/src/frontend/ssa.ts +1460 -44
- package/src/frontend/stackargs.ts +106 -57
- package/src/frontend/thumb.ts +2044 -318
- package/src/ir/alias.ts +42 -0
- package/src/ir/bits.ts +681 -1
- package/src/ir/core.ts +100 -5
- package/src/ir/discipline.ts +86 -21
- package/src/ir/entry.ts +159 -0
- package/src/{structure → ir}/loops.ts +6 -2
- package/src/ir/opcodes.ts +109 -9
- package/src/ir/types.ts +86 -2
- package/src/ir/verify.ts +80 -11
- package/src/l3/ast.ts +290 -18
- package/src/l3/basecse.ts +33 -5
- package/src/l3/constinit.ts +382 -0
- package/src/l3/dce.ts +2 -2
- package/src/l3/initfirst.ts +42 -11
- package/src/l3/intcell.ts +2 -1
- package/src/l3/narrowdecl.ts +77 -36
- package/src/l3/pollguard.ts +8 -2
- package/src/l3/ptrcell.ts +31 -34
- package/src/l3/ptrfield.ts +123 -8
- package/src/l3/regspell.ts +9 -4
- package/src/l3/reindex.ts +283 -96
- package/src/l3/storage.ts +98 -1
- package/src/l3/symbol-refs.ts +84 -6
- package/src/l3/tailmerge.ts +4 -2
- package/src/l3/typing.ts +108 -15
- package/src/l3/unreduce.ts +6 -5
- package/src/l3/volatileptr.ts +17 -54
- package/src/mangle.ts +30 -0
- package/src/pattern/engine.ts +361 -73
- package/src/pipeline.ts +541 -24
- package/src/proto-context.ts +905 -45
- package/src/proto.ts +555 -40
- package/src/raise/aggmembers.ts +290 -0
- package/src/raise/arrays.ts +5 -1
- package/src/raise/declared-volatile.ts +207 -31
- package/src/raise/floathelpers.ts +254 -33
- package/src/raise/gvn.ts +3 -3
- package/src/raise/latch.ts +162 -6
- package/src/raise/memberarrays.ts +10 -4
- package/src/raise/offsetnames.ts +10 -26
- package/src/raise/pairparams.ts +33 -0
- package/src/raise/paramwidth.ts +196 -6
- package/src/raise/pre-recovery.ts +102 -19
- package/src/raise/pre-structure.ts +54 -0
- package/src/raise/recover.ts +12 -5
- package/src/raise/reroll.ts +694 -0
- package/src/raise/shortcircuit.ts +26 -12
- package/src/raise/softdiv.ts +1 -1
- package/src/raise/storesign.ts +153 -0
- package/src/raise/struct-arrays.ts +30 -11
- package/src/raise/structs.ts +396 -33
- package/src/raise/truncload.ts +4 -3
- package/src/raise/unthread.ts +400 -0
- package/src/rank-declare.ts +118 -12
- package/src/rank-variations.ts +89 -31
- package/src/rank.ts +307 -54
- package/src/runtime-helpers.ts +140 -22
- package/src/structure/analysis.ts +165 -2
- package/src/structure/arith-ops.ts +23 -0
- package/src/structure/globalaccess.ts +111 -9
- package/src/structure/hazards.ts +16 -64
- package/src/structure/local-statics.ts +145 -9
- package/src/structure/namecoalesce.ts +1 -1
- package/src/structure/pointer-spelling.ts +1106 -0
- package/src/structure/redundant-test.ts +1 -1
- package/src/structure/structure.ts +2180 -1054
- package/src/structure/switch-recover.ts +244 -100
- package/src/symbols.ts +6 -0
- package/src/target.ts +765 -52
- package/src/trace.ts +26 -8
- package/src/variation-definitions.ts +126 -14
- package/src/variation-gates.ts +2 -0
- package/src/variation-tokens.ts +5 -1
package/src/backend/cfamily.ts
CHANGED
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
// too, shared with the scoring layer's synthesized declarations so the two cannot drift).
|
|
13
13
|
import { doubleLiteral } from '../ir/float-bits';
|
|
14
14
|
import { IrType, T, scalarTypeForAccess, typeEquals, typeToString } from '../ir/types';
|
|
15
|
-
import { BinOp, Expr, SFn, SStatic, Stmt, dotBase } from '../l3/ast';
|
|
15
|
+
import { BinOp, Expr, InitAddress, SFn, SStatic, Stmt, dotBase } from '../l3/ast';
|
|
16
|
+
import { foldConstantImages } from '../l3/constinit';
|
|
16
17
|
import { legalizePointerCells } from '../l3/ptrcell';
|
|
17
18
|
import { orderSlotLocals } from '../l3/slotorder';
|
|
18
19
|
import {
|
|
@@ -27,6 +28,7 @@ import {
|
|
|
27
28
|
renderedIntSignedness,
|
|
28
29
|
writesNonPointerIntoPointer,
|
|
29
30
|
} from '../l3/typing';
|
|
31
|
+
import { spellableType } from '../proto';
|
|
30
32
|
|
|
31
33
|
// C operator precedence (lower binds tighter). Used to emit MINIMAL parentheses. Shared: C++ has
|
|
32
34
|
// the same precedence for these operators.
|
|
@@ -35,6 +37,12 @@ const PREC: Record<BinOp, number> = {
|
|
|
35
37
|
'f/': 3,
|
|
36
38
|
'f+': 4,
|
|
37
39
|
'f-': 4,
|
|
40
|
+
'f<': 6,
|
|
41
|
+
'f<=': 6,
|
|
42
|
+
'f>': 6,
|
|
43
|
+
'f>=': 6,
|
|
44
|
+
'f==': 7,
|
|
45
|
+
'f!=': 7,
|
|
38
46
|
'*': 3,
|
|
39
47
|
'/': 3,
|
|
40
48
|
'/u': 3, // the unsigned twins spell as C's `/` and `%` (see C_SPELLING) — same precedence
|
|
@@ -72,8 +80,20 @@ const C_SPELLING: Partial<Record<BinOp, { token: string; signed: boolean }>> = {
|
|
|
72
80
|
};
|
|
73
81
|
|
|
74
82
|
/** 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
|
-
|
|
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
|
+
};
|
|
77
97
|
|
|
78
98
|
/** Spell a recovered type in the decomp C-family typedef vocabulary (`s32`/`u32`/`u8`/`T *`). */
|
|
79
99
|
export function cType(t: IrType): string {
|
|
@@ -197,10 +217,10 @@ function legalizedIndexBase(ix: Extract<Expr, { k: 'index' }>, vt: PrintEnv): Ex
|
|
|
197
217
|
*
|
|
198
218
|
* An existing integer cast AT THE SAME WIDTH is REPLACED rather than wrapped — `(u32)(s32)&g` and
|
|
199
219
|
* `(u32)&g` are the same bytes, and the arithmetic rules upstream do emit that inner cast
|
|
200
|
-
* (
|
|
201
|
-
* replacement CARRIES the qualifier: re-typing a `volatile`
|
|
202
|
-
* differ cannot referee the loss of, which is why
|
|
203
|
-
* same peel one pass over. */
|
|
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. */
|
|
204
224
|
function recastInt(x: Expr, signed: boolean, width: 32 | 64): Expr {
|
|
205
225
|
const replaced = x.k === 'cast' && x.to.kind === 'int' && x.to.width === width ? x : undefined;
|
|
206
226
|
return {
|
|
@@ -252,7 +272,8 @@ function pinnedOperands(e0: Extract<Expr, { k: 'bin' }>, wantSigned: boolean, vt
|
|
|
252
272
|
* made and the compiled code is C's: an object pointer to a pointer of another pointee (a conversion
|
|
253
273
|
* to `void *`, or one that only adds a qualifier, is implicit in C++ too), an integer to a pointer
|
|
254
274
|
* (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`)
|
|
275
|
+
* function compiled as C++ (`SFn.dialect`), or by a C front end that refuses the same three
|
|
276
|
+
* (`SFn.strictConversions` — mwcc). */
|
|
256
277
|
function argConversion(a: Expr, to: string, vt: PrintEnv): boolean {
|
|
257
278
|
const from = exprCType(a, vt.type);
|
|
258
279
|
// qualifiers never decide it: a `T * const` parameter is a pointer parameter
|
|
@@ -344,7 +365,15 @@ function printExpr(e: Expr, parentPrec: number, vt: PrintEnv, leaf?: LeafHook):
|
|
|
344
365
|
return `${e.fn}(${e.args
|
|
345
366
|
.map((a, i) => {
|
|
346
367
|
const to = declared?.[i];
|
|
347
|
-
|
|
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);
|
|
348
377
|
})
|
|
349
378
|
.join(', ')})`;
|
|
350
379
|
}
|
|
@@ -383,7 +412,7 @@ function printExpr(e: Expr, parentPrec: number, vt: PrintEnv, leaf?: LeafHook):
|
|
|
383
412
|
}
|
|
384
413
|
return `${rec(base, 1)}${e.lead.map((l) => `[${rec(l, 99)}]`).join('')}[${rec(e.idx, 99)}]`;
|
|
385
414
|
}
|
|
386
|
-
if (e.idx.k === 'const' && e.idx.value === 0) {
|
|
415
|
+
if (e.idx.k === 'const' && e.idx.value === 0 && e.subscript !== true) {
|
|
387
416
|
const s = `*${rec(base, 2)}`;
|
|
388
417
|
return parentPrec < 2 ? `(${s})` : s;
|
|
389
418
|
}
|
|
@@ -507,6 +536,7 @@ function printStmt(s: Stmt, indent: string, vt: PrintEnv, leaf?: LeafHook): stri
|
|
|
507
536
|
return out;
|
|
508
537
|
}
|
|
509
538
|
case 'while': {
|
|
539
|
+
refuseMisboundBreak(s.body, 'loop');
|
|
510
540
|
const out = [`${indent}while (${pe(s.cond, 99)}) {`];
|
|
511
541
|
for (const t of s.body) {
|
|
512
542
|
out.push(...printStmt(t, indent + ' ', vt, leaf));
|
|
@@ -515,6 +545,7 @@ function printStmt(s: Stmt, indent: string, vt: PrintEnv, leaf?: LeafHook): stri
|
|
|
515
545
|
return out;
|
|
516
546
|
}
|
|
517
547
|
case 'dowhile': {
|
|
548
|
+
refuseMisboundBreak(s.body, 'loop');
|
|
518
549
|
const out = [`${indent}do {`];
|
|
519
550
|
for (const t of s.body) {
|
|
520
551
|
out.push(...printStmt(t, indent + ' ', vt, leaf));
|
|
@@ -527,6 +558,7 @@ function printStmt(s: Stmt, indent: string, vt: PrintEnv, leaf?: LeafHook): stri
|
|
|
527
558
|
// `recognizeForLoops` and l3/reindex.ts): init/inc are each a SINGLE-LINE `assign` statement. `clause` renders one
|
|
528
559
|
// and strips its trailing `;` so it sits inside the header (`i = 0; c; i = i + 1`). A multi-line
|
|
529
560
|
// statement (an `if`/nested loop) would render mangled — but the recognizer never builds one here.
|
|
561
|
+
refuseMisboundBreak(s.body, 'loop');
|
|
530
562
|
const clause = (st: Stmt) => printStmt(st, '', vt, leaf).join(' ').replace(/;\s*$/, '').trim();
|
|
531
563
|
const out = [`${indent}for (${clause(s.init)}; ${pe(s.cond, 99)}; ${clause(s.inc)}) {`];
|
|
532
564
|
for (const t of s.body) {
|
|
@@ -550,11 +582,12 @@ function printStmt(s: Stmt, indent: string, vt: PrintEnv, leaf?: LeafHook): stri
|
|
|
550
582
|
* is one call. */
|
|
551
583
|
function printSwitchStmt(s: Extract<Stmt, { k: 'switch' }>, indent: string, vt: PrintEnv, leaf?: LeafHook): string[] {
|
|
552
584
|
const pe = (e: Expr, p: number) => printExpr(e, p, vt, leaf);
|
|
553
|
-
// A `break;` in an arm BODY is the innermost LOOP's in L3 (l3/ast.ts) and the SWITCH's in
|
|
554
|
-
// 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
|
|
555
587
|
// C. Refused HERE because the rebinding is the printer's, for every producer rather than for
|
|
556
|
-
// the two switch regimes
|
|
557
|
-
//
|
|
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.
|
|
558
591
|
//
|
|
559
592
|
// `continue;` is deliberately NOT refused: C binds it to the smallest enclosing ITERATION
|
|
560
593
|
// statement, which a `switch` is not, so it already means what L3 means. What a pass that re-spells
|
|
@@ -563,9 +596,7 @@ function printSwitchStmt(s: Extract<Stmt, { k: 'switch' }>, indent: string, vt:
|
|
|
563
596
|
// `respellCountdown` in l3/reindex.ts, and l3/unreduce.ts) each scan switch arms for the node
|
|
564
597
|
// before firing, and a fourth must too.
|
|
565
598
|
for (const body of [...s.cases.map((c) => c.body), s.default ?? []]) {
|
|
566
|
-
|
|
567
|
-
throw new Error('c backend: a switch arm carries a loop-scoped `break;`, which C would bind to the switch');
|
|
568
|
-
}
|
|
599
|
+
refuseMisboundBreak(body, 'switch');
|
|
569
600
|
}
|
|
570
601
|
const out = [`${indent}switch (${pe(s.scrutinee, 99)}) {`];
|
|
571
602
|
const ci = indent + ' '; // case-label indent
|
|
@@ -624,13 +655,26 @@ function printSwitchStmt(s: Extract<Stmt, { k: 'switch' }>, indent: string, vt:
|
|
|
624
655
|
return out;
|
|
625
656
|
}
|
|
626
657
|
|
|
627
|
-
/**
|
|
628
|
-
*
|
|
629
|
-
* `
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
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
|
+
}
|
|
634
678
|
}
|
|
635
679
|
|
|
636
680
|
// Does a statement list end in a control-flow terminator (so a trailing `break;` would be dead)?
|
|
@@ -645,6 +689,22 @@ function endsTerminated(body: Stmt[]): boolean {
|
|
|
645
689
|
);
|
|
646
690
|
}
|
|
647
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
|
+
|
|
648
708
|
// C-FAMILY WRITE LEGALIZATION (the assign-side sibling of the deref legalization in printExpr):
|
|
649
709
|
// the C family's answer to `writesNonPointerIntoPointer` is the reinterpret cast to the DECLARED
|
|
650
710
|
// type — exactly what the machine's register move does — where Pascal, having no such cast,
|
|
@@ -654,9 +714,18 @@ function legalizePointerWrites(fn: SFn): SFn {
|
|
|
654
714
|
const vt = declaredTypes(fn);
|
|
655
715
|
// …and the mirror: a rendered pointer into an INTEGER slot takes the slot's type. A cast keeps
|
|
656
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;
|
|
657
724
|
const castTo = (t: IrType | undefined, e: Expr): Expr =>
|
|
658
|
-
writesNonPointerIntoPointer(t, e, vt) ||
|
|
659
|
-
|
|
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 }
|
|
660
729
|
: e;
|
|
661
730
|
const fix = (s: Stmt): Stmt => {
|
|
662
731
|
switch (s.k) {
|
|
@@ -692,7 +761,7 @@ function legalizePointerWrites(fn: SFn): SFn {
|
|
|
692
761
|
/** The body of a C-family function: local declarations + statements, one string per line. The
|
|
693
762
|
* SIGNATURE (return type + name + params, plus any C++ scope/`this`/mangling) is the caller's —
|
|
694
763
|
* that is the language-divergent part each backend owns. */
|
|
695
|
-
function cFamilyBody(fn0: SFn, leaf
|
|
764
|
+
function cFamilyBody(fn0: SFn, leaf: LeafHook | undefined, designators: boolean): string[] {
|
|
696
765
|
const fn = legalizePointerWrites(fn0);
|
|
697
766
|
// The legalization env: every printed var's declared type and pointee volatility, from the SAME
|
|
698
767
|
// params/locals the emitted declarations come from — so the printer judges exactly the C the
|
|
@@ -710,7 +779,10 @@ function cFamilyBody(fn0: SFn, leaf?: LeafHook): string[] {
|
|
|
710
779
|
// no variation or recognizer produces one. The NAME is placed by `cDeclare`, the one
|
|
711
780
|
// placement every declaration position in this backend uses: a local can carry an array type
|
|
712
781
|
// — the storage extent a frame object pins — and C puts those extents after the name.
|
|
713
|
-
|
|
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
|
+
);
|
|
714
786
|
}
|
|
715
787
|
for (const s of fn.body) {
|
|
716
788
|
lines.push(...printStmt(s, ' ', vt, leaf));
|
|
@@ -718,11 +790,26 @@ function cFamilyBody(fn0: SFn, leaf?: LeafHook): string[] {
|
|
|
718
790
|
return lines;
|
|
719
791
|
}
|
|
720
792
|
|
|
721
|
-
/** One initial
|
|
793
|
+
/** One initial number: hex from 10 up, a negative one with its sign, so a byte table reads as the
|
|
722
794
|
* bytes and a signed one as its values. */
|
|
723
|
-
const
|
|
795
|
+
const initNumber = (v: number): string =>
|
|
724
796
|
Math.abs(v) < 10 ? `${v}` : `${v < 0 ? '-' : ''}0x${Math.abs(v).toString(16)}`;
|
|
725
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
|
+
|
|
726
813
|
/** A byte array's initial elements as a string literal: printable characters as themselves, every
|
|
727
814
|
* other byte in three-digit octal (which no following digit can extend), and a `?` after a `?`
|
|
728
815
|
* escaped so no trigraph forms. The trailing zeros are left to the declared size, which C fills
|
|
@@ -750,26 +837,45 @@ function stringLiteral(init: readonly number[]): string {
|
|
|
750
837
|
function staticDefinition(st: SStatic): string[] {
|
|
751
838
|
const aligned = st.align === undefined ? '' : ` __attribute__((aligned(${st.align})))`;
|
|
752
839
|
const head = ` static ${st.const ? 'const ' : ''}${cDeclare(st.type, st.name)}${aligned}`;
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
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)};`];
|
|
758
862
|
}
|
|
759
|
-
if (
|
|
760
|
-
return [`${head} = ${initElement(
|
|
863
|
+
if (type.kind !== 'array') {
|
|
864
|
+
return [`${head} = ${initElement(whole[0], type)};`];
|
|
761
865
|
}
|
|
762
|
-
|
|
763
|
-
|
|
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(', ')} };`];
|
|
764
875
|
}
|
|
765
876
|
const rows: string[] = [];
|
|
766
|
-
for (let i = 0; i <
|
|
767
|
-
rows.push(
|
|
768
|
-
` ${st.init
|
|
769
|
-
.slice(i, i + 8)
|
|
770
|
-
.map(initElement)
|
|
771
|
-
.join(', ')},`,
|
|
772
|
-
);
|
|
877
|
+
for (let i = 0; i < items.length; i += 8) {
|
|
878
|
+
rows.push(` ${items.slice(i, i + 8).join(', ')},`);
|
|
773
879
|
}
|
|
774
880
|
return [`${head} = {`, ...rows, ' };'];
|
|
775
881
|
}
|
|
@@ -783,16 +889,25 @@ function structDecls(fn: SFn): string[] {
|
|
|
783
889
|
return (fn.structs ?? []).map((s) => renderStructDecl(s.name, s.fields));
|
|
784
890
|
}
|
|
785
891
|
|
|
786
|
-
/** Assemble a full C-family function from a caller-supplied signature line and the shared body.
|
|
787
|
-
|
|
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 {
|
|
788
897
|
// The declaration list is put into the target's own frame order, and a pointer cell's integer
|
|
789
898
|
// uses converted (l3/ptrcell.ts), HERE — in the shared C-family assembler, reached only from
|
|
790
899
|
// `cBackend.emit` and `cppBackend(...).emit`, so both C-family backends print them and no `.emit(`
|
|
791
900
|
// call site does (l3/slotorder.ts says why `emit` owns it).
|
|
792
|
-
|
|
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)));
|
|
793
904
|
const decls = structDecls(fn);
|
|
794
905
|
const preamble = decls.length ? decls.join('\n') + '\n' : '';
|
|
795
|
-
return
|
|
906
|
+
return (
|
|
907
|
+
preamble +
|
|
908
|
+
[`${signature} {`, ...cFamilyBody(fn, leaf, designators && fn.designators === true), '}'].join('\n') +
|
|
909
|
+
'\n'
|
|
910
|
+
);
|
|
796
911
|
}
|
|
797
912
|
|
|
798
913
|
// One C-family comment spelling, shared by the C and C++ backends. A `*` followed by `/`
|
package/src/backend/cpp.ts
CHANGED
|
@@ -138,6 +138,7 @@ export function cppBackend(spec: CppFnSpec, floatSlots: FloatSlots | undefined):
|
|
|
138
138
|
return {
|
|
139
139
|
id: 'cpp',
|
|
140
140
|
spellsSwitchFallthrough: true,
|
|
141
|
+
spellsSwitchBreak: true,
|
|
141
142
|
emit(fn: SFn): string {
|
|
142
143
|
// Map each lifted param var → its C++ meaning: `this` (bare member access) or a named param
|
|
143
144
|
// (a pointer-to-class param uses `->`). A pointer-to-known-class param is a member receiver.
|
|
@@ -223,7 +224,7 @@ export function cppBackend(spec: CppFnSpec, floatSlots: FloatSlots | undefined):
|
|
|
223
224
|
const paramList = spec.params.map((p) => declareCpp(p.type, p.name)).join(', ');
|
|
224
225
|
const decls = classDecls(spec, paramList);
|
|
225
226
|
const signature = `${spellType(spec.retType)} ${spec.cls ? spec.cls + '::' : ''}${spec.method}(${paramList})`;
|
|
226
|
-
return (decls ? decls + '\n' : '') + emitCFamily(signature, fn, leaf);
|
|
227
|
+
return (decls ? decls + '\n' : '') + emitCFamily(signature, fn, leaf, false);
|
|
227
228
|
},
|
|
228
229
|
comment: cComment, // C++ shares C's block-comment spelling
|
|
229
230
|
};
|
package/src/backend/pascal.ts
CHANGED
|
@@ -140,10 +140,10 @@ function makePrinter(vt: VarTypes) {
|
|
|
140
140
|
// Casts have no faithful IDO-Pascal spelling yet — fail LOUD rather than emit silently-wrong
|
|
141
141
|
// source. Tree-level producers reaching here: the width-narrowing idiom casts (agbcc-gated,
|
|
142
142
|
// so never on this path today), structure.ts's STRUCT-pointer casts (unreachable too — the
|
|
143
|
-
// `field` case above throws first),
|
|
144
|
-
// byte-pointer walk of a pointer offset by a runtime value (any target,
|
|
145
|
-
// `field` in the tree
|
|
146
|
-
//
|
|
143
|
+
// `field` case above throws first), the arithmetic table's `(s32)ptr` legalization (any
|
|
144
|
+
// target), and the byte-pointer walk of a pointer offset by a runtime value (any target,
|
|
145
|
+
// and reachable with no `field` in the tree, where a Pascal spelling would walk ELEMENTS
|
|
146
|
+
// where the asm walked bytes).
|
|
147
147
|
// Scalar deref casts never appear in the tree — the index case above owns that judgment.
|
|
148
148
|
case 'cast':
|
|
149
149
|
throw new Error(`pascal backend: cast has no IDO Pascal spelling yet`);
|
|
@@ -202,13 +202,20 @@ function makePrinter(vt: VarTypes) {
|
|
|
202
202
|
return s.value ? [`${indent}${fnName} := ${pe(s.value)};`] : [];
|
|
203
203
|
case 'if': {
|
|
204
204
|
const cond = pe(s.cond);
|
|
205
|
-
|
|
206
|
-
|
|
205
|
+
// A branch goes bare only where it is ONE Pascal statement that cannot capture what follows.
|
|
206
|
+
// A `for` prints as two (its init, then a `while`), so it always takes begin/end. And with an
|
|
207
|
+
// `else` to come, a then-branch that is itself an `if` or a loop (whose body may end in one)
|
|
208
|
+
// would take that `else` as its own innermost `if`'s — Pascal's dangling else, which C's
|
|
209
|
+
// braces had ruled out: `if (a) { if (b) X; } else Y;` printed bare runs Y when b fails.
|
|
210
|
+
const bare = (stmts: Stmt[], open: boolean) =>
|
|
211
|
+
stmts.length === 1 && stmts[0].k !== 'for' && !(open && (stmts[0].k === 'if' || isLoop(stmts[0])));
|
|
212
|
+
const block = (stmts: Stmt[], ind: string, open = false) =>
|
|
213
|
+
bare(stmts, open)
|
|
207
214
|
? ps(fnName, stmts[0], ind, tail)
|
|
208
215
|
: [`${ind}begin`, ...list(stmts, ind + ' ', tail), `${ind}end`];
|
|
209
216
|
// Pascal: no `;` before `else`; the branch statements already carry their own.
|
|
210
217
|
const out = [`${indent}if ${cond} then`];
|
|
211
|
-
out.push(...block(s.then, indent + ' '));
|
|
218
|
+
out.push(...block(s.then, indent + ' ', s.else.length > 0));
|
|
212
219
|
if (s.else.length) {
|
|
213
220
|
out.push(`${indent}else`);
|
|
214
221
|
out.push(...block(s.else, indent + ' '));
|
|
@@ -218,7 +225,7 @@ function makePrinter(vt: VarTypes) {
|
|
|
218
225
|
case 'while': {
|
|
219
226
|
// loop bodies are NEVER tail position — control returns to the test
|
|
220
227
|
const body =
|
|
221
|
-
s.body.length === 1
|
|
228
|
+
s.body.length === 1 && s.body[0].k !== 'for'
|
|
222
229
|
? ps(fnName, s.body[0], indent + ' ')
|
|
223
230
|
: [`${indent}begin`, ...s.body.flatMap((x) => ps(fnName, x, indent + ' ')), `${indent}end`];
|
|
224
231
|
return [`${indent}while ${pe(s.cond)} do`, ...body];
|
|
@@ -253,7 +260,7 @@ function makePrinter(vt: VarTypes) {
|
|
|
253
260
|
// so a `fallsThrough` arm has no faithful spelling → loud-fail (mirrors the `field`/`cast`/`%`
|
|
254
261
|
// throws above). Each arm's body is a single statement or a begin/end block.
|
|
255
262
|
const arm = (body: Stmt[], ind: string) =>
|
|
256
|
-
body.length === 1
|
|
263
|
+
body.length === 1 && body[0].k !== 'for'
|
|
257
264
|
? ps(fnName, body[0], ind + ' ')
|
|
258
265
|
: [`${ind} begin`, ...body.flatMap((x) => ps(fnName, x, ind + ' ')), `${ind} end`];
|
|
259
266
|
// Pascal has no early `return`: a `return v` inside a case renders `fnName := v` and then FALLS
|
|
@@ -279,8 +286,11 @@ function makePrinter(vt: VarTypes) {
|
|
|
279
286
|
}
|
|
280
287
|
out.push(`${indent} ${c.values.join(', ')}:`, ...arm(c.body, indent + ' '));
|
|
281
288
|
}
|
|
282
|
-
|
|
283
|
-
|
|
289
|
+
// A C `switch` with no `default` runs on past a value it does not list; IDO Pascal's
|
|
290
|
+
// `case` raises a run-time `caseerror` there instead. So the clause is always printed, an
|
|
291
|
+
// empty one when the source had no default.
|
|
292
|
+
out.push(`${indent} otherwise`);
|
|
293
|
+
if (s.default?.length) {
|
|
284
294
|
out.push(...arm(s.default, indent + ' '));
|
|
285
295
|
}
|
|
286
296
|
out.push(`${indent}end;`);
|
|
@@ -298,17 +308,26 @@ export const pascalBackend: LanguageBackend = {
|
|
|
298
308
|
// spells as plain if-nesting, which Pascal prints, so the choice is between a decompiled
|
|
299
309
|
// function and a stub.
|
|
300
310
|
spellsSwitchFallthrough: false,
|
|
311
|
+
// Nor has Pascal a `break`, so recovery must not mint a switch's mid-arm one either: a closed arm
|
|
312
|
+
// that leaves early spells as nested `if`s that copy the arm's tail into each path.
|
|
313
|
+
spellsSwitchBreak: false,
|
|
301
314
|
emit(fn0: SFn): string {
|
|
302
315
|
// The declaration list is put into the target's own frame order HERE, as the C family does it
|
|
303
316
|
// in its shared assembler — owned by `emit`, never by a `.emit(` call site (l3/slotorder.ts).
|
|
304
317
|
const fn = orderSlotLocals(fn0);
|
|
305
318
|
// A function-scope static is a definition the MIPS frontends never recover, and IDO Pascal's
|
|
306
|
-
// spelling of one (a typed constant, or a unit-level var) has not been measured — loud.
|
|
319
|
+
// spelling of one (a typed constant, or a unit-level var) has not been measured — loud. So is
|
|
320
|
+
// the compiler's anonymous constant a local's initializer is copied from (l3/constinit.ts).
|
|
307
321
|
if (fn.statics?.length) {
|
|
308
322
|
throw new Error(
|
|
309
323
|
`pascal backend: the function-scope static '${fn.statics[0].name}' has no IDO Pascal spelling yet`,
|
|
310
324
|
);
|
|
311
325
|
}
|
|
326
|
+
if (fn.images?.length) {
|
|
327
|
+
throw new Error(
|
|
328
|
+
`pascal backend: the compiler's anonymous constant '${fn.images[0].name}' a local is initialized from has no IDO Pascal spelling yet`,
|
|
329
|
+
);
|
|
330
|
+
}
|
|
312
331
|
// Same env discipline as the C family (cfamily.ts cFamilyBody): the printer judges derefs
|
|
313
332
|
// against the exact declarations it emits.
|
|
314
333
|
const ps = makePrinter(declaredTypes(fn));
|