@asmlift/core 0.9.0 → 0.11.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 +2 -0
- package/src/backend/cfamily.ts +197 -61
- package/src/backend/cpp.ts +3 -1
- package/src/backend/lines.ts +10 -0
- package/src/backend/pascal.ts +47 -16
- package/src/cdecl/parse.ts +21 -0
- package/src/contracts.ts +246 -36
- package/src/declare.ts +69 -20
- package/src/frontend/arm-body.ts +155 -0
- package/src/frontend/asmdata.ts +9 -2
- package/src/frontend/call-plan.ts +1117 -61
- package/src/frontend/callee-body.ts +370 -0
- package/src/frontend/dead-reads.ts +628 -0
- package/src/frontend/device-pins.ts +33 -4
- package/src/frontend/emit.ts +4 -3
- package/src/frontend/fpu.ts +224 -17
- package/src/frontend/frame-objects.ts +1689 -206
- package/src/frontend/frontend.ts +84 -3
- package/src/frontend/high-half.ts +53 -4
- package/src/frontend/jump-table.ts +545 -0
- package/src/frontend/local-object.ts +158 -32
- package/src/frontend/mips.ts +2296 -217
- package/src/frontend/own-struct-return.ts +500 -0
- package/src/frontend/ppc.ts +2748 -245
- package/src/frontend/register-struct-call.ts +399 -0
- package/src/frontend/reloc-symbol.ts +22 -7
- package/src/frontend/ssa.ts +1721 -43
- package/src/frontend/stackargs.ts +199 -72
- package/src/frontend/thumb-entry.ts +340 -0
- package/src/frontend/thumb.ts +2400 -325
- package/src/ir/alias.ts +65 -2
- package/src/ir/bits.ts +1699 -4
- package/src/ir/core.ts +254 -9
- package/src/ir/discipline.ts +86 -21
- package/src/ir/entry.ts +157 -0
- package/src/{structure → ir}/loops.ts +6 -2
- package/src/ir/opcodes.ts +137 -9
- package/src/ir/parse.ts +1 -0
- package/src/ir/simplify.ts +61 -10
- package/src/ir/types.ts +86 -2
- package/src/ir/verify.ts +96 -11
- package/src/l3/argbase.ts +2 -0
- package/src/l3/ast.ts +582 -18
- package/src/l3/basecse.ts +68 -15
- package/src/l3/caseendgoto.ts +223 -0
- package/src/l3/coalesce.ts +42 -14
- package/src/l3/constinit.ts +410 -0
- package/src/l3/dce.ts +53 -9
- 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 +33 -34
- package/src/l3/ptrfield.ts +123 -8
- package/src/l3/regspell.ts +11 -4
- package/src/l3/reindex.ts +295 -99
- package/src/l3/scopebase.ts +2 -0
- package/src/l3/storage.ts +98 -1
- package/src/l3/symbol-refs.ts +84 -6
- package/src/l3/tailmerge.ts +6 -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 +1039 -38
- package/src/proto-context.ts +1039 -45
- package/src/proto.ts +555 -40
- package/src/raise/aggmembers.ts +290 -0
- package/src/raise/arrays.ts +5 -1
- package/src/raise/const.ts +25 -0
- package/src/raise/declared-volatile.ts +207 -31
- package/src/raise/divpow2.ts +192 -0
- 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 +422 -36
- package/src/raise/pairparams.ts +33 -0
- package/src/raise/paramwidth.ts +196 -6
- package/src/raise/pre-recovery.ts +107 -21
- package/src/raise/pre-structure.ts +73 -0
- package/src/raise/recover.ts +12 -5
- package/src/raise/reroll.ts +674 -0
- package/src/raise/shortcircuit.ts +217 -34
- 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/unpre.ts +594 -0
- package/src/raise/unthread.ts +400 -0
- package/src/rank-declare.ts +118 -12
- package/src/rank-variations.ts +186 -35
- package/src/rank.ts +536 -68
- package/src/runtime-helpers.ts +140 -22
- package/src/structure/analysis.ts +217 -8
- package/src/structure/arith-ops.ts +23 -0
- package/src/structure/cross-jumped-tail.ts +216 -0
- package/src/structure/globalaccess.ts +157 -10
- 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 +6192 -1213
- package/src/structure/switch-recover.ts +583 -120
- package/src/structure/unfolded-branch-constant.ts +90 -0
- package/src/symbols.ts +58 -15
- package/src/target.ts +1028 -54
- package/src/trace.ts +49 -17
- package/src/variation-definitions.ts +333 -20
- package/src/variation-gates.ts +4 -0
- package/src/variation-tokens.ts +68 -3
package/src/proto.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { parseTypeName } from './cdecl/parse';
|
|
2
2
|
import type { IrType } from './ir/types';
|
|
3
|
+
import { demangle, implicitArgumentWords, spellType } from './mangle';
|
|
3
4
|
import type { SymbolInfo, SymbolMap, SymbolTypeFacts } from './symbols';
|
|
4
5
|
import type { TargetDescription } from './target';
|
|
5
6
|
|
|
@@ -30,11 +31,44 @@ export type ParamType = string;
|
|
|
30
31
|
export interface FnProto {
|
|
31
32
|
/** declared parameters — either the typed parameter list a header extraction produces
|
|
32
33
|
* (`["u8", "s32"]`), which is a list of C PARAMETERS, or a bare COUNT, which is the user's word
|
|
33
|
-
* for how many argument REGISTERS the call occupies
|
|
34
|
-
*
|
|
35
|
-
*
|
|
34
|
+
* for how many argument REGISTERS the call occupies — read so at a call. On the function's OWN
|
|
35
|
+
* entry a count bounds no argument register (`ownArgumentWords` abstains): a header's count may
|
|
36
|
+
* be of C parameters, and one of those may span any number of words. The two are the
|
|
37
|
+
* same number only while every parameter fits in a register, and `declaredCallArgs` is what
|
|
38
|
+
* converts the first into the second. Omit — or spell a parameter that conversion cannot size — to let the frontend
|
|
36
39
|
* fall back to its contiguous-arg-register heuristic. */
|
|
37
40
|
params?: number | ParamType[];
|
|
41
|
+
/** The parameters, by index into a typed `params`, whose spelling `declaredWidth` cannot size and
|
|
42
|
+
* which still travel in one register: an enum, or an array or function type a typedef name
|
|
43
|
+
* stands for, which the parameter adjusts to a pointer. A context states it, reading the types
|
|
44
|
+
* behind the names (`prototypesFromContext`). Every reader sizes such a parameter at one word
|
|
45
|
+
* (`paramClassOf`): the call lowering's widths (`declaredCallArgs`) and a frontend's frame
|
|
46
|
+
* partition alike. */
|
|
47
|
+
wordParams?: readonly number[];
|
|
48
|
+
/** The parameters, by index into a typed `params`, that are a struct, union or class passed BY
|
|
49
|
+
* VALUE: mwcc passes the address of a copy it builds in the caller's own frame, which no source
|
|
50
|
+
* declares (`frontend/ppc.ts` `measurePpcFrame`). A context states it, reading the tag behind the
|
|
51
|
+
* name (`prototypesFromContext`). A spelling neither this nor `wordParams` lists, and that
|
|
52
|
+
* nothing else sizes, is UNSIZED — a name nothing here resolved, not a struct by inference — and
|
|
53
|
+
* every reader abstains on it. */
|
|
54
|
+
byValueParams?: readonly number[];
|
|
55
|
+
/** The members of each struct or union a typed `params` list passes BY VALUE, keyed by the
|
|
56
|
+
* parameter's index: what sizes it, and so how many argument words it takes on a target that
|
|
57
|
+
* passes one in words (`compilerBehaviors.byValueAggregateArgWords`, read by `aggregate.ts`
|
|
58
|
+
* `byValueCallArgs`). A context states it from the struct's definition, as it states
|
|
59
|
+
* `returnLayout`. A by-value parameter with no layout here is unsized, and a call to it refuses
|
|
60
|
+
* (`aggregate.ts` `byValueRefusal`) rather than take the argument-register guess. */
|
|
61
|
+
byValueLayouts?: Readonly<Record<number, AggregateLayout>>;
|
|
62
|
+
/** The FIXED parameters of a VARIADIC declaration, spelled as a typed `params` list spells them.
|
|
63
|
+
* Such a declaration states no `params`, because its arity is the call's and a list would read
|
|
64
|
+
* as the whole of it; `wordParams` and `byValueParams` index into this list instead. A context
|
|
65
|
+
* states it (`prototypesFromContext`). Read for two things, and never as a layout of its own.
|
|
66
|
+
* Where each fixed parameter is one word, they are the call's first argument words, and only
|
|
67
|
+
* what the `...` takes is guessed (frontend/call-plan.ts `variadicFloor`). Where one is not, the
|
|
68
|
+
* call refuses (`variadicRefusal`): the arity guess reads argument words one by one, so a fixed
|
|
69
|
+
* parameter that is not one word is split or dropped: `void takeV(struct W, ...)` would lift
|
|
70
|
+
* `takeV(*(s32 *)&gW, 5)`, which compiles against the header. */
|
|
71
|
+
variadicParams?: ParamType[];
|
|
38
72
|
/** The declared return type is `void`. Read for the function under decompilation, where a
|
|
39
73
|
* trailing `bx lr` leaves a meaningless return register that must not surface as a `return`
|
|
40
74
|
* value — and, since the out-parameter path landed, for a CALLEE, where it is the only thing
|
|
@@ -89,6 +123,16 @@ export interface FnProto {
|
|
|
89
123
|
* this key also says it of a typedef name, which spells no keyword, and carries the members
|
|
90
124
|
* whose layout decides whether the target returns it through memory at all (`aggregate.ts`). */
|
|
91
125
|
returnLayout?: AggregateLayout;
|
|
126
|
+
/** ON THE FUNCTION'S OWN ENTRY, `true` says the entry is its declaration context's header entry
|
|
127
|
+
* (proto-context.ts `ownContextEntry`) and not the caller's word: it bounds the argument
|
|
128
|
+
* registers SOFTLY (pipeline.ts `liftWithinContextBound`), and a struct it returns through
|
|
129
|
+
* memory is lifted as that struct where the lift proves the hidden pointer is only its storage
|
|
130
|
+
* (pipeline.ts `returnOwnStruct`), and kept as the pointer function with a note where it does
|
|
131
|
+
* not; it is read as nothing else — not as a scalar return, not by any reader of a stated own
|
|
132
|
+
* entry. Each entry path takes it out of the table before anything reads it (proto-context.ts `splitOwnContext`). It is how a table carries the
|
|
133
|
+
* entry a `--context` lift was handed: a benchmark row publishes it so its `--proto` replay
|
|
134
|
+
* lifts as the harness did. On a callee's entry it is ignored. */
|
|
135
|
+
fromContext?: boolean;
|
|
92
136
|
}
|
|
93
137
|
|
|
94
138
|
/** A struct or union a declaration spells: what it is, and its members as the header lists them.
|
|
@@ -118,8 +162,10 @@ export interface AggregateMember {
|
|
|
118
162
|
/** symbol → prototype. The function under decompilation and its callees share one table. */
|
|
119
163
|
export type Prototypes = Record<string, FnProto>;
|
|
120
164
|
|
|
121
|
-
/** How many argument
|
|
122
|
-
*
|
|
165
|
+
/** How many argument WORDS a list of parameter WIDTHS occupies: a parameter wider than a machine
|
|
166
|
+
* word travels in a pair of words, every other one in a single word, and where the target aligns
|
|
167
|
+
* an 8-byte argument (`TargetDescription.evenWordAlignedArgs`) a pair that would start at an odd
|
|
168
|
+
* word skips one.
|
|
123
169
|
*
|
|
124
170
|
* The rule lives here rather than beside either caller because it has two, and they are the two
|
|
125
171
|
* vocabularies a declaration is written in. `runtime-helpers.ts` states a compiler's own helper
|
|
@@ -127,17 +173,14 @@ export type Prototypes = Record<string, FnProto>;
|
|
|
127
173
|
* `declaredCallArgs` below reads the same widths off a project's C types. One ABI fact, one
|
|
128
174
|
* copy of it; a second copy is a rule that can disagree with itself.
|
|
129
175
|
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
* counts is one word per parameter. The lowering that builds an o32 pair owes the rule a home. */
|
|
139
|
-
export function wordsOf(params: readonly number[]): number {
|
|
140
|
-
return params.reduce((n, w) => n + (w > 32 ? 2 : 1), 0);
|
|
176
|
+
* agbcc DOES NOT ALIGN, a measured fact rather than an omission: its `thumb.h` computes an
|
|
177
|
+
* argument's register from a plain byte counter with no rounding, so `void f(s32, long long)`
|
|
178
|
+
* passes the pair in r1:r2 where AAPCS would pad to r2:r3. O32 does, for a scalar and a struct
|
|
179
|
+
* alike, and that is the one fact both read here: `aggregate.ts` `byValueCallArgs` places a struct
|
|
180
|
+
* by this count, and the lowering that builds an o32 pair, which `frontend/mips.ts` does not yet
|
|
181
|
+
* (it refuses a declared 64-bit parameter, `CallLowering.pairs`), counts its words the same way. */
|
|
182
|
+
export function wordsOf(params: readonly number[], target: Pick<TargetDescription, 'evenWordAlignedArgs'>): number {
|
|
183
|
+
return params.reduce((n, w) => (w > 32 ? n + (target.evenWordAlignedArgs === true ? n % 2 : 0) + 2 : n + 1), 0);
|
|
141
184
|
}
|
|
142
185
|
|
|
143
186
|
/** Whether a proto states a parameter list at all — readably or not. The tier question, asked
|
|
@@ -148,25 +191,49 @@ export function declaresParams(p: FnProto | undefined): boolean {
|
|
|
148
191
|
return typeof p?.params === 'number' || Array.isArray(p?.params);
|
|
149
192
|
}
|
|
150
193
|
|
|
194
|
+
/** The declaration a call to `callee` is read at — THE TIER ORDER, written once: the project's own
|
|
195
|
+
* entry where it states a parameter list (`declaresParams`), then the compiler's runtime table
|
|
196
|
+
* (`helpers`: `runtime-helpers.ts` `helperPrototypes` of the target), then the signatures the C
|
|
197
|
+
* standard fixes. `frontend/call-plan.ts` lowers every call at it, and the CLI asks it which
|
|
198
|
+
* callees a run guessed, so a callee the lowering reads a signature for is never reported as
|
|
199
|
+
* guessed, and one it guesses for always is. `Object.hasOwn` on all three tables: a callee named
|
|
200
|
+
* `toString` would otherwise read a `Function` off `Object.prototype` as its entry. */
|
|
201
|
+
export function calleeDeclaration(callee: string, prototypes: Prototypes, helpers: Prototypes): FnProto | undefined {
|
|
202
|
+
const known = (t: Prototypes) => (Object.hasOwn(t, callee) ? t[callee] : undefined);
|
|
203
|
+
const own = known(prototypes);
|
|
204
|
+
return declaresParams(own) ? own : (known(helpers) ?? known(STANDARD_SIGNATURES));
|
|
205
|
+
}
|
|
206
|
+
|
|
151
207
|
/** What a declaration says about the argument registers a call occupies — the width each declared
|
|
152
208
|
* parameter travels in, in argument order — or `undefined` when it says nothing this can act on.
|
|
153
209
|
*
|
|
154
210
|
* THREE WAYS TO SAY NOTHING, AND THEY ARE ONE ANSWER. `params` omitted; `params` malformed (a
|
|
155
211
|
* bare `"u8"` string, the shape the untyped CLI `--proto` JSON admits); and a list holding a
|
|
156
|
-
* spelling `declaredWidth`
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
212
|
+
* spelling neither `declaredWidth` reads nor the declaration sizes — a project typedef
|
|
213
|
+
* (`bool8`, `Direction`, `TaskFunc`) that `wordParams` does not list, a by-value struct, a
|
|
214
|
+
* floating type (a by-value struct is the exception below). The first two are not a reading of the
|
|
215
|
+
* list at all. The third is a list read to the end that cannot be CONVERTED: a parameter of
|
|
216
|
+
* unknown width occupies one argument register or two, and the choice moves every later argument's
|
|
217
|
+
* home. Where the declaration does settle it
|
|
218
|
+
* — a typedef'd enum, an array or a function type the context resolved to one word, which
|
|
219
|
+
* `wordParams` lists (`paramClassOf`) — the parameter is 32 bits and the list converts.
|
|
161
220
|
*
|
|
162
221
|
* SO IT ABSTAINS, AND ABSTAINING IS NEITHER A REFUSAL NOR A GUESS. The caller falls back to its
|
|
163
222
|
* own contiguous-arg-register scan — exactly what it does for a callee the project never declared
|
|
164
223
|
* — and that scan names itself a guess: `SsaBuilder.readGuessedArg` ASKS whether a register was
|
|
165
224
|
* set up, and `finish()` retracts the ones an intervening call destroyed. Refusing instead would
|
|
166
|
-
* be a function that lifts when told nothing and declines when told more.
|
|
225
|
+
* be a function that lifts when told nothing and declines when told more. The list's LENGTH is
|
|
226
|
+
* still read: every parameter is at least one word, so a guess that counts fewer registers has
|
|
227
|
+
* dropped one, and that refuses (frontend/call-plan.ts `refuseShortOfDeclared`). Returning
|
|
167
228
|
* one-register-each instead would be a layout nothing could know to be right, and the frontend
|
|
168
229
|
* would then ASSERT those registers rather than ask for them.
|
|
169
230
|
*
|
|
231
|
+
* A PARAMETER THE DECLARATION PASSES AS A STRUCT OR UNION BY VALUE IS THE EXCEPTION, because it is
|
|
232
|
+
* known not to be one register: the caller asks `aggregate.ts` `byValueCallArgs` to place it, and
|
|
233
|
+
* where no rule does (`byValueRefusal`) the call refuses (frontend/call-plan.ts
|
|
234
|
+
* `refuseUnplacedByValue`). That is a function that declines when told more, on purpose: a guess
|
|
235
|
+
* over a struct drops it or reads its words as separate arguments, and both compile.
|
|
236
|
+
*
|
|
170
237
|
* THE MACHINE IS NOT A WITNESS FOR A WIDTH NOBODY STATED, which is the reading this replaces. The
|
|
171
238
|
* contiguous scan is wrong in both directions and neither is recoverable here. It OVER-counts:
|
|
172
239
|
* it is read before `frontend/ssa.ts` `narrowToSetupArgs` trims the registers a call sat between,
|
|
@@ -186,7 +253,9 @@ export function declaresParams(p: FnProto | undefined): boolean {
|
|
|
186
253
|
* target that states no such words the list abstains. A `float` states no layout on either: it
|
|
187
254
|
* is one word on a soft-float target, but that word is a float only to a callee whose declaration
|
|
188
255
|
* the candidate prints, and `spellableProto` prints no `float` — an undeclared callee is handed a
|
|
189
|
-
* `float` promoted to a `double`, which is two words.
|
|
256
|
+
* `float` promoted to a `double`, which is two words. A known spelling with no layout is not the
|
|
257
|
+
* unsized abstention, so on a target with no FPU a call to such a declaration refuses rather than
|
|
258
|
+
* guess (frontend/call-plan.ts `refuseUnplacedFloat`). */
|
|
190
259
|
export function declaredCallArgs(
|
|
191
260
|
p: FnProto | undefined,
|
|
192
261
|
target: Pick<TargetDescription, 'doubleArgWords'>,
|
|
@@ -200,7 +269,7 @@ export function declaredCallArgs(
|
|
|
200
269
|
const widths: number[] = [];
|
|
201
270
|
const doubles = new Set<number>();
|
|
202
271
|
for (const [i, t] of p.params.entries()) {
|
|
203
|
-
const w = declaredWidth(t);
|
|
272
|
+
const w = declaredWidth(t) ?? (paramClassOf(p, p.params, i) === 'word' ? 32 : undefined);
|
|
204
273
|
if (w !== undefined) {
|
|
205
274
|
widths.push(w);
|
|
206
275
|
} else if (target.doubleArgWords !== undefined && isDoubleSpelling(t)) {
|
|
@@ -213,19 +282,310 @@ export function declaredCallArgs(
|
|
|
213
282
|
return { widths, doubles };
|
|
214
283
|
}
|
|
215
284
|
|
|
285
|
+
/** WHAT ONE DECLARED PARAMETER IS, as far as where a caller places it goes — the one reading every
|
|
286
|
+
* reader of a declaration takes, so the call lowering (`declaredCallArgs`) and a frontend's frame
|
|
287
|
+
* partition (`frontend/call-plan.ts` `declaredParams`) cannot give one declaration two answers: a
|
|
288
|
+
* value of one word (`declaredWidth` sizes it at 32 bits or less, or `wordParams` lists it), one
|
|
289
|
+
* wider than a word, a floating type (`isFloatingSpelling`), a struct or union passed by value
|
|
290
|
+
* (`byValueParams` lists it, or the spelling says so with its keyword: `struct B`), or UNSIZED — a
|
|
291
|
+
* spelling nothing here resolved, which states none of those and is not read as any of them. A
|
|
292
|
+
* count states words. */
|
|
293
|
+
export type ParamClass = 'word' | 'wide' | 'float' | 'by-value' | 'unsized';
|
|
294
|
+
|
|
295
|
+
/** A struct, union or class named by its keyword and tag, with no declarator: passed by value. */
|
|
296
|
+
const BY_VALUE_SPELLING = /^\s*(?:struct|union|class)\s+\w+\s*$/;
|
|
297
|
+
|
|
298
|
+
/** The class of parameter `k` of the typed list `params` that declaration `p` states (`ParamClass`). */
|
|
299
|
+
export function paramClassOf(p: FnProto, params: readonly ParamType[], k: number): ParamClass {
|
|
300
|
+
const spelling = params[k];
|
|
301
|
+
const w = declaredWidth(spelling);
|
|
302
|
+
return w !== undefined
|
|
303
|
+
? w > 32
|
|
304
|
+
? 'wide'
|
|
305
|
+
: 'word'
|
|
306
|
+
: isFloatingSpelling(spelling)
|
|
307
|
+
? 'float'
|
|
308
|
+
: p.wordParams?.includes(k)
|
|
309
|
+
? 'word'
|
|
310
|
+
: p.byValueParams?.includes(k) || BY_VALUE_SPELLING.test(spelling.replace(/\b(?:const|volatile)\b/g, ' '))
|
|
311
|
+
? 'by-value'
|
|
312
|
+
: 'unsized';
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** Why a call to the VARIADIC declaration `p` cannot be placed by the arity guess: its first fixed
|
|
316
|
+
* parameter (`FnProto.variadicParams`, an index into it) that is not one argument word, and the
|
|
317
|
+
* reason. Undefined where `p` is no variadic declaration, or where every fixed parameter is a word
|
|
318
|
+
* or UNSIZED — a spelling nothing here resolved, which may be one word, and on which every reader
|
|
319
|
+
* abstains (`declaredCallArgs`) rather than refuse. */
|
|
320
|
+
export function variadicRefusal(p: FnProto | undefined): { refused: string; param: number } | undefined {
|
|
321
|
+
const fixed = p?.variadicParams;
|
|
322
|
+
if (fixed === undefined || p?.params !== undefined) {
|
|
323
|
+
return undefined;
|
|
324
|
+
}
|
|
325
|
+
const reasons: Partial<Record<ParamClass, string>> = {
|
|
326
|
+
'by-value': 'reads a struct or union passed by value as that many separate words',
|
|
327
|
+
wide: 'reads a value wider than a word as two separate arguments',
|
|
328
|
+
float: 'reads a floating-point value as integer words, or misses it in the FPU registers',
|
|
329
|
+
};
|
|
330
|
+
for (const k of fixed.keys()) {
|
|
331
|
+
const why = reasons[paramClassOf(p!, fixed, k)];
|
|
332
|
+
if (why !== undefined) {
|
|
333
|
+
return {
|
|
334
|
+
refused: `its declaration is variadic: the call's arguments are placed by the arity guess, which ${why}`,
|
|
335
|
+
param: k,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return undefined;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/** HOW MANY GENERAL ARGUMENT WORDS THE FUNCTION UNDER DECOMPILATION'S OWN DECLARATION FILLS — an
|
|
343
|
+
* UPPER BOUND, never an exact layout — or `undefined` where the declaration bounds nothing.
|
|
344
|
+
*
|
|
345
|
+
* Its one reader is the live-in model (`frontend/ssa.ts` `registerLiveIns`): an argument register
|
|
346
|
+
* at or past this many words is one no caller fills, so a read of it before any write is an
|
|
347
|
+
* uninitialised local the compiler homed there, never a parameter. KMC and gcc 2.7.2 home a local
|
|
348
|
+
* in `a2`/`a3` past a two-parameter declaration, agbcc in `r3` and mwcc 2.3.3 in `r5`..`r10`, and
|
|
349
|
+
* unbounded, each such read is a made-up parameter (`uninitfirst(a0, *a1, a2, a3)`).
|
|
350
|
+
*
|
|
351
|
+
* AN UPPER BOUND, because the direction of every error decides what it costs. A bound too HIGH
|
|
352
|
+
* leaves a register a parameter, as no bound at all does. A bound
|
|
353
|
+
* too LOW turns a real argument into `uninit_<reg>`, which is wrong C. So:
|
|
354
|
+
* - the declared words are laid out by {@link wordsOf}, the call's own rule: a floating parameter
|
|
355
|
+
* takes no general word where floats count on their own (`fpu.slots: 'separate'`, the PowerPC
|
|
356
|
+
* EABI), and everywhere else a floating parameter takes two, as a `long long` does (O32's
|
|
357
|
+
* leading floats still take their integer slots, and agbcc passes both in general words).
|
|
358
|
+
* A `float` takes two as well: a list cannot say whether the definition is prototyped, and an
|
|
359
|
+
* old-style one (`int k(a, b) float a; int b;`) is handed `a` promoted to `double`, so `b`
|
|
360
|
+
* sits in r2 / a2. Two words are an upper bound for a prototyped `float` too.
|
|
361
|
+
* - UP TO TWO HIDDEN WORDS ARE ADDED to a typed list where they may come first, because no
|
|
362
|
+
* parameter list states them and each pushes every declared parameter one register up. A return the
|
|
363
|
+
* declaration does not show is a scalar or `void` may be a struct handed back through a
|
|
364
|
+
* pointer in argument 0, and a symbol that may name a C++ member function is handed `this`
|
|
365
|
+
* there (`mangle.ts` `implicitArgumentWords`). Both are "may": a static member or a struct
|
|
366
|
+
* returned in registers leaves one register a parameter.
|
|
367
|
+
*
|
|
368
|
+
* THE PARAMETERS ARE THE STATED ONES, or where the declaration states none, the ones a mangled
|
|
369
|
+
* symbol records (`demangle`: `uninitfirst__FiPi` is `(int, int *)`), which is the compiler's own
|
|
370
|
+
* copy of the declaration. A BARE COUNT BOUNDS NOTHING: it may count argument registers
|
|
371
|
+
* (`FnProto.params`) or C parameters, any of which may be a pair or a struct passed by value of
|
|
372
|
+
* any size. IT ABSTAINS where neither states a list, or on one it cannot lay out: an
|
|
373
|
+
* unsized spelling (which may be two words), a struct passed by value (sized by a layout this
|
|
374
|
+
* does not read), a `long double`; and on a constructor or destructor, which
|
|
375
|
+
* may be handed a flag beside `this`. A variadic declaration states no `params` and so
|
|
376
|
+
* abstains: its arity is each caller's.
|
|
377
|
+
*
|
|
378
|
+
* `registerReturn` says the declared struct return is one the target hands back in registers, so
|
|
379
|
+
* no hidden pointer comes ahead of the parameters: the target's rule, which this module cannot
|
|
380
|
+
* read, so every reader asks through `aggregate.ts` `ownArgumentWordsOn`, which reads it as the call
|
|
381
|
+
* lowering does. */
|
|
382
|
+
export function ownArgumentWords(
|
|
383
|
+
name: string,
|
|
384
|
+
p: FnProto | undefined,
|
|
385
|
+
target: Pick<TargetDescription, 'fpu' | 'evenWordAlignedArgs'>,
|
|
386
|
+
registerReturn = false,
|
|
387
|
+
): number | undefined {
|
|
388
|
+
const own = ownArguments(name, p, target, registerReturn);
|
|
389
|
+
if (own === undefined || own.hidden === undefined) {
|
|
390
|
+
return undefined;
|
|
391
|
+
}
|
|
392
|
+
// The hidden words are laid out WITH the declared ones, not added after them: they come first,
|
|
393
|
+
// so they move where the even-word rule puts a 64-bit parameter. O32's `struct P f(double x)`
|
|
394
|
+
// passes the struct pointer in a0, skips a1 and passes `x` in a2:a3 — 4 words, where `1 +
|
|
395
|
+
// wordsOf([64])` says 3 and turns a3 into `uninit_a3`. The call side counts the same way
|
|
396
|
+
// (`frontend/call-plan.ts`, `[32, ...params]`).
|
|
397
|
+
return wordsOf([...Array.from({ length: own.hidden }, () => 32), ...own.widths], target);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/** WHETHER THE OWN DECLARATION SAYS THE CALLER FILLS ARGUMENT 0, asked of a function that RETURNS A
|
|
401
|
+
* WORD — so no hidden struct pointer is in question: `true` where a declared parameter word or
|
|
402
|
+
* `this` comes first, `false` where it states no argument word at all (`g__Fv`, a free
|
|
403
|
+
* function of none), and `undefined` where it does not say (no declaration, one {@link
|
|
404
|
+
* ownArgumentWords} cannot lay out, or no parameters on a symbol that only MAY be a method, which a
|
|
405
|
+
* static member is not). The PowerPC frontend asks it before a bare return hands back r3's entry
|
|
406
|
+
* value (`frontend/ppc.ts` `r3IsAnArgument`), and it reads the declaration the bound reads, so the
|
|
407
|
+
* two cannot give one declaration two answers. */
|
|
408
|
+
export function ownFirstArgumentPassed(
|
|
409
|
+
name: string,
|
|
410
|
+
p: FnProto | undefined,
|
|
411
|
+
target: Pick<TargetDescription, 'fpu' | 'evenWordAlignedArgs'>,
|
|
412
|
+
): boolean | undefined {
|
|
413
|
+
const own = ownArguments(name, p, target);
|
|
414
|
+
if (own === undefined) {
|
|
415
|
+
return undefined;
|
|
416
|
+
}
|
|
417
|
+
if (own.widths.length > 0 || own.passesThis === true) {
|
|
418
|
+
return true;
|
|
419
|
+
}
|
|
420
|
+
return own.passesThis === false ? false : undefined;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/** The own declaration as both readers above read it: the declared parameter words' widths, the
|
|
424
|
+
* hidden words that may come ahead of them (`undefined` where they cannot be bounded: a
|
|
425
|
+
* constructor's or destructor's flag), and whether `this` is surely passed (`true` for a
|
|
426
|
+
* constructor or destructor, `false` for a free function, `undefined` for a symbol that only may
|
|
427
|
+
* be a method).
|
|
428
|
+
*
|
|
429
|
+
* A bare count is no declaration this can lay out, so it reads as none. */
|
|
430
|
+
function ownArguments(
|
|
431
|
+
name: string,
|
|
432
|
+
p: FnProto | undefined,
|
|
433
|
+
target: Pick<TargetDescription, 'fpu' | 'evenWordAlignedArgs'>,
|
|
434
|
+
registerReturn = false,
|
|
435
|
+
): { widths: number[]; hidden: number | undefined; passesThis: boolean | undefined } | undefined {
|
|
436
|
+
// A COUNT BOUNDS NOTHING: it may mean argument registers or C parameters, and a C parameter
|
|
437
|
+
// may be a pair or a struct passed by value of any size (`f(struct S12 s)` {params: 1} fills 3
|
|
438
|
+
// words), so no layout of it is an upper bound.
|
|
439
|
+
if (typeof p?.params === 'number') {
|
|
440
|
+
return undefined;
|
|
441
|
+
}
|
|
442
|
+
const implicit = implicitArgumentWords(name);
|
|
443
|
+
const widths = ownParamWidths(name, p ?? undefined, target);
|
|
444
|
+
if (widths === undefined) {
|
|
445
|
+
return undefined;
|
|
446
|
+
}
|
|
447
|
+
return {
|
|
448
|
+
widths,
|
|
449
|
+
hidden: implicit === undefined ? undefined : (registerReturn || showsScalarReturn(p) ? 0 : 1) + implicit,
|
|
450
|
+
passesThis: implicit === undefined ? true : implicit === 0 ? false : undefined,
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/** Whether a declaration shows its return is a scalar or `void`, so no struct pointer is handed
|
|
455
|
+
* in ahead of its parameters: a `void`, or a stated return that is no aggregate and whose width
|
|
456
|
+
* or floating type this can read. A project typedef (`Pt`) shows nothing. */
|
|
457
|
+
function showsScalarReturn(p: FnProto | undefined): boolean {
|
|
458
|
+
return (
|
|
459
|
+
declaresVoidReturn(p) ||
|
|
460
|
+
(p?.returns !== undefined &&
|
|
461
|
+
!declaresAggregateReturn(p) &&
|
|
462
|
+
(declaredWidth(p.returns) !== undefined || isFloatingSpelling(p.returns)))
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/** The parameter list the own declaration states, or where it states none the one a mangled symbol
|
|
467
|
+
* records (`demangle`), as {@link ownArgumentWords} reads it. */
|
|
468
|
+
function ownParamList(name: string, p: FnProto | undefined): (FnProto & { params: ParamType[] }) | undefined {
|
|
469
|
+
const mangled = Array.isArray(p?.params) ? null : demangle(name);
|
|
470
|
+
const decl: FnProto | undefined = Array.isArray(p?.params)
|
|
471
|
+
? p
|
|
472
|
+
: mangled === null
|
|
473
|
+
? undefined
|
|
474
|
+
: { params: mangled.params.map(spellType) };
|
|
475
|
+
return Array.isArray(decl?.params) ? (decl as FnProto & { params: ParamType[] }) : undefined;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/** HOW MANY OF THE FIRST ARGUMENT REGISTERS SURELY HOLD ONE OF THE FUNCTION'S OWN PARAMETERS, each a
|
|
479
|
+
* single integer-class word — a LOWER bound, where {@link ownArgumentWords} is an upper one, and 0
|
|
480
|
+
* where the declaration says nothing surely.
|
|
481
|
+
*
|
|
482
|
+
* Its reader is the guessed-arity gap (`frontend/call-plan.ts` `decideGap`), which may read an
|
|
483
|
+
* argument register left at its incoming value as an argument passed on only where that register
|
|
484
|
+
* surely holds a parameter. So every "may" counts against it: a hidden word that may come first (a
|
|
485
|
+
* return the declaration does not show is a scalar, a symbol that may be a method) makes it 0,
|
|
486
|
+
* and the count stops at the first parameter that is not one integer word — a floating one may
|
|
487
|
+
* travel in the FPU's registers and leave its general words holding nothing (O32's leading
|
|
488
|
+
* float), a wide or unsized one takes words this does not place.
|
|
489
|
+
*
|
|
490
|
+
* {@link ownLeadingWordRegisters} asks the other question the same run of parameters answers. */
|
|
491
|
+
export function ownLeadingWordParams(
|
|
492
|
+
name: string,
|
|
493
|
+
p: FnProto | undefined,
|
|
494
|
+
target: Pick<TargetDescription, 'fpu' | 'evenWordAlignedArgs'>,
|
|
495
|
+
registerReturn = false,
|
|
496
|
+
): number {
|
|
497
|
+
const decl = ownParamList(name, p);
|
|
498
|
+
const own = ownArguments(name, p, target, registerReturn);
|
|
499
|
+
return decl === undefined || own === undefined || own.hidden !== 0 ? 0 : leadingWordRun(decl);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/** HOW MANY OF THE FIRST ARGUMENT REGISTERS SURELY HOLD ONE INTEGER WORD, WHOEVER'S, by the own
|
|
503
|
+
* declaration (or the parameter list a mangled symbol records), and 0 where it states no list. A
|
|
504
|
+
* hidden word (a struct return's pointer, `this`, a constructor's flag) is a word too, and only
|
|
505
|
+
* moves the parameters to later registers, so each of the first that-many registers holds a word
|
|
506
|
+
* whichever way it falls, and a later parameter this cannot place does not move the ones before
|
|
507
|
+
* it. Its reader is the Thumb frame's by-value home test (`frontend/thumb.ts` `argumentHomes`): a
|
|
508
|
+
* register that surely holds a word is no by-value struct's home. */
|
|
509
|
+
export function ownLeadingWordRegisters(name: string, p: FnProto | undefined): number {
|
|
510
|
+
const decl = ownParamList(name, p);
|
|
511
|
+
return decl === undefined ? 0 : leadingWordRun(decl);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/** The run of leading parameters that are each one integer-class word: it stops at the first
|
|
515
|
+
* floating one or the first {@link paramClassOf} does not call `'word'`. */
|
|
516
|
+
function leadingWordRun(decl: FnProto & { params: ParamType[] }): number {
|
|
517
|
+
let n = 0;
|
|
518
|
+
while (
|
|
519
|
+
n < decl.params.length &&
|
|
520
|
+
!isFloatingSpelling(decl.params[n]) &&
|
|
521
|
+
paramClassOf(decl, decl.params, n) === 'word'
|
|
522
|
+
) {
|
|
523
|
+
n++;
|
|
524
|
+
}
|
|
525
|
+
return n;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/** Whether the own declaration (or the parameter list a mangled symbol records) states a floating
|
|
529
|
+
* parameter, or `undefined` where it states no list. Its reader is the guessed-arity gap
|
|
530
|
+
* (`frontend/call-plan.ts` `decidedCallRefusal`): a float may travel in an FPU register that no
|
|
531
|
+
* guess counts. */
|
|
532
|
+
export function ownDeclaresFloatingParam(name: string, p: FnProto | undefined): boolean | undefined {
|
|
533
|
+
return ownParamList(name, p)?.params.some((t) => isFloatingSpelling(t));
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/** The widths `ownArgumentWords` lays out: one per declared parameter that takes general words. */
|
|
537
|
+
function ownParamWidths(
|
|
538
|
+
name: string,
|
|
539
|
+
p: FnProto | undefined,
|
|
540
|
+
target: Pick<TargetDescription, 'fpu'>,
|
|
541
|
+
): number[] | undefined {
|
|
542
|
+
const decl = ownParamList(name, p);
|
|
543
|
+
if (decl === undefined) {
|
|
544
|
+
return undefined;
|
|
545
|
+
}
|
|
546
|
+
const params = decl.params;
|
|
547
|
+
const widths: number[] = [];
|
|
548
|
+
for (const k of params.keys()) {
|
|
549
|
+
const cls = paramClassOf(decl, params, k);
|
|
550
|
+
if (cls === 'word' || cls === 'wide') {
|
|
551
|
+
widths.push(cls === 'word' ? 32 : (declaredWidth(params[k]) ?? 64));
|
|
552
|
+
} else if (cls === 'float') {
|
|
553
|
+
const bare = params[k]
|
|
554
|
+
.replace(/\b(?:const|volatile)\b/g, ' ')
|
|
555
|
+
.trim()
|
|
556
|
+
.replace(/\s+/g, ' ');
|
|
557
|
+
const spelled = FLOAT_TYPEDEFS.get(bare) ?? bare;
|
|
558
|
+
if (spelled === 'long double') {
|
|
559
|
+
return undefined;
|
|
560
|
+
}
|
|
561
|
+
// a `float` may be an old-style definition's, promoted to `double`
|
|
562
|
+
if (target.fpu?.slots !== 'separate') {
|
|
563
|
+
widths.push(64);
|
|
564
|
+
}
|
|
565
|
+
} else {
|
|
566
|
+
return undefined;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return widths;
|
|
570
|
+
}
|
|
571
|
+
|
|
216
572
|
/** Whether a declared type is `double`, qualifiers aside. */
|
|
217
573
|
const isDoubleSpelling = (t: ParamType): boolean => t.replace(/\b(?:const|volatile)\b/g, ' ').trim() === 'double';
|
|
218
574
|
|
|
219
|
-
/**
|
|
220
|
-
*
|
|
221
|
-
export const
|
|
575
|
+
/** The floating type a declared type is, qualifiers aside — a C keyword, or a typedef of one every
|
|
576
|
+
* project declares (`FLOAT_TYPEDEFS`) — or undefined for any other type. */
|
|
577
|
+
export const floatingSpelling = (t: ParamType): 'float' | 'double' | 'long double' | undefined => {
|
|
222
578
|
const bare = t
|
|
223
579
|
.replace(/\b(?:const|volatile)\b/g, ' ')
|
|
224
580
|
.trim()
|
|
225
581
|
.replace(/\s+/g, ' ');
|
|
226
|
-
|
|
582
|
+
const spelled = FLOAT_TYPEDEFS.get(bare) ?? bare;
|
|
583
|
+
return spelled === 'float' || spelled === 'double' || spelled === 'long double' ? spelled : undefined;
|
|
227
584
|
};
|
|
228
585
|
|
|
586
|
+
/** Whether a declared type is a floating type (`floatingSpelling`). */
|
|
587
|
+
export const isFloatingSpelling = (t: ParamType): boolean => floatingSpelling(t) !== undefined;
|
|
588
|
+
|
|
229
589
|
/** A signature the C standard fixes is a COMPLETE one, which an `FnProto` is not: a project
|
|
230
590
|
* prototype is a lower bound assembled from whatever a header extraction could read, and omits
|
|
231
591
|
* what it could not. The standard omits nothing, so the RETURN is spelled here and is required —
|
|
@@ -233,6 +593,16 @@ export const isFloatingSpelling = (t: ParamType): boolean => {
|
|
|
233
593
|
interface StandardSignature extends FnProto {
|
|
234
594
|
/** the return type, as its C spelling (`"void *"`, `"u32"`, `"void"`) */
|
|
235
595
|
returns: ParamType;
|
|
596
|
+
/** for a BLOCK COPY, the operand each role is: it reads `[0, n)` through `src`, writes `[0, n)`
|
|
597
|
+
* through `dst`, and keeps neither address (`blockCopyRoles`) */
|
|
598
|
+
copies?: BlockCopyRoles;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/** The operands of a standard block copy, by role (`StandardSignature.copies`). */
|
|
602
|
+
export interface BlockCopyRoles {
|
|
603
|
+
readonly dst: number;
|
|
604
|
+
readonly src: number;
|
|
605
|
+
readonly n: number;
|
|
236
606
|
}
|
|
237
607
|
|
|
238
608
|
/** Signatures FIXED BY THE C STANDARD, so they are not a project fact and need no header to be
|
|
@@ -251,9 +621,20 @@ interface StandardSignature extends FnProto {
|
|
|
251
621
|
* standard and equally addable, and they are absent because nothing measures them — a table
|
|
252
622
|
* grown by appetite would be a table nobody priced. */
|
|
253
623
|
export const STANDARD_SIGNATURES: Record<string, StandardSignature> = {
|
|
254
|
-
memcpy: { params: ['void *', 'const void *', 'u32'], returns: 'void *' },
|
|
624
|
+
memcpy: { params: ['void *', 'const void *', 'u32'], returns: 'void *', copies: { dst: 0, src: 1, n: 2 } },
|
|
255
625
|
};
|
|
256
626
|
|
|
627
|
+
/** The roles of `callee`'s operands where the C standard fixes it as a BLOCK COPY, or `undefined`.
|
|
628
|
+
* ONE TABLE for every reader: the frame audit bounds the bytes a copy with a literal count reaches
|
|
629
|
+
* (frontend/frame-objects.ts `boundedCopy`), and the constant-initializer fold reads the copy an
|
|
630
|
+
* aggregate initializer compiles to (l3/constinit.ts). `memmove` is equally fixed, and absent for
|
|
631
|
+
* the reason the list above is short. */
|
|
632
|
+
export function blockCopyRoles(callee: unknown): BlockCopyRoles | undefined {
|
|
633
|
+
return typeof callee === 'string' && Object.hasOwn(STANDARD_SIGNATURES, callee)
|
|
634
|
+
? STANDARD_SIGNATURES[callee].copies
|
|
635
|
+
: undefined;
|
|
636
|
+
}
|
|
637
|
+
|
|
257
638
|
/** Whether a declaration says the function returns NO VALUE — the one question `returnsVoid` was
|
|
258
639
|
* added for, asked through both keys that can answer it.
|
|
259
640
|
*
|
|
@@ -271,6 +652,18 @@ export function declaresVoidReturn(p: FnProto | undefined): boolean {
|
|
|
271
652
|
return p?.returnsVoid === true || p?.returns?.trim() === 'void';
|
|
272
653
|
}
|
|
273
654
|
|
|
655
|
+
/** The bit width of the scalar the function UNDER DECOMPILATION declares it returns, or `undefined`
|
|
656
|
+
* where its declaration states none this can read: no `returns`, a struct or union by value, a
|
|
657
|
+
* floating type or a project typedef (`declaredWidth`'s "no opinion"). A pointer answers 32,
|
|
658
|
+
* `long long` 64.
|
|
659
|
+
*
|
|
660
|
+
* Not {@link declaredReturnWidth}, which answers for a CALLEE and only where the candidate can
|
|
661
|
+
* print its declaration: the lifted function's own signature is the one the candidate defines,
|
|
662
|
+
* so whether its parameters are spellable says nothing about what it returns. */
|
|
663
|
+
export function ownReturnWidth(p: FnProto | undefined): number | undefined {
|
|
664
|
+
return p?.returns === undefined || declaresAggregateReturn(p) ? undefined : declaredWidth(p.returns);
|
|
665
|
+
}
|
|
666
|
+
|
|
274
667
|
/** Whether a declaration says nothing any reader acts on about the return — no spelling, no
|
|
275
668
|
* layout, and no `returnsVoid: true`. `returnsVoid: false` is silence (`declaresVoidReturn`), and
|
|
276
669
|
* it is how a manifest spells a callee it knows only the parameters of. Such an entry can take a
|
|
@@ -370,6 +763,26 @@ export function declaredWidth(t: ParamType): number | undefined {
|
|
|
370
763
|
return BASE_WIDTHS.get(base === '' && s !== '' ? 'int' : base);
|
|
371
764
|
}
|
|
372
765
|
|
|
766
|
+
/** Whether one declared parameter type spells a signed integer, or `undefined` where it does not say:
|
|
767
|
+
* a project typedef, a pointer, a plain `char` (whose signedness is the compiler's). */
|
|
768
|
+
export function declaredSigned(t: ParamType): boolean | undefined {
|
|
769
|
+
const s = t
|
|
770
|
+
.replace(/\b(?:const|volatile)\b/g, ' ')
|
|
771
|
+
.trim()
|
|
772
|
+
.replace(/\s+/g, ' ');
|
|
773
|
+
if (isAddress(s)) {
|
|
774
|
+
return undefined;
|
|
775
|
+
}
|
|
776
|
+
const own = /^([su])(8|16|32|64)$/.exec(s);
|
|
777
|
+
if (own) {
|
|
778
|
+
return own[1] === 's';
|
|
779
|
+
}
|
|
780
|
+
if (/\bunsigned\b/.test(s) || /^uint\d+_t$/.test(s)) {
|
|
781
|
+
return false;
|
|
782
|
+
}
|
|
783
|
+
return /\bsigned\b/.test(s) || /^int\d+_t$/.test(s) || /^(?:short|int|long)\b/.test(s) ? true : undefined;
|
|
784
|
+
}
|
|
785
|
+
|
|
373
786
|
/** Whether the type name `s` is a pointer by its abstract declarator, the derivation nearest the
|
|
374
787
|
* absent name: `u8 *`, `void (*)(s32)`, `f32 (*)[3]`, `void (**)(void)`. A C++ pointer to a data
|
|
375
788
|
* member is one too, and a pointer to a member function is not: compiled with mwcc GC/1.2.5n,
|
|
@@ -519,15 +932,25 @@ export function spellableProto(
|
|
|
519
932
|
p: FnProto | undefined,
|
|
520
933
|
target: Pick<TargetDescription, 'doubleArgWords'>,
|
|
521
934
|
returned?: IrType,
|
|
935
|
+
byValue?: ReadonlySet<number>,
|
|
522
936
|
): { readonly params: readonly ParamType[]; readonly returns: ParamType } | undefined {
|
|
523
937
|
// A struct returned through memory prints as the headers spell the struct the lift typed its call
|
|
524
938
|
// as (`returned`), which the declarations block defines beside this prototype (declare.ts). With
|
|
525
939
|
// no such call it is a return this cannot print.
|
|
526
|
-
|
|
940
|
+
// A callee passed a struct by value prints whenever its declaration is complete, `returnsVoid`
|
|
941
|
+
// standing for `void`: the struct is defined only beside it, and the local the lift passes is of
|
|
942
|
+
// that type, so without it the candidate cannot declare its own local.
|
|
943
|
+
const passesStruct = byValue !== undefined && byValue.size > 0;
|
|
944
|
+
const returns =
|
|
945
|
+
returned?.kind === 'struct'
|
|
946
|
+
? (returned.declared ?? `struct ${returned.name}`)
|
|
947
|
+
: (p?.returns ?? (passesStruct && p?.returnsVoid === true ? 'void' : undefined));
|
|
527
948
|
if (
|
|
528
949
|
p === undefined ||
|
|
529
950
|
returns === undefined ||
|
|
530
|
-
(returned === undefined &&
|
|
951
|
+
(returned === undefined &&
|
|
952
|
+
!(passesStruct && returns.trim() === 'void') &&
|
|
953
|
+
(!spellableType(returns) || declaredWidth(returns) === undefined))
|
|
531
954
|
) {
|
|
532
955
|
return undefined;
|
|
533
956
|
}
|
|
@@ -535,7 +958,19 @@ export function spellableProto(
|
|
|
535
958
|
return { params: [], returns };
|
|
536
959
|
}
|
|
537
960
|
// A parameter prints where the frontend lays it out (`declaredCallArgs`): a type this spells, or a
|
|
538
|
-
// `double` the target gives general words, which as a C keyword needs no typedef
|
|
961
|
+
// `double` the target gives general words, which as a C keyword needs no typedef — or a struct
|
|
962
|
+
// the lift passed by value (`byValue`, by parameter index), which the declarations block defines
|
|
963
|
+
// beside this prototype from its `byValueLayouts`, as it defines a returned one.
|
|
964
|
+
if (Array.isArray(p.params) && byValue !== undefined && passesStruct) {
|
|
965
|
+
const params = p.params;
|
|
966
|
+
const rest = params.map((t, i) => (byValue.has(i) ? 'u32' : t));
|
|
967
|
+
const laid = declaredCallArgs({ ...p, params: rest }, target);
|
|
968
|
+
return laid !== undefined &&
|
|
969
|
+
[...byValue].every((i) => p.byValueLayouts !== undefined && Object.hasOwn(p.byValueLayouts, i)) &&
|
|
970
|
+
rest.every((t, i) => laid.doubles.has(i) || spellableType(t))
|
|
971
|
+
? { params, returns }
|
|
972
|
+
: undefined;
|
|
973
|
+
}
|
|
539
974
|
const laid = declaredCallArgs(p, target);
|
|
540
975
|
return Array.isArray(p.params) &&
|
|
541
976
|
laid !== undefined &&
|
|
@@ -586,16 +1021,96 @@ export function validatePrototypes(value: unknown): string[] {
|
|
|
586
1021
|
continue;
|
|
587
1022
|
}
|
|
588
1023
|
for (const key of Object.keys(proto)) {
|
|
589
|
-
if (
|
|
590
|
-
|
|
1024
|
+
if (
|
|
1025
|
+
key !== 'params' &&
|
|
1026
|
+
key !== 'returnsVoid' &&
|
|
1027
|
+
key !== 'returns' &&
|
|
1028
|
+
key !== 'returnLayout' &&
|
|
1029
|
+
key !== 'wordParams' &&
|
|
1030
|
+
key !== 'byValueParams' &&
|
|
1031
|
+
key !== 'byValueLayouts' &&
|
|
1032
|
+
key !== 'variadicParams' &&
|
|
1033
|
+
key !== 'fromContext'
|
|
1034
|
+
) {
|
|
1035
|
+
problems.push(
|
|
1036
|
+
`${sym}: unknown key "${key}" (expected "params", "returnsVoid", "returns", "returnLayout", "wordParams", ` +
|
|
1037
|
+
'"byValueParams", "byValueLayouts", "variadicParams" or "fromContext")',
|
|
1038
|
+
);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
if ('fromContext' in proto && typeof (proto as { fromContext?: unknown }).fromContext !== 'boolean') {
|
|
1042
|
+
problems.push(`${sym}: "fromContext" must be a boolean`);
|
|
1043
|
+
}
|
|
1044
|
+
const { params, returnsVoid, returns, returnLayout, wordParams, byValueParams, byValueLayouts, variadicParams } =
|
|
1045
|
+
proto as {
|
|
1046
|
+
params?: unknown;
|
|
1047
|
+
returnsVoid?: unknown;
|
|
1048
|
+
returns?: unknown;
|
|
1049
|
+
returnLayout?: unknown;
|
|
1050
|
+
wordParams?: unknown;
|
|
1051
|
+
byValueParams?: unknown;
|
|
1052
|
+
byValueLayouts?: unknown;
|
|
1053
|
+
variadicParams?: unknown;
|
|
1054
|
+
};
|
|
1055
|
+
// a variadic declaration's fixed parameters, which it states beside no typed list
|
|
1056
|
+
if (
|
|
1057
|
+
variadicParams !== undefined &&
|
|
1058
|
+
!(Array.isArray(variadicParams) && variadicParams.every((t) => typeof t === 'string') && params === undefined)
|
|
1059
|
+
) {
|
|
1060
|
+
problems.push(`${sym}: "variadicParams" must be a list of parameter types, beside no "params"`);
|
|
1061
|
+
}
|
|
1062
|
+
const listed = Array.isArray(params) ? params : Array.isArray(variadicParams) ? variadicParams : undefined;
|
|
1063
|
+
for (const [key, list] of [
|
|
1064
|
+
['wordParams', wordParams],
|
|
1065
|
+
['byValueParams', byValueParams],
|
|
1066
|
+
] as const) {
|
|
1067
|
+
if (
|
|
1068
|
+
list !== undefined &&
|
|
1069
|
+
!(
|
|
1070
|
+
listed !== undefined &&
|
|
1071
|
+
Array.isArray(list) &&
|
|
1072
|
+
list.every((k) => Number.isInteger(k) && k >= 0 && k < listed.length)
|
|
1073
|
+
)
|
|
1074
|
+
) {
|
|
1075
|
+
problems.push(`${sym}: "${key}" must list indices into a typed "params" or "variadicParams" list`);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
// Two answers to one question — whether the parameter travels in a register or as a copy.
|
|
1079
|
+
if (
|
|
1080
|
+
Array.isArray(wordParams) &&
|
|
1081
|
+
Array.isArray(byValueParams) &&
|
|
1082
|
+
wordParams.some((k) => byValueParams.includes(k))
|
|
1083
|
+
) {
|
|
1084
|
+
problems.push(`${sym}: "wordParams" and "byValueParams" both list one parameter`);
|
|
1085
|
+
}
|
|
1086
|
+
// A layout keyed by a parameter the list does not pass by value would size a word as a struct.
|
|
1087
|
+
if (byValueLayouts !== undefined) {
|
|
1088
|
+
if (typeof byValueLayouts !== 'object' || byValueLayouts === null || Array.isArray(byValueLayouts)) {
|
|
1089
|
+
problems.push(`${sym}: "byValueLayouts" must map a parameter index to its layout`);
|
|
1090
|
+
} else {
|
|
1091
|
+
for (const [key, layout] of Object.entries(byValueLayouts)) {
|
|
1092
|
+
const k = Number(key);
|
|
1093
|
+
if (
|
|
1094
|
+
!Array.isArray(params) ||
|
|
1095
|
+
!params.every((t) => typeof t === 'string') ||
|
|
1096
|
+
!/^\d+$/.test(key) ||
|
|
1097
|
+
k >= params.length ||
|
|
1098
|
+
paramClassOf(
|
|
1099
|
+
{
|
|
1100
|
+
wordParams: Array.isArray(wordParams) ? wordParams : [],
|
|
1101
|
+
byValueParams: Array.isArray(byValueParams) ? byValueParams : [],
|
|
1102
|
+
},
|
|
1103
|
+
params,
|
|
1104
|
+
k,
|
|
1105
|
+
) !== 'by-value'
|
|
1106
|
+
) {
|
|
1107
|
+
problems.push(`${sym}: "byValueLayouts" key "${key}" is not a parameter the typed "params" pass by value`);
|
|
1108
|
+
continue;
|
|
1109
|
+
}
|
|
1110
|
+
problems.push(...layoutProblems(`${sym}: "byValueLayouts" ${key}`, layout));
|
|
1111
|
+
}
|
|
591
1112
|
}
|
|
592
1113
|
}
|
|
593
|
-
const { params, returnsVoid, returns, returnLayout } = proto as {
|
|
594
|
-
params?: unknown;
|
|
595
|
-
returnsVoid?: unknown;
|
|
596
|
-
returns?: unknown;
|
|
597
|
-
returnLayout?: unknown;
|
|
598
|
-
};
|
|
599
1114
|
if (returnLayout !== undefined) {
|
|
600
1115
|
problems.push(...layoutProblems(`${sym}: "returnLayout"`, returnLayout));
|
|
601
1116
|
// A layout beside a return that says `void` or spells a scalar is two answers to one question
|