@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/trace.ts
CHANGED
|
@@ -13,10 +13,18 @@ import { verify } from './ir/verify';
|
|
|
13
13
|
import type { LanguageBackend } from './l3/ast';
|
|
14
14
|
import { DEFAULT_IDIOM_PATTERNS, RewritePattern, applyPattern, dce, patternApplies } from './pattern/engine';
|
|
15
15
|
import { type OnGap, raiseRecovered, structureChecked, stubResult } from './pipeline';
|
|
16
|
-
import type
|
|
17
|
-
import {
|
|
16
|
+
import { type Prototypes, prototypesFromSymbols } from './proto';
|
|
17
|
+
import { assumedShapes, inferGlobalArrays, orderLicensedGlobals } from './raise/globalshape';
|
|
18
|
+
import { type SymbolInfo, type SymbolMap, symbolsByName } from './symbols';
|
|
18
19
|
import { type TargetDescription, structureOptionsFor } from './target';
|
|
19
20
|
|
|
21
|
+
/** EVERY stage dump carries the write-order record (ir/print.ts `PrintOptions`) — not just
|
|
22
|
+
* `stage:lift`, even though only the frontend measures it. Two reasons: the raising folds MUTATE
|
|
23
|
+
* it (`foldWriteOrder`), so a stage that changes it and nothing else has something to show; and
|
|
24
|
+
* the Pipeline tab dims a stage whose dump equals the previous one, which is a claim about the
|
|
25
|
+
* whole program state and would be false if one dump carried the record and the next did not. */
|
|
26
|
+
const irDump = (fn: Fn): string => print(fn, { writeOrder: true });
|
|
27
|
+
|
|
20
28
|
export interface StageTrace {
|
|
21
29
|
id: string; // stable localization anchor: "stage:lift", "stage:recover", …
|
|
22
30
|
title: string; // human label = the transform this stage performs
|
|
@@ -52,6 +60,22 @@ export interface TraceReport {
|
|
|
52
60
|
/** Set ONLY on the annotate-mode stub path (empty trace): the failure reason, machine-readable —
|
|
53
61
|
* so a consumer of the reasoning trail is not reduced to parsing the stub's source comments. */
|
|
54
62
|
declineReason?: string;
|
|
63
|
+
/** The shapes the emitted source's spelling ASSUMES, derived from the assembly rather than read
|
|
64
|
+
* from a map — pipeline.ts `DecompileResult.assumedSymbols`, and the same obligation: a
|
|
65
|
+
* consumer showing the source must show these, because a bare `gSym[i]` means what the
|
|
66
|
+
* declaration of `gSym` says it means.
|
|
67
|
+
*
|
|
68
|
+
* NARROWER THAN THE `stage:globalshape` ENTRY ABOVE, on purpose. That entry reports what the
|
|
69
|
+
* stage DERIVED, so a spelling decision downstream is attributable to a stage; this reports
|
|
70
|
+
* what the SOURCE rests on, and a shape the structurer refused or a name the caller's own map
|
|
71
|
+
* described is not an obligation the reader has (raise/globalshape.ts `assumedShapes`). */
|
|
72
|
+
assumedSymbols: SymbolInfo[];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** `; dims [_][2]` for a derived rank, or '' for rank 1 — the trace note's tail. */
|
|
76
|
+
function dimsNote(i: SymbolInfo): string {
|
|
77
|
+
const inner = i.shape === 'array' ? (i.dims ?? []).slice(1) : [];
|
|
78
|
+
return inner.length === 0 ? '' : `, dims [_]${inner.map((d) => `[${d ?? '?'}]`).join('')}`;
|
|
55
79
|
}
|
|
56
80
|
|
|
57
81
|
// Every knob decompile() takes must exist here with the SAME default — a surface that disagrees
|
|
@@ -64,8 +88,13 @@ export interface TraceOptions {
|
|
|
64
88
|
symbols?: SymbolMap; // address→symbol map (symbols.ts), as in decompile(); absent ⇒ inert
|
|
65
89
|
onGap?: OnGap; // "strict" (default) | "annotate", as in decompile()
|
|
66
90
|
/** Score probe at pattern boundaries (cli report's objdiff hook). One call per boundary:
|
|
67
|
-
* pattern N's after-score is pattern N+1's before-score. Absent ⇒ score fields stay unset.
|
|
68
|
-
|
|
91
|
+
* pattern N's after-score is pattern N+1's before-score. Absent ⇒ score fields stay unset.
|
|
92
|
+
*
|
|
93
|
+
* THE DERIVED ARRAY SHAPES TRAVEL WITH THE CLONE: they are read once off the lift
|
|
94
|
+
* (`stage:globalshape`) and change the default spelling of every indexed global, so a probe
|
|
95
|
+
* that structures without them attributes its per-pattern delta to a source the main path does
|
|
96
|
+
* not emit. */
|
|
97
|
+
probeScore?: (fn: Fn, inferredSymbols: Map<string, SymbolInfo>) => number | undefined;
|
|
69
98
|
}
|
|
70
99
|
|
|
71
100
|
// The report's per-pass trace stage id + title, keyed by the shared PreRecoveryPass.id. Kept HERE
|
|
@@ -121,6 +150,7 @@ export function decompileTraced(
|
|
|
121
150
|
asm,
|
|
122
151
|
trace: [],
|
|
123
152
|
patternEvents: [],
|
|
153
|
+
assumedSymbols: [],
|
|
124
154
|
source: stub.source,
|
|
125
155
|
declineReason: e instanceof Error ? e.message : String(e),
|
|
126
156
|
},
|
|
@@ -135,7 +165,10 @@ function traceTower(
|
|
|
135
165
|
opts: TraceOptions,
|
|
136
166
|
): { source: string; report: TraceReport } {
|
|
137
167
|
const backend = opts.backend ?? cBackend;
|
|
138
|
-
|
|
168
|
+
// Merged exactly as pipeline.ts does: the project's own DWARF signatures fill in what the caller
|
|
169
|
+
// did not state. A trace that lifted from a different table would explain a run that never
|
|
170
|
+
// happened.
|
|
171
|
+
const prototypes = prototypesFromSymbols(opts.symbols, opts.prototypes ?? {});
|
|
139
172
|
const returnsVoid = prototypes[name]?.returnsVoid ?? false;
|
|
140
173
|
const trace: StageTrace[] = [];
|
|
141
174
|
const patternEvents: PatternEvent[] = [];
|
|
@@ -143,7 +176,27 @@ function traceTower(
|
|
|
143
176
|
// (1) lift → typed-SSA IR
|
|
144
177
|
const fn = frontendFor(target).lift(name, asm, target, prototypes, opts.asmData, opts.symbols);
|
|
145
178
|
verify(fn);
|
|
146
|
-
trace.push({ id: 'stage:lift', title: 'Lift (ISA frontend → typed-SSA IR)', irDump:
|
|
179
|
+
trace.push({ id: 'stage:lift', title: 'Lift (ISA frontend → typed-SSA IR)', irDump: irDump(fn), verified: true });
|
|
180
|
+
// The array shapes the assembly evidences, off the LIFTED fn — same reading, same reason, as
|
|
181
|
+
// pipeline.ts's runTower: the fold and the tower below destroy the order the licence reads.
|
|
182
|
+
//
|
|
183
|
+
// IT GETS ITS OWN TRACE ENTRY because it is the one stage whose product is not the IR: it
|
|
184
|
+
// changes the DEFAULT spelling of every indexed global and leaves no mark in any `irDump`, so
|
|
185
|
+
// without a line here a reader following the trail from `stage:lift` to `stage:structure` sees
|
|
186
|
+
// `gTbl[i]` appear with nothing that produced it, and a wrong shape is attributable to no stage.
|
|
187
|
+
const inferredSymbols = inferGlobalArrays(fn, target);
|
|
188
|
+
const orderLicensed = orderLicensedGlobals(fn, target);
|
|
189
|
+
trace.push({
|
|
190
|
+
id: 'stage:globalshape',
|
|
191
|
+
title: 'Array shape from stride evidence (globals no symbol map describes)',
|
|
192
|
+
verified: true,
|
|
193
|
+
note:
|
|
194
|
+
inferredSymbols.size === 0
|
|
195
|
+
? 'nothing evidenced — every indexed global keeps `((T *)&gSym)[i]`, valid under any declaration'
|
|
196
|
+
: [...inferredSymbols.values()]
|
|
197
|
+
.map((i) => `${i.name}: elem ${i.elemSize} ${i.elemSigned ? 'signed' : 'unsigned'}${dimsNote(i)}`)
|
|
198
|
+
.join('; '),
|
|
199
|
+
});
|
|
147
200
|
|
|
148
201
|
// (2) idiom fold (capability-gated), with an optional probed score per pattern boundary —
|
|
149
202
|
// the SAME default set as decompile()/decompileRanked
|
|
@@ -151,17 +204,17 @@ function traceTower(
|
|
|
151
204
|
// Probe economy: ONE probe per pattern boundary — pattern N's after-score IS pattern N+1's
|
|
152
205
|
// before-score (the state is identical), and each probe costs a full clone + tower + external
|
|
153
206
|
// compile + objdiff. Zero-hit patterns emit NO event (the IR is unchanged).
|
|
154
|
-
let scoreBefore = opts.probeScore?.(fn);
|
|
207
|
+
let scoreBefore = opts.probeScore?.(fn, inferredSymbols);
|
|
155
208
|
for (const p of active) {
|
|
156
|
-
const beforeIr =
|
|
209
|
+
const beforeIr = irDump(fn);
|
|
157
210
|
const hits = applyPattern(fn, p);
|
|
158
211
|
dce(fn);
|
|
159
212
|
verify(fn);
|
|
160
213
|
if (hits === 0) {
|
|
161
214
|
continue;
|
|
162
215
|
}
|
|
163
|
-
const afterIr =
|
|
164
|
-
const scoreAfter = opts.probeScore?.(fn);
|
|
216
|
+
const afterIr = irDump(fn);
|
|
217
|
+
const scoreAfter = opts.probeScore?.(fn, inferredSymbols);
|
|
165
218
|
patternEvents.push({
|
|
166
219
|
id: `pattern:${p.id}`,
|
|
167
220
|
patternId: p.id,
|
|
@@ -178,7 +231,7 @@ function traceTower(
|
|
|
178
231
|
trace.push({
|
|
179
232
|
id: 'stage:idiom',
|
|
180
233
|
title: `Idiom fold (${active.length} pattern(s), capability-gated)`,
|
|
181
|
-
irDump:
|
|
234
|
+
irDump: irDump(fn),
|
|
182
235
|
verified: true,
|
|
183
236
|
});
|
|
184
237
|
}
|
|
@@ -187,34 +240,55 @@ function traceTower(
|
|
|
187
240
|
// type recovery → return-sinking, byte-identical to decompile()/decompileRanked by
|
|
188
241
|
// construction. The trace's only additions are the entries, injected via the hooks
|
|
189
242
|
// (each fires post-verify).
|
|
190
|
-
raiseRecovered(
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
243
|
+
raiseRecovered(
|
|
244
|
+
fn,
|
|
245
|
+
target,
|
|
246
|
+
{
|
|
247
|
+
afterPass: (pass, result) => {
|
|
248
|
+
// A pass with no registered strings still traces under a generic title: the traced tower
|
|
249
|
+
// must never crash (and so diverge from decompile()) just because a NEW pre-recovery pass
|
|
250
|
+
// landed before its trace entry did.
|
|
251
|
+
const t = PRE_RECOVERY_TRACE[pass.id] ?? {
|
|
252
|
+
stage: `stage:${pass.id}`,
|
|
253
|
+
title: () => `${pass.id} (pre-recovery pass)`,
|
|
254
|
+
};
|
|
255
|
+
trace.push({ id: t.stage, title: t.title(result), irDump: irDump(fn), verified: true });
|
|
256
|
+
},
|
|
257
|
+
afterRecover: () =>
|
|
258
|
+
trace.push({
|
|
259
|
+
id: 'stage:recover',
|
|
260
|
+
title: 'Type recovery (in-place on IR)',
|
|
261
|
+
irDump: irDump(fn),
|
|
262
|
+
verified: true,
|
|
263
|
+
}),
|
|
264
|
+
afterRetsink: () =>
|
|
265
|
+
trace.push({
|
|
266
|
+
id: 'stage:retsink',
|
|
267
|
+
title: 'Return-sinking (tail-duplicate return-only merge)',
|
|
268
|
+
irDump: irDump(fn),
|
|
269
|
+
verified: true,
|
|
270
|
+
}),
|
|
271
|
+
afterLatchFold: () =>
|
|
272
|
+
trace.push({
|
|
273
|
+
id: 'stage:latchfold',
|
|
274
|
+
title: 'Empty-latch folding (splice out an SSA-emptied back-edge block)',
|
|
275
|
+
irDump: irDump(fn),
|
|
276
|
+
verified: true,
|
|
277
|
+
}),
|
|
200
278
|
},
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
afterRetsink: () =>
|
|
204
|
-
trace.push({
|
|
205
|
-
id: 'stage:retsink',
|
|
206
|
-
title: 'Return-sinking (tail-duplicate return-only merge)',
|
|
207
|
-
irDump: print(fn),
|
|
208
|
-
verified: true,
|
|
209
|
-
}),
|
|
210
|
-
});
|
|
279
|
+
prototypes[name],
|
|
280
|
+
);
|
|
211
281
|
|
|
212
282
|
// (4) structure → neutral AST; boundary contract: no unresolved value leaked (strict) or
|
|
213
283
|
// spelled as a loud ASMLIFT_ERROR marker (annotate) — same onGap lever as decompile()
|
|
284
|
+
const mapSymbols = opts.symbols ? symbolsByName(opts.symbols) : undefined;
|
|
214
285
|
const sfn = structureChecked(fn, {
|
|
215
286
|
...structureOptionsFor(target, returnsVoid),
|
|
287
|
+
spellSwitchFallthrough: (opts.backend ?? cBackend).spellsSwitchFallthrough,
|
|
216
288
|
onGap: opts.onGap ?? 'strict',
|
|
217
|
-
...(
|
|
289
|
+
...(mapSymbols ? { symbols: mapSymbols } : {}),
|
|
290
|
+
...(inferredSymbols.size ? { inferredSymbols } : {}),
|
|
291
|
+
...(orderLicensed.size ? { orderLicensedGlobals: orderLicensed } : {}),
|
|
218
292
|
});
|
|
219
293
|
trace.push({
|
|
220
294
|
id: 'stage:structure',
|
|
@@ -243,6 +317,11 @@ function traceTower(
|
|
|
243
317
|
trace,
|
|
244
318
|
patternEvents,
|
|
245
319
|
source,
|
|
320
|
+
// The same narrowing pipeline.ts's `DecompileResult` makes, and for the same reason: the
|
|
321
|
+
// `stage:globalshape` entry above reports what the STAGE derived, this reports what the
|
|
322
|
+
// SOURCE rests on, and they differ wherever a consumer refused the shape or the caller's
|
|
323
|
+
// own map already declared the name (raise/globalshape.ts `assumedShapes`).
|
|
324
|
+
assumedSymbols: assumedShapes(inferredSymbols, sfn, mapSymbols),
|
|
246
325
|
},
|
|
247
326
|
};
|
|
248
327
|
}
|