@asmlift/core 0.8.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/README.md +34 -30
  2. package/package.json +1 -1
  3. package/src/aggregate.ts +248 -0
  4. package/src/backend/c.ts +5 -2
  5. package/src/backend/cfamily.ts +236 -37
  6. package/src/backend/cpp.ts +114 -9
  7. package/src/backend/pascal.ts +39 -0
  8. package/src/cdecl/constant.ts +59 -0
  9. package/src/cdecl/lex.ts +209 -0
  10. package/src/cdecl/parse.ts +975 -0
  11. package/src/cdecl/spell.ts +69 -0
  12. package/src/codegen-flags.ts +38 -6
  13. package/src/compiler-diagnostics.ts +194 -0
  14. package/src/contracts.ts +615 -79
  15. package/src/declare.ts +75 -5
  16. package/src/frontend/asmdata.ts +19 -5
  17. package/src/frontend/call-plan.ts +686 -0
  18. package/src/frontend/device-pins.ts +277 -0
  19. package/src/frontend/errors.ts +5 -0
  20. package/src/frontend/flags-edge.ts +101 -0
  21. package/src/frontend/fpu.ts +167 -0
  22. package/src/frontend/frame-objects.ts +2131 -0
  23. package/src/frontend/high-half.ts +2 -1
  24. package/src/frontend/local-object.ts +490 -0
  25. package/src/frontend/mips.ts +363 -40
  26. package/src/frontend/opaque.ts +97 -18
  27. package/src/frontend/ppc.ts +520 -129
  28. package/src/frontend/reloc-symbol.ts +94 -13
  29. package/src/frontend/splat.ts +108 -5
  30. package/src/frontend/ssa.ts +669 -76
  31. package/src/frontend/stackargs.ts +120 -39
  32. package/src/frontend/thumb.ts +2764 -2018
  33. package/src/ir/alias.ts +29 -0
  34. package/src/ir/core.ts +101 -3
  35. package/src/ir/discipline.ts +248 -0
  36. package/src/ir/float-bits.ts +47 -0
  37. package/src/ir/opcodes.ts +84 -112
  38. package/src/ir/parse.ts +1 -0
  39. package/src/ir/types.ts +118 -2
  40. package/src/ir/verify.ts +150 -3
  41. package/src/l3/address.ts +5 -0
  42. package/src/l3/argcopy.ts +38 -6
  43. package/src/l3/ast.ts +186 -14
  44. package/src/l3/coalesce.ts +21 -13
  45. package/src/l3/dce.ts +32 -10
  46. package/src/l3/gates.ts +9 -3
  47. package/src/l3/hoist.ts +8 -7
  48. package/src/l3/initfirst.ts +27 -9
  49. package/src/l3/intcell.ts +97 -0
  50. package/src/l3/mentions.ts +10 -4
  51. package/src/l3/narrowdecl.ts +218 -0
  52. package/src/l3/nearbase.ts +10 -5
  53. package/src/l3/parkfirst.ts +8 -5
  54. package/src/l3/pollguard.ts +3 -2
  55. package/src/l3/ptrcell.ts +357 -0
  56. package/src/l3/regspell.ts +17 -3
  57. package/src/l3/reindex.ts +10 -7
  58. package/src/l3/symbol-refs.ts +104 -23
  59. package/src/l3/tailmerge.ts +6 -19
  60. package/src/l3/typing.ts +137 -25
  61. package/src/l3/unreduce.ts +4 -4
  62. package/src/l3/volstore.ts +96 -7
  63. package/src/mangle.ts +11 -1
  64. package/src/pattern/engine.ts +19 -16
  65. package/src/pipeline.ts +57 -12
  66. package/src/proto-context.ts +788 -0
  67. package/src/proto.ts +689 -50
  68. package/src/raise/arrays.ts +4 -3
  69. package/src/raise/declared-volatile.ts +131 -0
  70. package/src/raise/divpow2.ts +2 -2
  71. package/src/raise/errors.ts +17 -0
  72. package/src/raise/floathelpers.ts +243 -0
  73. package/src/raise/globalshape.ts +11 -5
  74. package/src/raise/gvn.ts +26 -5
  75. package/src/raise/narrow.ts +29 -2
  76. package/src/raise/narrowlocal.ts +11 -6
  77. package/src/raise/offsetnames.ts +510 -0
  78. package/src/raise/pairparams.ts +81 -0
  79. package/src/raise/paramwidth.ts +7 -0
  80. package/src/raise/pre-recovery.ts +78 -4
  81. package/src/raise/recover.ts +14 -10
  82. package/src/raise/retsink.ts +1 -1
  83. package/src/raise/shortcircuit.ts +41 -19
  84. package/src/raise/softdiv.ts +21 -23
  85. package/src/raise/struct-arrays.ts +5 -4
  86. package/src/raise/structs.ts +311 -98
  87. package/src/raise/truncload.ts +366 -0
  88. package/src/raise/widehelpers.ts +106 -0
  89. package/src/rank-declare.ts +30 -5
  90. package/src/rank-variations.ts +36 -8
  91. package/src/rank.ts +267 -79
  92. package/src/runtime-helpers.ts +287 -0
  93. package/src/stillborn.ts +289 -0
  94. package/src/structure/analysis.ts +1285 -448
  95. package/src/structure/hazards.ts +633 -58
  96. package/src/structure/local-statics.ts +381 -0
  97. package/src/structure/namecoalesce.ts +1 -1
  98. package/src/structure/redundant-test.ts +99 -0
  99. package/src/structure/structure.ts +1772 -395
  100. package/src/structure/switch-recover.ts +159 -78
  101. package/src/symbols.ts +38 -3
  102. package/src/target.ts +662 -59
  103. package/src/trace.ts +44 -24
  104. package/src/variation-definitions.ts +119 -1
  105. package/src/variation-tokens.ts +20 -6
