@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/cpp.ts
CHANGED
|
@@ -13,10 +13,14 @@
|
|
|
13
13
|
// Scope: free functions and non-virtual member functions with scalar/pointer params and named
|
|
14
14
|
// field access. Virtual dispatch, references, and constructors/destructors are deliberately not
|
|
15
15
|
// built ahead of an inhabitant.
|
|
16
|
-
import { Expr, LanguageBackend, SFn } from '../l3/ast';
|
|
17
|
-
import { type CppType, mangle, spellType } from '../mangle';
|
|
16
|
+
import { Expr, LanguageBackend, SFn, walkExprs } from '../l3/ast';
|
|
17
|
+
import { type CppType, declareCpp, mangle, spellType } from '../mangle';
|
|
18
|
+
import type { TargetDescription } from '../target';
|
|
18
19
|
import { LeafHook, cComment, emitCFamily } from './cfamily';
|
|
19
20
|
|
|
21
|
+
/** How the target's argument slots hold floats (`TargetDescription.fpu`). */
|
|
22
|
+
type FloatSlots = NonNullable<TargetDescription['fpu']>['slots'];
|
|
23
|
+
|
|
20
24
|
export interface CppClass {
|
|
21
25
|
fields: { name: string; type: CppType }[];
|
|
22
26
|
} // field i at word offset i
|
|
@@ -33,25 +37,127 @@ export function cppSymbol(spec: CppFnSpec): string {
|
|
|
33
37
|
return mangle({ name: spec.method, cls: spec.cls, params: spec.params.map((p) => p.type) });
|
|
34
38
|
}
|
|
35
39
|
|
|
36
|
-
/**
|
|
37
|
-
*
|
|
38
|
-
|
|
40
|
+
/** The lifted parameter each EXPLICIT spec parameter is (its SFn name), for a member function after
|
|
41
|
+
* `this` (SFn.params[0]). How depends on the target's float slot model (`TargetDescription.fpu`),
|
|
42
|
+
* because the lifted order is the ABI sort's (frontend/fpu.ts `fpuArgSlots`):
|
|
43
|
+
* - `'separate'` (PowerPC EABI): by register FILE, then by position within it. The files count
|
|
44
|
+
* independently and every float argument sorts after the integers, so `float g(float x, int n)`
|
|
45
|
+
* lifts as `(s32 a0, float a1)`. A float the body never reads leaves no hole, so the spec may
|
|
46
|
+
* have MORE floats than the lift (`int m(int n, float x)` lifts as `(s32 a0)`), never fewer.
|
|
47
|
+
* - `'leading'` (MIPS o32), and a target with no float file: by POSITION. The lifted order is the
|
|
48
|
+
* slot order, which is the source order, and an unread leading float is minted as an integer
|
|
49
|
+
* hole — so binding by file would hand `int k(float x, int n)`'s `n` the hole `a0`.
|
|
50
|
+
* NULL when the two contradict: a lifted float the spec has no float for, or has one of the other
|
|
51
|
+
* precision for. No binding of the rest is then trustworthy. The precision is part of the
|
|
52
|
+
* contradiction because the body is spelled over the spec's types: a lifted `double` bound to a
|
|
53
|
+
* `float a, float b` prints `a + b`, which is a single-precision add where the machine did a
|
|
54
|
+
* double one.
|
|
55
|
+
*
|
|
56
|
+
* THE PRECISION IS AN OPERATION'S, so only a parameter the body reads has one to contradict. A
|
|
57
|
+
* frontend types every float register of a function at the function's one precision
|
|
58
|
+
* (frontend/fpu.ts `fpPrecision`), and that includes an argument slot the body never reads, which
|
|
59
|
+
* it mints only to hold the later arguments' places: `float f1(double a, float b){ return b + b; }`
|
|
60
|
+
* is `fadds f1,f2,f2`, and its `a` is a single-precision hole that nothing states the width of. So
|
|
61
|
+
* is a lifted float of no stated precision (`T.fUnstated`) anywhere. Either binds a spec float of
|
|
62
|
+
* both widths, and the spec's is the one to print.
|
|
63
|
+
*
|
|
64
|
+
* EXCEPT WHERE THE HOLE'S WIDTH PLACED THE PARAMETERS AFTER IT. Under `'leading'` a float takes one
|
|
65
|
+
* integer slot for a single and two for a double, counted at the function's precision
|
|
66
|
+
* (`fpuArgSlots`), so an unread hole of the other width than the spec's moves every integer after
|
|
67
|
+
* it: `float m2(double a, float b, int *p, int *q){ *p = 0; return b + b; }` (gcc2.7.2kmc) is
|
|
68
|
+
* `sw zero,0(a3)` with `a` laid out as ONE slot, and binding by position would name `a3` `q`. So
|
|
69
|
+
* there an unread float followed by an integer parameter keeps its precision.
|
|
70
|
+
*
|
|
71
|
+
* A spec `double` bound to a lifted NON-float is the same misplacement. A double takes two integer
|
|
72
|
+
* slots under o32 and on a target with no float file (agbcc passes one in r0:r1), so a body that
|
|
73
|
+
* reads no FPU register mints it as two 32-bit holes: `void n1(double d, int *p, int *q){ *p = 0; }`
|
|
74
|
+
* (gcc2.7.2kmc) is `sw zero,0(a2)` and lifts as `(s32 a0, s32 a1, s32 *a2)`, where binding by
|
|
75
|
+
* position would name `a2` `q`. Only a lifted 64-bit parameter holds both slots, so a spec double
|
|
76
|
+
* over a narrower one refuses when a lifted parameter follows it, and when the body reads it — a
|
|
77
|
+
* read half would print as the whole double (agbcc `int u1(int a, double d)` returning the word
|
|
78
|
+
* `((int *)&d)[0]` is `add r0,r1,#0`). A lifted parameter after it is not a refusal when the double
|
|
79
|
+
* is the spec's last and the body reads none of the slots from it on, which is IDO homing a
|
|
80
|
+
* trailing double it never uses (`int o1(int a, double d){ return a; }` spills `a2`/`a3`): nothing
|
|
81
|
+
* then binds past it. */
|
|
82
|
+
export function bindSpecParams(
|
|
83
|
+
spec: Pick<CppFnSpec, 'cls' | 'params'>,
|
|
84
|
+
lifted: Pick<SFn, 'params' | 'body'>,
|
|
85
|
+
floatSlots: FloatSlots | undefined,
|
|
86
|
+
): (string | undefined)[] | null {
|
|
87
|
+
const floatBits = (t: CppType) => (t.ptr !== 0 ? null : t.base === 'float' ? 32 : t.base === 'double' ? 64 : null);
|
|
88
|
+
const isFloat = (t: CppType) => floatBits(t) !== null;
|
|
89
|
+
const read = new Set<string>();
|
|
90
|
+
for (const e of walkExprs(lifted.body)) {
|
|
91
|
+
if (e.k === 'var') {
|
|
92
|
+
read.add(e.name);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const clashes = (p: SFn['params'][number], t: CppType | undefined, laysOut: boolean) =>
|
|
96
|
+
p.type.kind === 'float' &&
|
|
97
|
+
(t === undefined ||
|
|
98
|
+
floatBits(t) === null ||
|
|
99
|
+
(p.type.width !== null && (laysOut || read.has(p.name)) && floatBits(t) !== p.type.width));
|
|
100
|
+
const explicit = lifted.params.slice(spec.cls ? 1 : 0);
|
|
101
|
+
const lastAndUnread = (i: number) =>
|
|
102
|
+
i === spec.params.length - 1 && explicit.slice(i).every((p) => !read.has(p.name));
|
|
103
|
+
if (floatSlots !== 'separate') {
|
|
104
|
+
const clash = explicit.some((p, i) => {
|
|
105
|
+
const t = spec.params[i]?.type;
|
|
106
|
+
const later = explicit.slice(i + 1);
|
|
107
|
+
const oneSlot = p.type.kind !== 'float' && !('width' in p.type && p.type.width === 64);
|
|
108
|
+
return (
|
|
109
|
+
clashes(
|
|
110
|
+
p,
|
|
111
|
+
t,
|
|
112
|
+
later.some((q) => q.type.kind !== 'float'),
|
|
113
|
+
) ||
|
|
114
|
+
(t !== undefined &&
|
|
115
|
+
floatBits(t) === 64 &&
|
|
116
|
+
oneSlot &&
|
|
117
|
+
(later.length > 0 || read.has(p.name)) &&
|
|
118
|
+
!lastAndUnread(i))
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
return clash ? null : spec.params.map((_, i) => explicit[i]?.name);
|
|
122
|
+
}
|
|
123
|
+
const floats = explicit.filter((p) => p.type.kind === 'float');
|
|
124
|
+
const others = explicit.filter((p) => p.type.kind !== 'float');
|
|
125
|
+
const specFloats = spec.params.filter((p) => isFloat(p.type));
|
|
126
|
+
if (specFloats.length < floats.length || floats.some((p, i) => clashes(p, specFloats[i].type, false))) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
let f = 0;
|
|
130
|
+
let o = 0;
|
|
131
|
+
return spec.params.map((p) => (isFloat(p.type) ? floats[f++] : others[o++])?.name);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Build a C++ backend for one function, parameterized by its recovered C++ signature and the float
|
|
135
|
+
* slot model of the target it was lifted for. For a member function SFn.params[0] is `this`; the
|
|
136
|
+
* rest bind to `params` by `bindSpecParams`. */
|
|
137
|
+
export function cppBackend(spec: CppFnSpec, floatSlots: FloatSlots | undefined): LanguageBackend {
|
|
39
138
|
return {
|
|
40
139
|
id: 'cpp',
|
|
41
140
|
spellsSwitchFallthrough: true,
|
|
141
|
+
spellsSwitchBreak: true,
|
|
42
142
|
emit(fn: SFn): string {
|
|
43
143
|
// Map each lifted param var → its C++ meaning: `this` (bare member access) or a named param
|
|
44
144
|
// (a pointer-to-class param uses `->`). A pointer-to-known-class param is a member receiver.
|
|
45
145
|
const thisVar = spec.cls ? fn.params[0]?.name : undefined;
|
|
46
|
-
const explicitStart = spec.cls ? 1 : 0;
|
|
47
146
|
const rename = new Map<string, string>(); // lifted var → C++ name
|
|
48
147
|
const recv = new Map<string, { cls: string; via: 'this' | string }>(); // var → member receiver
|
|
49
148
|
if (thisVar) {
|
|
50
149
|
rename.set(thisVar, 'this');
|
|
51
150
|
recv.set(thisVar, { cls: spec.cls!, via: 'this' });
|
|
52
151
|
}
|
|
152
|
+
const bound = bindSpecParams(spec, fn, floatSlots);
|
|
153
|
+
if (!bound) {
|
|
154
|
+
throw new Error(
|
|
155
|
+
`cpp backend: the spec's floating-point parameters do not match the lifted function's — ` +
|
|
156
|
+
`supply a signature whose float and integer parameters are the ones it takes`,
|
|
157
|
+
);
|
|
158
|
+
}
|
|
53
159
|
spec.params.forEach((p, i) => {
|
|
54
|
-
const v =
|
|
160
|
+
const v = bound[i];
|
|
55
161
|
if (!v) {
|
|
56
162
|
return;
|
|
57
163
|
}
|
|
@@ -115,10 +221,10 @@ export function cppBackend(spec: CppFnSpec): LanguageBackend {
|
|
|
115
221
|
return null;
|
|
116
222
|
};
|
|
117
223
|
|
|
118
|
-
const paramList = spec.params.map((p) =>
|
|
224
|
+
const paramList = spec.params.map((p) => declareCpp(p.type, p.name)).join(', ');
|
|
119
225
|
const decls = classDecls(spec, paramList);
|
|
120
226
|
const signature = `${spellType(spec.retType)} ${spec.cls ? spec.cls + '::' : ''}${spec.method}(${paramList})`;
|
|
121
|
-
return (decls ? decls + '\n' : '') + emitCFamily(signature, fn, leaf);
|
|
227
|
+
return (decls ? decls + '\n' : '') + emitCFamily(signature, fn, leaf, false);
|
|
122
228
|
},
|
|
123
229
|
comment: cComment, // C++ shares C's block-comment spelling
|
|
124
230
|
};
|
|
@@ -140,7 +246,7 @@ function typeWidth(t: CppType): number {
|
|
|
140
246
|
function classDecls(spec: CppFnSpec, paramList: string): string {
|
|
141
247
|
const out: string[] = [];
|
|
142
248
|
for (const [cname, cdef] of Object.entries(spec.classes ?? {})) {
|
|
143
|
-
const fields = cdef.fields.map((f) => `${
|
|
249
|
+
const fields = cdef.fields.map((f) => `${declareCpp(f.type, f.name)};`).join(' ');
|
|
144
250
|
const method = cname === spec.cls ? ` ${spellType(spec.retType)} ${spec.method}(${paramList});` : '';
|
|
145
251
|
out.push(`struct ${cname} { ${fields}${method} };`);
|
|
146
252
|
}
|
package/src/backend/pascal.ts
CHANGED
|
@@ -52,6 +52,13 @@ function pasType(t: IrType): string {
|
|
|
52
52
|
return '^' + pasType(t.to);
|
|
53
53
|
}
|
|
54
54
|
if (t.kind === 'int') {
|
|
55
|
+
// `Integer`/`Cardinal` are the machine word, and a NARROWER parameter or return fits in one,
|
|
56
|
+
// which is what this backend spells it as (a narrow local is refused where it is declared). A WIDER one does not: upas has no verified 64-bit type,
|
|
57
|
+
// and spelling a 64-bit value `Integer` would silently drop its high half. Loud, like every
|
|
58
|
+
// other unspellable construct in this backend.
|
|
59
|
+
if (t.width > 32) {
|
|
60
|
+
throw new Error(`pascal backend: no spelling for a ${t.width}-bit integer`);
|
|
61
|
+
}
|
|
55
62
|
return t.signed ? 'Integer' : 'Cardinal';
|
|
56
63
|
}
|
|
57
64
|
// `unknown` reaching a backend means recovery's totality contract already failed upstream —
|
|
@@ -81,7 +88,17 @@ function makePrinter(vt: VarTypes) {
|
|
|
81
88
|
throw new Error(`pascal backend: address-of global '${e.name}' has no IDO Pascal spelling yet`);
|
|
82
89
|
case 'const':
|
|
83
90
|
return String(e.value);
|
|
91
|
+
case 'fconst':
|
|
92
|
+
throw new Error('pascal backend: a double literal has no IDO Pascal spelling yet');
|
|
84
93
|
case 'call':
|
|
94
|
+
if (typeof e.fn !== 'string') {
|
|
95
|
+
throw new Error('pascal backend: a call through a pointer has no IDO Pascal spelling yet');
|
|
96
|
+
}
|
|
97
|
+
if (e.sret !== undefined) {
|
|
98
|
+
throw new Error(
|
|
99
|
+
`pascal backend: the call to '${e.fn}' returns a struct through memory, which has no spelling here`,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
85
102
|
return `${e.fn}(${e.args.map(pe).join(', ')})`;
|
|
86
103
|
case 'index': {
|
|
87
104
|
// The width-carrying access node (l3/ast.ts): each backend legalizes its own derefs. The
|
|
@@ -112,14 +129,21 @@ function makePrinter(vt: VarTypes) {
|
|
|
112
129
|
case 'field':
|
|
113
130
|
throw new Error(`pascal backend: struct field access '${e.name}' has no IDO Pascal spelling yet`);
|
|
114
131
|
case 'un':
|
|
132
|
+
if (e.op === 'f-') {
|
|
133
|
+
throw new Error(`pascal backend: a float negation has no faithful IDO Pascal spelling`);
|
|
134
|
+
}
|
|
115
135
|
return e.op === '~' ? `bitnot(${pe(e.e)})` : `(${e.op === '!' ? 'not ' : e.op}${pe(e.e)})`;
|
|
136
|
+
// IDO Pascal has no increment operator and no expression with a side effect to put one in,
|
|
137
|
+
// so the update has no place to go — decline LOUD rather than drop it, like the casts below.
|
|
138
|
+
case 'postincr':
|
|
139
|
+
throw new Error(`pascal backend: post-increment of '${e.name}' has no IDO Pascal spelling yet`);
|
|
116
140
|
// Casts have no faithful IDO-Pascal spelling yet — fail LOUD rather than emit silently-wrong
|
|
117
141
|
// source. Tree-level producers reaching here: the width-narrowing idiom casts (agbcc-gated,
|
|
118
142
|
// so never on this path today), structure.ts's STRUCT-pointer casts (unreachable too — the
|
|
119
|
-
// `field` case above throws first),
|
|
120
|
-
// byte-pointer walk of a pointer offset by a runtime value (any target,
|
|
121
|
-
// `field` in the tree
|
|
122
|
-
//
|
|
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).
|
|
123
147
|
// Scalar deref casts never appear in the tree — the index case above owns that judgment.
|
|
124
148
|
case 'cast':
|
|
125
149
|
throw new Error(`pascal backend: cast has no IDO Pascal spelling yet`);
|
|
@@ -178,13 +202,20 @@ function makePrinter(vt: VarTypes) {
|
|
|
178
202
|
return s.value ? [`${indent}${fnName} := ${pe(s.value)};`] : [];
|
|
179
203
|
case 'if': {
|
|
180
204
|
const cond = pe(s.cond);
|
|
181
|
-
|
|
182
|
-
|
|
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)
|
|
183
214
|
? ps(fnName, stmts[0], ind, tail)
|
|
184
215
|
: [`${ind}begin`, ...list(stmts, ind + ' ', tail), `${ind}end`];
|
|
185
216
|
// Pascal: no `;` before `else`; the branch statements already carry their own.
|
|
186
217
|
const out = [`${indent}if ${cond} then`];
|
|
187
|
-
out.push(...block(s.then, indent + ' '));
|
|
218
|
+
out.push(...block(s.then, indent + ' ', s.else.length > 0));
|
|
188
219
|
if (s.else.length) {
|
|
189
220
|
out.push(`${indent}else`);
|
|
190
221
|
out.push(...block(s.else, indent + ' '));
|
|
@@ -194,7 +225,7 @@ function makePrinter(vt: VarTypes) {
|
|
|
194
225
|
case 'while': {
|
|
195
226
|
// loop bodies are NEVER tail position — control returns to the test
|
|
196
227
|
const body =
|
|
197
|
-
s.body.length === 1
|
|
228
|
+
s.body.length === 1 && s.body[0].k !== 'for'
|
|
198
229
|
? ps(fnName, s.body[0], indent + ' ')
|
|
199
230
|
: [`${indent}begin`, ...s.body.flatMap((x) => ps(fnName, x, indent + ' ')), `${indent}end`];
|
|
200
231
|
return [`${indent}while ${pe(s.cond)} do`, ...body];
|
|
@@ -229,7 +260,7 @@ function makePrinter(vt: VarTypes) {
|
|
|
229
260
|
// so a `fallsThrough` arm has no faithful spelling → loud-fail (mirrors the `field`/`cast`/`%`
|
|
230
261
|
// throws above). Each arm's body is a single statement or a begin/end block.
|
|
231
262
|
const arm = (body: Stmt[], ind: string) =>
|
|
232
|
-
body.length === 1
|
|
263
|
+
body.length === 1 && body[0].k !== 'for'
|
|
233
264
|
? ps(fnName, body[0], ind + ' ')
|
|
234
265
|
: [`${ind} begin`, ...body.flatMap((x) => ps(fnName, x, ind + ' ')), `${ind} end`];
|
|
235
266
|
// Pascal has no early `return`: a `return v` inside a case renders `fnName := v` and then FALLS
|
|
@@ -255,8 +286,11 @@ function makePrinter(vt: VarTypes) {
|
|
|
255
286
|
}
|
|
256
287
|
out.push(`${indent} ${c.values.join(', ')}:`, ...arm(c.body, indent + ' '));
|
|
257
288
|
}
|
|
258
|
-
|
|
259
|
-
|
|
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) {
|
|
260
294
|
out.push(...arm(s.default, indent + ' '));
|
|
261
295
|
}
|
|
262
296
|
out.push(`${indent}end;`);
|
|
@@ -274,10 +308,26 @@ export const pascalBackend: LanguageBackend = {
|
|
|
274
308
|
// spells as plain if-nesting, which Pascal prints, so the choice is between a decompiled
|
|
275
309
|
// function and a stub.
|
|
276
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,
|
|
277
314
|
emit(fn0: SFn): string {
|
|
278
315
|
// The declaration list is put into the target's own frame order HERE, as the C family does it
|
|
279
316
|
// in its shared assembler — owned by `emit`, never by a `.emit(` call site (l3/slotorder.ts).
|
|
280
317
|
const fn = orderSlotLocals(fn0);
|
|
318
|
+
// A function-scope static is a definition the MIPS frontends never recover, and IDO Pascal's
|
|
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).
|
|
321
|
+
if (fn.statics?.length) {
|
|
322
|
+
throw new Error(
|
|
323
|
+
`pascal backend: the function-scope static '${fn.statics[0].name}' has no IDO Pascal spelling yet`,
|
|
324
|
+
);
|
|
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
|
+
}
|
|
281
331
|
// Same env discipline as the C family (cfamily.ts cFamilyBody): the printer judges derefs
|
|
282
332
|
// against the exact declarations it emits.
|
|
283
333
|
const ps = makePrinter(declaredTypes(fn));
|
|
@@ -290,6 +340,14 @@ export const pascalBackend: LanguageBackend = {
|
|
|
290
340
|
: `function ${fn.name}(${params}): ${pasType(fn.retType)};`,
|
|
291
341
|
];
|
|
292
342
|
if (fn.locals.length) {
|
|
343
|
+
// A narrow LOCAL truncates every value assigned to it, and `Integer`/`Cardinal` would not: the
|
|
344
|
+
// width is the declaration's whole meaning, so it has no spelling here. A narrow parameter
|
|
345
|
+
// arrives already narrowed and keeps the word spelling.
|
|
346
|
+
for (const l of fn.locals) {
|
|
347
|
+
if (l.type.kind === 'int' && l.type.width < 32) {
|
|
348
|
+
throw new Error(`pascal backend: no spelling for a narrow local (${l.type.width} bits)`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
293
351
|
lines.push('var', ...fn.locals.map((l) => ` ${l.name}: ${pasType(l.type)};`));
|
|
294
352
|
}
|
|
295
353
|
lines.push('begin');
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// asmlift — the INTEGER CONSTANTS a declaration writes: a literal's value, and the value of a constant
|
|
2
|
+
// expression of literals, as an array's extent or a bit-field's width is written.
|
|
3
|
+
import type { Tokens } from './lex';
|
|
4
|
+
import type { Range } from './parse';
|
|
5
|
+
|
|
6
|
+
/** An integer literal's value and its suffix (`u`, `l`, `ll` in any case and order), read as C reads
|
|
7
|
+
* it: `0x` hexadecimal, a leading `0` octal. Undefined for any other text: `08`, `1.5`, `SIZE`. */
|
|
8
|
+
export function integerLiteral(text: string): { value: bigint; suffix: string } | undefined {
|
|
9
|
+
const m = /^(?:0x([0-9a-f]+)|0([0-7]*)|([1-9]\d*))([ul]*)$/i.exec(text);
|
|
10
|
+
if (m === null) {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
const [, hex, octal, decimal, suffix] = m;
|
|
14
|
+
const value =
|
|
15
|
+
hex !== undefined ? BigInt(`0x${hex}`) : octal !== undefined ? BigInt(`0o${octal || '0'}`) : BigInt(decimal);
|
|
16
|
+
return { value, suffix };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** The value of the integer constant expression in `r`, of literals, `+ - * /` and parentheses — the
|
|
20
|
+
* `u8 pad3[0x4 - 0x3]` a decomp header sizes its padding with — or undefined for anything else. */
|
|
21
|
+
export function constantValue(tokens: Tokens, r: Range): number | undefined {
|
|
22
|
+
let at = r.from;
|
|
23
|
+
const next = (): string | undefined => (at < r.to ? tokens.text(at++) : undefined);
|
|
24
|
+
const primary = (): number | undefined => {
|
|
25
|
+
const k = at;
|
|
26
|
+
const t = next();
|
|
27
|
+
if (t === '(') {
|
|
28
|
+
const v = sum();
|
|
29
|
+
return next() === ')' ? v : undefined;
|
|
30
|
+
}
|
|
31
|
+
if (t === '-') {
|
|
32
|
+
const v = primary();
|
|
33
|
+
return v === undefined ? undefined : -v;
|
|
34
|
+
}
|
|
35
|
+
const literal = t !== undefined && tokens.kind(k) === 'number' ? integerLiteral(t) : undefined;
|
|
36
|
+
return literal === undefined ? undefined : Number(literal.value);
|
|
37
|
+
};
|
|
38
|
+
const product = (): number | undefined => {
|
|
39
|
+
let v = primary();
|
|
40
|
+
while (v !== undefined && at < r.to && (tokens.is(at, '*') || tokens.is(at, '/'))) {
|
|
41
|
+
const op = next();
|
|
42
|
+
const right = primary();
|
|
43
|
+
v =
|
|
44
|
+
right === undefined || (op === '/' && right === 0) ? undefined : op === '*' ? v * right : Math.trunc(v / right);
|
|
45
|
+
}
|
|
46
|
+
return v;
|
|
47
|
+
};
|
|
48
|
+
const sum = (): number | undefined => {
|
|
49
|
+
let v = product();
|
|
50
|
+
while (v !== undefined && at < r.to && (tokens.is(at, '+') || tokens.is(at, '-'))) {
|
|
51
|
+
const op = next();
|
|
52
|
+
const right = product();
|
|
53
|
+
v = right === undefined ? undefined : op === '+' ? v + right : v - right;
|
|
54
|
+
}
|
|
55
|
+
return v;
|
|
56
|
+
};
|
|
57
|
+
const v = sum();
|
|
58
|
+
return at === r.to ? v : undefined;
|
|
59
|
+
}
|
package/src/cdecl/lex.ts
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// asmlift — the TOKENS of a C or C++ declaration context: one pass that decides what is code.
|
|
2
|
+
//
|
|
3
|
+
// Whitespace, comments and preprocessor lines (continuations included) are not tokens: a context is
|
|
4
|
+
// preprocessed, so a `#` line left in it is a line marker or a pragma, and a raw header's
|
|
5
|
+
// conditionals are read as if every branch were taken. A string or character literal is one token,
|
|
6
|
+
// and ends at its newline when nothing closes it. `::` and `...` are one token each; every other
|
|
7
|
+
// punctuator is one character, so `>>` closing two template argument lists is two `>`.
|
|
8
|
+
//
|
|
9
|
+
// Brackets are matched once, here: a `}` closes everything opened since its `{`, and a `)` or `]`
|
|
10
|
+
// that does not close the innermost open bracket matches nothing. So an unbalanced `(` inside a
|
|
11
|
+
// body cannot carry the rest of the context with it.
|
|
12
|
+
//
|
|
13
|
+
// Storage is one typed array per field, indexed by token: a context is up to a few megabytes, and an
|
|
14
|
+
// object per token is what would make reading it slow.
|
|
15
|
+
|
|
16
|
+
export type TokenKind = 'identifier' | 'number' | 'string' | 'punct' | 'end';
|
|
17
|
+
|
|
18
|
+
export interface Tokens {
|
|
19
|
+
readonly src: string;
|
|
20
|
+
readonly count: number;
|
|
21
|
+
/** `end` past the last token */
|
|
22
|
+
kind(i: number): TokenKind;
|
|
23
|
+
text(i: number): string;
|
|
24
|
+
is(i: number, s: string): boolean;
|
|
25
|
+
/** a punctuator's first character code, or -1 for any other token */
|
|
26
|
+
char(i: number): number;
|
|
27
|
+
/** the matching close of an open `{ ( [`, the open of a close, or -1 */
|
|
28
|
+
match(i: number): number;
|
|
29
|
+
/** the source offset of the token's first character */
|
|
30
|
+
start(i: number): number;
|
|
31
|
+
/** the source offset just past the token */
|
|
32
|
+
end(i: number): number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const KINDS: readonly TokenKind[] = ['end', 'identifier', 'number', 'string', 'punct'];
|
|
36
|
+
const IDENTIFIER = 1;
|
|
37
|
+
const NUMBER = 2;
|
|
38
|
+
const STRING = 3;
|
|
39
|
+
const PUNCT = 4;
|
|
40
|
+
|
|
41
|
+
const isIdentifierStart = (c: number): boolean => (c >= 97 && c <= 122) || (c >= 65 && c <= 90) || c === 95 || c === 36;
|
|
42
|
+
const isDigit = (c: number): boolean => c >= 48 && c <= 57;
|
|
43
|
+
/** whether the newline at `i` ends a line that a backslash continues */
|
|
44
|
+
const continued = (src: string, i: number): boolean =>
|
|
45
|
+
src.charCodeAt(i - 1) === 92 || (src.charCodeAt(i - 1) === 13 && src.charCodeAt(i - 2) === 92);
|
|
46
|
+
|
|
47
|
+
class TokenArrays implements Tokens {
|
|
48
|
+
readonly src: string;
|
|
49
|
+
readonly count: number;
|
|
50
|
+
readonly #kind: Uint8Array;
|
|
51
|
+
readonly #start: Int32Array;
|
|
52
|
+
readonly #end: Int32Array;
|
|
53
|
+
readonly #match: Int32Array;
|
|
54
|
+
|
|
55
|
+
constructor(src: string, count: number, kind: Uint8Array, start: Int32Array, end: Int32Array, match: Int32Array) {
|
|
56
|
+
this.src = src;
|
|
57
|
+
this.count = count;
|
|
58
|
+
this.#kind = kind;
|
|
59
|
+
this.#start = start;
|
|
60
|
+
this.#end = end;
|
|
61
|
+
this.#match = match;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
kind(i: number): TokenKind {
|
|
65
|
+
return i < this.count ? KINDS[this.#kind[i]] : 'end';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
text(i: number): string {
|
|
69
|
+
return this.src.slice(this.#start[i], this.#end[i]);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
is(i: number, s: string): boolean {
|
|
73
|
+
return i < this.count && this.#end[i] - this.#start[i] === s.length && this.src.startsWith(s, this.#start[i]);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
char(i: number): number {
|
|
77
|
+
return i < this.count && this.#kind[i] === PUNCT ? this.src.charCodeAt(this.#start[i]) : -1;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
match(i: number): number {
|
|
81
|
+
return i < this.count ? this.#match[i] : -1;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
start(i: number): number {
|
|
85
|
+
return i < this.count ? this.#start[i] : this.src.length;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
end(i: number): number {
|
|
89
|
+
return i < this.count ? this.#end[i] : this.src.length;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function lex(src: string): Tokens {
|
|
94
|
+
let capacity = Math.max(16, src.length >> 2);
|
|
95
|
+
let kind = new Uint8Array(capacity);
|
|
96
|
+
let start = new Int32Array(capacity);
|
|
97
|
+
let end = new Int32Array(capacity);
|
|
98
|
+
let count = 0;
|
|
99
|
+
const push = (k: number, from: number, to: number): void => {
|
|
100
|
+
if (count === capacity) {
|
|
101
|
+
capacity *= 2;
|
|
102
|
+
const k2 = new Uint8Array(capacity);
|
|
103
|
+
k2.set(kind);
|
|
104
|
+
kind = k2;
|
|
105
|
+
const s2 = new Int32Array(capacity);
|
|
106
|
+
s2.set(start);
|
|
107
|
+
start = s2;
|
|
108
|
+
const e2 = new Int32Array(capacity);
|
|
109
|
+
e2.set(end);
|
|
110
|
+
end = e2;
|
|
111
|
+
}
|
|
112
|
+
kind[count] = k;
|
|
113
|
+
start[count] = from;
|
|
114
|
+
end[count] = to;
|
|
115
|
+
count++;
|
|
116
|
+
};
|
|
117
|
+
const length = src.length;
|
|
118
|
+
// only whitespace and comments since the last newline: a `#` here starts a directive
|
|
119
|
+
let lineStart = true;
|
|
120
|
+
let i = 0;
|
|
121
|
+
while (i < length) {
|
|
122
|
+
const c = src.charCodeAt(i);
|
|
123
|
+
if (c === 10) {
|
|
124
|
+
lineStart = true;
|
|
125
|
+
i++;
|
|
126
|
+
} else if (c === 32 || c === 9 || c === 13 || c === 12 || c === 11) {
|
|
127
|
+
i++;
|
|
128
|
+
} else if (c === 47 && src.charCodeAt(i + 1) === 47) {
|
|
129
|
+
const newline = src.indexOf('\n', i);
|
|
130
|
+
i = newline < 0 ? length : newline;
|
|
131
|
+
} else if (c === 47 && src.charCodeAt(i + 1) === 42) {
|
|
132
|
+
const close = src.indexOf('*/', i + 2);
|
|
133
|
+
i = close < 0 ? length : close + 2;
|
|
134
|
+
} else if (c === 35 && lineStart) {
|
|
135
|
+
while (i < length && !(src.charCodeAt(i) === 10 && !continued(src, i))) {
|
|
136
|
+
i++;
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
lineStart = false;
|
|
140
|
+
let j = i + 1;
|
|
141
|
+
if (isIdentifierStart(c)) {
|
|
142
|
+
while (j < length && (isIdentifierStart(src.charCodeAt(j)) || isDigit(src.charCodeAt(j)))) {
|
|
143
|
+
j++;
|
|
144
|
+
}
|
|
145
|
+
push(IDENTIFIER, i, j);
|
|
146
|
+
} else if (isDigit(c)) {
|
|
147
|
+
// a preprocessing number: `0x1F`, `1.5f`, `1e-3`
|
|
148
|
+
for (; j < length; j++) {
|
|
149
|
+
const d = src.charCodeAt(j);
|
|
150
|
+
const sign = (d === 43 || d === 45) && /[eEpP]/.test(src[j - 1]);
|
|
151
|
+
if (!(isIdentifierStart(d) || isDigit(d) || d === 46 || sign)) {
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
push(NUMBER, i, j);
|
|
156
|
+
} else if (c === 34 || c === 39) {
|
|
157
|
+
while (j < length && src.charCodeAt(j) !== c && src.charCodeAt(j) !== 10) {
|
|
158
|
+
j += src.charCodeAt(j) === 92 ? 2 : 1;
|
|
159
|
+
}
|
|
160
|
+
if (j < length && src.charCodeAt(j) === c) {
|
|
161
|
+
j++;
|
|
162
|
+
}
|
|
163
|
+
push(STRING, i, j);
|
|
164
|
+
} else if (c === 58 && src.charCodeAt(j) === 58) {
|
|
165
|
+
j++;
|
|
166
|
+
push(PUNCT, i, j);
|
|
167
|
+
} else if (c === 46 && src.charCodeAt(j) === 46 && src.charCodeAt(j + 1) === 46) {
|
|
168
|
+
j += 2;
|
|
169
|
+
push(PUNCT, i, j);
|
|
170
|
+
} else {
|
|
171
|
+
push(PUNCT, i, j);
|
|
172
|
+
}
|
|
173
|
+
i = j;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return new TokenArrays(src, count, kind, start, end, matchBrackets(src, count, kind, start));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function matchBrackets(src: string, count: number, kind: Uint8Array, start: Int32Array): Int32Array {
|
|
180
|
+
const match = new Int32Array(count).fill(-1);
|
|
181
|
+
const open: number[] = [];
|
|
182
|
+
for (let t = 0; t < count; t++) {
|
|
183
|
+
if (kind[t] !== PUNCT) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
const c = src.charCodeAt(start[t]);
|
|
187
|
+
if (c === 123 || c === 40 || c === 91) {
|
|
188
|
+
open.push(t);
|
|
189
|
+
} else if (c === 125) {
|
|
190
|
+
let k = open.length - 1;
|
|
191
|
+
while (k >= 0 && src.charCodeAt(start[open[k]]) !== 123) {
|
|
192
|
+
k--;
|
|
193
|
+
}
|
|
194
|
+
if (k >= 0) {
|
|
195
|
+
match[open[k]] = t;
|
|
196
|
+
match[t] = open[k];
|
|
197
|
+
open.length = k;
|
|
198
|
+
}
|
|
199
|
+
} else if (c === 41 || c === 93) {
|
|
200
|
+
const top = open[open.length - 1];
|
|
201
|
+
if (top !== undefined && src.charCodeAt(start[top]) === (c === 41 ? 40 : 91)) {
|
|
202
|
+
match[top] = t;
|
|
203
|
+
match[t] = top;
|
|
204
|
+
open.pop();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return match;
|
|
209
|
+
}
|