@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.
Files changed (130) hide show
  1. package/README.md +36 -31
  2. package/package.json +1 -1
  3. package/src/aggregate.ts +397 -0
  4. package/src/backend/c.ts +6 -2
  5. package/src/backend/cfamily.ts +370 -56
  6. package/src/backend/cpp.ts +116 -10
  7. package/src/backend/pascal.ts +69 -11
  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 +777 -84
  15. package/src/declare.ts +132 -13
  16. package/src/frontend/asmdata.ts +26 -6
  17. package/src/frontend/call-plan.ts +1590 -0
  18. package/src/frontend/dead-reads.ts +628 -0
  19. package/src/frontend/device-pins.ts +296 -0
  20. package/src/frontend/emit.ts +4 -3
  21. package/src/frontend/errors.ts +5 -0
  22. package/src/frontend/flags-edge.ts +101 -0
  23. package/src/frontend/fpu.ts +374 -0
  24. package/src/frontend/frame-objects.ts +3171 -0
  25. package/src/frontend/frontend.ts +64 -3
  26. package/src/frontend/high-half.ts +55 -5
  27. package/src/frontend/jump-table.ts +530 -0
  28. package/src/frontend/local-object.ts +616 -0
  29. package/src/frontend/mips.ts +2392 -208
  30. package/src/frontend/opaque.ts +97 -18
  31. package/src/frontend/own-struct-return.ts +500 -0
  32. package/src/frontend/ppc.ts +3137 -267
  33. package/src/frontend/register-struct-call.ts +484 -0
  34. package/src/frontend/reloc-symbol.ts +109 -13
  35. package/src/frontend/splat.ts +108 -5
  36. package/src/frontend/ssa.ts +2098 -89
  37. package/src/frontend/stackargs.ts +172 -42
  38. package/src/frontend/thumb.ts +4563 -2091
  39. package/src/ir/alias.ts +71 -0
  40. package/src/ir/bits.ts +681 -1
  41. package/src/ir/core.ts +199 -6
  42. package/src/ir/discipline.ts +313 -0
  43. package/src/ir/entry.ts +159 -0
  44. package/src/ir/float-bits.ts +47 -0
  45. package/src/{structure → ir}/loops.ts +6 -2
  46. package/src/ir/opcodes.ts +193 -121
  47. package/src/ir/parse.ts +1 -0
  48. package/src/ir/types.ts +202 -2
  49. package/src/ir/verify.ts +219 -3
  50. package/src/l3/address.ts +5 -0
  51. package/src/l3/argcopy.ts +38 -6
  52. package/src/l3/ast.ts +467 -23
  53. package/src/l3/basecse.ts +33 -5
  54. package/src/l3/coalesce.ts +21 -13
  55. package/src/l3/constinit.ts +382 -0
  56. package/src/l3/dce.ts +34 -12
  57. package/src/l3/gates.ts +9 -3
  58. package/src/l3/hoist.ts +8 -7
  59. package/src/l3/initfirst.ts +68 -19
  60. package/src/l3/intcell.ts +98 -0
  61. package/src/l3/mentions.ts +10 -4
  62. package/src/l3/narrowdecl.ts +259 -0
  63. package/src/l3/nearbase.ts +10 -5
  64. package/src/l3/parkfirst.ts +8 -5
  65. package/src/l3/pollguard.ts +11 -4
  66. package/src/l3/ptrcell.ts +354 -0
  67. package/src/l3/ptrfield.ts +123 -8
  68. package/src/l3/regspell.ts +26 -7
  69. package/src/l3/reindex.ts +293 -103
  70. package/src/l3/storage.ts +98 -1
  71. package/src/l3/symbol-refs.ts +182 -23
  72. package/src/l3/tailmerge.ts +9 -20
  73. package/src/l3/typing.ts +237 -32
  74. package/src/l3/unreduce.ts +10 -9
  75. package/src/l3/volatileptr.ts +17 -54
  76. package/src/l3/volstore.ts +96 -7
  77. package/src/mangle.ts +41 -1
  78. package/src/pattern/engine.ts +373 -82
  79. package/src/pipeline.ts +589 -27
  80. package/src/proto-context.ts +1648 -0
  81. package/src/proto.ts +1203 -49
  82. package/src/raise/aggmembers.ts +290 -0
  83. package/src/raise/arrays.ts +8 -3
  84. package/src/raise/declared-volatile.ts +307 -0
  85. package/src/raise/divpow2.ts +2 -2
  86. package/src/raise/errors.ts +17 -0
  87. package/src/raise/floathelpers.ts +464 -0
  88. package/src/raise/globalshape.ts +11 -5
  89. package/src/raise/gvn.ts +29 -8
  90. package/src/raise/latch.ts +162 -6
  91. package/src/raise/memberarrays.ts +10 -4
  92. package/src/raise/narrow.ts +29 -2
  93. package/src/raise/narrowlocal.ts +11 -6
  94. package/src/raise/offsetnames.ts +494 -0
  95. package/src/raise/pairparams.ts +114 -0
  96. package/src/raise/paramwidth.ts +203 -6
  97. package/src/raise/pre-recovery.ts +174 -17
  98. package/src/raise/pre-structure.ts +54 -0
  99. package/src/raise/recover.ts +26 -15
  100. package/src/raise/reroll.ts +694 -0
  101. package/src/raise/retsink.ts +1 -1
  102. package/src/raise/shortcircuit.ts +66 -30
  103. package/src/raise/softdiv.ts +21 -23
  104. package/src/raise/storesign.ts +153 -0
  105. package/src/raise/struct-arrays.ts +34 -14
  106. package/src/raise/structs.ts +674 -98
  107. package/src/raise/truncload.ts +367 -0
  108. package/src/raise/unthread.ts +400 -0
  109. package/src/raise/widehelpers.ts +106 -0
  110. package/src/rank-declare.ts +143 -12
  111. package/src/rank-variations.ts +117 -31
  112. package/src/rank.ts +563 -122
  113. package/src/runtime-helpers.ts +405 -0
  114. package/src/stillborn.ts +289 -0
  115. package/src/structure/analysis.ts +1449 -449
  116. package/src/structure/arith-ops.ts +23 -0
  117. package/src/structure/globalaccess.ts +111 -9
  118. package/src/structure/hazards.ts +641 -114
  119. package/src/structure/local-statics.ts +517 -0
  120. package/src/structure/namecoalesce.ts +2 -2
  121. package/src/structure/pointer-spelling.ts +1106 -0
  122. package/src/structure/redundant-test.ts +99 -0
  123. package/src/structure/structure.ts +3478 -975
  124. package/src/structure/switch-recover.ts +403 -178
  125. package/src/symbols.ts +44 -3
  126. package/src/target.ts +1387 -71
  127. package/src/trace.ts +66 -28
  128. package/src/variation-definitions.ts +243 -13
  129. package/src/variation-gates.ts +2 -0
  130. package/src/variation-tokens.ts +25 -7
