@asmlift/core 0.8.0 → 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.
- package/README.md +34 -30
- package/package.json +1 -1
- package/src/aggregate.ts +248 -0
- package/src/backend/c.ts +5 -2
- package/src/backend/cfamily.ts +236 -37
- package/src/backend/cpp.ts +114 -9
- package/src/backend/pascal.ts +39 -0
- package/src/cdecl/constant.ts +59 -0
- package/src/cdecl/lex.ts +209 -0
- package/src/cdecl/parse.ts +975 -0
- package/src/cdecl/spell.ts +69 -0
- package/src/codegen-flags.ts +38 -6
- package/src/compiler-diagnostics.ts +194 -0
- package/src/contracts.ts +615 -79
- package/src/declare.ts +75 -5
- package/src/frontend/asmdata.ts +19 -5
- package/src/frontend/call-plan.ts +686 -0
- package/src/frontend/device-pins.ts +277 -0
- package/src/frontend/errors.ts +5 -0
- package/src/frontend/flags-edge.ts +101 -0
- package/src/frontend/fpu.ts +167 -0
- package/src/frontend/frame-objects.ts +2131 -0
- package/src/frontend/high-half.ts +2 -1
- package/src/frontend/local-object.ts +490 -0
- package/src/frontend/mips.ts +363 -40
- package/src/frontend/opaque.ts +97 -18
- package/src/frontend/ppc.ts +520 -129
- package/src/frontend/reloc-symbol.ts +94 -13
- package/src/frontend/splat.ts +108 -5
- package/src/frontend/ssa.ts +669 -76
- package/src/frontend/stackargs.ts +120 -39
- package/src/frontend/thumb.ts +2764 -2018
- package/src/ir/alias.ts +29 -0
- package/src/ir/core.ts +101 -3
- package/src/ir/discipline.ts +248 -0
- package/src/ir/float-bits.ts +47 -0
- package/src/ir/opcodes.ts +84 -112
- package/src/ir/parse.ts +1 -0
- package/src/ir/types.ts +118 -2
- package/src/ir/verify.ts +150 -3
- package/src/l3/address.ts +5 -0
- package/src/l3/argcopy.ts +38 -6
- package/src/l3/ast.ts +186 -14
- package/src/l3/coalesce.ts +21 -13
- package/src/l3/dce.ts +32 -10
- package/src/l3/gates.ts +9 -3
- package/src/l3/hoist.ts +8 -7
- package/src/l3/initfirst.ts +27 -9
- package/src/l3/intcell.ts +97 -0
- package/src/l3/mentions.ts +10 -4
- package/src/l3/narrowdecl.ts +218 -0
- package/src/l3/nearbase.ts +10 -5
- package/src/l3/parkfirst.ts +8 -5
- package/src/l3/pollguard.ts +3 -2
- package/src/l3/ptrcell.ts +357 -0
- package/src/l3/regspell.ts +17 -3
- package/src/l3/reindex.ts +10 -7
- package/src/l3/symbol-refs.ts +104 -23
- package/src/l3/tailmerge.ts +6 -19
- package/src/l3/typing.ts +137 -25
- package/src/l3/unreduce.ts +4 -4
- package/src/l3/volstore.ts +96 -7
- package/src/mangle.ts +11 -1
- package/src/pattern/engine.ts +19 -16
- package/src/pipeline.ts +57 -12
- package/src/proto-context.ts +788 -0
- package/src/proto.ts +689 -50
- package/src/raise/arrays.ts +4 -3
- package/src/raise/declared-volatile.ts +131 -0
- package/src/raise/divpow2.ts +2 -2
- package/src/raise/errors.ts +17 -0
- package/src/raise/floathelpers.ts +243 -0
- package/src/raise/globalshape.ts +11 -5
- package/src/raise/gvn.ts +26 -5
- package/src/raise/narrow.ts +29 -2
- package/src/raise/narrowlocal.ts +11 -6
- package/src/raise/offsetnames.ts +510 -0
- package/src/raise/pairparams.ts +81 -0
- package/src/raise/paramwidth.ts +7 -0
- package/src/raise/pre-recovery.ts +78 -4
- package/src/raise/recover.ts +14 -10
- package/src/raise/retsink.ts +1 -1
- package/src/raise/shortcircuit.ts +41 -19
- package/src/raise/softdiv.ts +21 -23
- package/src/raise/struct-arrays.ts +5 -4
- package/src/raise/structs.ts +311 -98
- package/src/raise/truncload.ts +366 -0
- package/src/raise/widehelpers.ts +106 -0
- package/src/rank-declare.ts +30 -5
- package/src/rank-variations.ts +36 -8
- package/src/rank.ts +276 -80
- package/src/runtime-helpers.ts +287 -0
- package/src/stillborn.ts +289 -0
- package/src/structure/analysis.ts +1285 -448
- package/src/structure/hazards.ts +633 -58
- package/src/structure/local-statics.ts +381 -0
- package/src/structure/namecoalesce.ts +1 -1
- package/src/structure/redundant-test.ts +99 -0
- package/src/structure/structure.ts +1772 -395
- package/src/structure/switch-recover.ts +159 -78
- package/src/symbols.ts +38 -3
- package/src/target.ts +662 -59
- package/src/trace.ts +44 -24
- package/src/variation-definitions.ts +119 -1
- 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
|
+
}
|