@asmlift/core 0.9.0 → 0.11.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 +2 -0
- package/src/backend/cfamily.ts +197 -61
- package/src/backend/cpp.ts +3 -1
- package/src/backend/lines.ts +10 -0
- package/src/backend/pascal.ts +47 -16
- package/src/cdecl/parse.ts +21 -0
- package/src/contracts.ts +246 -36
- package/src/declare.ts +69 -20
- package/src/frontend/arm-body.ts +155 -0
- package/src/frontend/asmdata.ts +9 -2
- package/src/frontend/call-plan.ts +1117 -61
- package/src/frontend/callee-body.ts +370 -0
- package/src/frontend/dead-reads.ts +628 -0
- package/src/frontend/device-pins.ts +33 -4
- package/src/frontend/emit.ts +4 -3
- package/src/frontend/fpu.ts +224 -17
- package/src/frontend/frame-objects.ts +1689 -206
- package/src/frontend/frontend.ts +84 -3
- package/src/frontend/high-half.ts +53 -4
- package/src/frontend/jump-table.ts +545 -0
- package/src/frontend/local-object.ts +158 -32
- package/src/frontend/mips.ts +2296 -217
- package/src/frontend/own-struct-return.ts +500 -0
- package/src/frontend/ppc.ts +2748 -245
- package/src/frontend/register-struct-call.ts +399 -0
- package/src/frontend/reloc-symbol.ts +22 -7
- package/src/frontend/ssa.ts +1721 -43
- package/src/frontend/stackargs.ts +199 -72
- package/src/frontend/thumb-entry.ts +340 -0
- package/src/frontend/thumb.ts +2400 -325
- package/src/ir/alias.ts +65 -2
- package/src/ir/bits.ts +1699 -4
- package/src/ir/core.ts +254 -9
- package/src/ir/discipline.ts +86 -21
- package/src/ir/entry.ts +157 -0
- package/src/{structure → ir}/loops.ts +6 -2
- package/src/ir/opcodes.ts +137 -9
- package/src/ir/parse.ts +1 -0
- package/src/ir/simplify.ts +61 -10
- package/src/ir/types.ts +86 -2
- package/src/ir/verify.ts +96 -11
- package/src/l3/argbase.ts +2 -0
- package/src/l3/ast.ts +582 -18
- package/src/l3/basecse.ts +68 -15
- package/src/l3/caseendgoto.ts +223 -0
- package/src/l3/coalesce.ts +42 -14
- package/src/l3/constinit.ts +410 -0
- package/src/l3/dce.ts +53 -9
- 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 +33 -34
- package/src/l3/ptrfield.ts +123 -8
- package/src/l3/regspell.ts +11 -4
- package/src/l3/reindex.ts +295 -99
- package/src/l3/scopebase.ts +2 -0
- package/src/l3/storage.ts +98 -1
- package/src/l3/symbol-refs.ts +84 -6
- package/src/l3/tailmerge.ts +6 -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 +1039 -38
- package/src/proto-context.ts +1039 -45
- package/src/proto.ts +555 -40
- package/src/raise/aggmembers.ts +290 -0
- package/src/raise/arrays.ts +5 -1
- package/src/raise/const.ts +25 -0
- package/src/raise/declared-volatile.ts +207 -31
- package/src/raise/divpow2.ts +192 -0
- 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 +422 -36
- package/src/raise/pairparams.ts +33 -0
- package/src/raise/paramwidth.ts +196 -6
- package/src/raise/pre-recovery.ts +107 -21
- package/src/raise/pre-structure.ts +73 -0
- package/src/raise/recover.ts +12 -5
- package/src/raise/reroll.ts +674 -0
- package/src/raise/shortcircuit.ts +217 -34
- 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/unpre.ts +594 -0
- package/src/raise/unthread.ts +400 -0
- package/src/rank-declare.ts +118 -12
- package/src/rank-variations.ts +186 -35
- package/src/rank.ts +536 -68
- package/src/runtime-helpers.ts +140 -22
- package/src/structure/analysis.ts +217 -8
- package/src/structure/arith-ops.ts +23 -0
- package/src/structure/cross-jumped-tail.ts +216 -0
- package/src/structure/globalaccess.ts +157 -10
- 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 +6192 -1213
- package/src/structure/switch-recover.ts +583 -120
- package/src/structure/unfolded-branch-constant.ts +90 -0
- package/src/symbols.ts +58 -15
- package/src/target.ts +1028 -54
- package/src/trace.ts +49 -17
- package/src/variation-definitions.ts +333 -20
- package/src/variation-gates.ts +4 -0
- package/src/variation-tokens.ts +68 -3
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, caseGotoViolation, 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,8 @@ import {
|
|
|
27
28
|
renderedIntSignedness,
|
|
28
29
|
writesNonPointerIntoPointer,
|
|
29
30
|
} from '../l3/typing';
|
|
31
|
+
import { spellableType } from '../proto';
|
|
32
|
+
import { append } from './lines';
|
|
30
33
|
|
|
31
34
|
// C operator precedence (lower binds tighter). Used to emit MINIMAL parentheses. Shared: C++ has
|
|
32
35
|
// the same precedence for these operators.
|
|
@@ -35,6 +38,12 @@ const PREC: Record<BinOp, number> = {
|
|
|
35
38
|
'f/': 3,
|
|
36
39
|
'f+': 4,
|
|
37
40
|
'f-': 4,
|
|
41
|
+
'f<': 6,
|
|
42
|
+
'f<=': 6,
|
|
43
|
+
'f>': 6,
|
|
44
|
+
'f>=': 6,
|
|
45
|
+
'f==': 7,
|
|
46
|
+
'f!=': 7,
|
|
38
47
|
'*': 3,
|
|
39
48
|
'/': 3,
|
|
40
49
|
'/u': 3, // the unsigned twins spell as C's `/` and `%` (see C_SPELLING) — same precedence
|
|
@@ -72,8 +81,20 @@ const C_SPELLING: Partial<Record<BinOp, { token: string; signed: boolean }>> = {
|
|
|
72
81
|
};
|
|
73
82
|
|
|
74
83
|
/** 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
|
-
|
|
84
|
+
* arithmetic over two floats is the IEEE operation the machine did, and its relations over two
|
|
85
|
+
* floats are the IEEE compares — false on a NaN, `!=` true. */
|
|
86
|
+
const FLOAT_TOKEN: Partial<Record<BinOp, string>> = {
|
|
87
|
+
'f+': '+',
|
|
88
|
+
'f-': '-',
|
|
89
|
+
'f*': '*',
|
|
90
|
+
'f/': '/',
|
|
91
|
+
'f<': '<',
|
|
92
|
+
'f<=': '<=',
|
|
93
|
+
'f>': '>',
|
|
94
|
+
'f>=': '>=',
|
|
95
|
+
'f==': '==',
|
|
96
|
+
'f!=': '!=',
|
|
97
|
+
};
|
|
77
98
|
|
|
78
99
|
/** Spell a recovered type in the decomp C-family typedef vocabulary (`s32`/`u32`/`u8`/`T *`). */
|
|
79
100
|
export function cType(t: IrType): string {
|
|
@@ -197,10 +218,10 @@ function legalizedIndexBase(ix: Extract<Expr, { k: 'index' }>, vt: PrintEnv): Ex
|
|
|
197
218
|
*
|
|
198
219
|
* An existing integer cast AT THE SAME WIDTH is REPLACED rather than wrapped — `(u32)(s32)&g` and
|
|
199
220
|
* `(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. */
|
|
221
|
+
* (structure/pointer-spelling.ts ARITH_ROWS). A cast at a DIFFERENT width is a narrowing the
|
|
222
|
+
* source wrote and must be kept. The replacement CARRIES the qualifier: re-typing a `volatile`
|
|
223
|
+
* cast without it drops an assertion the differ cannot referee the loss of, which is why
|
|
224
|
+
* l3/initfirst.ts's `stripWideIntCast` refuses the same peel one pass over. */
|
|
204
225
|
function recastInt(x: Expr, signed: boolean, width: 32 | 64): Expr {
|
|
205
226
|
const replaced = x.k === 'cast' && x.to.kind === 'int' && x.to.width === width ? x : undefined;
|
|
206
227
|
return {
|
|
@@ -252,7 +273,8 @@ function pinnedOperands(e0: Extract<Expr, { k: 'bin' }>, wantSigned: boolean, vt
|
|
|
252
273
|
* made and the compiled code is C's: an object pointer to a pointer of another pointee (a conversion
|
|
253
274
|
* to `void *`, or one that only adds a qualifier, is implicit in C++ too), an integer to a pointer
|
|
254
275
|
* (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`)
|
|
276
|
+
* function compiled as C++ (`SFn.dialect`), or by a C front end that refuses the same three
|
|
277
|
+
* (`SFn.strictConversions` — mwcc). */
|
|
256
278
|
function argConversion(a: Expr, to: string, vt: PrintEnv): boolean {
|
|
257
279
|
const from = exprCType(a, vt.type);
|
|
258
280
|
// qualifiers never decide it: a `T * const` parameter is a pointer parameter
|
|
@@ -344,7 +366,15 @@ function printExpr(e: Expr, parentPrec: number, vt: PrintEnv, leaf?: LeafHook):
|
|
|
344
366
|
return `${e.fn}(${e.args
|
|
345
367
|
.map((a, i) => {
|
|
346
368
|
const to = declared?.[i];
|
|
347
|
-
|
|
369
|
+
// A parameter type this cannot spell is an object pointer (target.ts `declaredArgTypes`),
|
|
370
|
+
// reached through `void *` from an argument whose own type is known to differ — a value
|
|
371
|
+
// this function types, or the address of an object it declares (a static) — never from
|
|
372
|
+
// `&gW` of a global only the project declares, which may already be the parameter's type.
|
|
373
|
+
const opaque = to !== undefined && !spellableType(to);
|
|
374
|
+
const typed = exprCType(a, vt.type) !== undefined || (a.k === 'addr' && vt.type(a.name) !== undefined);
|
|
375
|
+
return to !== undefined && (!opaque || typed) && argConversion(a, to, vt)
|
|
376
|
+
? `(${opaque ? 'void *' : to})${rec(a, 2)}`
|
|
377
|
+
: printConverted(a, vt, leaf);
|
|
348
378
|
})
|
|
349
379
|
.join(', ')})`;
|
|
350
380
|
}
|
|
@@ -383,7 +413,7 @@ function printExpr(e: Expr, parentPrec: number, vt: PrintEnv, leaf?: LeafHook):
|
|
|
383
413
|
}
|
|
384
414
|
return `${rec(base, 1)}${e.lead.map((l) => `[${rec(l, 99)}]`).join('')}[${rec(e.idx, 99)}]`;
|
|
385
415
|
}
|
|
386
|
-
if (e.idx.k === 'const' && e.idx.value === 0) {
|
|
416
|
+
if (e.idx.k === 'const' && e.idx.value === 0 && e.subscript !== true) {
|
|
387
417
|
const s = `*${rec(base, 2)}`;
|
|
388
418
|
return parentPrec < 2 ? `(${s})` : s;
|
|
389
419
|
}
|
|
@@ -495,29 +525,31 @@ function printStmt(s: Stmt, indent: string, vt: PrintEnv, leaf?: LeafHook): stri
|
|
|
495
525
|
}
|
|
496
526
|
const out = [`${indent}if (${cond}) {`];
|
|
497
527
|
for (const t of s.then) {
|
|
498
|
-
out
|
|
528
|
+
append(out, printStmt(t, indent + ' ', vt, leaf));
|
|
499
529
|
}
|
|
500
530
|
if (s.else.length) {
|
|
501
531
|
out.push(`${indent}} else {`);
|
|
502
532
|
for (const e of s.else) {
|
|
503
|
-
out
|
|
533
|
+
append(out, printStmt(e, indent + ' ', vt, leaf));
|
|
504
534
|
}
|
|
505
535
|
}
|
|
506
536
|
out.push(`${indent}}`);
|
|
507
537
|
return out;
|
|
508
538
|
}
|
|
509
539
|
case 'while': {
|
|
540
|
+
refuseMisboundBreak(s.body, 'loop');
|
|
510
541
|
const out = [`${indent}while (${pe(s.cond, 99)}) {`];
|
|
511
542
|
for (const t of s.body) {
|
|
512
|
-
out
|
|
543
|
+
append(out, printStmt(t, indent + ' ', vt, leaf));
|
|
513
544
|
}
|
|
514
545
|
out.push(`${indent}}`);
|
|
515
546
|
return out;
|
|
516
547
|
}
|
|
517
548
|
case 'dowhile': {
|
|
549
|
+
refuseMisboundBreak(s.body, 'loop');
|
|
518
550
|
const out = [`${indent}do {`];
|
|
519
551
|
for (const t of s.body) {
|
|
520
|
-
out
|
|
552
|
+
append(out, printStmt(t, indent + ' ', vt, leaf));
|
|
521
553
|
}
|
|
522
554
|
out.push(`${indent}} while (${pe(s.cond, 99)});`);
|
|
523
555
|
return out;
|
|
@@ -527,18 +559,28 @@ function printStmt(s: Stmt, indent: string, vt: PrintEnv, leaf?: LeafHook): stri
|
|
|
527
559
|
// `recognizeForLoops` and l3/reindex.ts): init/inc are each a SINGLE-LINE `assign` statement. `clause` renders one
|
|
528
560
|
// and strips its trailing `;` so it sits inside the header (`i = 0; c; i = i + 1`). A multi-line
|
|
529
561
|
// statement (an `if`/nested loop) would render mangled — but the recognizer never builds one here.
|
|
562
|
+
refuseMisboundBreak(s.body, 'loop');
|
|
563
|
+
// An identity init (`i = i`, structure.ts `forPlanAt`: the name already holds the entry value)
|
|
564
|
+
// is the empty clause.
|
|
530
565
|
const clause = (st: Stmt) => printStmt(st, '', vt, leaf).join(' ').replace(/;\s*$/, '').trim();
|
|
531
|
-
const
|
|
566
|
+
const identity = s.init.k === 'assign' && s.init.value.k === 'var' && s.init.value.name === s.init.name;
|
|
567
|
+
const out = [`${indent}for (${identity ? '' : clause(s.init)}; ${pe(s.cond, 99)}; ${clause(s.inc)}) {`];
|
|
532
568
|
for (const t of s.body) {
|
|
533
|
-
out
|
|
569
|
+
append(out, printStmt(t, indent + ' ', vt, leaf));
|
|
534
570
|
}
|
|
535
571
|
out.push(`${indent}}`);
|
|
536
572
|
return out;
|
|
537
573
|
}
|
|
574
|
+
// A labelled `break` is the end of a switch arm a case-end `goto` lands on (l3/ast.ts).
|
|
538
575
|
case 'break':
|
|
539
|
-
return [`${indent}break;`];
|
|
576
|
+
return s.label === undefined ? [`${indent}break;`] : [`${indent}${s.label}:`, `${indent}break;`];
|
|
540
577
|
case 'continue':
|
|
541
578
|
return [`${indent}continue;`];
|
|
579
|
+
// Where it may sit is the tree's contract (l3/ast.ts `caseGotoViolation`), which `emitCFamily`
|
|
580
|
+
// asks of the whole function before printing any of it.
|
|
581
|
+
case 'goto':
|
|
582
|
+
case 'caseEndGoto':
|
|
583
|
+
return [`${indent}goto ${s.label};`];
|
|
542
584
|
case 'switch':
|
|
543
585
|
return printSwitchStmt(s, indent, vt, leaf);
|
|
544
586
|
}
|
|
@@ -550,11 +592,12 @@ function printStmt(s: Stmt, indent: string, vt: PrintEnv, leaf?: LeafHook): stri
|
|
|
550
592
|
* is one call. */
|
|
551
593
|
function printSwitchStmt(s: Extract<Stmt, { k: 'switch' }>, indent: string, vt: PrintEnv, leaf?: LeafHook): string[] {
|
|
552
594
|
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
|
|
595
|
+
// A plain `break;` in an arm BODY is the innermost LOOP's in L3 (l3/ast.ts) and the SWITCH's in
|
|
596
|
+
// C, so printing one between `case` labels rebinds it — a changed program that reads as ordinary
|
|
555
597
|
// C. Refused HERE because the rebinding is the printer's, for every producer rather than for
|
|
556
|
-
// the two switch regimes
|
|
557
|
-
//
|
|
598
|
+
// the two switch regimes (`refuseMisboundBreak`, which checks a loop body the same way for a
|
|
599
|
+
// `break` marked `ofSwitch`). The structurer spells a loop exit inside an arm as the switch's
|
|
600
|
+
// own `break` or refuses it, so no recovery reaches either today: a contract on the next one.
|
|
558
601
|
//
|
|
559
602
|
// `continue;` is deliberately NOT refused: C binds it to the smallest enclosing ITERATION
|
|
560
603
|
// statement, which a `switch` is not, so it already means what L3 means. What a pass that re-spells
|
|
@@ -563,9 +606,7 @@ function printSwitchStmt(s: Extract<Stmt, { k: 'switch' }>, indent: string, vt:
|
|
|
563
606
|
// `respellCountdown` in l3/reindex.ts, and l3/unreduce.ts) each scan switch arms for the node
|
|
564
607
|
// before firing, and a fourth must too.
|
|
565
608
|
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
|
-
}
|
|
609
|
+
refuseMisboundBreak(body, 'switch');
|
|
569
610
|
}
|
|
570
611
|
const out = [`${indent}switch (${pe(s.scrutinee, 99)}) {`];
|
|
571
612
|
const ci = indent + ' '; // case-label indent
|
|
@@ -585,7 +626,7 @@ function printSwitchStmt(s: Extract<Stmt, { k: 'switch' }>, indent: string, vt:
|
|
|
585
626
|
const printDefault = (): void => {
|
|
586
627
|
out.push(`${ci}default:`);
|
|
587
628
|
for (const t of s.default!) {
|
|
588
|
-
out
|
|
629
|
+
append(out, printStmt(t, bi, vt, leaf));
|
|
589
630
|
}
|
|
590
631
|
// A default that is NOT last would otherwise fall into the case below it — the mirror of the
|
|
591
632
|
// rule for cases. The last one needs no `break;` because there is nothing under it.
|
|
@@ -608,8 +649,13 @@ function printSwitchStmt(s: Extract<Stmt, { k: 'switch' }>, indent: string, vt:
|
|
|
608
649
|
for (const v of c.values) {
|
|
609
650
|
out.push(`${ci}case ${v}:`);
|
|
610
651
|
}
|
|
652
|
+
// The label a `goto` of this switch lands on, after the arm's case labels: entering it there
|
|
653
|
+
// runs exactly what falling into the arm runs.
|
|
654
|
+
if (c.label !== undefined) {
|
|
655
|
+
out.push(`${ci}${c.label}:`);
|
|
656
|
+
}
|
|
611
657
|
for (const t of c.body) {
|
|
612
|
-
out
|
|
658
|
+
append(out, printStmt(t, bi, vt, leaf));
|
|
613
659
|
}
|
|
614
660
|
// A case whose body ends in `return`/`break` (a terminated arm) needs no `break;`; only an
|
|
615
661
|
// open non-fall-through arm gets one. `fallsThrough` omits it so control drops to the next case.
|
|
@@ -624,13 +670,26 @@ function printSwitchStmt(s: Extract<Stmt, { k: 'switch' }>, indent: string, vt:
|
|
|
624
670
|
return out;
|
|
625
671
|
}
|
|
626
672
|
|
|
627
|
-
/**
|
|
628
|
-
*
|
|
629
|
-
* `
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
673
|
+
/** L3 has two `break`s (l3/ast.ts) and C one: C binds `break;` to the innermost enclosing loop OR
|
|
674
|
+
* switch, so a `break` prints as what L3 meant only where its own kind is that innermost
|
|
675
|
+
* statement. `body` is a loop body or a switch arm (`owner`); a loop or switch opened inside it
|
|
676
|
+
* captures its own breaks and is checked when it is printed, so it is not walked. Refused loud on
|
|
677
|
+
* either mismatch — a plain (loop) `break` in an arm, a switch's `break` in a loop body — since
|
|
678
|
+
* each prints as ordinary C that leaves a different statement. */
|
|
679
|
+
function refuseMisboundBreak(body: Stmt[], owner: 'loop' | 'switch'): void {
|
|
680
|
+
const misbound = (ss: Stmt[]): boolean =>
|
|
681
|
+
ss.some(
|
|
682
|
+
(s) =>
|
|
683
|
+
(s.k === 'break' && (s.ofSwitch === true) !== (owner === 'switch')) ||
|
|
684
|
+
(s.k === 'if' && (misbound(s.then) || misbound(s.else))),
|
|
685
|
+
);
|
|
686
|
+
if (misbound(body)) {
|
|
687
|
+
throw new Error(
|
|
688
|
+
owner === 'switch'
|
|
689
|
+
? 'c backend: a switch arm carries a loop-scoped `break;`, which C would bind to the switch'
|
|
690
|
+
: 'c backend: a loop body carries a switch-scoped `break;`, which C would bind to the loop',
|
|
691
|
+
);
|
|
692
|
+
}
|
|
634
693
|
}
|
|
635
694
|
|
|
636
695
|
// Does a statement list end in a control-flow terminator (so a trailing `break;` would be dead)?
|
|
@@ -641,10 +700,28 @@ function endsTerminated(body: Stmt[]): boolean {
|
|
|
641
700
|
(last.k === 'return' ||
|
|
642
701
|
last.k === 'break' ||
|
|
643
702
|
last.k === 'continue' ||
|
|
703
|
+
last.k === 'goto' ||
|
|
704
|
+
last.k === 'caseEndGoto' ||
|
|
644
705
|
(last.k === 'if' && endsTerminated(last.then) && last.else.length > 0 && endsTerminated(last.else)))
|
|
645
706
|
);
|
|
646
707
|
}
|
|
647
708
|
|
|
709
|
+
/** Is `e` a pointer to another pointee than the pointer slot `t` declares, as rendered? Its pointer
|
|
710
|
+
* type is its C type, or, for the address of a declared object (which `exprCType` leaves
|
|
711
|
+
* unknown), a pointer to that object or to an array's element, the way it decays. A `void *` slot
|
|
712
|
+
* takes every object pointer implicitly. A `void *` value converts into any pointer slot in C
|
|
713
|
+
* (mwcc's C takes `gp = (void *)x` into an `s32 *gp`, compiled) but not in C++ (`cxx`). An unknown
|
|
714
|
+
* type is no opinion. */
|
|
715
|
+
function otherPointee(t: IrType | undefined, e: Expr, vt: (name: string) => IrType | undefined, cxx: boolean): boolean {
|
|
716
|
+
if (t?.kind !== 'ptr' || t.to.kind === 'void') {
|
|
717
|
+
return false;
|
|
718
|
+
}
|
|
719
|
+
const of = e.k === 'addr' ? vt(e.name) : undefined;
|
|
720
|
+
const ct = of !== undefined ? T.ptr(of.kind === 'array' ? of.elem : of) : exprCType(e, vt);
|
|
721
|
+
const p = ct?.kind === 'array' ? T.ptr(ct.elem) : ct;
|
|
722
|
+
return p?.kind === 'ptr' && (cxx || p.to.kind !== 'void') && !typeEquals(p, t);
|
|
723
|
+
}
|
|
724
|
+
|
|
648
725
|
// C-FAMILY WRITE LEGALIZATION (the assign-side sibling of the deref legalization in printExpr):
|
|
649
726
|
// the C family's answer to `writesNonPointerIntoPointer` is the reinterpret cast to the DECLARED
|
|
650
727
|
// type — exactly what the machine's register move does — where Pascal, having no such cast,
|
|
@@ -654,9 +731,18 @@ function legalizePointerWrites(fn: SFn): SFn {
|
|
|
654
731
|
const vt = declaredTypes(fn);
|
|
655
732
|
// …and the mirror: a rendered pointer into an INTEGER slot takes the slot's type. A cast keeps
|
|
656
733
|
// the sum it wraps, so the bytes are the uncast write's; CodeWarrior rejects that write outright.
|
|
734
|
+
//
|
|
735
|
+
// …and, where the compiler refuses the third implicit conversion too (C++, or mwcc's C:
|
|
736
|
+
// `SFn.strictConversions`), a pointer of ANOTHER pointee into a pointer slot: mwcc rejects
|
|
737
|
+
// `return t;` from an `s32 *` function when `t` is a `const u32[2]` static (mwcc: "illegal implicit
|
|
738
|
+
// conversion" of an `unsigned int *` to an `int *`, compiled), and takes it cast (`otherPointee`
|
|
739
|
+
// says which `void *` converts implicitly).
|
|
740
|
+
const strict = fn.dialect === 'c++' || fn.strictConversions === true;
|
|
657
741
|
const castTo = (t: IrType | undefined, e: Expr): Expr =>
|
|
658
|
-
writesNonPointerIntoPointer(t, e, vt) ||
|
|
659
|
-
|
|
742
|
+
writesNonPointerIntoPointer(t, e, vt) ||
|
|
743
|
+
(t?.kind === 'int' && exprCType(e, vt)?.kind === 'ptr') ||
|
|
744
|
+
(strict && otherPointee(t, e, vt, fn.dialect === 'c++'))
|
|
745
|
+
? { k: 'cast', to: t!, e }
|
|
660
746
|
: e;
|
|
661
747
|
const fix = (s: Stmt): Stmt => {
|
|
662
748
|
switch (s.k) {
|
|
@@ -692,7 +778,7 @@ function legalizePointerWrites(fn: SFn): SFn {
|
|
|
692
778
|
/** The body of a C-family function: local declarations + statements, one string per line. The
|
|
693
779
|
* SIGNATURE (return type + name + params, plus any C++ scope/`this`/mangling) is the caller's —
|
|
694
780
|
* that is the language-divergent part each backend owns. */
|
|
695
|
-
function cFamilyBody(fn0: SFn, leaf
|
|
781
|
+
function cFamilyBody(fn0: SFn, leaf: LeafHook | undefined, designators: boolean): string[] {
|
|
696
782
|
const fn = legalizePointerWrites(fn0);
|
|
697
783
|
// The legalization env: every printed var's declared type and pointee volatility, from the SAME
|
|
698
784
|
// params/locals the emitted declarations come from — so the printer judges exactly the C the
|
|
@@ -700,7 +786,7 @@ function cFamilyBody(fn0: SFn, leaf?: LeafHook): string[] {
|
|
|
700
786
|
const vt: PrintEnv = printEnv(fn);
|
|
701
787
|
const lines: string[] = [];
|
|
702
788
|
for (const st of fn.statics ?? []) {
|
|
703
|
-
lines
|
|
789
|
+
append(lines, staticDefinition(st));
|
|
704
790
|
}
|
|
705
791
|
for (const l of fn.locals) {
|
|
706
792
|
// Both facts render at the PREFIX position, where C's declarator grammar reads them
|
|
@@ -710,19 +796,37 @@ function cFamilyBody(fn0: SFn, leaf?: LeafHook): string[] {
|
|
|
710
796
|
// no variation or recognizer produces one. The NAME is placed by `cDeclare`, the one
|
|
711
797
|
// placement every declaration position in this backend uses: a local can carry an array type
|
|
712
798
|
// — the storage extent a frame object pins — and C puts those extents after the name.
|
|
713
|
-
|
|
799
|
+
const head = ` ${l.volatile || l.pointeeVolatile ? 'volatile ' : ''}${cDeclare(l.type, l.name)}`;
|
|
800
|
+
lines.push(
|
|
801
|
+
...(l.init === undefined ? [`${head};`] : initialized(head, l.type, l.init, l.initString === true, designators)),
|
|
802
|
+
);
|
|
714
803
|
}
|
|
715
804
|
for (const s of fn.body) {
|
|
716
|
-
lines
|
|
805
|
+
append(lines, printStmt(s, ' ', vt, leaf));
|
|
717
806
|
}
|
|
718
807
|
return lines;
|
|
719
808
|
}
|
|
720
809
|
|
|
721
|
-
/** One initial
|
|
810
|
+
/** One initial number: hex from 10 up, a negative one with its sign, so a byte table reads as the
|
|
722
811
|
* bytes and a signed one as its values. */
|
|
723
|
-
const
|
|
812
|
+
const initNumber = (v: number): string =>
|
|
724
813
|
Math.abs(v) < 10 ? `${v}` : `${v < 0 ? '-' : ''}0x${Math.abs(v).toString(16)}`;
|
|
725
814
|
|
|
815
|
+
/** One initial element of type `elem`: a number, or the address constant a relocated one holds,
|
|
816
|
+
* converted to the element's integer type — `(u32)&sym`, and with an addend `(u32)((u8 *)&sym + k)`:
|
|
817
|
+
* the offset in bytes, as the relocation counts it. Not `(u32)&sym + k`, which agbcc folds alike but
|
|
818
|
+
* mwcc refuses as an illegal constant expression (compiled); through a byte pointer both write the
|
|
819
|
+
* pointer-typed source's word and relocation (`&garr[2]`, `&garr[-1]`, compiled on both). */
|
|
820
|
+
function initElement(v: number | InitAddress, elem: IrType): string {
|
|
821
|
+
if (typeof v === 'number') {
|
|
822
|
+
return initNumber(v);
|
|
823
|
+
}
|
|
824
|
+
const t = cType(elem);
|
|
825
|
+
return v.addend === 0
|
|
826
|
+
? `(${t})&${v.symbol}`
|
|
827
|
+
: `(${t})((u8 *)&${v.symbol} ${v.addend < 0 ? '-' : '+'} ${initNumber(Math.abs(v.addend))})`;
|
|
828
|
+
}
|
|
829
|
+
|
|
726
830
|
/** A byte array's initial elements as a string literal: printable characters as themselves, every
|
|
727
831
|
* other byte in three-digit octal (which no following digit can extend), and a `?` after a `?`
|
|
728
832
|
* escaped so no trigraph forms. The trailing zeros are left to the declared size, which C fills
|
|
@@ -750,26 +854,45 @@ function stringLiteral(init: readonly number[]): string {
|
|
|
750
854
|
function staticDefinition(st: SStatic): string[] {
|
|
751
855
|
const aligned = st.align === undefined ? '' : ` __attribute__((aligned(${st.align})))`;
|
|
752
856
|
const head = ` static ${st.const ? 'const ' : ''}${cDeclare(st.type, st.name)}${aligned}`;
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
857
|
+
return st.init === undefined ? [`${head};`] : initialized(head, st.type, st.init, st.string === true, false);
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
/** A declaration `head` of `type` with its initializer: a string literal, one scalar, or an
|
|
861
|
+
* array's elements laid out eight to a line. An element left out (null) is skipped and the next
|
|
862
|
+
* one spelled by designator, `[k] = v` — or, where the compiler cannot parse one (`SFn.designators`
|
|
863
|
+
* absent: IDO, mwcc) or the language has none (C++), spelled as the zero it is. */
|
|
864
|
+
function initialized(
|
|
865
|
+
head: string,
|
|
866
|
+
type: IrType,
|
|
867
|
+
init: readonly (number | InitAddress | null)[],
|
|
868
|
+
string: boolean,
|
|
869
|
+
designators: boolean,
|
|
870
|
+
): string[] {
|
|
871
|
+
const whole = init.map((v) => v ?? 0);
|
|
872
|
+
if (string) {
|
|
873
|
+
// a string's elements are bytes, and no byte holds an address (structure/local-statics.ts)
|
|
874
|
+
const bytes = whole.filter((v): v is number => typeof v === 'number');
|
|
875
|
+
if (bytes.length !== whole.length) {
|
|
876
|
+
throw new Error(`internal: a string initializer of '${head.trim()}' holds an address`);
|
|
877
|
+
}
|
|
878
|
+
return [`${head} = ${stringLiteral(bytes)};`];
|
|
758
879
|
}
|
|
759
|
-
if (
|
|
760
|
-
return [`${head} = ${initElement(
|
|
880
|
+
if (type.kind !== 'array') {
|
|
881
|
+
return [`${head} = ${initElement(whole[0], type)};`];
|
|
761
882
|
}
|
|
762
|
-
|
|
763
|
-
|
|
883
|
+
const items: string[] = [];
|
|
884
|
+
init.forEach((v, i) => {
|
|
885
|
+
if (v !== null || !designators) {
|
|
886
|
+
const e = initElement(v ?? 0, type.elem);
|
|
887
|
+
items.push(init[i - 1] === null && designators ? `[${i}] = ${e}` : e);
|
|
888
|
+
}
|
|
889
|
+
});
|
|
890
|
+
if (items.length <= 8) {
|
|
891
|
+
return [`${head} = { ${items.join(', ')} };`];
|
|
764
892
|
}
|
|
765
893
|
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
|
-
);
|
|
894
|
+
for (let i = 0; i < items.length; i += 8) {
|
|
895
|
+
rows.push(` ${items.slice(i, i + 8).join(', ')},`);
|
|
773
896
|
}
|
|
774
897
|
return [`${head} = {`, ...rows, ' };'];
|
|
775
898
|
}
|
|
@@ -783,16 +906,29 @@ function structDecls(fn: SFn): string[] {
|
|
|
783
906
|
return (fn.structs ?? []).map((s) => renderStructDecl(s.name, s.fields));
|
|
784
907
|
}
|
|
785
908
|
|
|
786
|
-
/** Assemble a full C-family function from a caller-supplied signature line and the shared body.
|
|
787
|
-
|
|
909
|
+
/** Assemble a full C-family function from a caller-supplied signature line and the shared body.
|
|
910
|
+
* `designators`: the LANGUAGE spells an array element by index in an initializer (C, not C++).
|
|
911
|
+
* Whether the compiler's dialect parses one is the function's own fact (`SFn.designators`); an
|
|
912
|
+
* initializer is spelled by index only where both say yes. */
|
|
913
|
+
export function emitCFamily(signature: string, fn0: SFn, leaf?: LeafHook, designators = true): string {
|
|
788
914
|
// The declaration list is put into the target's own frame order, and a pointer cell's integer
|
|
789
915
|
// uses converted (l3/ptrcell.ts), HERE — in the shared C-family assembler, reached only from
|
|
790
916
|
// `cBackend.emit` and `cppBackend(...).emit`, so both C-family backends print them and no `.emit(`
|
|
791
917
|
// call site does (l3/slotorder.ts says why `emit` owns it).
|
|
792
|
-
|
|
918
|
+
// …and the compiler's anonymous constant images folded into the initializers of the locals
|
|
919
|
+
// their copies fill (l3/constinit.ts), which the tree keeps as copies until here.
|
|
920
|
+
const fn = orderSlotLocals(legalizePointerCells(foldConstantImages(fn0)));
|
|
921
|
+
const stray = caseGotoViolation(fn.body);
|
|
922
|
+
if (stray !== null) {
|
|
923
|
+
throw new Error(`c backend: ${stray}`);
|
|
924
|
+
}
|
|
793
925
|
const decls = structDecls(fn);
|
|
794
926
|
const preamble = decls.length ? decls.join('\n') + '\n' : '';
|
|
795
|
-
return
|
|
927
|
+
return (
|
|
928
|
+
preamble +
|
|
929
|
+
[`${signature} {`, ...cFamilyBody(fn, leaf, designators && fn.designators === true), '}'].join('\n') +
|
|
930
|
+
'\n'
|
|
931
|
+
);
|
|
796
932
|
}
|
|
797
933
|
|
|
798
934
|
// One C-family comment spelling, shared by the C and C++ backends. A `*` followed by `/`
|
package/src/backend/cpp.ts
CHANGED
|
@@ -138,6 +138,8 @@ export function cppBackend(spec: CppFnSpec, floatSlots: FloatSlots | undefined):
|
|
|
138
138
|
return {
|
|
139
139
|
id: 'cpp',
|
|
140
140
|
spellsSwitchFallthrough: true,
|
|
141
|
+
spellsSwitchBreak: true,
|
|
142
|
+
spellsEarlyReturn: true,
|
|
141
143
|
emit(fn: SFn): string {
|
|
142
144
|
// Map each lifted param var → its C++ meaning: `this` (bare member access) or a named param
|
|
143
145
|
// (a pointer-to-class param uses `->`). A pointer-to-known-class param is a member receiver.
|
|
@@ -223,7 +225,7 @@ export function cppBackend(spec: CppFnSpec, floatSlots: FloatSlots | undefined):
|
|
|
223
225
|
const paramList = spec.params.map((p) => declareCpp(p.type, p.name)).join(', ');
|
|
224
226
|
const decls = classDecls(spec, paramList);
|
|
225
227
|
const signature = `${spellType(spec.retType)} ${spec.cls ? spec.cls + '::' : ''}${spec.method}(${paramList})`;
|
|
226
|
-
return (decls ? decls + '\n' : '') + emitCFamily(signature, fn, leaf);
|
|
228
|
+
return (decls ? decls + '\n' : '') + emitCFamily(signature, fn, leaf, false);
|
|
227
229
|
},
|
|
228
230
|
comment: cComment, // C++ shares C's block-comment spelling
|
|
229
231
|
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** `out.push(...lines)` without the spread: a spread passes every line as an ARGUMENT, and V8 throws
|
|
2
|
+
* "Maximum call stack size exceeded" past about 120k of them (120,000 print, 130,000 throw, node
|
|
3
|
+
* 24). A structuring with a long arm reaches that at a nesting depth of a few dozen, so the throw
|
|
4
|
+
* reads as deep recursion when it is only size. Every printer appends a child's lines with this,
|
|
5
|
+
* so the limit binds no backend (the C family and Pascal alike). */
|
|
6
|
+
export function append(out: string[], lines: readonly string[]): void {
|
|
7
|
+
for (const line of lines) {
|
|
8
|
+
out.push(line);
|
|
9
|
+
}
|
|
10
|
+
}
|
package/src/backend/pascal.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { IrType, typeToString } from '../ir/types';
|
|
|
12
12
|
import { BinOp, Expr, LanguageBackend, SFn, Stmt, isLoop } from '../l3/ast';
|
|
13
13
|
import { orderSlotLocals } from '../l3/slotorder';
|
|
14
14
|
import { type VarTypes, declaredTypes, derefStrideOk, exprCType, writesNonPointerIntoPointer } from '../l3/typing';
|
|
15
|
+
import { append } from './lines';
|
|
15
16
|
|
|
16
17
|
// Infix operators IDO Pascal spells directly.
|
|
17
18
|
const OP: Partial<Record<BinOp, string>> = {
|
|
@@ -140,10 +141,10 @@ function makePrinter(vt: VarTypes) {
|
|
|
140
141
|
// Casts have no faithful IDO-Pascal spelling yet — fail LOUD rather than emit silently-wrong
|
|
141
142
|
// source. Tree-level producers reaching here: the width-narrowing idiom casts (agbcc-gated,
|
|
142
143
|
// 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
|
-
//
|
|
144
|
+
// `field` case above throws first), the arithmetic table's `(s32)ptr` legalization (any
|
|
145
|
+
// target), and the byte-pointer walk of a pointer offset by a runtime value (any target,
|
|
146
|
+
// and reachable with no `field` in the tree, where a Pascal spelling would walk ELEMENTS
|
|
147
|
+
// where the asm walked bytes).
|
|
147
148
|
// Scalar deref casts never appear in the tree — the index case above owns that judgment.
|
|
148
149
|
case 'cast':
|
|
149
150
|
throw new Error(`pascal backend: cast has no IDO Pascal spelling yet`);
|
|
@@ -202,23 +203,30 @@ function makePrinter(vt: VarTypes) {
|
|
|
202
203
|
return s.value ? [`${indent}${fnName} := ${pe(s.value)};`] : [];
|
|
203
204
|
case 'if': {
|
|
204
205
|
const cond = pe(s.cond);
|
|
205
|
-
|
|
206
|
-
|
|
206
|
+
// A branch goes bare only where it is ONE Pascal statement that cannot capture what follows.
|
|
207
|
+
// A `for` prints as two (its init, then a `while`), so it always takes begin/end. And with an
|
|
208
|
+
// `else` to come, a then-branch that is itself an `if` or a loop (whose body may end in one)
|
|
209
|
+
// would take that `else` as its own innermost `if`'s — Pascal's dangling else, which C's
|
|
210
|
+
// braces had ruled out: `if (a) { if (b) X; } else Y;` printed bare runs Y when b fails.
|
|
211
|
+
const bare = (stmts: Stmt[], open: boolean) =>
|
|
212
|
+
stmts.length === 1 && stmts[0].k !== 'for' && !(open && (stmts[0].k === 'if' || isLoop(stmts[0])));
|
|
213
|
+
const block = (stmts: Stmt[], ind: string, open = false) =>
|
|
214
|
+
bare(stmts, open)
|
|
207
215
|
? ps(fnName, stmts[0], ind, tail)
|
|
208
216
|
: [`${ind}begin`, ...list(stmts, ind + ' ', tail), `${ind}end`];
|
|
209
217
|
// Pascal: no `;` before `else`; the branch statements already carry their own.
|
|
210
218
|
const out = [`${indent}if ${cond} then`];
|
|
211
|
-
out
|
|
219
|
+
append(out, block(s.then, indent + ' ', s.else.length > 0));
|
|
212
220
|
if (s.else.length) {
|
|
213
221
|
out.push(`${indent}else`);
|
|
214
|
-
out
|
|
222
|
+
append(out, block(s.else, indent + ' '));
|
|
215
223
|
}
|
|
216
224
|
return out;
|
|
217
225
|
}
|
|
218
226
|
case 'while': {
|
|
219
227
|
// loop bodies are NEVER tail position — control returns to the test
|
|
220
228
|
const body =
|
|
221
|
-
s.body.length === 1
|
|
229
|
+
s.body.length === 1 && s.body[0].k !== 'for'
|
|
222
230
|
? ps(fnName, s.body[0], indent + ' ')
|
|
223
231
|
: [`${indent}begin`, ...s.body.flatMap((x) => ps(fnName, x, indent + ' ')), `${indent}end`];
|
|
224
232
|
return [`${indent}while ${pe(s.cond)} do`, ...body];
|
|
@@ -248,12 +256,19 @@ function makePrinter(vt: VarTypes) {
|
|
|
248
256
|
throw new Error('pascal backend: `break` has no IDO Pascal spelling');
|
|
249
257
|
case 'continue':
|
|
250
258
|
throw new Error('pascal backend: `continue` has no IDO Pascal spelling');
|
|
259
|
+
// A jump into another arm of a `case-of` has no Pascal spelling: an arm is a statement, and a
|
|
260
|
+
// label may not be entered from outside the statement that holds it.
|
|
261
|
+
case 'goto':
|
|
262
|
+
throw new Error('pascal backend: a `goto` into a case arm has no IDO Pascal spelling');
|
|
263
|
+
// A jump to an arm's end lands on a `break`, which Pascal has none of either.
|
|
264
|
+
case 'caseEndGoto':
|
|
265
|
+
throw new Error('pascal backend: a `goto` to the end of a case arm has no IDO Pascal spelling');
|
|
251
266
|
case 'switch': {
|
|
252
267
|
// IDO/SGI Pascal `case E of L: S; … otherwise S end`. There is NO fall-through in Pascal case-of,
|
|
253
268
|
// so a `fallsThrough` arm has no faithful spelling → loud-fail (mirrors the `field`/`cast`/`%`
|
|
254
269
|
// throws above). Each arm's body is a single statement or a begin/end block.
|
|
255
270
|
const arm = (body: Stmt[], ind: string) =>
|
|
256
|
-
body.length === 1
|
|
271
|
+
body.length === 1 && body[0].k !== 'for'
|
|
257
272
|
? ps(fnName, body[0], ind + ' ')
|
|
258
273
|
: [`${ind} begin`, ...body.flatMap((x) => ps(fnName, x, ind + ' ')), `${ind} end`];
|
|
259
274
|
// Pascal has no early `return`: a `return v` inside a case renders `fnName := v` and then FALLS
|
|
@@ -277,11 +292,15 @@ function makePrinter(vt: VarTypes) {
|
|
|
277
292
|
if (c.fallsThrough) {
|
|
278
293
|
throw new Error('pascal backend: switch fall-through has no faithful IDO Pascal case-of spelling');
|
|
279
294
|
}
|
|
280
|
-
out.push(`${indent} ${c.values.join(', ')}
|
|
295
|
+
out.push(`${indent} ${c.values.join(', ')}:`);
|
|
296
|
+
append(out, arm(c.body, indent + ' '));
|
|
281
297
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
298
|
+
// A C `switch` with no `default` runs on past a value it does not list; IDO Pascal's
|
|
299
|
+
// `case` raises a run-time `caseerror` there instead. So the clause is always printed, an
|
|
300
|
+
// empty one when the source had no default.
|
|
301
|
+
out.push(`${indent} otherwise`);
|
|
302
|
+
if (s.default?.length) {
|
|
303
|
+
append(out, arm(s.default, indent + ' '));
|
|
285
304
|
}
|
|
286
305
|
out.push(`${indent}end;`);
|
|
287
306
|
return out;
|
|
@@ -298,17 +317,29 @@ export const pascalBackend: LanguageBackend = {
|
|
|
298
317
|
// spells as plain if-nesting, which Pascal prints, so the choice is between a decompiled
|
|
299
318
|
// function and a stub.
|
|
300
319
|
spellsSwitchFallthrough: false,
|
|
320
|
+
// Nor has Pascal a `break`, so recovery must not mint a switch's mid-arm one either: a closed arm
|
|
321
|
+
// that leaves early spells as nested `if`s that copy the arm's tail into each path.
|
|
322
|
+
spellsSwitchBreak: false,
|
|
323
|
+
// Nor an early `return` (the `return` case below loud-fails one not in tail position), so
|
|
324
|
+
// structuring reads the return-tail copies rather than the join past early returns.
|
|
325
|
+
spellsEarlyReturn: false,
|
|
301
326
|
emit(fn0: SFn): string {
|
|
302
327
|
// The declaration list is put into the target's own frame order HERE, as the C family does it
|
|
303
328
|
// in its shared assembler — owned by `emit`, never by a `.emit(` call site (l3/slotorder.ts).
|
|
304
329
|
const fn = orderSlotLocals(fn0);
|
|
305
330
|
// 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.
|
|
331
|
+
// spelling of one (a typed constant, or a unit-level var) has not been measured — loud. So is
|
|
332
|
+
// the compiler's anonymous constant a local's initializer is copied from (l3/constinit.ts).
|
|
307
333
|
if (fn.statics?.length) {
|
|
308
334
|
throw new Error(
|
|
309
335
|
`pascal backend: the function-scope static '${fn.statics[0].name}' has no IDO Pascal spelling yet`,
|
|
310
336
|
);
|
|
311
337
|
}
|
|
338
|
+
if (fn.images?.length) {
|
|
339
|
+
throw new Error(
|
|
340
|
+
`pascal backend: the compiler's anonymous constant '${fn.images[0].name}' a local is initialized from has no IDO Pascal spelling yet`,
|
|
341
|
+
);
|
|
342
|
+
}
|
|
312
343
|
// Same env discipline as the C family (cfamily.ts cFamilyBody): the printer judges derefs
|
|
313
344
|
// against the exact declarations it emits.
|
|
314
345
|
const ps = makePrinter(declaredTypes(fn));
|
|
@@ -332,7 +363,7 @@ export const pascalBackend: LanguageBackend = {
|
|
|
332
363
|
lines.push('var', ...fn.locals.map((l) => ` ${l.name}: ${pasType(l.type)};`));
|
|
333
364
|
}
|
|
334
365
|
lines.push('begin');
|
|
335
|
-
fn.body.forEach((s, i) => lines
|
|
366
|
+
fn.body.forEach((s, i) => append(lines, ps(fn.name, s, ' ', i === fn.body.length - 1)));
|
|
336
367
|
lines.push('end;');
|
|
337
368
|
return lines.join('\n') + '\n';
|
|
338
369
|
},
|