@@ -0,0 +1,788 @@
1
+ import { constantValue, integerLiteral } from './cdecl/constant';
2
+ import type { Tokens } from './cdecl/lex';
3
+ import {
4
+ type Attribute,
5
+ type Declaration,
6
+ type Declarator,
7
+ type DeclaredType,
8
+ type Derivation,
9
+ type Language,
10
+ type Parameter,
11
+ type ParsedContext,
12
+ type Qualifier,
13
+ type Range,
14
+ type TypeSpecifier,
15
+ declaredType,
16
+ memberDeclarations,
17
+ parseDeclarations,
18
+ } from './cdecl/parse';
19
+ import { asParameter, spellType } from './cdecl/spell';
20
+ import type { AggregateLayout, AggregateMember, FnProto, ParamType, Prototypes } from './proto';
21
+ import {
22
+ declaredCallArgs,
23
+ declaredWidth,
24
+ declaresAggregateReturn,
25
+ isFloatingSpelling,
26
+ statesNoReturn,
27
+ symbolPrototype,
28
+ validatePrototypes,
29
+ } from './proto';
30
+ import type { SymbolMap } from './symbols';
31
+ import type { TargetDescription } from './target';
32
+
33
+ // asmlift — callee prototypes read out of a DECLARATION CONTEXT: the preprocessed headers a
34
+ // candidate is compiled against (a decomp project's `ctx.h`, m2c's `--context`). The compiler
35
+ // checks every candidate's calls against those declarations, so a call asmlift writes at a guessed
36
+ // arity is a candidate the context then refuses; read here, the declarations decide the arity
37
+ // instead of the argument-register scan.
38
+ //
39
+ // WHAT IS READ is what the parser reads (`cdecl/parse.ts`). Of it, this takes the functions the top
40
+ // level declares or defines, and the typedefs, tags and bodies their types name.
41
+ //
42
+ // KEYED BY THE DECLARED NAME, which is the symbol a call in the assembly names only for C linkage:
43
+ // a C++ free function is called by its mangled symbol, so linkage decides itself at the lookup. A
44
+ // name declared twice with different signatures (an overload) is dropped: the table cannot say which
45
+ // one a call means.
46
+ //
47
+ // A TYPEDEF RESOLVES ONLY TO THE SAME TYPE. A spelling here is printed into candidates beside the
48
+ // project's own headers (`declare.ts`), where a different type is a conflicting declaration. So a
49
+ // typedef resolves along its chain only until `declaredWidth` can size the spelling (`BOOL` →
50
+ // `int`); a function-pointer typedef resolves to its own abstract declarator (`void (*)(s32)`);
51
+ // a struct or enum keeps its name, which sizes to nothing and makes the list abstain. Resolution
52
+ // is on the parsed type, never its spelling: a typedef name stands for its base and derivations,
53
+ // and the name's own qualifiers qualify the derivation they bind (`const Cb` is
54
+ // `void (* const)(s32)`).
55
+ //
56
+ // A TYPEDEF WITH AN ATTRIBUTE NAMES NO TYPE HERE: the attribute may make it a different type from
57
+ // the one it spells (`realigns`), so no spelling resolves through it. One on a parameter of a
58
+ // function the typedef points at is that parameter's (`ownAttributes`).
59
+
60
+ /** The layout of the struct or union a type names, at a nesting depth. */
61
+ type LayoutOf = (t: DeclaredType, depth: number) => AggregateLayout | undefined;
62
+
63
+ /** The callee prototypes a preprocessed declaration context states. `language` decides what an
64
+ * empty parameter list means: none in C++, unstated in C (a pre-ANSI declaration). */
65
+ export function prototypesFromContext(src: string, language: Language): Prototypes {
66
+ const ctx = parseDeclarations(src, language);
67
+ const table = typeTable(ctx);
68
+ return functionPrototypes(ctx, table, layoutReader(ctx, table));
69
+ }
70
+
71
+ const WORD = /^[A-Za-z_]\w*$/;
72
+
73
+ /** `mode(…)` in an attribute, in either spelling: it retypes what it is written on */
74
+ const MODE = /\b(?:__)?mode(?:__)?\s*\(/;
75
+
76
+ function typeTable(ctx: ParsedContext): TypeTable {
77
+ const table: TableBuilder = {
78
+ typedefs: new Map(),
79
+ tagged: new Map(),
80
+ named: new Map(),
81
+ unspelledPointers: new Set(),
82
+ enums: new Set(),
83
+ unsizedEnums: new Set(),
84
+ wideEnumerators: new Set(),
85
+ realigned: false,
86
+ };
87
+ for (const d of ctx.declarations) {
88
+ const tag = d.specifiers.type.kind === 'tag' ? d.specifiers.type : undefined;
89
+ const attributes = d.specifiers.typedef || tag?.body !== undefined ? ownAttributes(d) : [];
90
+ table.realigned ||= realigns(d, attributes);
91
+ const unsizedEnum = tag !== undefined && readTag(table, d, tag, laysOut(d, attributes), ctx);
92
+ if (d.specifiers.typedef && attributes.length === 0) {
93
+ readTypedefs(table, d, unsizedEnum);
94
+ }
95
+ }
96
+ return table;
97
+ }
98
+
99
+ /** What the declarations say about the types a prototype or a layout spells, while they are read. */
100
+ interface TableBuilder {
101
+ /** each typedef name, and the type it stands for, before resolution */
102
+ typedefs: Map<string, DeclaredType>;
103
+ /** struct and union bodies by `struct Tag` spelling; none for a body an attribute lays out
104
+ * (`laysOut`) */
105
+ tagged: Map<string, Range | undefined>;
106
+ /** struct and union bodies by a typedef name bound to a body, which resolves to itself and spells
107
+ * no keyword — as C++ spells every tag, a declared one with no body included */
108
+ named: Map<string, { kind: AggregateLayout['kind']; body?: Range }>;
109
+ /** a typedef name for a pointer to a body nothing else names: a word, and no spelling of its own */
110
+ unspelledPointers: Set<string>;
111
+ /** a typedef name bound to an enum body */
112
+ enums: Set<string>;
113
+ /** an enum, by `enum Tag` or typedef name, that is not the target's enumBytes: `packed` makes it
114
+ * the smallest integer its values fit (agbcc c-common.c:446, c-decl.c:6123), and so does a value
115
+ * an int cannot hold (`enumMayWiden`) */
116
+ unsizedEnums: Set<string>;
117
+ /** the enumerators of an enum wider than an int, which widen any enum that names one */
118
+ wideEnumerators: Set<string>;
119
+ /** a typedef changed a type after its layout (`realigns`), which moves the layout of whatever
120
+ * holds it, so no layout is read */
121
+ realigned: boolean;
122
+ }
123
+
124
+ /** What the declarations say about the types a prototype or a layout spells, once read. */
125
+ type TypeTable = Readonly<Omit<TableBuilder, 'wideEnumerators'>>;
126
+
127
+ /** What a struct, union, enum or class specifier says about its tag; whether it defines an enum
128
+ * the target does not size (`unsizedEnums`). `attributed`: an attribute lays its body out. */
129
+ function readTag(
130
+ table: TableBuilder,
131
+ d: Declaration,
132
+ tag: Extract<TypeSpecifier, { kind: 'tag' }>,
133
+ attributed: boolean,
134
+ ctx: ParsedContext,
135
+ ): boolean {
136
+ const name = tag.tag !== undefined && WORD.test(tag.tag) ? tag.tag : undefined;
137
+ const { body, keyword } = tag;
138
+ const aggregate = keyword === 'struct' || keyword === 'union' ? keyword : undefined;
139
+ if (aggregate !== undefined && body !== undefined && name !== undefined && !tag.base) {
140
+ table.tagged.set(`${aggregate} ${name}`, attributed ? undefined : body);
141
+ }
142
+ // In C++ a class is a struct too. Its body (access labels, member functions), or one after a
143
+ // base clause, whose members start past the base's, is none this lays out: the kind is known and
144
+ // the members are not. A forward declaration states the kind alone, so a definition after it
145
+ // replaces it, and nothing replaces a definition.
146
+ if (
147
+ ctx.language === 'c++' &&
148
+ keyword !== 'enum' &&
149
+ name !== undefined &&
150
+ (body !== undefined || forwardDeclares(d)) &&
151
+ table.named.get(name)?.body === undefined
152
+ ) {
153
+ const layable = aggregate !== undefined && !tag.base && !attributed;
154
+ table.named.set(name, { kind: keyword === 'union' ? 'union' : 'struct', body: layable ? body : undefined });
155
+ }
156
+ if (keyword !== 'enum' || body === undefined) {
157
+ return false;
158
+ }
159
+ const wide = enumMayWiden(ctx.tokens, body, table.wideEnumerators);
160
+ if (wide) {
161
+ for (const e of enumerators(ctx.tokens, body)) {
162
+ table.wideEnumerators.add(e);
163
+ }
164
+ }
165
+ if ((attributed || wide) && name !== undefined) {
166
+ table.unsizedEnums.add(`enum ${name}`);
167
+ }
168
+ return attributed || wide;
169
+ }
170
+
171
+ /** Every name a typedef declares, into the table. `unsizedEnum`: the body it names is an enum the
172
+ * target does not size. */
173
+ function readTypedefs(table: TableBuilder, d: Declaration, unsizedEnum: boolean): void {
174
+ const { type } = d.specifiers;
175
+ const keyword = type.kind === 'tag' ? type.keyword : undefined;
176
+ for (const td of typedefNames(d)) {
177
+ table.typedefs.set(td.name, td.type);
178
+ if (td.names === 'unspelled pointer') {
179
+ table.unspelledPointers.add(td.name);
180
+ } else if (td.names === 'body' && (keyword === 'struct' || keyword === 'union')) {
181
+ table.named.set(td.name, { kind: keyword, body: type.kind === 'tag' ? type.body : undefined });
182
+ } else if (td.names === 'body' && keyword === 'enum') {
183
+ table.enums.add(td.name);
184
+ if (unsizedEnum) {
185
+ table.unsizedEnums.add(td.name);
186
+ }
187
+ }
188
+ }
189
+ }
190
+
191
+ /** Whether the declaration states a tag and nothing else: `struct Fwd;`. */
192
+ function forwardDeclares(d: Declaration): boolean {
193
+ const s = d.specifiers;
194
+ return (
195
+ d.declarators.length === 0 &&
196
+ !s.typedef &&
197
+ s.storage.length === 0 &&
198
+ s.qualifiers.length === 0 &&
199
+ s.attributes.length === 0 &&
200
+ s.unknownWords.length === 0
201
+ );
202
+ }
203
+
204
+ /** One name a `typedef` declares: the type it stands for (before resolution), and whether it names
205
+ * the struct, union or enum body the declaration defines, or a pointer to one that has no other name
206
+ * to spell it by. */
207
+ interface TypedefName {
208
+ name: string;
209
+ type: DeclaredType;
210
+ names: 'body' | 'unspelled pointer' | 'other';
211
+ }
212
+
213
+ /** The type a lone name spells, which resolves to itself. */
214
+ const named = (name: string): DeclaredType => ({
215
+ qualifiers: [],
216
+ type: { kind: 'words', words: [name] },
217
+ unknownWords: [],
218
+ spelling: name,
219
+ derivations: [],
220
+ });
221
+
222
+ /** The one name `t` is, where it is a name and nothing else: what a typedef may have declared. */
223
+ const nameOf = (t: DeclaredType): string | undefined =>
224
+ t.derivations.length === 0 && t.unknownWords.length === 0 && t.type.kind === 'words' && t.type.words.length === 1
225
+ ? t.type.words[0]
226
+ : undefined;
227
+
228
+ /** `struct Tag`, `union Tag` or `enum Tag` where `t` is that tag and nothing else, qualifiers aside. */
229
+ const tagOf = (t: DeclaredType): string | undefined =>
230
+ t.derivations.length === 0 &&
231
+ t.unknownWords.length === 0 &&
232
+ t.type.kind === 'tag' &&
233
+ t.type.tag !== undefined &&
234
+ WORD.test(t.type.tag)
235
+ ? `${t.type.keyword} ${t.type.tag}`
236
+ : undefined;
237
+
238
+ const isPointer = (x: Derivation): boolean => x.kind === 'pointer' && x.member === undefined;
239
+
240
+ /** The derivations of what a function pointer returns, where `derivations` are a plain `(*)` to a
241
+ * function returning the base or a pointer to it; otherwise undefined. */
242
+ function functionPointer(derivations: readonly Derivation[]): readonly Derivation[] | undefined {
243
+ const [first, second, ...returns] = derivations;
244
+ return first?.kind === 'pointer' &&
245
+ first.qualifiers.length === 0 &&
246
+ isPointer(first) &&
247
+ second?.kind === 'function' &&
248
+ returns.every(isPointer)
249
+ ? returns
250
+ : undefined;
251
+ }
252
+
253
+ /** Every name a `typedef` declares (`typedef struct R {…} R, *RP;` declares two). A plain declarator
254
+ * of a body names that body, qualified or not (`} const CR;`), and resolves to itself. A pointer
255
+ * declarator is a pointer to the body, qualifiers and all, spelled by the body's tag or by a plain
256
+ * name the same declaration gives it, and resolves to itself where the body has neither
257
+ * (`typedef struct {…} *PS;`). Without a body, a name is read when its type is the specifiers'
258
+ * own, a pointer to it (`u8 *`, `u8 * const *`) or a function pointer (`void (*)(s32)`); any other
259
+ * type, an array's or one that groups more than a function pointer, is not read. */
260
+ function typedefNames(d: Declaration): TypedefName[] {
261
+ const s = d.specifiers;
262
+ const declared = d.declarators.flatMap((x) =>
263
+ x.name !== undefined && WORD.test(x.name) ? [{ ...x, name: x.name }] : [],
264
+ );
265
+ if (s.type.kind === 'tag' && s.type.body !== undefined) {
266
+ // a plain name stands for the body with its qualifiers; a tag, without them
267
+ const plain = declared.find((x) => x.derivations.length === 0)?.name;
268
+ const tag = tagOf({ ...declaredType(s, []), unknownWords: [] });
269
+ const pointee =
270
+ tag !== undefined
271
+ ? { ...declaredType(s, []), unknownWords: [], spelling: tag }
272
+ : plain !== undefined
273
+ ? named(plain)
274
+ : undefined;
275
+ return declared.flatMap((x): TypedefName[] => {
276
+ if (x.derivations.length === 0) {
277
+ return [{ name: x.name, type: named(x.name), names: 'body' }];
278
+ }
279
+ if (!x.derivations.every(isPointer)) {
280
+ return [];
281
+ }
282
+ return [
283
+ pointee === undefined
284
+ ? { name: x.name, type: named(x.name), names: 'unspelled pointer' }
285
+ : { name: x.name, type: { ...pointee, derivations: x.derivations }, names: 'other' },
286
+ ];
287
+ });
288
+ }
289
+ return declared
290
+ .filter((x) => x.derivations.every(isPointer) || functionPointer(x.derivations) !== undefined)
291
+ .map((x) => ({ name: x.name, type: declaredType(s, x.derivations), names: 'other' }));
292
+ }
293
+
294
+ /** The attributes a declaration writes on what it declares: among its specifiers and on its
295
+ * declarators. One inside a parameter list is the parameter's, and compiled, agbcc lets it reach no
296
+ * type outside it: it refuses `aligned` there and ignores `packed`. */
297
+ function ownAttributes(d: Declaration): Attribute[] {
298
+ return [...d.specifiers.attributes, ...d.declarators.flatMap((x) => x.attributes)];
299
+ }
300
+
301
+ /** The attributes a declaration writes on what it declares and on the parameters of each function it
302
+ * declares, each parameter's own. One inside a parameter's own parameter list is that inner
303
+ * parameter's, and no parameter of the function. */
304
+ function parameterAttributes(d: Declaration): Attribute[] {
305
+ return [
306
+ ...ownAttributes(d),
307
+ ...d.declarators.flatMap((x) => {
308
+ const fn = x.derivations[0];
309
+ return fn?.kind === 'function'
310
+ ? (fn.params ?? []).flatMap((p) => [...p.specifiers.attributes, ...p.declarator.attributes])
311
+ : [];
312
+ }),
313
+ ];
314
+ }
315
+
316
+ /** Whether an attribute in this declaration can move the layout of the body it defines. One in the
317
+ * specifier — ahead of the body or straight after it — lays that body out (c-parse.in:1464-1503).
318
+ * One elsewhere on a variable reaches that variable alone; one elsewhere in a typedef is read as
319
+ * moving it too, whatever it says (`realigns` has what it may do). */
320
+ function laysOut(d: Declaration, attributes: readonly Attribute[]): boolean {
321
+ return d.specifiers.typedef
322
+ ? attributes.length > 0
323
+ : d.specifiers.attributes.some((a) => a.site === 'before-body' || a.site === 'after-body');
324
+ }
325
+
326
+ /** Whether a typedef in this declaration may change, after its layout, a type no body here stands
327
+ * for. An attribute outside a body's specifier is applied to the type the typedef names (c-common.c:
328
+ * 392-399, 444-446, 623-624), whatever spelling it takes (`aligned`, `__aligned__`, :345-351): a
329
+ * struct tag, a pointer or a scalar such as `unsigned int` is re-aligned, which lays anything that
330
+ * holds it out anew, and an enum whose body comes later is packed. Compiled, `typedef struct R *RP
331
+ * __attribute__((aligned(8)))` makes `struct { struct R *p; }` 8 bytes, and `typedef enum E EA
332
+ * __attribute__((packed))` ahead of `enum E {…}` makes it 1. mwcc's `__declspec` is such an
333
+ * attribute too: compiled with mwcc 4.3, `typedef __declspec(align(8)) int AI` lays `struct { char c;
334
+ * AI a; }` out in 16 bytes. Which type that is, is not worked out here. Where the declaration's plain
335
+ * declarators name its own body, `laysOut` leaves that body unread instead — unless one attribute is
336
+ * `mode`, which hands every attribute after it a shared scalar type in place of the body
337
+ * (c-common.c:563, 996-1000): compiled, `typedef struct R {…} A __attribute__((mode(SI),
338
+ * aligned(8)))` makes every `int` 8-aligned. */
339
+ function realigns(d: Declaration, attributes: readonly Attribute[]): boolean {
340
+ if (!d.specifiers.typedef || attributes.length === 0) {
341
+ return false;
342
+ }
343
+ if (attributes.some((a) => MODE.test(a.text))) {
344
+ return true;
345
+ }
346
+ const { type } = d.specifiers;
347
+ const ownBody = type.kind === 'tag' && type.keyword !== 'class' && type.body !== undefined;
348
+ return !(ownBody && d.declarators.length > 0 && d.declarators.every((x) => x.derivations.length === 0));
349
+ }
350
+
351
+ /** Whether agbcc may lay an enum with this body out wider than an int. It does for a value past 32
352
+ * bits (c-decl.c:6116-6123): a literal past 32 bits or of type `long long`, or an enumerator of an
353
+ * enum already that wide (`wide`). Compiled, `enum {B0, B1 = 0x100000000LL}` is 8 bytes, and so is
354
+ * `enum {C0, C1 = B1}`, where `enum {N0 = -1, N1 = 0xFFFFFFFF}` is 4. */
355
+ function enumMayWiden(tokens: Tokens, body: Range, wide: ReadonlySet<string>): boolean {
356
+ for (let k = body.from; k < body.to; k++) {
357
+ const kind = tokens.kind(k);
358
+ const literal = kind === 'number' ? integerLiteral(tokens.text(k)) : undefined;
359
+ if (literal !== undefined && (literal.value > 0xffffffffn || /l.*l/i.test(literal.suffix))) {
360
+ return true;
361
+ }
362
+ if (
363
+ kind === 'identifier' &&
364
+ ((wide.size > 0 && wide.has(tokens.text(k))) || (tokens.is(k, 'long') && tokens.is(k + 1, 'long')))
365
+ ) {
366
+ return true;
367
+ }
368
+ }
369
+ return false;
370
+ }
371
+
372
+ /** The names an enum body declares: the word that starts it, and each word after a comma. */
373
+ function enumerators(tokens: Tokens, body: Range): string[] {
374
+ const out: string[] = [];
375
+ for (let k = body.from; k < body.to; k++) {
376
+ if ((k === body.from || tokens.is(k - 1, ',')) && tokens.kind(k) === 'identifier') {
377
+ out.push(tokens.text(k));
378
+ }
379
+ }
380
+ return out;
381
+ }
382
+
383
+ /** `t` with the typedef names it is built on resolved, each to the same type, until `declaredWidth`
384
+ * can size it: a pointer resolves what it points at, and a typedef name is replaced by the type it
385
+ * stands for. An array, a function, a reference or a pointer to member is kept as declared. */
386
+ function resolve(t: DeclaredType, typedefs: ReadonlyMap<string, DeclaredType>, tokens: Tokens): DeclaredType {
387
+ const [first, ...rest] = t.derivations;
388
+ if (first !== undefined) {
389
+ if (!isPointer(first)) {
390
+ return t;
391
+ }
392
+ const pointee = resolve({ ...t, derivations: rest }, typedefs, tokens);
393
+ return { ...pointee, derivations: [first, ...pointee.derivations] };
394
+ }
395
+ let cur = t;
396
+ for (let hops = 0; hops < 16 && declaredWidth(spellType(cur, tokens)) === undefined; hops++) {
397
+ const name = nameOf(cur);
398
+ const next = name === undefined ? undefined : typedefs.get(name);
399
+ if (next === undefined || nameOf(next) === name) {
400
+ break;
401
+ }
402
+ cur = qualified(next, cur.qualifiers);
403
+ }
404
+ return cur;
405
+ }
406
+
407
+ /** `t` qualified by `q`, as a typedef name's qualifiers qualify the type it stands for: the derivation
408
+ * nearest the name, or the base where there is none. An array's qualifier is its element's, and a
409
+ * function or a reference takes none. */
410
+ function qualified(t: DeclaredType, q: readonly Qualifier[]): DeclaredType {
411
+ const union = (own: readonly Qualifier[]): Qualifier[] => [...q, ...own.filter((x) => !q.includes(x))];
412
+ const [first, ...rest] = t.derivations;
413
+ if (q.length === 0 || first?.kind === 'function' || first?.kind === 'reference') {
414
+ return t;
415
+ }
416
+ if (first === undefined) {
417
+ return { ...t, qualifiers: union(t.qualifiers) };
418
+ }
419
+ if (first.kind === 'pointer') {
420
+ return { ...t, derivations: [{ ...first, qualifiers: union(first.qualifiers) }, ...rest] };
421
+ }
422
+ const element = qualified({ ...t, derivations: rest }, q);
423
+ return { ...element, derivations: [first, ...element.derivations] };
424
+ }
425
+
426
+ /** A pointer to `t`, or `t` where it is a pointer already. */
427
+ const pointerTo = (t: DeclaredType): DeclaredType =>
428
+ t.derivations.length > 0 && t.derivations.every(isPointer)
429
+ ? t
430
+ : { ...t, derivations: [{ kind: 'pointer', qualifiers: [] }, ...t.derivations] };
431
+
432
+ /** The layout of each struct or union a type names, read from its body when asked for.
433
+ * Memoised per type and depth: a body whose members point at bodies is walked once per depth, not
434
+ * once per path to it — each pointer member lays its pointee out, and K of them to depth 8 is K^8
435
+ * walks. */
436
+ function layoutReader(ctx: ParsedContext, table: TypeTable): LayoutOf {
437
+ const laidOut = new Map<string, AggregateLayout | undefined>();
438
+ const layoutOf: LayoutOf = (t, depth) => {
439
+ const keyword = t.type.kind === 'tag' ? t.type.keyword : undefined;
440
+ const tag = keyword === 'struct' || keyword === 'union' ? tagOf(t) : undefined;
441
+ const name = tag ?? nameOf(t);
442
+ if (name === undefined) {
443
+ return undefined;
444
+ }
445
+ const key = `${depth} ${name}`;
446
+ if (laidOut.has(key)) {
447
+ return laidOut.get(key);
448
+ }
449
+ const kind = tag !== undefined ? (keyword as AggregateLayout['kind']) : table.named.get(name)?.kind;
450
+ let layout: AggregateLayout | undefined;
451
+ if (kind !== undefined) {
452
+ const body = tag !== undefined ? table.tagged.get(tag) : table.named.get(name)?.body;
453
+ const members = body === undefined || table.realigned ? undefined : readMembers(body, depth);
454
+ layout = members === undefined ? { kind } : { kind, members };
455
+ }
456
+ laidOut.set(key, layout);
457
+ return layout;
458
+ };
459
+ // A body's members, or undefined when one of them is a type this cannot lay out — a project
460
+ // typedef that resolves to nothing sized, a nested aggregate with no body here, a flexible extent
461
+ // or one that is not a constant expression — or carries an attribute of its own, which may place it
462
+ // anywhere. Bounded in depth, since a body may name its own tag.
463
+ const readMembers = (body: Range, depth: number): AggregateMember[] | undefined => {
464
+ const declarations = depth > 8 ? undefined : memberDeclarations(ctx, body);
465
+ if (declarations === undefined) {
466
+ return undefined;
467
+ }
468
+ const out: AggregateMember[] = [];
469
+ for (const m of declarations) {
470
+ const read = members(m, depth);
471
+ if (read === undefined) {
472
+ return undefined;
473
+ }
474
+ out.push(...read);
475
+ }
476
+ return out;
477
+ };
478
+ // One member declaration's members: a static, a typedef or a declaration of no name is none this
479
+ // places.
480
+ const members = (m: Declaration, depth: number): AggregateMember[] | undefined => {
481
+ const s = m.specifiers;
482
+ if (s.typedef || s.storage.length > 0 || m.declarators.length === 0 || ownAttributes(m).length > 0) {
483
+ return undefined;
484
+ }
485
+ let inline: AggregateLayout | undefined;
486
+ if (s.type.kind === 'tag' && s.type.body !== undefined) {
487
+ const kind = s.type.keyword;
488
+ if (kind !== 'struct' && kind !== 'union') {
489
+ return undefined;
490
+ }
491
+ const read = readMembers(s.type.body, depth + 1);
492
+ if (read === undefined) {
493
+ return undefined;
494
+ }
495
+ inline = { kind, members: read };
496
+ }
497
+ const out: AggregateMember[] = [];
498
+ for (const d of m.declarators) {
499
+ const shape = memberShape(d, ctx.tokens);
500
+ const type =
501
+ shape === undefined ? undefined : (inline ?? memberType(declaredType(s, shape.returns), shape.pointer, depth));
502
+ if (shape === undefined || type === undefined || (typeof type !== 'string' && shape.bits !== undefined)) {
503
+ return undefined;
504
+ }
505
+ out.push({
506
+ name: shape.name,
507
+ type: shape.pointer && typeof type !== 'string' ? 'void *' : type,
508
+ ...(shape.dims ? { dims: shape.dims } : {}),
509
+ ...(shape.bits !== undefined ? { bits: shape.bits } : {}),
510
+ });
511
+ }
512
+ return out;
513
+ };
514
+ // The type of a member of `base`, or of a pointer to one (`pointer`), or undefined where it cannot
515
+ // be laid out.
516
+ const memberType = (base: DeclaredType, pointer: boolean, depth: number): ParamType | AggregateLayout | undefined => {
517
+ const t = resolve(base, table.typedefs, ctx.tokens);
518
+ const spelled = spellType(t, ctx.tokens);
519
+ const spell = (x: DeclaredType): string => spellType(pointer ? pointerTo(x) : x, ctx.tokens);
520
+ if (declaredWidth(spelled) !== undefined || spelled === 'float' || spelled === 'double') {
521
+ return spell(t);
522
+ }
523
+ if (table.unspelledPointers.has(spelled)) {
524
+ return 'void *';
525
+ }
526
+ if (table.unsizedEnums.has(spelled)) {
527
+ return undefined;
528
+ }
529
+ // an enum, which the target sizes whatever it is called: spelled `enum` and its name
530
+ if (table.enums.has(spelled)) {
531
+ return spell(named(`enum ${spelled}`));
532
+ }
533
+ if (t.qualifiers.length === 0 && t.type.kind === 'tag' && t.type.keyword === 'enum' && tagOf(t) !== undefined) {
534
+ return spell(t);
535
+ }
536
+ const nested = layoutOf(t, depth + 1);
537
+ if (nested?.members !== undefined) {
538
+ return nested;
539
+ }
540
+ // a pointer to it is still a word; anything else of it cannot be laid out
541
+ return pointer ? spell(t) : undefined;
542
+ };
543
+ return layoutOf;
544
+ }
545
+
546
+ /** One member declarator: its name, whether it declares a pointer, its extents, its bit width, and
547
+ * the derivations of what a function pointer returns, which belong to the type it points at. Its
548
+ * extents are arrays of what it holds, and what it holds is the base, a pointer to it or a function
549
+ * pointer. */
550
+ function memberShape(
551
+ d: Declarator,
552
+ tokens: Tokens,
553
+ ): { name: string; pointer: boolean; dims?: number[]; bits?: number; returns: readonly Derivation[] } | undefined {
554
+ if (d.bits !== undefined) {
555
+ const bits = constantValue(tokens, d.bits);
556
+ return d.derivations.length > 0 || bits === undefined
557
+ ? undefined
558
+ : { name: d.name ?? '', pointer: false, bits, returns: [] };
559
+ }
560
+ if (d.name === undefined || !WORD.test(d.name)) {
561
+ return undefined;
562
+ }
563
+ const returns = functionPointer(d.derivations);
564
+ if (returns !== undefined) {
565
+ return { name: d.name, pointer: true, returns };
566
+ }
567
+ const dims: number[] = [];
568
+ let k = 0;
569
+ for (let x = d.derivations[k]; x?.kind === 'array'; x = d.derivations[++k]) {
570
+ const n = x.size === undefined ? undefined : constantValue(tokens, x.size);
571
+ if (n === undefined || n === 0) {
572
+ return undefined;
573
+ }
574
+ dims.push(n);
575
+ }
576
+ const stars = d.derivations.slice(k);
577
+ if (!stars.every(isPointer)) {
578
+ return undefined;
579
+ }
580
+ return { name: d.name, pointer: stars.length > 0, ...(dims.length > 0 ? { dims } : {}), returns: [] };
581
+ }
582
+
583
+ /** Every function the declarations name by a plain identifier, keyed by it. A declaration that
584
+ * spells no type is a constructor's, a destructor's or a conversion operator's; one that defines a
585
+ * body, or spells a `class`, declares no C function. */
586
+ function functionPrototypes(ctx: ParsedContext, table: TypeTable, layoutOf: LayoutOf): Prototypes {
587
+ const found = new Map<string, FnProto | null>();
588
+ // a function one of whose declarations retypes a parameter with `mode`, which gives it the type
589
+ // the mode names in place of the one spelled (c-common.c:563): compiled, `int x
590
+ // __attribute__((mode(DI)))` takes a register pair. Its parameters are not read, in any
591
+ // declaration of it; its return is.
592
+ const unreadParams = new Set<string>();
593
+ const returnOnly = ({ params: _unread, ...rest }: FnProto): FnProto => rest;
594
+ for (const d of ctx.declarations) {
595
+ const s = d.specifiers;
596
+ if (
597
+ s.typedef ||
598
+ (s.spelling === '' && s.qualifiers.length === 0) ||
599
+ (s.type.kind === 'tag' && (s.type.body !== undefined || s.type.keyword === 'class'))
600
+ ) {
601
+ continue;
602
+ }
603
+ const retyped = parameterAttributes(d).some((a) => MODE.test(a.text));
604
+ for (const x of d.declarators) {
605
+ const [fn, ...returns] = x.derivations;
606
+ if (
607
+ fn?.kind !== 'function' ||
608
+ x.name === undefined ||
609
+ !WORD.test(x.name) ||
610
+ !returns.every((r) => r.kind === 'pointer' || r.kind === 'reference')
611
+ ) {
612
+ continue;
613
+ }
614
+ if (retyped) {
615
+ unreadParams.add(x.name);
616
+ }
617
+ const read = readSignature(declaredType(s, returns), fn, ctx, table.typedefs, (t) => layoutOf(t, 0));
618
+ const proto = unreadParams.has(x.name) ? returnOnly(read) : read;
619
+ const prior = found.get(x.name);
620
+ const had = prior && unreadParams.has(x.name) ? returnOnly(prior) : prior;
621
+ found.set(x.name, had === undefined || same(had, proto) ? proto : null);
622
+ }
623
+ }
624
+ const out: Prototypes = {};
625
+ for (const [name, p] of found) {
626
+ const valid = p === null ? undefined : admissible(name, p);
627
+ if (valid !== undefined) {
628
+ out[name] = valid;
629
+ }
630
+ }
631
+ return out;
632
+ }
633
+
634
+ /** The entry as a table `validatePrototypes` accepts, the check every `--proto` table passes, so a
635
+ * context's prototypes can travel as one: a `returns` it refuses is dropped — a return wider than a
636
+ * register with a parameter list that cannot be printed — and the entry with it if that is not
637
+ * enough. */
638
+ function admissible(name: string, p: FnProto): FnProto | undefined {
639
+ if (validatePrototypes({ [name]: p }).length === 0) {
640
+ return p;
641
+ }
642
+ const { returns: _dropped, ...rest } = p;
643
+ return validatePrototypes({ [name]: rest }).length === 0 ? rest : undefined;
644
+ }
645
+
646
+ function readSignature(
647
+ ret: DeclaredType,
648
+ fn: Extract<Derivation, { kind: 'function' }>,
649
+ { tokens, language }: ParsedContext,
650
+ typedefs: ReadonlyMap<string, DeclaredType>,
651
+ layoutOf: (t: DeclaredType) => AggregateLayout | undefined,
652
+ ): FnProto {
653
+ const proto: FnProto = {};
654
+ const resolved = resolve(ret, typedefs, tokens);
655
+ const r = spellType(resolved, tokens);
656
+ // A struct or union returned by value is kept, spelled as the header spells it: it is the fact
657
+ // that moves every argument one register up on a target that returns it through a hidden pointer.
658
+ // A spelling that names one and reads as no type (`struct Blob64 EWRAM_FN`, a macro this never
659
+ // expands) still returns one, and says nothing else about it.
660
+ //
661
+ // A floating return is kept as its keyword: where the FPU carries it, it is what refuses a call
662
+ // that reads the general return register (frontend/call-plan.ts).
663
+ //
664
+ // A spelling that reads as no type and names no aggregate states nothing, and the parameters are
665
+ // kept: in a vendored context that is an enum typedef, whose arguments sit where they are
666
+ // declared. KNOWN GAP: a typedef this never saw (`Blob64T`, defined behind an
667
+ // `#include` that the lexer skips) may be a struct returned through memory, whose hidden pointer
668
+ // is then read as argument 0; a symbol map that sizes the return closes it
669
+ // (`prototypesFromSymbols`).
670
+ const layout = declaredWidth(r) === undefined ? layoutOf(resolved) : undefined;
671
+ const keyword = resolved.type.kind === 'tag' ? resolved.type.keyword : undefined;
672
+ if (r === 'void') {
673
+ proto.returnsVoid = true;
674
+ } else if (declaredWidth(r) !== undefined || isFloatingSpelling(r)) {
675
+ proto.returns = r;
676
+ } else if (layout !== undefined) {
677
+ proto.returns = r;
678
+ proto.returnLayout = layout;
679
+ } else if (keyword !== undefined && keyword !== 'enum' && resolved.derivations.length === 0) {
680
+ proto.returnLayout = { kind: keyword === 'union' ? 'union' : 'struct' };
681
+ }
682
+ const { params } = fn;
683
+ if (params === undefined) {
684
+ return proto;
685
+ }
686
+ if (params.length === 0 && !fn.variadic) {
687
+ if (language === 'c++') {
688
+ proto.params = [];
689
+ }
690
+ return proto;
691
+ }
692
+ if (fn.list.to === fn.list.from + 1 && tokens.is(fn.list.from, 'void')) {
693
+ proto.params = [];
694
+ return proto;
695
+ }
696
+ if (fn.variadic) {
697
+ return proto;
698
+ }
699
+ proto.params = params.map((p) => parameterType(p, tokens, typedefs));
700
+ return proto;
701
+ }
702
+
703
+ /** A parameter's type, its name taken off: an array or a function nearest the name is a pointer, and
704
+ * the typedef names it is built on resolve as a return's do. */
705
+ function parameterType(p: Parameter, tokens: Tokens, typedefs: ReadonlyMap<string, DeclaredType>): ParamType {
706
+ const t = asParameter(declaredType(p.specifiers, p.declarator.derivations));
707
+ return spellType(resolve(t, typedefs, tokens), tokens);
708
+ }
709
+
710
+ /** The prototypes a lift of `own` reads when a context is in hand: `stated` — a caller's own
711
+ * prototypes, which win per name — over the context's declarations less those the symbol map
712
+ * states better (`contextPrototypesUnder`). `own`'s declaration is left out, as the map's is
713
+ * (`asIfUndecompiled`): a header's signature for the function being decompiled is that kind of
714
+ * fact, and only what the caller states about it is kept.
715
+ *
716
+ * A stated entry that says nothing of the return (`statesNoReturn`, which counts `returnsVoid:
717
+ * false` as nothing) keeps a struct return the context states: it is what says argument 0 may be
718
+ * a hidden pointer, and an entry stating only the arity would otherwise hand that pointer to the
719
+ * call as its first argument. */
720
+ export function withContextPrototypes(
721
+ stated: Prototypes | undefined,
722
+ context: Prototypes,
723
+ own: string,
724
+ symbols: SymbolMap | undefined,
725
+ target: Pick<TargetDescription, 'doubleArgWords'>,
726
+ ): Prototypes {
727
+ const { [own]: _own, ...callees } = contextPrototypesUnder(context, symbols, target);
728
+ const out: Prototypes = { ...callees, ...stated };
729
+ for (const [name, p] of Object.entries(stated ?? {})) {
730
+ const heard = Object.hasOwn(callees, name) ? callees[name] : undefined;
731
+ if (p && statesNoReturn(p)) {
732
+ out[name] = { ...p, ...aggregateReturnOf(heard) };
733
+ }
734
+ }
735
+ return out;
736
+ }
737
+
738
+ /** The keys that state `p`'s struct or union return, or none where it states no such return. */
739
+ function aggregateReturnOf(p: FnProto | undefined): Pick<FnProto, 'returns' | 'returnLayout'> {
740
+ if (p === undefined || !declaresAggregateReturn(p)) {
741
+ return {};
742
+ }
743
+ return {
744
+ ...(p.returns !== undefined ? { returns: p.returns } : {}),
745
+ ...(p.returnLayout !== undefined ? { returnLayout: p.returnLayout } : {}),
746
+ };
747
+ }
748
+
749
+ const same = (a: FnProto | null, b: FnProto): boolean => a !== null && JSON.stringify(a) === JSON.stringify(b);
750
+
751
+ /** A context's prototypes, less the entries the symbol map states better. `prototypesFromSymbols`
752
+ * lets any entry it is handed shadow the map's signature for that name, so what reaches it decides
753
+ * which source wins, per name: a context entry that sizes every parameter wins — it is the
754
+ * declaration the candidate is compiled against, and its spellings are the ones a C++ call's casts
755
+ * need; an entry that cannot size one yields to a map signature that can be spelled, which sizes by
756
+ * byte count what a declaration names (a by-value struct of a register's width); and where the map
757
+ * spells nothing either, the context entry stays. */
758
+ function contextPrototypesUnder(
759
+ context: Prototypes,
760
+ symbols: SymbolMap | undefined,
761
+ target: Pick<TargetDescription, 'doubleArgWords'>,
762
+ ): Prototypes {
763
+ if (symbols === undefined) {
764
+ return context;
765
+ }
766
+ const mapped = new Map<string, FnProto>();
767
+ for (const infos of symbols.values()) {
768
+ for (const info of infos) {
769
+ const signed = symbolPrototype(info);
770
+ if (signed?.params !== undefined && !mapped.has(info.name)) {
771
+ mapped.set(info.name, signed);
772
+ }
773
+ }
774
+ }
775
+ // An entry that yields keeps a struct return it states, over the map's parameters: DWARF names no
776
+ // struct, so the map states one only by its size (`symbolPrototype`), and never its members.
777
+ return Object.fromEntries(
778
+ Object.entries(context).flatMap(([name, p]): [string, FnProto][] => {
779
+ const signed = mapped.get(name);
780
+ if (declaredCallArgs(p, target) !== undefined || signed === undefined) {
781
+ return [[name, p]];
782
+ }
783
+ return declaresAggregateReturn(p) && signed.returnsVoid !== true
784
+ ? [[name, { ...signed, ...aggregateReturnOf(p) }]]
785
+ : [];
786
+ }),
787
+ );
788
+ }