@@ -0,0 +1,1648 @@
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 ParsedContext,
11
+ type Qualifier,
12
+ type Range,
13
+ type TypeSpecifier,
14
+ declaredType,
15
+ memberDeclarations,
16
+ parseDeclarations,
17
+ } from './cdecl/parse';
18
+ import { asParameter, spellType } from './cdecl/spell';
19
+ import { demangledName, implicitArgumentWords } from './mangle';
20
+ import type { AggregateLayout, AggregateMember, FnProto, ParamType, Prototypes } from './proto';
21
+ import {
22
+ declaredCallArgs,
23
+ declaredWidth,
24
+ declaresAggregateReturn,
25
+ declaresVoidReturn,
26
+ isFloatingSpelling,
27
+ paramClassOf,
28
+ statesNoReturn,
29
+ symbolPrototype,
30
+ validatePrototypes,
31
+ } from './proto';
32
+ import type { SymbolMap, SymbolTypeFacts } from './symbols';
33
+ import type { TargetDescription } from './target';
34
+
35
+ // asmlift — callee prototypes read out of a DECLARATION CONTEXT: the preprocessed headers a
36
+ // candidate is compiled against (a decomp project's `ctx.h`, m2c's `--context`). The compiler
37
+ // checks every candidate's calls against those declarations, so a call asmlift writes at a guessed
38
+ // arity is a candidate the context then refuses; read here, the declarations decide the arity
39
+ // instead of the argument-register scan.
40
+ //
41
+ // WHAT IS READ is what the parser reads (`cdecl/parse.ts`). Of it, this takes the functions the top
42
+ // level declares or defines, and the typedefs, tags and bodies their types name.
43
+ //
44
+ // KEYED BY THE DECLARED NAME, which is the symbol a call in the assembly names only for C linkage:
45
+ // a C++ free function is called by its mangled symbol, so linkage decides itself at the lookup. A
46
+ // name declared twice with different signatures (an overload) is dropped: the table cannot say which
47
+ // one a call means.
48
+ //
49
+ // A TYPEDEF RESOLVES ONLY TO THE SAME TYPE. A spelling here is printed into candidates beside the
50
+ // project's own headers (`declare.ts`), where a different type is a conflicting declaration. So a
51
+ // typedef resolves along its chain only until `declaredWidth` can size the spelling (`BOOL` →
52
+ // `int`); a function-pointer typedef resolves to its own abstract declarator (`void (*)(s32)`);
53
+ // a struct or enum keeps its name, which sizes to nothing and makes the list abstain. Resolution
54
+ // is on the parsed type, never its spelling: a typedef name stands for its base and derivations,
55
+ // and the name's own qualifiers qualify the derivation they bind (`const Cb` is
56
+ // `void (* const)(s32)`).
57
+ //
58
+ // A TYPEDEF WITH AN ATTRIBUTE NAMES NO TYPE HERE: the attribute may make it a different type from
59
+ // the one it spells (`realigns`), so no spelling resolves through it. One on a parameter of a
60
+ // function the typedef points at is that parameter's (`ownAttributes`).
61
+
62
+ /** The layout of the struct or union a type names, at a nesting depth. */
63
+ type LayoutOf = (t: DeclaredType, depth: number) => AggregateLayout | undefined;
64
+
65
+ /** The callee prototypes a preprocessed declaration context states. `language` decides what an
66
+ * empty parameter list means: none in C++, unstated in C (a pre-ANSI declaration). */
67
+ export function prototypesFromContext(src: string, language: Language): Prototypes {
68
+ const ctx = parseDeclarations(src, language);
69
+ const table = typeTable(ctx);
70
+ return functionPrototypes(ctx, table, layoutReader(ctx, table));
71
+ }
72
+
73
+ /** What a preprocessed declaration context states about the globals it declares, beyond their
74
+ * prototypes. The sets travel together: the aggregate spelling `*(T *)&g` is a cast, which drops
75
+ * the declaration's qualifiers, so a reader handed the aggregates without the volatile objects
76
+ * would print a plain read of a volatile one. */
77
+ export interface ContextGlobals {
78
+ /** The objects declared as a struct, a union or an array. Such an object has no value a bare name
79
+ * reads: `v = gInfo;` over `extern struct Info gInfo;` is a compile error, and over
80
+ * `extern u8 gBuf[];` it reads the address. A project whose symbol map does not describe a
81
+ * global (one its linker script places has no DWARF) states it here, so the structurer reads
82
+ * this set where it would read the map's `shape`. A type this cannot resolve — an unexpanded
83
+ * macro — is left out, which keeps the usage-inferred spelling. */
84
+ aggregates: ReadonlySet<string>;
85
+ /** The objects any byte of which is volatile (`hasVolatileStorage`): the object, an array's
86
+ * element, a typedef in its chain, or a member of a struct or union in it, nested or anonymous.
87
+ * A body this cannot read counts as volatile. The set is not per byte: a read of the plain member
88
+ * of `struct { vu16 a; u16 b; }` is placed too, which costs at most a read the compiler kept that
89
+ * the source did not ask it to, never one it may drop. raise/declared-volatile.ts stamps every
90
+ * access to such an object the map does not lay out, and the structurer then puts the qualifier
91
+ * on the cast (`*(volatile u16 *)&gV`). Scalars are in it too: the header already qualifies
92
+ * `gReg` where it is spelled bare, and the stamp keeps the raising passes from merging two of its
93
+ * reads. */
94
+ volatiles: ReadonlySet<string>;
95
+ /** The objects declared as a pointer whose pointee has a volatile byte, read as `volatiles` reads
96
+ * an object: `extern volatile u16 *gP;`, `extern volatile struct V *gQ;`. The pointer itself is
97
+ * plain, so a read of `gP` is not placed; an access through the value it holds is, and the
98
+ * structurer puts the qualifier on the cast it is spelled through (`*(volatile u16 *)gP`).
99
+ * Without it, `while (*gP == 0);` lifts as a plain read the compiler hoists out of the loop. */
100
+ pointeeVolatiles: ReadonlySet<string>;
101
+ /** Every object the context declares, volatile or not; a function is no object. Read beside
102
+ * `volatiles`: an object here and not there is declared with no volatile byte, which is what the
103
+ * dead-read pin's `declared-plain` gate reads (frontend/dead-reads.ts) — the source spelled a
104
+ * read of it plain, so a read the target keeps that nothing uses is not the source's `volatile`. */
105
+ declared: ReadonlySet<string>;
106
+ /** The type each object of `declared` that is no array, pointer or function is declared as, as
107
+ * written (`Q_t`, `struct Q`). An object passed bare as a struct by value must be declared as
108
+ * that struct (frontend/call-plan.ts `foldByValueArgs`): `gQs` over `extern struct Q gQs[4];` or
109
+ * `gO2` over `extern struct O2 gO2;` names the right address and is a type error as an argument
110
+ * of `struct Q`. Compared by the struct it names (`structNamed`, which follows the typedefs),
111
+ * not by spelling. */
112
+ objectTypes: ReadonlyMap<string, string>;
113
+ /** The struct or union each typedef name stands for, as far as its chain goes with no
114
+ * derivation: by its tag (`struct Q`), or, for an untagged body, by the typedef name that binds
115
+ * it. Read through `structNamed`, which is how two spellings of one struct are told to be one:
116
+ * `typedef struct Q {…} Q;` spells the struct `Q`, `struct Q` and every other alias of it. */
117
+ structTypes: ReadonlyMap<string, string>;
118
+ }
119
+
120
+ /** The struct or union `spelling` names, qualifiers aside, by the identity `structTypes` keys it
121
+ * under: `struct Tag` as written, or what a typedef name stands for. Undefined for any other
122
+ * spelling, and for a name the context does not resolve to one — which then names no struct this
123
+ * can compare, and the spellings are compared as written. */
124
+ export function structNamed(
125
+ spelling: string,
126
+ context: Pick<ContextGlobals, 'structTypes'> | undefined,
127
+ ): string | undefined {
128
+ const s = spelling
129
+ .replace(/\b(?:const|volatile)\b/g, ' ')
130
+ .trim()
131
+ .replace(/\s+/g, ' ');
132
+ return /^(?:struct|union) [A-Za-z_$][\w$]*$/.test(s) ? s : context?.structTypes.get(s);
133
+ }
134
+
135
+ /** The globals a preprocessed declaration context declares, read for {@link ContextGlobals}. */
136
+ export function globalsFromContext(src: string, language: Language): ContextGlobals {
137
+ const ctx = parseDeclarations(src, language);
138
+ const table = typeTable(ctx);
139
+ const scope: VolatileScope = { table, typedefs: typedefTypes(ctx), bodies: tagBodies(ctx), ctx, read: new Map() };
140
+ const aggregates = new Set<string>();
141
+ const volatiles = new Set<string>();
142
+ const pointeeVolatiles = new Set<string>();
143
+ const declared = new Set<string>();
144
+ const objectTypes = new Map<string, string>();
145
+ for (const d of ctx.declarations) {
146
+ if (d.specifiers.typedef || d.body !== undefined) {
147
+ continue;
148
+ }
149
+ for (const x of d.declarators) {
150
+ if (x.name === undefined || !WORD.test(x.name)) {
151
+ continue;
152
+ }
153
+ const t = declaredType(d.specifiers, x.derivations);
154
+ if (t.derivations[0]?.kind !== 'function') {
155
+ declared.add(x.name);
156
+ }
157
+ if (t.derivations.length === 0) {
158
+ objectTypes.set(x.name, t.spelling);
159
+ }
160
+ if (isAggregate(t, table, scope.typedefs)) {
161
+ aggregates.add(x.name);
162
+ }
163
+ if (hasVolatileStorage(t, scope, 0)) {
164
+ volatiles.add(x.name);
165
+ }
166
+ // derivations run outermost first: the object is a pointer, and what it points at is the rest
167
+ if (t.derivations[0]?.kind === 'pointer') {
168
+ const pointee = { ...t, derivations: t.derivations.slice(1) };
169
+ if (hasVolatileStorage(pointee, scope, 0)) {
170
+ pointeeVolatiles.add(x.name);
171
+ }
172
+ }
173
+ }
174
+ }
175
+ const structTypes = new Map<string, string>();
176
+ for (const name of scope.typedefs.keys()) {
177
+ const named = structOfTypedef(name, scope.typedefs);
178
+ if (named !== undefined) {
179
+ structTypes.set(name, named);
180
+ }
181
+ }
182
+ return { aggregates, volatiles, pointeeVolatiles, declared, objectTypes, structTypes };
183
+ }
184
+
185
+ /** The struct or union the typedef `name` stands for (`ContextGlobals.structTypes`): its tag where
186
+ * the chain ends at one, else the last name in the chain, which binds the untagged body. */
187
+ function structOfTypedef(name: string, typedefs: ReadonlyMap<string, DeclaredType>): string | undefined {
188
+ let binder = name;
189
+ let at = typedefs.get(name);
190
+ for (let hops = 0; hops < 16 && at !== undefined && at.derivations.length === 0; hops++) {
191
+ const t = at.type;
192
+ if (t.kind === 'tag') {
193
+ return t.keyword === 'struct' || t.keyword === 'union'
194
+ ? t.tag === undefined
195
+ ? binder
196
+ : `${t.keyword} ${t.tag}`
197
+ : undefined;
198
+ }
199
+ if (t.words.length !== 1 || at.unknownWords.length > 0) {
200
+ return undefined;
201
+ }
202
+ binder = t.words[0];
203
+ at = typedefs.get(binder);
204
+ }
205
+ return undefined;
206
+ }
207
+
208
+ /** Every struct, union and class body the context defines, by `keyword Tag` (and, in C++, by the bare
209
+ * tag, as C++ spells one), attributed or not, at the top level and nested in another body. */
210
+ function tagBodies(ctx: ParsedContext): Map<string, Range> {
211
+ const out = new Map<string, Range>();
212
+ const visit = (ds: readonly Declaration[], depth: number): void => {
213
+ for (const d of ds) {
214
+ const t = d.specifiers.type;
215
+ if (t.kind !== 'tag' || t.keyword === 'enum' || t.body === undefined) {
216
+ continue;
217
+ }
218
+ if (t.tag !== undefined && WORD.test(t.tag)) {
219
+ out.set(`${t.keyword} ${t.tag}`, t.body);
220
+ if (ctx.language === 'c++') {
221
+ out.set(t.tag, t.body);
222
+ }
223
+ }
224
+ const members = depth < 8 ? memberDeclarations(ctx, t.body) : undefined;
225
+ if (members !== undefined) {
226
+ visit(members, depth + 1);
227
+ }
228
+ }
229
+ };
230
+ visit(ctx.declarations, 0);
231
+ return out;
232
+ }
233
+
234
+ /** Every typedef name the context declares, and the type it stands for as written: with the body a
235
+ * typedef defines (`typedef struct { vu8 a; } S;`), its qualifiers (`typedef volatile struct {…} S;`)
236
+ * and an array's bounds (`typedef vu8 Buf[4];`), none of which the type table keeps — it binds a
237
+ * body's name to the name itself, and it reads no typedef of an array. */
238
+ function typedefTypes(ctx: ParsedContext): Map<string, DeclaredType> {
239
+ const out = new Map<string, DeclaredType>();
240
+ for (const d of ctx.declarations) {
241
+ if (!d.specifiers.typedef) {
242
+ continue;
243
+ }
244
+ for (const x of d.declarators) {
245
+ const t = declaredType(d.specifiers, x.derivations);
246
+ // `typedef S S;` restates a name and keeps the type it already stands for
247
+ const restates = t.type.kind === 'words' && t.type.words.length === 1 && t.type.words[0] === x.name;
248
+ if (x.name !== undefined && WORD.test(x.name) && !(restates && t.derivations.length === 0)) {
249
+ out.set(x.name, t);
250
+ }
251
+ }
252
+ }
253
+ return out;
254
+ }
255
+
256
+ /** What `hasVolatileStorage` reads a type through, and what it has already read: each typedef name
257
+ * and each body by its first token, so a type every member reaches through the same typedefs
258
+ * (`mActor_name_t`, then `u16`) is walked once, and a long chain of them costs no nesting depth. */
259
+ interface VolatileScope {
260
+ table: TypeTable;
261
+ typedefs: ReadonlyMap<string, DeclaredType>;
262
+ bodies: ReadonlyMap<string, Range>;
263
+ ctx: ParsedContext;
264
+ read: Map<string, boolean>;
265
+ }
266
+
267
+ /** Whether any byte of an object of type `t` is volatile: the type or an array's element is
268
+ * qualified, through the typedef names it is built on, or a struct or union in it has such a member.
269
+ * A pointer ends the walk at its own qualifiers, since what it points at is no byte of the object.
270
+ * A body that cannot be read, or bodies nested past the bound, is read as volatile: a qualifier
271
+ * placed where none was declared only keeps a read the compiler may have dropped. A tag with no
272
+ * body here has no member to qualify.
273
+ * A name met again while it is being read is a cycle, read as volatile (`once`). */
274
+ function hasVolatileStorage(t: DeclaredType, scope: VolatileScope, depth: number): boolean {
275
+ if (depth > 32) {
276
+ return true;
277
+ }
278
+ for (const x of t.derivations) {
279
+ if (x.kind === 'pointer') {
280
+ return x.qualifiers.includes('volatile');
281
+ }
282
+ if (x.kind !== 'array') {
283
+ return false;
284
+ }
285
+ }
286
+ // GNU's alternate spellings, which the declaration parser keeps as words it does not know, or as
287
+ // the type's own name when a typedef name follows (`__volatile__ u8 a;`)
288
+ const words = t.type.kind === 'words' ? [...t.type.words, ...t.unknownWords] : t.unknownWords;
289
+ if (t.qualifiers.includes('volatile') || words.some((w) => w === '__volatile' || w === '__volatile__')) {
290
+ return true;
291
+ }
292
+ let body: Range | undefined;
293
+ if (t.type.kind === 'tag') {
294
+ if (t.type.keyword === 'enum') {
295
+ return false;
296
+ }
297
+ body = t.type.body ?? (t.type.tag !== undefined ? scope.bodies.get(`${t.type.keyword} ${t.type.tag}`) : undefined);
298
+ } else if (t.type.words.length === 1) {
299
+ const name = t.type.words[0];
300
+ const next = scope.typedefs.get(name);
301
+ if (next !== undefined) {
302
+ const resolves = next.type.kind === 'words' && next.type.words.length === 1 && next.type.words[0] === name;
303
+ return !resolves && once(scope, `typedef ${name}`, () => hasVolatileStorage(next, scope, depth));
304
+ }
305
+ body = scope.table.named.get(name)?.body ?? scope.bodies.get(name);
306
+ }
307
+ if (body === undefined) {
308
+ return false;
309
+ }
310
+ const members = memberDeclarations(scope.ctx, body);
311
+ const bases = baseClause(scope.ctx, body);
312
+ if (members === undefined || bases === undefined) {
313
+ return true;
314
+ }
315
+ // a C++ member typedef names a type for the members after it, so they are read through it, in a
316
+ // scope of their own. That scope also reads a body defined elsewhere through it, which can only
317
+ // leave more out.
318
+ const local = typedefTypes({ ...scope.ctx, declarations: members });
319
+ const inner: VolatileScope =
320
+ local.size === 0 ? scope : { ...scope, typedefs: new Map([...scope.typedefs, ...local]), read: new Map() };
321
+ return once(
322
+ scope,
323
+ `body ${body.from}`,
324
+ () =>
325
+ // a C++ base's members are bytes of the object too, and a base with no body here (one a
326
+ // namespace defines, which this does not read) is read as volatile
327
+ bases.some((b) => !definesBody(b, inner) || hasVolatileStorage(b, inner, depth + 1)) ||
328
+ members.some((m) =>
329
+ // a C++ member typedef or static member is no byte of the object
330
+ m.specifiers.typedef || m.specifiers.storage.includes('static')
331
+ ? false
332
+ : m.declarators.length === 0
333
+ ? hasVolatileStorage(declaredType(m.specifiers, []), inner, depth + 1)
334
+ : m.declarators.some((x) =>
335
+ hasVolatileStorage(declaredType(m.specifiers, x.derivations), inner, depth + 1),
336
+ ),
337
+ ),
338
+ );
339
+ }
340
+
341
+ /** The C++ base classes ahead of `body`, each as the type its name spells, or undefined when a
342
+ * clause holds anything else (a qualified name, a template argument list, `decltype`), which the
343
+ * caller reads as volatile. Read backwards from the `{` to the clause's `:`; reaching the
344
+ * tag keyword first means there is no clause. */
345
+ function baseClause(ctx: ParsedContext, body: Range): DeclaredType[] | undefined {
346
+ if (ctx.language !== 'c++') {
347
+ return [];
348
+ }
349
+ const t = ctx.tokens;
350
+ let k = body.from - 2;
351
+ for (; k >= 0 && !t.is(k, ':'); k--) {
352
+ if (TAG_KEYWORDS.has(t.text(k))) {
353
+ return [];
354
+ }
355
+ if (t.kind(k) !== 'identifier' && !t.is(k, '::') && !t.is(k, ',')) {
356
+ // an attribute, a template argument list: a `:` between it and the tag keyword is a clause
357
+ // this does not read, and none means there is no clause
358
+ for (let j = k; j >= 0 && !t.is(j, ';') && !t.is(j, '}') && !t.is(j, '{') && !TAG_KEYWORDS.has(t.text(j)); j--) {
359
+ if (t.is(j, ':')) {
360
+ return undefined;
361
+ }
362
+ }
363
+ return [];
364
+ }
365
+ }
366
+ if (k < 0) {
367
+ return [];
368
+ }
369
+ const out: DeclaredType[] = [];
370
+ let last: string | undefined;
371
+ for (let j = k + 1; j < body.from; j++) {
372
+ if (t.is(j, ',') || j === body.from - 1) {
373
+ if (last === undefined) {
374
+ return undefined;
375
+ }
376
+ out.push({
377
+ qualifiers: [],
378
+ type: { kind: 'words', words: [last] },
379
+ unknownWords: [],
380
+ spelling: last,
381
+ derivations: [],
382
+ });
383
+ last = undefined;
384
+ } else if (t.is(j, '::')) {
385
+ return undefined;
386
+ } else if (t.kind(j) === 'identifier' && !BASE_WORDS.has(t.text(j))) {
387
+ last = t.text(j);
388
+ }
389
+ }
390
+ return out;
391
+ }
392
+
393
+ /** Whether the base `b` names a typedef or a body this context defines. */
394
+ function definesBody(b: DeclaredType, scope: VolatileScope): boolean {
395
+ const name = b.spelling;
396
+ return scope.typedefs.has(name) || scope.table.named.get(name)?.body !== undefined || scope.bodies.has(name);
397
+ }
398
+
399
+ const TAG_KEYWORDS: ReadonlySet<string> = new Set(['struct', 'class', 'union', 'enum']);
400
+ const BASE_WORDS: ReadonlySet<string> = new Set(['public', 'protected', 'private', 'virtual']);
401
+
402
+ /** `read()`, remembered under `key`. A key met again while it is being read is a cycle no compiler
403
+ * lays out (`typedef T U; typedef U T;`, which C11 and C++ accept), read as volatile, for the same
404
+ * reason as an unreadable body. */
405
+ function once(scope: VolatileScope, key: string, read: () => boolean): boolean {
406
+ const known = scope.read.get(key);
407
+ if (known !== undefined) {
408
+ return known;
409
+ }
410
+ scope.read.set(key, true);
411
+ const v = read();
412
+ scope.read.set(key, v);
413
+ return v;
414
+ }
415
+
416
+ /** Whether `t` is a struct, union, class or array type, through the typedef names it is built on.
417
+ * A typedef the type table does not read — one of an array, `typedef u16 Arr4[4];` — is followed
418
+ * as written (`typedefTypes`), or `extern Arr4 gTB;` reads as a scalar whose offset-0 read is
419
+ * spelled `gTB`, the array's address. */
420
+ function isAggregate(t: DeclaredType, table: TypeTable, written: ReadonlyMap<string, DeclaredType>): boolean {
421
+ let cur = t;
422
+ for (let hops = 0; hops < 16; hops++) {
423
+ const [first] = cur.derivations;
424
+ if (first !== undefined) {
425
+ return first.kind === 'array';
426
+ }
427
+ if (cur.type.kind === 'tag') {
428
+ return cur.type.keyword !== 'enum';
429
+ }
430
+ const name = nameOf(cur);
431
+ if (name === undefined) {
432
+ return false;
433
+ }
434
+ if (table.named.has(name)) {
435
+ return true;
436
+ }
437
+ const next = table.typedefs.get(name) ?? written.get(name);
438
+ if (next === undefined || nameOf(next) === name) {
439
+ return false;
440
+ }
441
+ cur = next;
442
+ }
443
+ return false;
444
+ }
445
+
446
+ const WORD = /^[A-Za-z_]\w*$/;
447
+
448
+ /** `mode(…)` in an attribute, in either spelling: it retypes what it is written on */
449
+ const MODE = /\b(?:__)?mode(?:__)?\s*\(/;
450
+
451
+ function typeTable(ctx: ParsedContext): TypeTable {
452
+ const table: TableBuilder = {
453
+ typedefs: new Map(),
454
+ tagged: new Map(),
455
+ named: new Map(),
456
+ unspelledPointers: new Set(),
457
+ enums: new Set(),
458
+ unsizedEnums: new Set(),
459
+ wideEnumerators: new Set(),
460
+ realigned: false,
461
+ };
462
+ for (const d of ctx.declarations) {
463
+ const tag = d.specifiers.type.kind === 'tag' ? d.specifiers.type : undefined;
464
+ const attributes = d.specifiers.typedef || tag?.body !== undefined ? ownAttributes(d) : [];
465
+ table.realigned ||= realigns(d, attributes);
466
+ const unsizedEnum = tag !== undefined && readTag(table, d, tag, laysOut(d, attributes), ctx);
467
+ if (d.specifiers.typedef && attributes.length === 0) {
468
+ readTypedefs(table, d, unsizedEnum);
469
+ }
470
+ }
471
+ return table;
472
+ }
473
+
474
+ /** What the declarations say about the types a prototype or a layout spells, while they are read. */
475
+ interface TableBuilder {
476
+ /** each typedef name, and the type it stands for, before resolution */
477
+ typedefs: Map<string, DeclaredType>;
478
+ /** struct and union bodies by `struct Tag` spelling; none for a body an attribute lays out
479
+ * (`laysOut`) */
480
+ tagged: Map<string, Range | undefined>;
481
+ /** struct and union bodies by a typedef name bound to a body, which resolves to itself and spells
482
+ * no keyword — as C++ spells every tag, a declared one with no body included */
483
+ named: Map<string, { kind: AggregateLayout['kind']; body?: Range }>;
484
+ /** a typedef name for a pointer to a body nothing else names: a word, and no spelling of its own */
485
+ unspelledPointers: Set<string>;
486
+ /** a typedef name bound to an enum body */
487
+ enums: Set<string>;
488
+ /** an enum, by `enum Tag` or typedef name, that is not the target's enumBytes: `packed` makes it
489
+ * the smallest integer its values fit (agbcc c-common.c:446, c-decl.c:6123), and so does a value
490
+ * an int cannot hold (`enumMayWiden`) */
491
+ unsizedEnums: Set<string>;
492
+ /** the enumerators of an enum wider than an int, which widen any enum that names one */
493
+ wideEnumerators: Set<string>;
494
+ /** a typedef changed a type after its layout (`realigns`), which moves the layout of whatever
495
+ * holds it, so no layout is read */
496
+ realigned: boolean;
497
+ }
498
+
499
+ /** What the declarations say about the types a prototype or a layout spells, once read. */
500
+ type TypeTable = Readonly<Omit<TableBuilder, 'wideEnumerators'>>;
501
+
502
+ /** What a struct, union, enum or class specifier says about its tag; whether it defines an enum
503
+ * the target does not size (`unsizedEnums`). `attributed`: an attribute lays its body out. */
504
+ function readTag(
505
+ table: TableBuilder,
506
+ d: Declaration,
507
+ tag: Extract<TypeSpecifier, { kind: 'tag' }>,
508
+ attributed: boolean,
509
+ ctx: ParsedContext,
510
+ ): boolean {
511
+ const name = tag.tag !== undefined && WORD.test(tag.tag) ? tag.tag : undefined;
512
+ const { body, keyword } = tag;
513
+ const aggregate = keyword === 'struct' || keyword === 'union' ? keyword : undefined;
514
+ if (aggregate !== undefined && body !== undefined && name !== undefined && !tag.base) {
515
+ table.tagged.set(`${aggregate} ${name}`, attributed ? undefined : body);
516
+ }
517
+ // In C++ a class is a struct too. Its body (access labels, member functions), or one after a
518
+ // base clause, whose members start past the base's, is none this lays out: the kind is known and
519
+ // the members are not. A forward declaration states the kind alone, so a definition after it
520
+ // replaces it, and nothing replaces a definition.
521
+ if (
522
+ ctx.language === 'c++' &&
523
+ keyword !== 'enum' &&
524
+ name !== undefined &&
525
+ (body !== undefined || forwardDeclares(d)) &&
526
+ table.named.get(name)?.body === undefined
527
+ ) {
528
+ const layable = aggregate !== undefined && !tag.base && !attributed;
529
+ table.named.set(name, { kind: keyword === 'union' ? 'union' : 'struct', body: layable ? body : undefined });
530
+ }
531
+ if (keyword !== 'enum' || body === undefined) {
532
+ return false;
533
+ }
534
+ const wide = enumMayWiden(ctx.tokens, body, table.wideEnumerators);
535
+ if (wide) {
536
+ for (const e of enumerators(ctx.tokens, body)) {
537
+ table.wideEnumerators.add(e);
538
+ }
539
+ }
540
+ if ((attributed || wide) && name !== undefined) {
541
+ table.unsizedEnums.add(`enum ${name}`);
542
+ }
543
+ // In C++ an enum's tag names its type with no keyword, as a typedef bound to its body does in C
544
+ // (`readTypedefs`), so the bare `Fmt` of `enum Fmt { … }` is that enum, sized as it is.
545
+ if (ctx.language === 'c++' && name !== undefined) {
546
+ table.enums.add(name);
547
+ if (attributed || wide) {
548
+ table.unsizedEnums.add(name);
549
+ }
550
+ }
551
+ return attributed || wide;
552
+ }
553
+
554
+ /** Every name a typedef declares, into the table. `unsizedEnum`: the body it names is an enum the
555
+ * target does not size. */
556
+ function readTypedefs(table: TableBuilder, d: Declaration, unsizedEnum: boolean): void {
557
+ const { type } = d.specifiers;
558
+ const keyword = type.kind === 'tag' ? type.keyword : undefined;
559
+ for (const td of typedefNames(d)) {
560
+ table.typedefs.set(td.name, td.type);
561
+ if (td.names === 'unspelled pointer') {
562
+ table.unspelledPointers.add(td.name);
563
+ } else if (td.names === 'body' && (keyword === 'struct' || keyword === 'union')) {
564
+ table.named.set(td.name, { kind: keyword, body: type.kind === 'tag' ? type.body : undefined });
565
+ } else if (td.names === 'body' && keyword === 'enum') {
566
+ table.enums.add(td.name);
567
+ if (unsizedEnum) {
568
+ table.unsizedEnums.add(td.name);
569
+ }
570
+ }
571
+ }
572
+ }
573
+
574
+ /** Whether the declaration states a tag and nothing else: `struct Fwd;`. */
575
+ function forwardDeclares(d: Declaration): boolean {
576
+ const s = d.specifiers;
577
+ return (
578
+ d.declarators.length === 0 &&
579
+ !s.typedef &&
580
+ s.storage.length === 0 &&
581
+ s.qualifiers.length === 0 &&
582
+ s.attributes.length === 0 &&
583
+ s.unknownWords.length === 0
584
+ );
585
+ }
586
+
587
+ /** One name a `typedef` declares: the type it stands for (before resolution), and whether it names
588
+ * the struct, union or enum body the declaration defines, or a pointer to one that has no other name
589
+ * to spell it by. */
590
+ interface TypedefName {
591
+ name: string;
592
+ type: DeclaredType;
593
+ names: 'body' | 'unspelled pointer' | 'other';
594
+ }
595
+
596
+ /** The type a lone name spells, which resolves to itself. */
597
+ const named = (name: string): DeclaredType => ({
598
+ qualifiers: [],
599
+ type: { kind: 'words', words: [name] },
600
+ unknownWords: [],
601
+ spelling: name,
602
+ derivations: [],
603
+ });
604
+
605
+ /** The one name `t` is, where it is a name and nothing else: what a typedef may have declared. */
606
+ const nameOf = (t: DeclaredType): string | undefined =>
607
+ t.derivations.length === 0 && t.unknownWords.length === 0 && t.type.kind === 'words' && t.type.words.length === 1
608
+ ? t.type.words[0]
609
+ : undefined;
610
+
611
+ /** `struct Tag`, `union Tag` or `enum Tag` where `t` is that tag and nothing else, qualifiers aside. */
612
+ const tagOf = (t: DeclaredType): string | undefined =>
613
+ t.derivations.length === 0 &&
614
+ t.unknownWords.length === 0 &&
615
+ t.type.kind === 'tag' &&
616
+ t.type.tag !== undefined &&
617
+ WORD.test(t.type.tag)
618
+ ? `${t.type.keyword} ${t.type.tag}`
619
+ : undefined;
620
+
621
+ const isPointer = (x: Derivation): boolean => x.kind === 'pointer' && x.member === undefined;
622
+
623
+ /** The derivations of what a function pointer returns, where `derivations` are a plain `(*)` to a
624
+ * function returning the base or a pointer to it; otherwise undefined. */
625
+ function functionPointer(derivations: readonly Derivation[]): readonly Derivation[] | undefined {
626
+ const [first, second, ...returns] = derivations;
627
+ return first?.kind === 'pointer' &&
628
+ first.qualifiers.length === 0 &&
629
+ isPointer(first) &&
630
+ second?.kind === 'function' &&
631
+ returns.every(isPointer)
632
+ ? returns
633
+ : undefined;
634
+ }
635
+
636
+ /** Every name a `typedef` declares (`typedef struct R {…} R, *RP;` declares two). A plain declarator
637
+ * of a body names that body, qualified or not (`} const CR;`), and resolves to itself. A pointer
638
+ * declarator is a pointer to the body, qualifiers and all, spelled by the body's tag or by a plain
639
+ * name the same declaration gives it, and resolves to itself where the body has neither
640
+ * (`typedef struct {…} *PS;`). Without a body, a name is read when its type is the specifiers'
641
+ * own, a pointer to it (`u8 *`, `u8 * const *`) or a function pointer (`void (*)(s32)`); any other
642
+ * type, an array's or one that groups more than a function pointer, is not read. */
643
+ function typedefNames(d: Declaration): TypedefName[] {
644
+ const s = d.specifiers;
645
+ const declared = d.declarators.flatMap((x) =>
646
+ x.name !== undefined && WORD.test(x.name) ? [{ ...x, name: x.name }] : [],
647
+ );
648
+ if (s.type.kind === 'tag' && s.type.body !== undefined) {
649
+ // a plain name stands for the body with its qualifiers; a tag, without them
650
+ const plain = declared.find((x) => x.derivations.length === 0)?.name;
651
+ const tag = tagOf({ ...declaredType(s, []), unknownWords: [] });
652
+ const pointee =
653
+ tag !== undefined
654
+ ? { ...declaredType(s, []), unknownWords: [], spelling: tag }
655
+ : plain !== undefined
656
+ ? named(plain)
657
+ : undefined;
658
+ return declared.flatMap((x): TypedefName[] => {
659
+ if (x.derivations.length === 0) {
660
+ return [{ name: x.name, type: named(x.name), names: 'body' }];
661
+ }
662
+ if (!x.derivations.every(isPointer)) {
663
+ return [];
664
+ }
665
+ return [
666
+ pointee === undefined
667
+ ? { name: x.name, type: named(x.name), names: 'unspelled pointer' }
668
+ : { name: x.name, type: { ...pointee, derivations: x.derivations }, names: 'other' },
669
+ ];
670
+ });
671
+ }
672
+ return declared
673
+ .filter((x) => x.derivations.every(isPointer) || functionPointer(x.derivations) !== undefined)
674
+ .map((x) => ({ name: x.name, type: declaredType(s, x.derivations), names: 'other' }));
675
+ }
676
+
677
+ /** The attributes a declaration writes on what it declares: among its specifiers and on its
678
+ * declarators. One inside a parameter list is the parameter's, and compiled, agbcc lets it reach no
679
+ * type outside it: it refuses `aligned` there and ignores `packed`. */
680
+ function ownAttributes(d: Declaration): Attribute[] {
681
+ return [...d.specifiers.attributes, ...d.declarators.flatMap((x) => x.attributes)];
682
+ }
683
+
684
+ /** The attributes a declaration writes on what it declares and on the parameters of each function it
685
+ * declares, each parameter's own. One inside a parameter's own parameter list is that inner
686
+ * parameter's, and no parameter of the function. */
687
+ function parameterAttributes(d: Declaration): Attribute[] {
688
+ return [
689
+ ...ownAttributes(d),
690
+ ...d.declarators.flatMap((x) => {
691
+ const fn = x.derivations[0];
692
+ return fn?.kind === 'function'
693
+ ? (fn.params ?? []).flatMap((p) => [...p.specifiers.attributes, ...p.declarator.attributes])
694
+ : [];
695
+ }),
696
+ ];
697
+ }
698
+
699
+ /** Whether an attribute in this declaration can move the layout of the body it defines. One in the
700
+ * specifier — ahead of the body or straight after it — lays that body out (c-parse.in:1464-1503).
701
+ * One elsewhere on a variable reaches that variable alone; one elsewhere in a typedef is read as
702
+ * moving it too, whatever it says (`realigns` has what it may do). */
703
+ function laysOut(d: Declaration, attributes: readonly Attribute[]): boolean {
704
+ return d.specifiers.typedef
705
+ ? attributes.length > 0
706
+ : d.specifiers.attributes.some((a) => a.site === 'before-body' || a.site === 'after-body');
707
+ }
708
+
709
+ /** Whether a typedef in this declaration may change, after its layout, a type no body here stands
710
+ * for. An attribute outside a body's specifier is applied to the type the typedef names (c-common.c:
711
+ * 392-399, 444-446, 623-624), whatever spelling it takes (`aligned`, `__aligned__`, :345-351): a
712
+ * struct tag, a pointer or a scalar such as `unsigned int` is re-aligned, which lays anything that
713
+ * holds it out anew, and an enum whose body comes later is packed. Compiled, `typedef struct R *RP
714
+ * __attribute__((aligned(8)))` makes `struct { struct R *p; }` 8 bytes, and `typedef enum E EA
715
+ * __attribute__((packed))` ahead of `enum E {…}` makes it 1. mwcc's `__declspec` is such an
716
+ * attribute too: compiled with mwcc 4.3, `typedef __declspec(align(8)) int AI` lays `struct { char c;
717
+ * AI a; }` out in 16 bytes. Which type that is, is not worked out here. Where the declaration's plain
718
+ * declarators name its own body, `laysOut` leaves that body unread instead — unless one attribute is
719
+ * `mode`, which hands every attribute after it a shared scalar type in place of the body
720
+ * (c-common.c:563, 996-1000): compiled, `typedef struct R {…} A __attribute__((mode(SI),
721
+ * aligned(8)))` makes every `int` 8-aligned. */
722
+ function realigns(d: Declaration, attributes: readonly Attribute[]): boolean {
723
+ if (!d.specifiers.typedef || attributes.length === 0) {
724
+ return false;
725
+ }
726
+ if (attributes.some((a) => MODE.test(a.text))) {
727
+ return true;
728
+ }
729
+ const { type } = d.specifiers;
730
+ const ownBody = type.kind === 'tag' && type.keyword !== 'class' && type.body !== undefined;
731
+ return !(ownBody && d.declarators.length > 0 && d.declarators.every((x) => x.derivations.length === 0));
732
+ }
733
+
734
+ /** Whether agbcc may lay an enum with this body out wider than an int. It does for a value past 32
735
+ * bits (c-decl.c:6116-6123): a literal past 32 bits or of type `long long`, or an enumerator of an
736
+ * enum already that wide (`wide`). Compiled, `enum {B0, B1 = 0x100000000LL}` is 8 bytes, and so is
737
+ * `enum {C0, C1 = B1}`, where `enum {N0 = -1, N1 = 0xFFFFFFFF}` is 4. */
738
+ function enumMayWiden(tokens: Tokens, body: Range, wide: ReadonlySet<string>): boolean {
739
+ for (let k = body.from; k < body.to; k++) {
740
+ const kind = tokens.kind(k);
741
+ const literal = kind === 'number' ? integerLiteral(tokens.text(k)) : undefined;
742
+ if (literal !== undefined && (literal.value > 0xffffffffn || /l.*l/i.test(literal.suffix))) {
743
+ return true;
744
+ }
745
+ if (
746
+ kind === 'identifier' &&
747
+ ((wide.size > 0 && wide.has(tokens.text(k))) || (tokens.is(k, 'long') && tokens.is(k + 1, 'long')))
748
+ ) {
749
+ return true;
750
+ }
751
+ }
752
+ return false;
753
+ }
754
+
755
+ /** The names an enum body declares: the word that starts it, and each word after a comma. */
756
+ function enumerators(tokens: Tokens, body: Range): string[] {
757
+ const out: string[] = [];
758
+ for (let k = body.from; k < body.to; k++) {
759
+ if ((k === body.from || tokens.is(k - 1, ',')) && tokens.kind(k) === 'identifier') {
760
+ out.push(tokens.text(k));
761
+ }
762
+ }
763
+ return out;
764
+ }
765
+
766
+ /** `t` with the typedef names it is built on resolved, each to the same type, until `declaredWidth`
767
+ * can size it: a pointer resolves what it points at, and a typedef name is replaced by the type it
768
+ * stands for. An array, a function, a reference or a pointer to member is kept as declared. */
769
+ function resolve(t: DeclaredType, typedefs: ReadonlyMap<string, DeclaredType>, tokens: Tokens): DeclaredType {
770
+ const [first, ...rest] = t.derivations;
771
+ if (first !== undefined) {
772
+ if (!isPointer(first)) {
773
+ return t;
774
+ }
775
+ const pointee = resolve({ ...t, derivations: rest }, typedefs, tokens);
776
+ return { ...pointee, derivations: [first, ...pointee.derivations] };
777
+ }
778
+ let cur = t;
779
+ for (let hops = 0; hops < 16 && declaredWidth(spellType(cur, tokens)) === undefined; hops++) {
780
+ const name = nameOf(cur);
781
+ const next = name === undefined ? undefined : typedefs.get(name);
782
+ if (next === undefined || nameOf(next) === name) {
783
+ break;
784
+ }
785
+ cur = qualified(next, cur.qualifiers);
786
+ }
787
+ return cur;
788
+ }
789
+
790
+ /** `t` qualified by `q`, as a typedef name's qualifiers qualify the type it stands for: the derivation
791
+ * nearest the name, or the base where there is none. An array's qualifier is its element's, and a
792
+ * function or a reference takes none. */
793
+ function qualified(t: DeclaredType, q: readonly Qualifier[]): DeclaredType {
794
+ const union = (own: readonly Qualifier[]): Qualifier[] => [...q, ...own.filter((x) => !q.includes(x))];
795
+ const [first, ...rest] = t.derivations;
796
+ if (q.length === 0 || first?.kind === 'function' || first?.kind === 'reference') {
797
+ return t;
798
+ }
799
+ if (first === undefined) {
800
+ return { ...t, qualifiers: union(t.qualifiers) };
801
+ }
802
+ if (first.kind === 'pointer') {
803
+ return { ...t, derivations: [{ ...first, qualifiers: union(first.qualifiers) }, ...rest] };
804
+ }
805
+ const element = qualified({ ...t, derivations: rest }, q);
806
+ return { ...element, derivations: [first, ...element.derivations] };
807
+ }
808
+
809
+ /** A pointer to `t`, or `t` where it is a pointer already. */
810
+ const pointerTo = (t: DeclaredType): DeclaredType =>
811
+ t.derivations.length > 0 && t.derivations.every(isPointer)
812
+ ? t
813
+ : { ...t, derivations: [{ kind: 'pointer', qualifiers: [] }, ...t.derivations] };
814
+
815
+ /** The layout of each struct or union a type names, read from its body when asked for.
816
+ * Memoised per type and depth: a body whose members point at bodies is walked once per depth, not
817
+ * once per path to it — each pointer member lays its pointee out, and K of them to depth 8 is K^8
818
+ * walks. */
819
+ function layoutReader(ctx: ParsedContext, table: TypeTable): LayoutOf {
820
+ const laidOut = new Map<string, AggregateLayout | undefined>();
821
+ const layoutOf: LayoutOf = (t, depth) => {
822
+ const keyword = t.type.kind === 'tag' ? t.type.keyword : undefined;
823
+ const tag = keyword === 'struct' || keyword === 'union' ? tagOf(t) : undefined;
824
+ const name = tag ?? nameOf(t);
825
+ if (name === undefined) {
826
+ return undefined;
827
+ }
828
+ const key = `${depth} ${name}`;
829
+ if (laidOut.has(key)) {
830
+ return laidOut.get(key);
831
+ }
832
+ const kind = tag !== undefined ? (keyword as AggregateLayout['kind']) : table.named.get(name)?.kind;
833
+ let layout: AggregateLayout | undefined;
834
+ if (kind !== undefined) {
835
+ const body = tag !== undefined ? table.tagged.get(tag) : table.named.get(name)?.body;
836
+ const members = body === undefined || table.realigned ? undefined : readMembers(body, depth);
837
+ layout = members === undefined ? { kind } : { kind, members };
838
+ }
839
+ laidOut.set(key, layout);
840
+ return layout;
841
+ };
842
+ // A body's members, or undefined when one of them is a type this cannot lay out — a project
843
+ // typedef that resolves to nothing sized, a nested aggregate with no body here, a flexible extent
844
+ // or one that is not a constant expression — or carries an attribute of its own, which may place it
845
+ // anywhere. Bounded in depth, since a body may name its own tag.
846
+ const readMembers = (body: Range, depth: number): AggregateMember[] | undefined => {
847
+ const declarations = depth > 8 ? undefined : memberDeclarations(ctx, body);
848
+ if (declarations === undefined) {
849
+ return undefined;
850
+ }
851
+ const out: AggregateMember[] = [];
852
+ for (const m of declarations) {
853
+ const read = members(m, depth);
854
+ if (read === undefined) {
855
+ return undefined;
856
+ }
857
+ out.push(...read);
858
+ }
859
+ return out;
860
+ };
861
+ // One member declaration's members: a static, a typedef or a declaration of no name is none this
862
+ // places.
863
+ const members = (m: Declaration, depth: number): AggregateMember[] | undefined => {
864
+ const s = m.specifiers;
865
+ if (s.typedef || s.storage.length > 0 || m.declarators.length === 0 || ownAttributes(m).length > 0) {
866
+ return undefined;
867
+ }
868
+ let inline: AggregateLayout | undefined;
869
+ if (s.type.kind === 'tag' && s.type.body !== undefined) {
870
+ const kind = s.type.keyword;
871
+ if (kind !== 'struct' && kind !== 'union') {
872
+ return undefined;
873
+ }
874
+ const read = readMembers(s.type.body, depth + 1);
875
+ if (read === undefined) {
876
+ return undefined;
877
+ }
878
+ inline = { kind, members: read };
879
+ }
880
+ const out: AggregateMember[] = [];
881
+ for (const d of m.declarators) {
882
+ const shape = memberShape(d, ctx.tokens);
883
+ const type =
884
+ shape === undefined ? undefined : (inline ?? memberType(declaredType(s, shape.returns), shape.pointer, depth));
885
+ if (shape === undefined || type === undefined || (typeof type !== 'string' && shape.bits !== undefined)) {
886
+ return undefined;
887
+ }
888
+ out.push({
889
+ name: shape.name,
890
+ type: shape.pointer && typeof type !== 'string' ? 'void *' : type,
891
+ ...(shape.dims ? { dims: shape.dims } : {}),
892
+ ...(shape.bits !== undefined ? { bits: shape.bits } : {}),
893
+ });
894
+ }
895
+ return out;
896
+ };
897
+ // The type of a member of `base`, or of a pointer to one (`pointer`), or undefined where it cannot
898
+ // be laid out.
899
+ const memberType = (base: DeclaredType, pointer: boolean, depth: number): ParamType | AggregateLayout | undefined => {
900
+ const t = resolve(base, table.typedefs, ctx.tokens);
901
+ const spelled = spellType(t, ctx.tokens);
902
+ const spell = (x: DeclaredType): string => spellType(pointer ? pointerTo(x) : x, ctx.tokens);
903
+ if (declaredWidth(spelled) !== undefined || spelled === 'float' || spelled === 'double') {
904
+ return spell(t);
905
+ }
906
+ if (table.unspelledPointers.has(spelled)) {
907
+ return 'void *';
908
+ }
909
+ if (table.unsizedEnums.has(spelled)) {
910
+ return undefined;
911
+ }
912
+ // an enum, which the target sizes whatever it is called: spelled `enum` and its name
913
+ if (table.enums.has(spelled)) {
914
+ return spell(named(`enum ${spelled}`));
915
+ }
916
+ if (t.qualifiers.length === 0 && t.type.kind === 'tag' && t.type.keyword === 'enum' && tagOf(t) !== undefined) {
917
+ return spell(t);
918
+ }
919
+ const nested = layoutOf(t, depth + 1);
920
+ if (nested?.members !== undefined) {
921
+ return nested;
922
+ }
923
+ // a pointer to it is still a word; anything else of it cannot be laid out
924
+ return pointer ? spell(t) : undefined;
925
+ };
926
+ return layoutOf;
927
+ }
928
+
929
+ /** One member declarator: its name, whether it declares a pointer, its extents, its bit width, and
930
+ * the derivations of what a function pointer returns, which belong to the type it points at. Its
931
+ * extents are arrays of what it holds, and what it holds is the base, a pointer to it or a function
932
+ * pointer. */
933
+ function memberShape(
934
+ d: Declarator,
935
+ tokens: Tokens,
936
+ ): { name: string; pointer: boolean; dims?: number[]; bits?: number; returns: readonly Derivation[] } | undefined {
937
+ if (d.bits !== undefined) {
938
+ const bits = constantValue(tokens, d.bits);
939
+ return d.derivations.length > 0 || bits === undefined
940
+ ? undefined
941
+ : { name: d.name ?? '', pointer: false, bits, returns: [] };
942
+ }
943
+ if (d.name === undefined || !WORD.test(d.name)) {
944
+ return undefined;
945
+ }
946
+ const returns = functionPointer(d.derivations);
947
+ if (returns !== undefined) {
948
+ return { name: d.name, pointer: true, returns };
949
+ }
950
+ const dims: number[] = [];
951
+ let k = 0;
952
+ for (let x = d.derivations[k]; x?.kind === 'array'; x = d.derivations[++k]) {
953
+ const n = x.size === undefined ? undefined : constantValue(tokens, x.size);
954
+ if (n === undefined || n === 0) {
955
+ return undefined;
956
+ }
957
+ dims.push(n);
958
+ }
959
+ const stars = d.derivations.slice(k);
960
+ if (!stars.every(isPointer)) {
961
+ return undefined;
962
+ }
963
+ return { name: d.name, pointer: stars.length > 0, ...(dims.length > 0 ? { dims } : {}), returns: [] };
964
+ }
965
+
966
+ /** A declaration's reading: the entry as written, the `key` two declarations are compared by, and
967
+ * the `lists` of every declaration it stands for, which say whether a declaration that states no
968
+ * parameters can be one with a declaration that does (`composable`). */
969
+ type Read = { proto: FnProto; key: FnProto; lists: readonly ParameterList[] };
970
+
971
+ /** What a declaration's parentheses say of its parameters, as `composable` needs it: `()` in C,
972
+ * which states none; a list this could not read, with how many FIXED parameters its top-level commas
973
+ * separate and whether it ends in `...`; or a list it read, with whether every parameter keeps its
974
+ * type under the default argument promotions (C99 6.5.2.2p6) — undefined where one may not. */
975
+ type ParameterList =
976
+ | { kind: 'unstated' }
977
+ | { kind: 'unread'; fixed: number; variadic: boolean }
978
+ | { kind: 'read'; fixed: number; variadic: boolean; promotes: boolean };
979
+
980
+ /** Every function the declarations name by a plain identifier, keyed by it. A declaration that
981
+ * spells no type is a constructor's, a destructor's or a conversion operator's; one that defines a
982
+ * body, or spells a `class`, declares no C function. */
983
+ function functionPrototypes(ctx: ParsedContext, table: TypeTable, layoutOf: LayoutOf): Prototypes {
984
+ const found = new Map<string, Read | null>();
985
+ const written = typedefTypes(ctx);
986
+ // a function one of whose declarations retypes a parameter with `mode`, which gives it the type
987
+ // the mode names in place of the one spelled (c-common.c:563): compiled, `int x
988
+ // __attribute__((mode(DI)))` takes a register pair. Its parameters are not read, in any
989
+ // declaration of it; its return is.
990
+ const unreadParams = new Set<string>();
991
+ // Two declarations of one function name one type, and the entry is that type's: the same reading
992
+ // twice, or — in C — a declaration whose parameters are not read (`()`, which states none, or a
993
+ // list this cannot read, `const char *restrict fmt`) beside one whose are, where the return reads
994
+ // alike and the two are COMPATIBLE (`composable`). The C compiler takes the composite of the two
995
+ // (C99 6.2.7p3), which is the one that states the parameters, so the entry is that one: dropping
996
+ // the name would drop the refusals its parameters carry with it, and send the call to the arity
997
+ // guess. Anything else is a conflict (C++ overloads, or a header this misreads), and the name is
998
+ // dropped.
999
+ //
1000
+ // A `()` is read with a list only where it PRECEDES it: one that follows a list is a conflict,
1001
+ // whatever the types (below). And `()` beside a list that is not compatible with it is no
1002
+ // composite: agbcc, gcc2.7.2kmc and
1003
+ // gcc2.7.2 compile `void f(char); void f();` without a word, the later `()` wins, and a call is
1004
+ // compiled UNPROTOTYPED — any arity, no conversion. Read as `f(char)`, `f(1, 2)` would lift as
1005
+ // `f(1)`, and `f(5)` under `f(short, char)` as `f(5, a1)`.
1006
+ const unstated = (p: FnProto) => p.params === undefined && p.variadicParams === undefined;
1007
+ const merged = (kept: Read, a: Read, b: Read): Read => ({ ...kept, lists: [...a.lists, ...b.lists] });
1008
+ const composite = (a: Read, b: Read): Read | null => {
1009
+ if (b.lists.some((l) => l.kind === 'unstated') && a.lists.some((l) => l.kind === 'read')) {
1010
+ // a `()` AFTER a parameter list: mwcc (2.4.2, 2.4.7, 2.3.3) lets it replace the prototype
1011
+ // outright, even where every parameter is a word — `void f(int); void f();` compiles `f(1, 2)`
1012
+ // as two arguments, `f(1.5)` in f1, and `f(1)` under `void f(double)` in r3 — so the list is
1013
+ // no fact about the call. Read as the list, `f(1, 2)` lifts as `f(1)`. gcc and IDO reject what
1014
+ // that call would need, so nothing is lost on them by a conflict.
1015
+ return null;
1016
+ }
1017
+ if (same(a.key, b.key)) {
1018
+ return merged(b, a, b);
1019
+ }
1020
+ // …and where the list follows a `()` that itself followed a list this could not read, the `()`
1021
+ // still replaced it: the two keep their return (`same`, above), and no later list joins them
1022
+ const all = [...a.lists, ...b.lists];
1023
+ if (
1024
+ ctx.language !== 'c' ||
1025
+ all.some((l, i) => l.kind === 'unstated' && all.slice(0, i).some((m) => m.kind !== 'unstated'))
1026
+ ) {
1027
+ return null;
1028
+ }
1029
+ if (unstated(a.key) && same(a.key, returnOnly(b.key)) && composable(a.lists, b.lists)) {
1030
+ return merged(b, a, b);
1031
+ }
1032
+ return unstated(b.key) && same(b.key, returnOnly(a.key)) && composable(b.lists, a.lists) ? merged(a, a, b) : null;
1033
+ };
1034
+ const returnOnly = ({
1035
+ params: _unread,
1036
+ wordParams: _words,
1037
+ byValueParams: _copies,
1038
+ byValueLayouts: _layouts,
1039
+ variadicParams: _fixed,
1040
+ ...rest
1041
+ }: FnProto): FnProto => rest;
1042
+ for (const d of ctx.declarations) {
1043
+ const s = d.specifiers;
1044
+ if (
1045
+ s.typedef ||
1046
+ (s.spelling === '' && s.qualifiers.length === 0) ||
1047
+ (s.type.kind === 'tag' && (s.type.body !== undefined || s.type.keyword === 'class'))
1048
+ ) {
1049
+ continue;
1050
+ }
1051
+ const retyped = parameterAttributes(d).some((a) => MODE.test(a.text));
1052
+ for (const x of d.declarators) {
1053
+ const [fn, ...returns] = x.derivations;
1054
+ if (
1055
+ fn?.kind !== 'function' ||
1056
+ x.name === undefined ||
1057
+ !WORD.test(x.name) ||
1058
+ !returns.every((r) => r.kind === 'pointer' || r.kind === 'reference')
1059
+ ) {
1060
+ continue;
1061
+ }
1062
+ if (retyped) {
1063
+ unreadParams.add(x.name);
1064
+ }
1065
+ const reading = (qualifiers: 'kept' | 'dropped') =>
1066
+ readSignature(
1067
+ declaredType(s, returns),
1068
+ fn,
1069
+ ctx,
1070
+ table.typedefs,
1071
+ (t) => layoutOf(t, 0),
1072
+ (t) => passingOf(t, table, written),
1073
+ qualifiers,
1074
+ );
1075
+ const read = reading('kept');
1076
+ // what the declaration says of the function's TYPE, which is what two declarations of it are
1077
+ // compared by: a parameter's top-level qualifiers are not part of it (C99 6.7.5.3p15), so
1078
+ // `void takeW(const struct W w);` and `void takeW(struct W w);` declare one function. Read
1079
+ // as a conflict, they would drop the name, and with it every refusal its entry carries
1080
+ // (`byValueRefusal`, `variadicRefusal`): the call would take the arity guess they exist to
1081
+ // refuse. The entry kept is the later declaration's, spelled as written.
1082
+ const typed = reading('dropped');
1083
+ const [proto, key] = unreadParams.has(x.name) ? [returnOnly(read), returnOnly(typed)] : [read, typed];
1084
+ const lists = [parameterList(fn, ctx, table.typedefs, (t) => passingOf(t, table, written))];
1085
+ const prior = found.get(x.name);
1086
+ const had =
1087
+ prior && unreadParams.has(x.name)
1088
+ ? { ...prior, proto: returnOnly(prior.proto), key: returnOnly(prior.key) }
1089
+ : prior;
1090
+ found.set(
1091
+ x.name,
1092
+ had === undefined ? { proto, key, lists } : had === null ? null : composite(had, { proto, key, lists }),
1093
+ );
1094
+ }
1095
+ }
1096
+ const out: Prototypes = {};
1097
+ for (const [name, read] of found) {
1098
+ const p = read?.proto ?? null;
1099
+ const valid = p === null ? undefined : admissible(name, p);
1100
+ if (valid !== undefined) {
1101
+ out[name] = valid;
1102
+ }
1103
+ }
1104
+ return out;
1105
+ }
1106
+
1107
+ /** Whether declarations that state no parameters (`unstated`, each `()` or a list this could not
1108
+ * read) are compatible with ones that state them (`stated`), as two declarations of one C function
1109
+ * must be for their composite to be the stated one (C99 6.7.5.3p15). `()` is compatible with a
1110
+ * list that is not variadic and whose every parameter keeps its type under the default argument
1111
+ * promotions: not `char`, `short`, `float`, `_Bool`, nor a typedef or an enum that may be one — an
1112
+ * enum's type is the compiler's to pick, and it may be narrow (`-fshort-enums`, mwcc's `-enum
1113
+ * min`). A name the context never resolves is read by its spelling as `declaredWidth` reads it
1114
+ * (`s32` is a word, `u8` a byte) and is otherwise taken to promote. An unread list is compatible with a list of as many fixed parameters, variadic alike. */
1115
+ function composable(unstated: readonly ParameterList[], stated: readonly ParameterList[]): boolean {
1116
+ const lists = stated.filter((l) => l.kind === 'read');
1117
+ return (
1118
+ lists.length > 0 &&
1119
+ unstated.every((u) =>
1120
+ lists.every((l) =>
1121
+ u.kind === 'unstated'
1122
+ ? !l.variadic && !l.promotes
1123
+ : u.kind === 'unread' && u.fixed === l.fixed && u.variadic === l.variadic,
1124
+ ),
1125
+ )
1126
+ );
1127
+ }
1128
+
1129
+ /** Whether `fn`'s parentheses are C's empty list: `(void)`, or one parameter whose type is `void`
1130
+ * through a typedef (`typedef void V; int k(V);`, C99 6.7.5.3p10). Read as a list of one
1131
+ * parameter spelled `void`, the second would have the call pass a register nothing set up for it
1132
+ * (`k()` as `k(a0)`). */
1133
+ function isVoidList(
1134
+ fn: Extract<Derivation, { kind: 'function' }>,
1135
+ tokens: Tokens,
1136
+ typedefs: ReadonlyMap<string, DeclaredType>,
1137
+ ): boolean {
1138
+ if (fn.list.to === fn.list.from + 1 && tokens.is(fn.list.from, 'void')) {
1139
+ return true;
1140
+ }
1141
+ const only = fn.params?.length === 1 && !fn.variadic ? fn.params[0] : undefined;
1142
+ return (
1143
+ only !== undefined &&
1144
+ spellType(resolve(declaredType(only.specifiers, only.declarator.derivations), typedefs, tokens), tokens) === 'void'
1145
+ );
1146
+ }
1147
+
1148
+ /** What `fn`'s parentheses say of its parameters (`ParameterList`). */
1149
+ function parameterList(
1150
+ fn: Extract<Derivation, { kind: 'function' }>,
1151
+ { tokens, language }: ParsedContext,
1152
+ typedefs: ReadonlyMap<string, DeclaredType>,
1153
+ passing: (t: DeclaredType) => Passing,
1154
+ ): ParameterList {
1155
+ const { params, list } = fn;
1156
+ if (params === undefined) {
1157
+ // the top-level commas, and a trailing `...`
1158
+ let items = list.to > list.from ? 1 : 0;
1159
+ let variadic = false;
1160
+ for (let i = list.from; i < list.to; i++) {
1161
+ const close = tokens.match(i);
1162
+ if (close > i) {
1163
+ i = close;
1164
+ } else if (tokens.is(i, ',')) {
1165
+ items++;
1166
+ } else if (tokens.is(i, '...')) {
1167
+ variadic = true;
1168
+ }
1169
+ }
1170
+ return { kind: 'unread', fixed: variadic ? items - 1 : items, variadic };
1171
+ }
1172
+ if (params.length === 0 && !fn.variadic && language === 'c') {
1173
+ return { kind: 'unstated' };
1174
+ }
1175
+ if (isVoidList(fn, tokens, typedefs)) {
1176
+ return { kind: 'read', fixed: 0, variadic: false, promotes: false };
1177
+ }
1178
+ const promotes = params.some((p) => {
1179
+ const declared = asParameter(declaredType(p.specifiers, p.declarator.derivations));
1180
+ if (passing(declared) === 'by-value') {
1181
+ // a struct, union or class, which no promotion converts
1182
+ return false;
1183
+ }
1184
+ const t = resolve(declared, typedefs, tokens);
1185
+ if (t.derivations.length > 0) {
1186
+ // a pointer, or an array or a function the parameter adjusts to one
1187
+ return false;
1188
+ }
1189
+ if (t.type.kind === 'tag') {
1190
+ return t.type.keyword === 'enum';
1191
+ }
1192
+ const spelled = spellType(withoutTopQualifiers(t), tokens);
1193
+ if (spelled === 'double' || spelled === 'long double') {
1194
+ return false;
1195
+ }
1196
+ const width = declaredWidth(spelled);
1197
+ return width === undefined || width < 32;
1198
+ });
1199
+ return { kind: 'read', fixed: params.length, variadic: fn.variadic, promotes };
1200
+ }
1201
+
1202
+ /** The entry as a table `validatePrototypes` accepts, the check every `--proto` table passes, so a
1203
+ * context's prototypes can travel as one: a `returns` it refuses is dropped — a return wider than a
1204
+ * register with a parameter list that cannot be printed — and the entry with it if that is not
1205
+ * enough. */
1206
+ function admissible(name: string, p: FnProto): FnProto | undefined {
1207
+ if (validatePrototypes({ [name]: p }).length === 0) {
1208
+ return p;
1209
+ }
1210
+ const { returns: _dropped, ...rest } = p;
1211
+ return validatePrototypes({ [name]: rest }).length === 0 ? rest : undefined;
1212
+ }
1213
+
1214
+ function readSignature(
1215
+ ret: DeclaredType,
1216
+ fn: Extract<Derivation, { kind: 'function' }>,
1217
+ { tokens, language }: ParsedContext,
1218
+ typedefs: ReadonlyMap<string, DeclaredType>,
1219
+ layoutOf: (t: DeclaredType) => AggregateLayout | undefined,
1220
+ passing: (t: DeclaredType) => Passing,
1221
+ qualifiers: 'kept' | 'dropped' = 'kept',
1222
+ ): FnProto {
1223
+ const proto: FnProto = {};
1224
+ const resolved = resolve(ret, typedefs, tokens);
1225
+ const r = spellType(resolved, tokens);
1226
+ // A struct or union returned by value is kept, spelled as the header spells it: it is the fact
1227
+ // that moves every argument one register up on a target that returns it through a hidden pointer.
1228
+ // A spelling that names one and reads as no type (`struct Blob64 EWRAM_FN`, a macro this never
1229
+ // expands) still returns one, and says nothing else about it.
1230
+ //
1231
+ // A floating return is kept as its keyword: where the FPU carries it, it is what refuses a call
1232
+ // that reads the general return register (frontend/call-plan.ts).
1233
+ //
1234
+ // A spelling that reads as no type and names no aggregate states nothing, and the parameters are
1235
+ // kept: in a vendored context that is an enum typedef, whose arguments sit where they are
1236
+ // declared. KNOWN GAP: a typedef this never saw (`Blob64T`, defined behind an
1237
+ // `#include` that the lexer skips) may be a struct returned through memory, whose hidden pointer
1238
+ // is then read as argument 0; a symbol map that sizes the return closes it
1239
+ // (`prototypesFromSymbols`).
1240
+ const layout = declaredWidth(r) === undefined ? layoutOf(resolved) : undefined;
1241
+ const keyword = resolved.type.kind === 'tag' ? resolved.type.keyword : undefined;
1242
+ if (r === 'void') {
1243
+ proto.returnsVoid = true;
1244
+ } else if (declaredWidth(r) !== undefined || isFloatingSpelling(r)) {
1245
+ proto.returns = r;
1246
+ } else if (layout !== undefined) {
1247
+ proto.returns = r;
1248
+ proto.returnLayout = layout;
1249
+ } else if (keyword !== undefined && keyword !== 'enum' && resolved.derivations.length === 0) {
1250
+ proto.returnLayout = { kind: keyword === 'union' ? 'union' : 'struct' };
1251
+ }
1252
+ const { params } = fn;
1253
+ if (params === undefined) {
1254
+ return proto;
1255
+ }
1256
+ if (params.length === 0 && !fn.variadic) {
1257
+ if (language === 'c++') {
1258
+ proto.params = [];
1259
+ }
1260
+ return proto;
1261
+ }
1262
+ if (isVoidList(fn, tokens, typedefs)) {
1263
+ proto.params = [];
1264
+ return proto;
1265
+ }
1266
+ // Each parameter's type, its name taken off: an array or a function nearest the name is a
1267
+ // pointer, and the typedef names it is built on resolve as a return's do. A spelling that sizes to
1268
+ // nothing and is no floating type is read through those names for what it positively is: a type
1269
+ // that still travels in one register — an enum, or an array or function type behind a typedef
1270
+ // name, which the parameter adjusts to a pointer (`GXTexFmt`, `Mtx`, `__va_list`) — listed in
1271
+ // `FnProto.wordParams`; or a struct, union or class passed BY VALUE, which mwcc passes as the
1272
+ // address of a copy in the caller's frame — listed in `FnProto.byValueParams`. A name the context
1273
+ // never resolves is in neither, and every reader leaves it unsized.
1274
+ //
1275
+ // A VARIADIC declaration's are read alike and kept as its fixed parameters (`variadicParams`): its
1276
+ // arity is the call's, so they are no `params`, and no layout is read for a struct among them,
1277
+ // whose call refuses (`variadicRefusal`).
1278
+ const types = params.map((p) => asParameter(declaredType(p.specifiers, p.declarator.derivations)));
1279
+ const spelled = types.map((t) => {
1280
+ const r = resolve(t, typedefs, tokens);
1281
+ return spellType(qualifiers === 'kept' ? r : withoutTopQualifiers(r), tokens);
1282
+ });
1283
+ if (fn.variadic) {
1284
+ proto.variadicParams = spelled;
1285
+ } else {
1286
+ proto.params = spelled;
1287
+ }
1288
+ const unsized = (k: number) => declaredWidth(spelled[k]) === undefined && !isFloatingSpelling(spelled[k]);
1289
+ const listed = (as: Passing) => types.flatMap((t, k) => (unsized(k) && passing(t) === as ? [k] : []));
1290
+ const words = listed('word');
1291
+ const copies = listed('by-value');
1292
+ if (words.length > 0) {
1293
+ proto.wordParams = words;
1294
+ }
1295
+ if (copies.length > 0) {
1296
+ proto.byValueParams = copies;
1297
+ }
1298
+ if (copies.length > 0 && !fn.variadic) {
1299
+ // …and the members of each one the context defines, which size it (`FnProto.byValueLayouts`)
1300
+ const layouts = Object.fromEntries(
1301
+ copies.flatMap((k) => {
1302
+ const l = layoutOf(resolve(types[k], typedefs, tokens));
1303
+ return l === undefined ? [] : [[k, l] as const];
1304
+ }),
1305
+ );
1306
+ if (Object.keys(layouts).length > 0) {
1307
+ proto.byValueLayouts = layouts;
1308
+ }
1309
+ }
1310
+ return proto;
1311
+ }
1312
+
1313
+ /** `t` less its top-level qualifiers: the derivation nearest the name's (`char *const`), or the
1314
+ * base's where there is none (`const struct W`). A pointer's target keeps its own (`const char *`). */
1315
+ function withoutTopQualifiers(t: DeclaredType): DeclaredType {
1316
+ const [first, ...rest] = t.derivations;
1317
+ if (first === undefined) {
1318
+ return { ...t, qualifiers: [] };
1319
+ }
1320
+ return first.kind === 'pointer' ? { ...t, derivations: [{ ...first, qualifiers: [] }, ...rest] } : t;
1321
+ }
1322
+
1323
+ /** How a parameter of type `t` is passed, read through the typedef names it is built on: in ONE
1324
+ * WORD — an enum the target sizes; a pointer; an array or a function, which a parameter adjusts to
1325
+ * a pointer — or BY VALUE, a struct, union or class (a tag, or a name bound to a body). Undefined
1326
+ * where nothing here says: a name this never saw, or an enum the target does not size
1327
+ * (`unsizedEnums`: `packed`, or a value an int cannot hold), which may take two words. */
1328
+ type Passing = 'word' | 'by-value' | undefined;
1329
+
1330
+ function passingOf(t: DeclaredType, table: TypeTable, written: ReadonlyMap<string, DeclaredType>): Passing {
1331
+ let cur = t;
1332
+ for (let hops = 0; hops < 16; hops++) {
1333
+ const [first] = cur.derivations;
1334
+ if (first !== undefined) {
1335
+ return first.kind === 'array' || first.kind === 'function' || first.kind === 'reference' || isPointer(first)
1336
+ ? 'word'
1337
+ : undefined;
1338
+ }
1339
+ if (cur.type.kind === 'tag') {
1340
+ const { keyword, tag } = cur.type;
1341
+ if (keyword !== 'enum') {
1342
+ return 'by-value';
1343
+ }
1344
+ return tag !== undefined && table.unsizedEnums.has(`enum ${tag}`) ? undefined : 'word';
1345
+ }
1346
+ const name = nameOf(cur);
1347
+ if (name === undefined) {
1348
+ return undefined;
1349
+ }
1350
+ if (table.named.has(name)) {
1351
+ return 'by-value';
1352
+ }
1353
+ if (table.enums.has(name)) {
1354
+ return table.unsizedEnums.has(name) ? undefined : 'word';
1355
+ }
1356
+ const next = table.typedefs.get(name) ?? written.get(name);
1357
+ if (next === undefined || nameOf(next) === name) {
1358
+ return undefined;
1359
+ }
1360
+ cur = next;
1361
+ }
1362
+ return undefined;
1363
+ }
1364
+
1365
+ /** The prototypes a lift of `own` reads when a context is in hand: `stated` — a caller's own
1366
+ * prototypes, which win per name — over the context's declarations less those the symbol map
1367
+ * states better (`contextPrototypesUnder`). `own`'s declaration is left out of the table, as the
1368
+ * map's is (`asIfUndecompiled`): a header's signature for the function being decompiled was written
1369
+ * with no compiler checking it against a definition while the function was still `INCLUDE_ASM`, so
1370
+ * it is not stated, and only what the caller states about it bounds the lift HARD.
1371
+ *
1372
+ * IT BOUNDS THE ARGUMENT REGISTERS SOFTLY INSTEAD ({@link ownContextEntry}, pipeline.ts
1373
+ * `liftWithinContextBound`). A hard bound puts the risk on the very functions a user decompiles:
1374
+ * an entry too short turns a real argument into `uninit_<reg>` — wrong C, where every other error
1375
+ * of the bound only keeps a parameter — and sbk2 has 16 `void f(void);` task callbacks whose
1376
+ * bodies read a0. The soft bound drops only the registers past the entry that the finished lift
1377
+ * never reads; one it reads (or one the machine homes to a slot, as IDO -g3 does) stays a
1378
+ * parameter, with an `[own-arity]` note naming the declaration and the register. (On kleod, whose
1379
+ * every header its compiler checked, the entry stated as a hard bound takes the lifts with more
1380
+ * parameters than their header from 52 of 377 to 1 of 373.) Only a typed list the bound can lay
1381
+ * out is handed over; every other shape abstains. A struct the entry returns through memory is
1382
+ * read on the same soft footing (pipeline.ts `returnOwnStruct`): lifted as that struct where the
1383
+ * machine proves the hidden pointer is only its storage, and kept as the pointer function with an
1384
+ * `[own-return]` note where it does not.
1385
+ *
1386
+ * The benchmark hands each row the same entry (`withOwnContextMark`, apps/benchmark
1387
+ * context-proto.ts `rowPrototypes`) where its manifest `proto` states none for the row's own
1388
+ * function, as m2c's vendored context already carries it; not every real row restates its own
1389
+ * header entry as `proto`. A user who trusts the entry outright restates it with `--proto`, which
1390
+ * bounds hard.
1391
+ *
1392
+ * A stated entry that says nothing of the return (`statesNoReturn`, which counts `returnsVoid:
1393
+ * false` as nothing) keeps the context's return by `contextReturnUnder`'s rule: a struct return
1394
+ * always — it is what says argument 0 may be a hidden pointer, and an entry stating only the arity
1395
+ * would otherwise hand that pointer to the call as its first argument; a register return only
1396
+ * where the stated `params` number as many as the header's parameters. A list is parameters; a
1397
+ * COUNT is argument registers (`FnProto.params`), and it is compared with the header's parameter
1398
+ * count all the same, so under a 64-bit parameter a correct register count keeps no return. That
1399
+ * costs a lift, never a wrong one: a return kept is the header's own, and one dropped declines as
1400
+ * an undeclared callee does. `{ returnsVoid: false }` alone states no count, so it keeps no
1401
+ * register return. */
1402
+ export function withContextPrototypes(
1403
+ stated: Prototypes | undefined,
1404
+ context: Prototypes,
1405
+ own: string,
1406
+ symbols: SymbolMap | undefined,
1407
+ target: Pick<TargetDescription, 'doubleArgWords'>,
1408
+ ): Prototypes {
1409
+ const { [own]: _own, ...callees } = contextPrototypesUnder(context, symbols, target);
1410
+ const out: Prototypes = { ...callees, ...stated };
1411
+ for (const [name, p] of Object.entries(stated ?? {})) {
1412
+ const heard = Object.hasOwn(callees, name) ? callees[name] : undefined;
1413
+ if (p && statesNoReturn(p)) {
1414
+ // `heard` is the entry as the map left it: its return, where it has one, is either the whole
1415
+ // context entry's or one `contextPrototypesUnder` already checked against the map
1416
+ out[name] = { ...p, ...contextReturnUnder(heard ?? undefined, p.params, undefined) };
1417
+ }
1418
+ }
1419
+ return out;
1420
+ }
1421
+
1422
+ /** THE CONTEXT'S OWN ENTRY FOR `own`, where it is a TYPED LIST a lift may bound its argument
1423
+ * registers by — the entry `withContextPrototypes` leaves out — and `undefined` everywhere else.
1424
+ *
1425
+ * It has two readers, and both are SOFT. The context bound (pipeline.ts `liftWithinContextBound`):
1426
+ * a register past it that the lift goes on to read stays a parameter, so a header entry too short
1427
+ * costs a diagnostic, never `uninit_<reg>`. And the entry's struct return (pipeline.ts
1428
+ * `ownStructReturnThroughMemory`), lifted as that struct only where the machine proves the hidden
1429
+ * pointer is its storage, and kept as the pointer function with an `[own-return]` note where it
1430
+ * does not. An entry whose every parameter the bound can lay out is handed over, and so is one
1431
+ * that returns a struct or union by value whatever its parameters say, since what the return reads
1432
+ * is the return alone: a hidden pointer comes first in every parameter list, `...` or a struct
1433
+ * passed by value included (kleod's `struct Unk_0800BEF0_2 sub_0800BEF0(struct Unk_0800BEF0)`).
1434
+ * The bound reads such an entry through `ownArgumentWordsOn`, which lays out none of those lists
1435
+ * and so bounds nothing. Where the entry returns no struct, every other shape ABSTAINS:
1436
+ * - no entry, or one the context dropped (two declarations that conflict: C++ overloads);
1437
+ * - a variadic declaration (`variadicParams`, no `params`): its arity is each caller's;
1438
+ * - `()` (no `params` at all): it states none, and a K&R definition may take any;
1439
+ * - a struct passed by value, or a spelling nothing here sizes: either may fill any number of
1440
+ * words;
1441
+ * - a symbol that names a C++ member function or a mangled free function, whose declaration the
1442
+ * context does not key by the symbol; a context naming one the same is not its declaration, so
1443
+ * this abstains on it whatever it returns.
1444
+ * A bare count is no context entry (`prototypesFromContext` reads lists), and abstains as well. */
1445
+ export function ownContextEntry(context: Prototypes, own: string): FnProto | undefined {
1446
+ const p = Object.hasOwn(context, own) ? context[own] : undefined;
1447
+ if (p === undefined || p === null || typeof p.params === 'number') {
1448
+ return undefined;
1449
+ }
1450
+ if (demangledName(own) !== null || implicitArgumentWords(own) !== 0) {
1451
+ return undefined;
1452
+ }
1453
+ if (declaresAggregateReturn(p)) {
1454
+ return p;
1455
+ }
1456
+ if (!Array.isArray(p.params) || p.variadicParams !== undefined) {
1457
+ return undefined;
1458
+ }
1459
+ const params = p.params;
1460
+ const laidOut = params.every((_, k) => {
1461
+ const cls = paramClassOf(p, params, k);
1462
+ return cls === 'word' || cls === 'wide' || cls === 'float';
1463
+ });
1464
+ return laidOut ? p : undefined;
1465
+ }
1466
+
1467
+ /** A TABLE'S OWN CONTEXT ENTRY, TAKEN OUT OF IT: where `prototypes`' entry for `own` carries the
1468
+ * mark (`FnProto.fromContext`), the table without it, and the entry as the soft bound reads it
1469
+ * ({@link ownContextEntry}, which abstains on any shape it cannot lay out); otherwise the table as
1470
+ * it is. `given` — an entry the caller hands over beside the table — wins over a marked one.
1471
+ *
1472
+ * Every entry path asks this before any reader looks at the table, so a marked entry is never
1473
+ * read as a stated one: a stated entry bounds hard, and a context entry a body reads past would
1474
+ * turn a real argument into `uninit_<reg>`. */
1475
+ export function splitOwnContext(
1476
+ prototypes: Prototypes | undefined,
1477
+ own: string,
1478
+ given?: FnProto,
1479
+ ): { prototypes: Prototypes | undefined; ownContextProto: FnProto | undefined } {
1480
+ const p = prototypes !== undefined && Object.hasOwn(prototypes, own) ? prototypes[own] : undefined;
1481
+ if (p?.fromContext !== true) {
1482
+ return { prototypes, ownContextProto: given };
1483
+ }
1484
+ const { [own]: _marked, ...rest } = prototypes!;
1485
+ const { fromContext: _mark, ...entry } = p;
1486
+ return { prototypes: rest, ownContextProto: given ?? ownContextEntry({ [own]: entry }, own) };
1487
+ }
1488
+
1489
+ /** `prototypes` with the context's own entry for `own` carried in it under the mark
1490
+ * ({@link splitOwnContext} reads it back), where the context has one {@link ownContextEntry}
1491
+ * hands over and the table states none: how a benchmark row publishes the entry its lift read, so
1492
+ * that its `--proto` replay lifts as it did. */
1493
+ export function withOwnContextMark(
1494
+ prototypes: Prototypes | undefined,
1495
+ own: string,
1496
+ context: Prototypes,
1497
+ ): Prototypes | undefined {
1498
+ if (prototypes !== undefined && Object.hasOwn(prototypes, own)) {
1499
+ return prototypes;
1500
+ }
1501
+ const entry = ownContextEntry(context, own);
1502
+ return entry === undefined ? prototypes : { ...prototypes, [own]: { ...entry, fromContext: true } };
1503
+ }
1504
+
1505
+ /** The caller's stated entry for `own`, with the struct or union its stated `returns` names laid out
1506
+ * by the context — `struct S`, or a typedef of one, `T8` over `typedef struct {u32 a, b;} T8;` —
1507
+ * read as the context reads a declaration's return (`readSignature`, through a declaration of that
1508
+ * return the context is handed for the purpose). A `--proto` table spells a type and lays nothing
1509
+ * out, and `withContextPrototypes` leaves the context's own entry for `own` out, so without this
1510
+ * the lifted function's own struct return is a spelling: `struct S1` sized by nothing, where the
1511
+ * rule that decides whether it comes back through a hidden pointer reads a size
1512
+ * (`returnsInMemory`), and `T8`, which reads as no aggregate at all and lifts as the pointer
1513
+ * function its hidden pointer makes it (pipeline.ts `ownStructReturnThroughMemory`).
1514
+ * A return already laid out, a scalar, a pointer or a floating type is left as stated, and so is
1515
+ * a name the context does not lay out. */
1516
+ export function withOwnReturnLayout(
1517
+ stated: Prototypes | undefined,
1518
+ own: string,
1519
+ src: string,
1520
+ language: Language,
1521
+ ): Prototypes | undefined {
1522
+ const p = stated !== undefined && Object.hasOwn(stated, own) ? stated[own] : undefined;
1523
+ const r = p?.returns?.trim();
1524
+ if (
1525
+ p === undefined ||
1526
+ p === null ||
1527
+ r === undefined ||
1528
+ p.returnLayout !== undefined ||
1529
+ r === 'void' ||
1530
+ r.includes('*') ||
1531
+ declaredWidth(r) !== undefined ||
1532
+ isFloatingSpelling(r)
1533
+ ) {
1534
+ return stated;
1535
+ }
1536
+ const probe = '__asmlift_own_return';
1537
+ const layout = prototypesFromContext(`${src}\n${r} ${probe}(void);\n`, language)[probe]?.returnLayout;
1538
+ return layout === undefined ? stated : { ...stated, [own]: { ...p, returnLayout: layout } };
1539
+ }
1540
+
1541
+ const same = (a: FnProto | null, b: FnProto): boolean => a !== null && JSON.stringify(a) === JSON.stringify(b);
1542
+
1543
+ /** A context's prototypes, less the entries the symbol map states better. `prototypesFromSymbols`
1544
+ * lets any entry it is handed shadow the map's signature for that name, so what reaches it decides
1545
+ * which source wins, per name: a context entry that sizes every parameter wins — it is the
1546
+ * declaration the candidate is compiled against, and its spellings are the ones a C++ call's casts
1547
+ * need; it is taken whole, its return included, and the map is not consulted for it. An entry that
1548
+ * cannot size one yields to a map signature that can be spelled, which sizes by byte count what a
1549
+ * declaration names (a by-value struct of a register's width); and where the map spells nothing
1550
+ * either, the context entry stays.
1551
+ *
1552
+ * AN ENTRY THAT YIELDS ITS PARAMETERS KEEPS THE RETURN IT STATES where `contextReturnUnder` says
1553
+ * it may, because the map's signature carries no `returns`. DWARF does say that a pointer, or a
1554
+ * signed or unsigned base type, comes back in a register; `symbolPrototype` chooses not to spell it
1555
+ * (`FnProto.returns`: a header states it and nothing derives it), because a spelled return also
1556
+ * types the call's result and prints in the candidate's declarations, and the map's `void *` is not
1557
+ * the header's `u8 *` — C++ does not convert it implicitly. So a map-only lift (no context) still
1558
+ * declines this shape, and a context entry dropped whole leaves every non-void call to that name
1559
+ * undeclared — and a frame address handed to an undeclared callee may be its hidden struct-return
1560
+ * pointer (`returnsWithoutHiddenPointer`). pokeemerald's `u8 *ConvertIntToDecimalStringN(u8 *,
1561
+ * s32, enum StringConvertMode, u8)` is the case: its enum parameter has no width its SPELLING
1562
+ * states, so the entry yields its parameters and keeps its `u8 *`.
1563
+ *
1564
+ * THE QUESTION HERE IS ASKED OF THE SPELLINGS, the context's own reading of the names behind them
1565
+ * (`wordParams`, `byValueParams`) set aside: which source states the declaration better is not
1566
+ * which one a lowering can place. The map sizes an enum or an array typedef by its byte count and
1567
+ * spells it, which the context's reading of the name only classifies; so an entry that sizes such
1568
+ * a parameter through that reading still yields to a map that spells it. Whichever entry wins is the one every reader of it takes. */
1569
+ function contextPrototypesUnder(
1570
+ context: Prototypes,
1571
+ symbols: SymbolMap | undefined,
1572
+ target: Pick<TargetDescription, 'doubleArgWords'>,
1573
+ ): Prototypes {
1574
+ if (symbols === undefined) {
1575
+ return context;
1576
+ }
1577
+ const mapped = new Map<string, { signed: FnProto; returned: SymbolTypeFacts | null }>();
1578
+ for (const infos of symbols.values()) {
1579
+ for (const info of infos) {
1580
+ const signed = symbolPrototype(info);
1581
+ if (signed?.params !== undefined && info.signature !== undefined && !mapped.has(info.name)) {
1582
+ mapped.set(info.name, { signed, returned: info.signature.returns });
1583
+ }
1584
+ }
1585
+ }
1586
+ return Object.fromEntries(
1587
+ Object.entries(context).flatMap(([name, p]): [string, FnProto][] => {
1588
+ const map = mapped.get(name);
1589
+ if (declaredCallArgs({ params: p.params }, target) !== undefined || map === undefined) {
1590
+ return [[name, p]];
1591
+ }
1592
+ const kept = contextReturnUnder(p, map.signed.params, map.returned);
1593
+ return kept === undefined ? [] : [[name, { ...map.signed, ...kept }]];
1594
+ }),
1595
+ );
1596
+ }
1597
+
1598
+ /** The return a context entry `p` keeps under parameters another source states (`under`: the
1599
+ * map's signature, or a stated entry that says nothing of the return) — or `undefined` when it
1600
+ * keeps none. `returned` is the map's DWARF return where the map's signature is the other source,
1601
+ * `null` for a map that says `void`, and `undefined` where the map is not consulted.
1602
+ *
1603
+ * A `void` return is not kept: whether the callee returns is the map's to state.
1604
+ *
1605
+ * A STRUCT RETURN is kept as stated: DWARF names no struct, so the map states one only by its size
1606
+ * (`symbolPrototype`), and never its members; and keeping it is the safe direction, since it only
1607
+ * says argument 0 may be a hidden pointer.
1608
+ *
1609
+ * A REGISTER RETURN says argument 0 is NOT one, so it is the direction that lifts, and it is kept
1610
+ * only where it is the same fact as the parameters it sits under:
1611
+ * - the context states every parameter, and as many as `under` does — a count `under` is read as
1612
+ * a number of parameters here, though it states registers (`withContextPrototypes`). A variadic
1613
+ * declaration, and a K&R one, state none (`readSignature`), and the map's DWARF parameters
1614
+ * leave the `...` out — so `int pr(char *, const char *, ...)` under a two-parameter map
1615
+ * signature would lower `pr(buf, s, a, b)` as `pr(buf, s)`. Dropped whole, the call declines;
1616
+ * - the return has a width (`declaredWidth`). `float` and `double` have none here, cannot say
1617
+ * anything about a hidden pointer the map's word does not, and on a target with an FPU make
1618
+ * call-plan refuse every call to the name (`$f0`, `f1`), whose result is used or not;
1619
+ * - the map, where it is consulted, does not contradict it: a return the map calls `void`, or a
1620
+ * width its byte size disagrees with (`int` declared, `u8` defined). This is a rule of the merged entry only — its parameters are the map's and its
1621
+ * return the header's, and the two are one fact a table must not hold half of. A context entry
1622
+ * taken whole (it sizes every parameter) never meets the map, and keeps its return unchecked.
1623
+ * Each refusal leaves the call undeclared (`FnProto.returns`). */
1624
+ function contextReturnUnder(
1625
+ p: FnProto | undefined,
1626
+ under: FnProto['params'],
1627
+ returned: SymbolTypeFacts | null | undefined,
1628
+ ): Pick<FnProto, 'returns' | 'returnLayout'> | undefined {
1629
+ if (p === undefined || returned === null || statesNoReturn(p) || declaresVoidReturn(p)) {
1630
+ return undefined;
1631
+ }
1632
+ const kept = {
1633
+ ...(p.returns !== undefined ? { returns: p.returns } : {}),
1634
+ ...(p.returnLayout !== undefined ? { returnLayout: p.returnLayout } : {}),
1635
+ };
1636
+ if (declaresAggregateReturn(p)) {
1637
+ return kept;
1638
+ }
1639
+ const arity = typeof under === 'number' ? under : under?.length;
1640
+ if (!Array.isArray(p.params) || p.params.length !== arity) {
1641
+ return undefined;
1642
+ }
1643
+ const width = p.returns === undefined ? undefined : declaredWidth(p.returns);
1644
+ if (width === undefined) {
1645
+ return undefined;
1646
+ }
1647
+ return returned !== undefined && returned.size !== null && returned.size * 8 !== width ? undefined : kept;
1648
+ }