@asmlift/core 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -24
- package/package.json +1 -1
- package/src/aggregate.ts +149 -0
- package/src/backend/c.ts +1 -0
- package/src/backend/cfamily.ts +165 -50
- package/src/backend/cpp.ts +2 -1
- package/src/backend/pascal.ts +31 -12
- package/src/contracts.ts +173 -16
- package/src/declare.ts +69 -20
- package/src/frontend/asmdata.ts +7 -1
- package/src/frontend/call-plan.ts +964 -60
- package/src/frontend/dead-reads.ts +628 -0
- package/src/frontend/device-pins.ts +23 -4
- package/src/frontend/emit.ts +4 -3
- package/src/frontend/fpu.ts +224 -17
- package/src/frontend/frame-objects.ts +1232 -192
- package/src/frontend/frontend.ts +64 -3
- package/src/frontend/high-half.ts +53 -4
- package/src/frontend/jump-table.ts +530 -0
- package/src/frontend/local-object.ts +158 -32
- package/src/frontend/mips.ts +2076 -215
- package/src/frontend/own-struct-return.ts +500 -0
- package/src/frontend/ppc.ts +2711 -232
- package/src/frontend/register-struct-call.ts +484 -0
- package/src/frontend/reloc-symbol.ts +22 -7
- package/src/frontend/ssa.ts +1460 -44
- package/src/frontend/stackargs.ts +106 -57
- package/src/frontend/thumb.ts +2044 -318
- package/src/ir/alias.ts +42 -0
- package/src/ir/bits.ts +681 -1
- package/src/ir/core.ts +100 -5
- package/src/ir/discipline.ts +86 -21
- package/src/ir/entry.ts +159 -0
- package/src/{structure → ir}/loops.ts +6 -2
- package/src/ir/opcodes.ts +109 -9
- package/src/ir/types.ts +86 -2
- package/src/ir/verify.ts +80 -11
- package/src/l3/ast.ts +290 -18
- package/src/l3/basecse.ts +33 -5
- package/src/l3/constinit.ts +382 -0
- package/src/l3/dce.ts +2 -2
- package/src/l3/initfirst.ts +42 -11
- package/src/l3/intcell.ts +2 -1
- package/src/l3/narrowdecl.ts +77 -36
- package/src/l3/pollguard.ts +8 -2
- package/src/l3/ptrcell.ts +31 -34
- package/src/l3/ptrfield.ts +123 -8
- package/src/l3/regspell.ts +9 -4
- package/src/l3/reindex.ts +283 -96
- package/src/l3/storage.ts +98 -1
- package/src/l3/symbol-refs.ts +84 -6
- package/src/l3/tailmerge.ts +4 -2
- package/src/l3/typing.ts +108 -15
- package/src/l3/unreduce.ts +6 -5
- package/src/l3/volatileptr.ts +17 -54
- package/src/mangle.ts +30 -0
- package/src/pattern/engine.ts +361 -73
- package/src/pipeline.ts +541 -24
- package/src/proto-context.ts +905 -45
- package/src/proto.ts +555 -40
- package/src/raise/aggmembers.ts +290 -0
- package/src/raise/arrays.ts +5 -1
- package/src/raise/declared-volatile.ts +207 -31
- package/src/raise/floathelpers.ts +254 -33
- package/src/raise/gvn.ts +3 -3
- package/src/raise/latch.ts +162 -6
- package/src/raise/memberarrays.ts +10 -4
- package/src/raise/offsetnames.ts +10 -26
- package/src/raise/pairparams.ts +33 -0
- package/src/raise/paramwidth.ts +196 -6
- package/src/raise/pre-recovery.ts +102 -19
- package/src/raise/pre-structure.ts +54 -0
- package/src/raise/recover.ts +12 -5
- package/src/raise/reroll.ts +694 -0
- package/src/raise/shortcircuit.ts +26 -12
- package/src/raise/softdiv.ts +1 -1
- package/src/raise/storesign.ts +153 -0
- package/src/raise/struct-arrays.ts +30 -11
- package/src/raise/structs.ts +396 -33
- package/src/raise/truncload.ts +4 -3
- package/src/raise/unthread.ts +400 -0
- package/src/rank-declare.ts +118 -12
- package/src/rank-variations.ts +89 -31
- package/src/rank.ts +307 -54
- package/src/runtime-helpers.ts +140 -22
- package/src/structure/analysis.ts +165 -2
- package/src/structure/arith-ops.ts +23 -0
- package/src/structure/globalaccess.ts +111 -9
- package/src/structure/hazards.ts +16 -64
- package/src/structure/local-statics.ts +145 -9
- package/src/structure/namecoalesce.ts +1 -1
- package/src/structure/pointer-spelling.ts +1106 -0
- package/src/structure/redundant-test.ts +1 -1
- package/src/structure/structure.ts +2180 -1054
- package/src/structure/switch-recover.ts +244 -100
- package/src/symbols.ts +6 -0
- package/src/target.ts +765 -52
- package/src/trace.ts +26 -8
- package/src/variation-definitions.ts +126 -14
- package/src/variation-gates.ts +2 -0
- package/src/variation-tokens.ts +5 -1
package/src/proto-context.ts
CHANGED
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
type DeclaredType,
|
|
8
8
|
type Derivation,
|
|
9
9
|
type Language,
|
|
10
|
-
type Parameter,
|
|
11
10
|
type ParsedContext,
|
|
12
11
|
type Qualifier,
|
|
13
12
|
type Range,
|
|
@@ -17,17 +16,20 @@ import {
|
|
|
17
16
|
parseDeclarations,
|
|
18
17
|
} from './cdecl/parse';
|
|
19
18
|
import { asParameter, spellType } from './cdecl/spell';
|
|
19
|
+
import { demangledName, implicitArgumentWords } from './mangle';
|
|
20
20
|
import type { AggregateLayout, AggregateMember, FnProto, ParamType, Prototypes } from './proto';
|
|
21
21
|
import {
|
|
22
22
|
declaredCallArgs,
|
|
23
23
|
declaredWidth,
|
|
24
24
|
declaresAggregateReturn,
|
|
25
|
+
declaresVoidReturn,
|
|
25
26
|
isFloatingSpelling,
|
|
27
|
+
paramClassOf,
|
|
26
28
|
statesNoReturn,
|
|
27
29
|
symbolPrototype,
|
|
28
30
|
validatePrototypes,
|
|
29
31
|
} from './proto';
|
|
30
|
-
import type { SymbolMap } from './symbols';
|
|
32
|
+
import type { SymbolMap, SymbolTypeFacts } from './symbols';
|
|
31
33
|
import type { TargetDescription } from './target';
|
|
32
34
|
|
|
33
35
|
// asmlift — callee prototypes read out of a DECLARATION CONTEXT: the preprocessed headers a
|
|
@@ -68,6 +70,379 @@ export function prototypesFromContext(src: string, language: Language): Prototyp
|
|
|
68
70
|
return functionPrototypes(ctx, table, layoutReader(ctx, table));
|
|
69
71
|
}
|
|
70
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
|
+
|
|
71
446
|
const WORD = /^[A-Za-z_]\w*$/;
|
|
72
447
|
|
|
73
448
|
/** `mode(…)` in an attribute, in either spelling: it retypes what it is written on */
|
|
@@ -165,6 +540,14 @@ function readTag(
|
|
|
165
540
|
if ((attributed || wide) && name !== undefined) {
|
|
166
541
|
table.unsizedEnums.add(`enum ${name}`);
|
|
167
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
|
+
}
|
|
168
551
|
return attributed || wide;
|
|
169
552
|
}
|
|
170
553
|
|
|
@@ -580,17 +963,82 @@ function memberShape(
|
|
|
580
963
|
return { name: d.name, pointer: stars.length > 0, ...(dims.length > 0 ? { dims } : {}), returns: [] };
|
|
581
964
|
}
|
|
582
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
|
+
|
|
583
980
|
/** Every function the declarations name by a plain identifier, keyed by it. A declaration that
|
|
584
981
|
* spells no type is a constructor's, a destructor's or a conversion operator's; one that defines a
|
|
585
982
|
* body, or spells a `class`, declares no C function. */
|
|
586
983
|
function functionPrototypes(ctx: ParsedContext, table: TypeTable, layoutOf: LayoutOf): Prototypes {
|
|
587
|
-
const found = new Map<string,
|
|
984
|
+
const found = new Map<string, Read | null>();
|
|
985
|
+
const written = typedefTypes(ctx);
|
|
588
986
|
// a function one of whose declarations retypes a parameter with `mode`, which gives it the type
|
|
589
987
|
// the mode names in place of the one spelled (c-common.c:563): compiled, `int x
|
|
590
988
|
// __attribute__((mode(DI)))` takes a register pair. Its parameters are not read, in any
|
|
591
989
|
// declaration of it; its return is.
|
|
592
990
|
const unreadParams = new Set<string>();
|
|
593
|
-
|
|
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;
|
|
594
1042
|
for (const d of ctx.declarations) {
|
|
595
1043
|
const s = d.specifiers;
|
|
596
1044
|
if (
|
|
@@ -614,15 +1062,40 @@ function functionPrototypes(ctx: ParsedContext, table: TypeTable, layoutOf: Layo
|
|
|
614
1062
|
if (retyped) {
|
|
615
1063
|
unreadParams.add(x.name);
|
|
616
1064
|
}
|
|
617
|
-
const
|
|
618
|
-
|
|
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))];
|
|
619
1085
|
const prior = found.get(x.name);
|
|
620
|
-
const had =
|
|
621
|
-
|
|
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
|
+
);
|
|
622
1094
|
}
|
|
623
1095
|
}
|
|
624
1096
|
const out: Prototypes = {};
|
|
625
|
-
for (const [name,
|
|
1097
|
+
for (const [name, read] of found) {
|
|
1098
|
+
const p = read?.proto ?? null;
|
|
626
1099
|
const valid = p === null ? undefined : admissible(name, p);
|
|
627
1100
|
if (valid !== undefined) {
|
|
628
1101
|
out[name] = valid;
|
|
@@ -631,6 +1104,101 @@ function functionPrototypes(ctx: ParsedContext, table: TypeTable, layoutOf: Layo
|
|
|
631
1104
|
return out;
|
|
632
1105
|
}
|
|
633
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
|
+
|
|
634
1202
|
/** The entry as a table `validatePrototypes` accepts, the check every `--proto` table passes, so a
|
|
635
1203
|
* context's prototypes can travel as one: a `returns` it refuses is dropped — a return wider than a
|
|
636
1204
|
* register with a parameter list that cannot be printed — and the entry with it if that is not
|
|
@@ -649,6 +1217,8 @@ function readSignature(
|
|
|
649
1217
|
{ tokens, language }: ParsedContext,
|
|
650
1218
|
typedefs: ReadonlyMap<string, DeclaredType>,
|
|
651
1219
|
layoutOf: (t: DeclaredType) => AggregateLayout | undefined,
|
|
1220
|
+
passing: (t: DeclaredType) => Passing,
|
|
1221
|
+
qualifiers: 'kept' | 'dropped' = 'kept',
|
|
652
1222
|
): FnProto {
|
|
653
1223
|
const proto: FnProto = {};
|
|
654
1224
|
const resolved = resolve(ret, typedefs, tokens);
|
|
@@ -689,34 +1259,146 @@ function readSignature(
|
|
|
689
1259
|
}
|
|
690
1260
|
return proto;
|
|
691
1261
|
}
|
|
692
|
-
if (fn
|
|
1262
|
+
if (isVoidList(fn, tokens, typedefs)) {
|
|
693
1263
|
proto.params = [];
|
|
694
1264
|
return proto;
|
|
695
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
|
+
});
|
|
696
1283
|
if (fn.variadic) {
|
|
697
|
-
|
|
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
|
+
}
|
|
698
1309
|
}
|
|
699
|
-
proto.params = params.map((p) => parameterType(p, tokens, typedefs));
|
|
700
1310
|
return proto;
|
|
701
1311
|
}
|
|
702
1312
|
|
|
703
|
-
/**
|
|
704
|
-
*
|
|
705
|
-
function
|
|
706
|
-
const
|
|
707
|
-
|
|
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;
|
|
708
1363
|
}
|
|
709
1364
|
|
|
710
1365
|
/** The prototypes a lift of `own` reads when a context is in hand: `stated` — a caller's own
|
|
711
1366
|
* 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
|
|
713
|
-
* (`asIfUndecompiled`): a header's signature for the function being decompiled
|
|
714
|
-
*
|
|
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.
|
|
715
1391
|
*
|
|
716
1392
|
* A stated entry that says nothing of the return (`statesNoReturn`, which counts `returnsVoid:
|
|
717
|
-
* false` as nothing) keeps
|
|
718
|
-
* a hidden pointer, and an entry stating only the arity
|
|
719
|
-
* call as its first argument
|
|
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. */
|
|
720
1402
|
export function withContextPrototypes(
|
|
721
1403
|
stated: Prototypes | undefined,
|
|
722
1404
|
context: Prototypes,
|
|
@@ -729,21 +1411,131 @@ export function withContextPrototypes(
|
|
|
729
1411
|
for (const [name, p] of Object.entries(stated ?? {})) {
|
|
730
1412
|
const heard = Object.hasOwn(callees, name) ? callees[name] : undefined;
|
|
731
1413
|
if (p && statesNoReturn(p)) {
|
|
732
|
-
|
|
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) };
|
|
733
1417
|
}
|
|
734
1418
|
}
|
|
735
1419
|
return out;
|
|
736
1420
|
}
|
|
737
1421
|
|
|
738
|
-
/**
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
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;
|
|
742
1449
|
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
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 } };
|
|
747
1539
|
}
|
|
748
1540
|
|
|
749
1541
|
const same = (a: FnProto | null, b: FnProto): boolean => a !== null && JSON.stringify(a) === JSON.stringify(b);
|
|
@@ -752,9 +1544,28 @@ const same = (a: FnProto | null, b: FnProto): boolean => a !== null && JSON.stri
|
|
|
752
1544
|
* lets any entry it is handed shadow the map's signature for that name, so what reaches it decides
|
|
753
1545
|
* which source wins, per name: a context entry that sizes every parameter wins — it is the
|
|
754
1546
|
* declaration the candidate is compiled against, and its spellings are the ones a C++ call's casts
|
|
755
|
-
* need;
|
|
756
|
-
*
|
|
757
|
-
*
|
|
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. */
|
|
758
1569
|
function contextPrototypesUnder(
|
|
759
1570
|
context: Prototypes,
|
|
760
1571
|
symbols: SymbolMap | undefined,
|
|
@@ -763,26 +1574,75 @@ function contextPrototypesUnder(
|
|
|
763
1574
|
if (symbols === undefined) {
|
|
764
1575
|
return context;
|
|
765
1576
|
}
|
|
766
|
-
const mapped = new Map<string, FnProto>();
|
|
1577
|
+
const mapped = new Map<string, { signed: FnProto; returned: SymbolTypeFacts | null }>();
|
|
767
1578
|
for (const infos of symbols.values()) {
|
|
768
1579
|
for (const info of infos) {
|
|
769
1580
|
const signed = symbolPrototype(info);
|
|
770
|
-
if (signed?.params !== undefined && !mapped.has(info.name)) {
|
|
771
|
-
mapped.set(info.name, signed);
|
|
1581
|
+
if (signed?.params !== undefined && info.signature !== undefined && !mapped.has(info.name)) {
|
|
1582
|
+
mapped.set(info.name, { signed, returned: info.signature.returns });
|
|
772
1583
|
}
|
|
773
1584
|
}
|
|
774
1585
|
}
|
|
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
1586
|
return Object.fromEntries(
|
|
778
1587
|
Object.entries(context).flatMap(([name, p]): [string, FnProto][] => {
|
|
779
|
-
const
|
|
780
|
-
if (declaredCallArgs(p, target) !== undefined ||
|
|
1588
|
+
const map = mapped.get(name);
|
|
1589
|
+
if (declaredCallArgs({ params: p.params }, target) !== undefined || map === undefined) {
|
|
781
1590
|
return [[name, p]];
|
|
782
1591
|
}
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
: [];
|
|
1592
|
+
const kept = contextReturnUnder(p, map.signed.params, map.returned);
|
|
1593
|
+
return kept === undefined ? [] : [[name, { ...map.signed, ...kept }]];
|
|
786
1594
|
}),
|
|
787
1595
|
);
|
|
788
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
|
+
}
|