@asmlift/core 0.5.0 → 0.6.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 +22 -16
- package/package.json +1 -1
- package/src/backend/c.ts +1 -0
- package/src/backend/cfamily.ts +238 -167
- package/src/backend/cpp.ts +1 -0
- package/src/backend/pascal.ts +26 -12
- package/src/contracts.ts +194 -39
- package/src/declare.ts +41 -4
- package/src/frontend/mips.ts +11 -0
- package/src/frontend/ppc.ts +43 -7
- package/src/frontend/ssa.ts +404 -29
- package/src/frontend/thumb.ts +2176 -686
- package/src/ir/alias.ts +54 -0
- package/src/ir/bits.ts +75 -0
- package/src/ir/core.ts +337 -2
- package/src/ir/opcodes.ts +140 -21
- package/src/ir/parse.ts +19 -2
- package/src/ir/print.ts +27 -2
- package/src/ir/simplify.ts +190 -3
- package/src/ir/struct-names.ts +42 -0
- package/src/ir/verify.ts +43 -49
- package/src/l3/address.ts +62 -0
- package/src/l3/argbase.ts +2 -1
- package/src/l3/ast.ts +464 -57
- package/src/l3/basecse.ts +664 -76
- package/src/l3/coalesce.ts +429 -43
- package/src/l3/dce.ts +31 -9
- package/src/l3/gates.ts +21 -0
- package/src/l3/hoist.ts +293 -14
- package/src/l3/homesplit.ts +285 -0
- package/src/l3/initfirst.ts +301 -0
- package/src/l3/inlinebase.ts +193 -0
- package/src/l3/mentions.ts +113 -0
- package/src/l3/mulfirst.ts +42 -0
- package/src/l3/nearbase.ts +152 -0
- package/src/l3/offmember.ts +371 -0
- package/src/l3/parkfirst.ts +96 -0
- package/src/l3/pollguard.ts +154 -0
- package/src/l3/ptrfield.ts +227 -0
- package/src/l3/regspell.ts +110 -85
- package/src/l3/reindex.ts +715 -78
- package/src/l3/scopebase.ts +644 -218
- package/src/l3/sinkinit.ts +40 -0
- package/src/l3/slotorder.ts +123 -0
- package/src/l3/storage.ts +48 -0
- package/src/l3/symbol-refs.ts +41 -8
- package/src/l3/tailmerge.ts +15 -0
- package/src/l3/typing.ts +198 -9
- package/src/l3/unmerge.ts +263 -0
- package/src/l3/unreduce.ts +971 -0
- package/src/l3/volatileptr.ts +207 -0
- package/src/l3/volatileval.ts +130 -0
- package/src/l3/volstore.ts +229 -0
- package/src/l3/zerosub.ts +62 -0
- package/src/pattern/engine.ts +236 -13
- package/src/pipeline.ts +157 -56
- package/src/proto.ts +112 -14
- package/src/raise/arrays.ts +6 -1
- package/src/raise/divpow2.ts +2 -2
- package/src/raise/globalshape.ts +1038 -0
- package/src/raise/gvn.ts +33 -18
- package/src/raise/latch.ts +126 -0
- package/src/raise/memberarrays.ts +594 -0
- package/src/raise/narrow.ts +124 -0
- package/src/raise/narrowlocal.ts +556 -0
- package/src/raise/paramwidth.ts +179 -0
- package/src/raise/pre-recovery.ts +97 -14
- package/src/raise/recover.ts +56 -23
- package/src/raise/retsink.ts +210 -10
- package/src/raise/shortcircuit.ts +474 -74
- package/src/raise/struct-arrays.ts +19 -2
- package/src/raise/structs.ts +33 -3
- package/src/rank-axes.ts +630 -0
- package/src/rank-declare.ts +256 -0
- package/src/rank.ts +1723 -272
- package/src/structure/analysis.ts +1392 -141
- package/src/structure/bitfields.ts +332 -0
- package/src/structure/globalaccess.ts +274 -0
- package/src/structure/hazards.ts +411 -20
- package/src/structure/loops.ts +2 -49
- package/src/structure/namecoalesce.ts +435 -0
- package/src/structure/structure.ts +2678 -526
- package/src/structure/switch-recover.ts +616 -144
- package/src/symbols.ts +62 -1
- package/src/target.ts +367 -24
- package/src/trace.ts +111 -32
package/src/proto.ts
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
|
-
import type { SymbolMap } from './symbols';
|
|
1
|
+
import type { SymbolMap, SymbolTypeFacts } from './symbols';
|
|
2
2
|
|
|
3
3
|
// asmlift — function prototypes: the single carrier for the caller-supplied facts a
|
|
4
|
-
// matching-decomp project reads from its headers (arg counts, void-ness). One
|
|
5
|
-
// map, keyed by symbol, is threaded through every entry point and resolved at the
|
|
6
|
-
// use — a callee's `params` gives its call-site arity, a function's own entry gives its
|
|
7
|
-
// `returnsVoid
|
|
8
|
-
// not a grab-bag of ISA-specific options.
|
|
4
|
+
// matching-decomp project reads from its headers (arg counts, parameter widths, void-ness). One
|
|
5
|
+
// `Prototypes` map, keyed by symbol, is threaded through every entry point and resolved at the
|
|
6
|
+
// point of use — a callee's `params` gives its call-site arity, a function's own entry gives its
|
|
7
|
+
// `returnsVoid` and the widths raise/paramwidth.ts checks against. It also keeps the frontend seam
|
|
8
|
+
// honest: a frontend receives prototypes, not a grab-bag of ISA-specific options.
|
|
9
9
|
|
|
10
|
-
/** One declared parameter, as its C type text (`"u8"`, `"s32"`, `"void *"`).
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
/** One declared parameter, as its C type text (`"u8"`, `"s32"`, `"void *"`, `"int"`). Two facts
|
|
11
|
+
* are read off it: the list's LENGTH is the call-site arity (`protoArity`), and one entry's WIDTH
|
|
12
|
+
* (`declaredWidth`) is what raise/paramwidth.ts checks its inference against.
|
|
13
|
+
*
|
|
14
|
+
* A DECLARED WIDTH ONLY VETOES, never pins. Where the asm carries a prologue extension the
|
|
15
|
+
* declaration contradicts, the declaration wins — it is a fact from the project's headers, where
|
|
16
|
+
* the extension is an inference off an encoding two different C sources produce. Where the asm
|
|
17
|
+
* carries no extension, this list is NOT consulted: pinning there would type every parameter of
|
|
18
|
+
* every row from the declaration, and a declared `u32` kills rank.ts's signed arm before the
|
|
19
|
+
* differ ever sees it. That half is an axis question and is not answered here. */
|
|
13
20
|
export type ParamType = string;
|
|
14
21
|
|
|
15
22
|
/** What the headers know about one function. All fields optional: a partial table (only
|
|
@@ -17,10 +24,26 @@ export type ParamType = string;
|
|
|
17
24
|
export interface FnProto {
|
|
18
25
|
/** declared parameters — either a bare arity COUNT or the typed parameter list a header
|
|
19
26
|
* extraction produces (`["u8", "s32"]`). BOTH forms yield the call-site arity via
|
|
20
|
-
* `protoArity`;
|
|
27
|
+
* `protoArity`; only the typed form carries a width. Omit to let the frontend fall back to its
|
|
28
|
+
* contiguous-arg-register heuristic. */
|
|
21
29
|
params?: number | ParamType[];
|
|
22
|
-
/**
|
|
23
|
-
* return register that must not surface as a `return`
|
|
30
|
+
/** The declared return type is `void`. Read for the function under decompilation, where a
|
|
31
|
+
* trailing `bx lr` leaves a meaningless return register that must not surface as a `return`
|
|
32
|
+
* value — and, since the out-parameter path landed, for a CALLEE, where it is the only thing
|
|
33
|
+
* that tells an out-parameter frame from a hidden struct-return pointer.
|
|
34
|
+
*
|
|
35
|
+
* THAT SECOND READER IS LOAD-BEARING AND THE FIELD IS UNCHECKED DATA, which is worth knowing
|
|
36
|
+
* before authoring one. `validatePrototypes` type-checks the boolean and can check no more:
|
|
37
|
+
* nothing in the assembly distinguishes the two frames, which is why the refusal exists. So a
|
|
38
|
+
* callee wrongly declared `void` turns a loud decline into a compiling, plausible, wrong
|
|
39
|
+
* program — measured on the shape the guard is for, `struct S4 mk(int); struct S4 s = mk(x);`
|
|
40
|
+
* lifts as `mk(&sp0); return (u8)sp0;` when `mk` is declared `returnsVoid` — where a callee
|
|
41
|
+
* wrongly declared NON-void, or left undeclared, only costs the lift. Under-declaring is the
|
|
42
|
+
* safe direction and this project has already shipped one wrong entry (a dataset row declaring
|
|
43
|
+
* `returnsVoid: true` for a function whose own reference returns `void *`).
|
|
44
|
+
*
|
|
45
|
+
* Both readers see one field through two trust levels: caller-supplied on `--proto`, and
|
|
46
|
+
* machine-derived from DWARF through `prototypesFromSymbols`. Neither is distinguished here. */
|
|
24
47
|
returnsVoid?: boolean;
|
|
25
48
|
}
|
|
26
49
|
|
|
@@ -43,10 +66,85 @@ export function protoArity(p: FnProto | undefined): number | undefined {
|
|
|
43
66
|
return undefined;
|
|
44
67
|
}
|
|
45
68
|
|
|
69
|
+
/** Bit width per C89 base type on every target asmlift lifts (all ILP32). `long` is 32 here and
|
|
70
|
+
* would not be on an LP64 host, so it is a target fact rather than a language one. */
|
|
71
|
+
const BASE_WIDTHS: ReadonlyMap<string, number> = new Map([
|
|
72
|
+
['char', 8],
|
|
73
|
+
['short', 16],
|
|
74
|
+
['short int', 16],
|
|
75
|
+
['int', 32],
|
|
76
|
+
['long', 32],
|
|
77
|
+
['long int', 32],
|
|
78
|
+
]);
|
|
79
|
+
|
|
80
|
+
/** The bit width one declared parameter type spells, or `undefined` for a spelling this does not
|
|
81
|
+
* read — a project typedef, a struct, a `float`. UNDEFINED IS "NO OPINION", never "wide": the one
|
|
82
|
+
* consumer treats a width it can read as authority and a width it cannot as absence, so an
|
|
83
|
+
* unrecognized spelling leaves the asm's own inference standing.
|
|
84
|
+
*
|
|
85
|
+
* A pointer is register-wide whatever it points at, which is the fact the `*` test carries. */
|
|
86
|
+
export function declaredWidth(t: ParamType): number | undefined {
|
|
87
|
+
const s = t
|
|
88
|
+
.replace(/\b(?:const|volatile)\b/g, ' ')
|
|
89
|
+
.trim()
|
|
90
|
+
.replace(/\s+/g, ' ');
|
|
91
|
+
if (s.endsWith('*')) {
|
|
92
|
+
return 32;
|
|
93
|
+
}
|
|
94
|
+
const own = /^([su])(8|16|32)$/.exec(s);
|
|
95
|
+
if (own) {
|
|
96
|
+
return Number(own[2]);
|
|
97
|
+
}
|
|
98
|
+
// `unsigned`/`signed` alone is `unsigned int`/`signed int`; the signedness itself is not a width.
|
|
99
|
+
const base = s
|
|
100
|
+
.replace(/\b(?:signed|unsigned)\b/g, ' ')
|
|
101
|
+
.trim()
|
|
102
|
+
.replace(/\s+/g, ' ');
|
|
103
|
+
return BASE_WIDTHS.get(base === '' && s !== '' ? 'int' : base);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Problems with a HAND-WRITTEN prototype table — empty when it is well formed.
|
|
107
|
+
*
|
|
108
|
+
* `protoArity` above falls back to the arg-register heuristic on a `params` it cannot read, which
|
|
109
|
+
* is right when `params` is omitted and silent when it is mistyped: `params: "2"` then decompiles
|
|
110
|
+
* at a guessed arity, and a misspelled `returnsVoid` does nothing at all. Neither is visible in
|
|
111
|
+
* the output, so a table that came from outside is checked before it reaches either. */
|
|
112
|
+
export function validatePrototypes(value: unknown): string[] {
|
|
113
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
114
|
+
return ['must be an object mapping a symbol name to its prototype'];
|
|
115
|
+
}
|
|
116
|
+
const problems: string[] = [];
|
|
117
|
+
for (const [sym, proto] of Object.entries(value)) {
|
|
118
|
+
if (typeof proto !== 'object' || proto === null || Array.isArray(proto)) {
|
|
119
|
+
problems.push(`${sym}: must be an object, e.g. {"params": 2}`);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
for (const key of Object.keys(proto)) {
|
|
123
|
+
if (key !== 'params' && key !== 'returnsVoid') {
|
|
124
|
+
problems.push(`${sym}: unknown key "${key}" (expected "params" or "returnsVoid")`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const { params, returnsVoid } = proto as { params?: unknown; returnsVoid?: unknown };
|
|
128
|
+
if (params !== undefined) {
|
|
129
|
+
const countOk = typeof params === 'number' && Number.isInteger(params) && params >= 0;
|
|
130
|
+
const listOk = Array.isArray(params) && params.every((t) => typeof t === 'string');
|
|
131
|
+
if (!countOk && !listOk) {
|
|
132
|
+
problems.push(`${sym}: "params" must be a non-negative integer or a list of type strings`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (returnsVoid !== undefined && typeof returnsVoid !== 'boolean') {
|
|
136
|
+
problems.push(`${sym}: "returnsVoid" must be a boolean`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return problems;
|
|
140
|
+
}
|
|
141
|
+
|
|
46
142
|
/** The C type spelling for one declared parameter/return, or null when the facts do not
|
|
47
143
|
* determine one. A pointer is `void *` — address-identical to any object pointer, and asmlift
|
|
48
|
-
* makes every stride explicit — so nothing is guessed about what it points at.
|
|
49
|
-
|
|
144
|
+
* makes every stride explicit — so nothing is guessed about what it points at. A richer spelling
|
|
145
|
+
* would also be INERT: `declaredWidth` answers 32 for every `*`, and a CALLEE's parameter types
|
|
146
|
+
* are read for the list's length alone (test/param-pointee-axis.test.ts). */
|
|
147
|
+
function typeSpelling(t: SymbolTypeFacts): ParamType | null {
|
|
50
148
|
if (t.pointer) {
|
|
51
149
|
return 'void *';
|
|
52
150
|
}
|
package/src/raise/arrays.ts
CHANGED
|
@@ -61,7 +61,12 @@ export function recognizeArrays(fn: Fn): number {
|
|
|
61
61
|
b.ops[i] = mkOp('aload', {
|
|
62
62
|
operands: [m.base, m.index],
|
|
63
63
|
results: [res],
|
|
64
|
-
|
|
64
|
+
// listOrder rides along: an ldmia-expanded load keeps its stream-order caveat as an aload
|
|
65
|
+
attrs: {
|
|
66
|
+
elemSize: op.attrs.width as number,
|
|
67
|
+
signed: op.attrs.signed as boolean,
|
|
68
|
+
...(op.attrs.listOrder === true && { listOrder: true }),
|
|
69
|
+
},
|
|
65
70
|
});
|
|
66
71
|
replaceAllUsesWith(fn, op.results[0], res);
|
|
67
72
|
count++;
|
package/src/raise/divpow2.ts
CHANGED
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
// the biased arm must be the negative one — the consistency check m2c's `49b5d87` also adds),
|
|
42
42
|
// because every one of those is a way for a superficially similar diamond to mean something else.
|
|
43
43
|
import { Block, Fn, Op, Value, defOpMap, mkOp, mkValue, predecessors, replaceAllUsesWith } from '../ir/core';
|
|
44
|
-
import {
|
|
44
|
+
import { EFFECTFUL_OPS } from '../ir/opcodes';
|
|
45
45
|
import { T } from '../ir/types';
|
|
46
46
|
|
|
47
47
|
/** `shr_s v {imm=k}` → k, else null. */
|
|
@@ -110,7 +110,7 @@ export function recognizeDivPow2(fn: Fn): boolean {
|
|
|
110
110
|
continue;
|
|
111
111
|
}
|
|
112
112
|
const bp = preds.get(bias) ?? [];
|
|
113
|
-
if (bp.length !== 1 || bias.ops.some((op) =>
|
|
113
|
+
if (bp.length !== 1 || bias.ops.some((op) => EFFECTFUL_OPS.has(op.opcode))) {
|
|
114
114
|
continue;
|
|
115
115
|
}
|
|
116
116
|
const h = bp[0];
|