@asmlift/core 0.7.0 → 0.8.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 +48 -24
- package/package.json +1 -1
- package/src/backend/pascal.ts +2 -2
- package/src/codegen-flags.ts +640 -0
- package/src/frontend/disasm.ts +141 -11
- package/src/frontend/high-half.ts +149 -0
- package/src/frontend/mips.ts +458 -209
- package/src/frontend/ppc.ts +332 -67
- package/src/frontend/reloc-symbol.ts +109 -0
- package/src/frontend/splat.ts +56 -18
- package/src/frontend/ssa.ts +126 -29
- package/src/frontend/stackargs.ts +420 -0
- package/src/frontend/thumb.ts +207 -230
- package/src/ir/core.ts +62 -3
- package/src/ir/opcodes.ts +9 -0
- package/src/ir/parse.ts +7 -1
- package/src/l3/advance.ts +2 -2
- package/src/l3/argbase.ts +2 -2
- package/src/l3/argcopy.ts +269 -0
- package/src/l3/ast.ts +45 -1
- package/src/l3/basecse.ts +2 -2
- package/src/l3/coalesce.ts +109 -52
- package/src/l3/scopebase.ts +4 -4
- package/src/l3/tailret.ts +70 -0
- package/src/l3/unmerge.ts +2 -2
- package/src/l3/unreduce.ts +2 -1
- package/src/mangle.ts +49 -0
- package/src/pattern/engine.ts +128 -13
- package/src/pipeline.ts +22 -11
- package/src/raise/extscale.ts +5 -2
- package/src/raise/paramwidth.ts +111 -3
- package/src/raise/pre-recovery.ts +11 -1
- package/src/raise/retsink.ts +8 -4
- package/src/raise/tailsink.ts +17 -2
- package/src/rank-declare.ts +17 -9
- package/src/rank.ts +45 -19
- package/src/structure/retspell.ts +95 -0
- package/src/structure/structure.ts +12 -3
- package/src/structure/switch-recover.ts +1 -1
- package/src/target.ts +224 -14
- package/src/trace.ts +27 -18
- package/src/variation-definitions.ts +52 -2
- package/src/variation-gates.ts +3 -0
- package/src/variation-tokens.ts +1 -0
package/README.md
CHANGED
|
@@ -15,7 +15,8 @@ The operative invariant everywhere: **loud decline > silent miscompile**. Where
|
|
|
15
15
|
cannot be byte-faithful it throws a typed error (strict mode) or emits an `ASMLIFT_ERROR`-marked
|
|
16
16
|
stub (`onGap: "annotate"`) — never plausible wrong code.
|
|
17
17
|
|
|
18
|
-
>
|
|
18
|
+
> Published as [`@asmlift/core`](https://www.npmjs.com/package/@asmlift/core); inside this repo it
|
|
19
|
+
> resolves through the pnpm workspace.
|
|
19
20
|
|
|
20
21
|
## Usage
|
|
21
22
|
|
|
@@ -31,7 +32,10 @@ console.log(source); // s32 my_func(s32 a0) { ... }
|
|
|
31
32
|
Input is **text**, following what each target's toolchain produces:
|
|
32
33
|
|
|
33
34
|
- The ARM target reads GBA `.s`, produced by agbcc and pret-style project splits.
|
|
34
|
-
- The MIPS
|
|
35
|
+
- The MIPS targets read `objdump -d --no-show-raw-insn` output and Splat-disassembled `.s`.
|
|
36
|
+
- The PPC target reads `powerpc-eabi-objdump -d -r -M gekko --no-show-raw-insn` output. `-r` carries
|
|
37
|
+
the relocation lines a `bl`'s callee name lives in, and `-M gekko` names CodeWarrior's machine:
|
|
38
|
+
the generic dialect decodes the GameCube's paired-single opcodes as POWER VSX.
|
|
35
39
|
|
|
36
40
|
### `decompile(name, asm, target, opts?)`
|
|
37
41
|
|
|
@@ -41,9 +45,20 @@ Input is **text**, following what each target's toolchain produces:
|
|
|
41
45
|
| `patterns` | Idiom rewrite patterns. Omitted = `DEFAULT_IDIOM_PATTERNS` (self-selects per target: most are compiler-gated, the boolean-negation folds are universal); `[]` = none |
|
|
42
46
|
| `prototypes` | Callee arities + void-ness, as a real project takes them from headers — drives call-argument recovery |
|
|
43
47
|
| `asmData` | Optional `objdump -s -r -t` side-table; required to recover MIPS/PPC jump-table switches |
|
|
48
|
+
| `symbols` | The project's address→symbol map: names, declaration shapes and signatures for the addresses the assembly only numbers. What each channel buys and how to build the ELF behind it: [`@asmlift/cli`](../cli/README.md#the-symbol-map-elf) |
|
|
44
49
|
| `onGap` | `"strict"` (default): throw on any gap. `"annotate"`: emit best-effort source with `ASMLIFT_ERROR` markers; every gap is also returned in the structured `diagnostics` array (empty ⇔ gap-free) |
|
|
45
50
|
|
|
46
|
-
|
|
51
|
+
`DecompileResult.assumedSymbols` carries the declarations the returned source is correct only
|
|
52
|
+
BESIDE — array shapes derived from the assembly rather than read from `symbols`. A caller that
|
|
53
|
+
publishes the source alone publishes a spelling whose meaning it has not stated, so render them
|
|
54
|
+
with it.
|
|
55
|
+
|
|
56
|
+
Targets: `ARMV4T_AGBCC`, `MIPS_IDO`, `MIPS_GCC`, `PPC_MWCC` (`@asmlift/core/target`). A toolchain
|
|
57
|
+
id plus the flags its build compiles the function with resolves to one through
|
|
58
|
+
`targetFor(toolchain, cflags)`, which returns that description alongside the `CodegenProfile` the
|
|
59
|
+
flags describe — optimisation level, debug info, and the words the flag table does not name.
|
|
60
|
+
`TOOLCHAIN_TARGETS` is the registry it reads, and `canonicalFlagsOf` gives a toolchain's default
|
|
61
|
+
set where it has one.
|
|
47
62
|
|
|
48
63
|
### Other entry points
|
|
49
64
|
|
|
@@ -58,8 +73,8 @@ Everything under `src/` is importable as `@asmlift/core/<path>` (e.g.
|
|
|
58
73
|
|
|
59
74
|
## Architecture
|
|
60
75
|
|
|
61
|
-
Three ISA frontends (ARMv4T/Thumb, MIPS, PowerPC),
|
|
62
|
-
|
|
76
|
+
Three ISA frontends (ARMv4T/Thumb, MIPS, PowerPC), four compiler families (`agbcc`, `ido`, `gcc`,
|
|
77
|
+
`mwcc` — `codegen-flags.ts`'s `FlagFamily`) over seven target keys, three language backends over one neutral AST — all scored across the package seam
|
|
63
78
|
by [`@asmlift/cli`](../cli/README.md) with the community `objdiff` engine (in-process, pinned
|
|
64
79
|
`objdiff-wasm`; asmlift never hand-rolls a diff).
|
|
65
80
|
|
|
@@ -79,20 +94,24 @@ injected via hooks, never copied. `verify()` runs after every IR-mutating pass;
|
|
|
79
94
|
|
|
80
95
|
### Modules
|
|
81
96
|
|
|
82
|
-
| Module
|
|
83
|
-
|
|
|
84
|
-
| `ir
|
|
85
|
-
| `frontend/{thumb,mips,ppc}.ts`
|
|
86
|
-
| `pattern/engine.ts`
|
|
87
|
-
| `raise/*.ts`
|
|
88
|
-
| `structure/*.ts`
|
|
89
|
-
| `l3/*.ts`
|
|
90
|
-
| `backend/{c,cpp,cfamily,pascal}.ts`
|
|
91
|
-
| `pipeline.ts`
|
|
92
|
-
| `trace.ts`
|
|
93
|
-
| `rank.ts`
|
|
94
|
-
| `
|
|
95
|
-
| `
|
|
97
|
+
| Module | What it is |
|
|
98
|
+
| ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
99
|
+
| `ir/*.ts` | MLIR-lite substrate (`types`, `core`, `opcodes`, `print`, `parse`, `verify`, plus `alias.ts`'s alias predicate, `simplify.ts`, `bits.ts` and `struct-names.ts`): CFG of blocks + typed **block-arguments**, the typed opcode registry (`Opcode`, the one `effects` table DCE and hoist guards derive from), printer + parser (round-trip for L1/scalar types — see the domain note in parse.ts), verifier (arity/attrs/terminators/SSA dominance, located errors); `core.ts` also owns the CFG facts every level reads — `successorsOf`, `predecessors`, `dominators` |
|
|
100
|
+
| `frontend/{thumb,mips,ppc}.ts` | ISA frontends: decode → CFG → L1 with **Braun-2013 block-arg SSA** (`ssa.ts`), incl. loops, calls (signature-driven arity), memory, jump tables. Shared scaffolding: `disasm.ts` (objdump parsing), `splat.ts` (Splat-dialect MIPS → objdump-shaped instrs), `format.ts` (input-format classification), `emit.ts` (per-block emitter kit + `switch_br`), `opaque.ts` (the unmodelled-op → loud-`opaque` contract), `errors.ts` (`FrontendUnsupportedError`; PPC's subclass), `registry.ts`, `asmdata.ts` (Regime-B jump-table side-table), `high-half.ts` (a relocated address's high half is a link-time placeholder, never a value), `reloc-symbol.ts` (the per-KIND policy deciding which linker names C can spell), `stackargs.ts` (the outgoing stack-argument fixpoint, over digested slot events rather than instructions) |
|
|
101
|
+
| `pattern/engine.ts` | Idiom layer: **rewrite patterns as data** + greedy driver + DCE; `patternApplies` gates on Target capabilities |
|
|
102
|
+
| `raise/*.ts` | The pre-recovery recognizers, in ONE ordered list — `PRE_RECOVERY_PASSES` in `pre-recovery.ts` is the source of truth for the order, and reads: address-numbering (`gvn.ts`) → const-materialize → magic-division (`magicdiv.ts`, Hacker's Delight inverse) → pow2-division (`divpow2.ts`) → soft-division → scaled-extension (`extscale.ts`) → array-legalize → struct-array → member-array (`memberarrays.ts`, a constant offset feeding a variable-index walk is a struct's ARRAY member) → struct-pointer → short-circuit → branch-short-circuit → narrow-reads (`narrow.ts`) → narrow-local (`narrowlocal.ts`, a carrier read only through its own extension IS that width) → parameter-width (`paramwidth.ts`, a parameter extended in the prologue is DECLARED at that width) → scaled-extension-restore; plus `recover.ts` (L1→L2 type recovery), `retsink.ts` + `tailsink.ts` (return- and tail-sinking), `latch.ts` (empty-latch folding), `globalshape.ts` (which derived shapes the source is only correct beside), `errors.ts` (`RaiseUnsupportedError`) |
|
|
103
|
+
| `structure/*.ts` | L2→L3: `loops.ts` (natural-loop discovery, over `ir/core.ts`'s dominators), `analysis.ts` (use registry, liveness, C4 materialization), `switch-recover.ts` (Regime-A comparison-tree recovery), `hazards.ts` (the checks a loop emitter runs before committing to a loop form), `namecoalesce.ts` (copy coalescing over the interference graph — the `/merge-names` variation), `structure.ts` (SSA-destruction coalescing with interference checks + emission: if/while/do-while/for/switch, break/early-return), `retspell.ts` (which `return;` the source wrote, read off the epilogue's in-edges), `bitfields.ts` + `globalaccess.ts` (the map-driven bitfield and global-access spellings, both refusal machines) |
|
|
104
|
+
| `l3/*.ts` | `ast.ts`: language-**neutral** structured AST, the one traversal vocabulary (`exprChildren` etc.), and the `LanguageBackend` seam. Post-structure passes `dce.ts` + `basecse.ts`, the shared hoist mechanism `hoist.ts`, the differ-ranked respell variations (`regspell.ts`, `reindex.ts`, `argbase.ts`, `scopebase.ts`, `argcopy.ts` — five of the 31 the registry holds; a module may mint more than one, as `scopebase.ts` does with `/scopebase` and `/regionbase`), the spelling pass `tailret.ts` (drop a void `return;` `structure/retspell.ts` marked unspelled), and `typing.ts` (the rendered-expression C type the backends and contracts share) |
|
|
105
|
+
| `backend/{c,cpp,cfamily,pascal}.ts` | Three backends: C and C++ (CodeWarrior mangling via `mangle.ts`) over the shared `cfamily.ts` substrate, and Pascal (`:=`, `div`, tail-position returns; unspellable constructs throw) |
|
|
106
|
+
| `pipeline.ts` | `decompile()` + the shared tower spine + annotate-mode stubs/diagnostics |
|
|
107
|
+
| `trace.ts` | `decompileTraced` — the traced tower (per-stage IR dumps + pattern before/after events), browser-pure; @asmlift/cli's `report.ts` enriches it with objdiff scores/candidates, the playground's Pipeline tab renders it directly |
|
|
108
|
+
| `rank.ts` | Pure candidate enumeration + `rankBy` (an injected score function ranks). @asmlift/cli's differ ranks through `rankBy`; the playground's wasm scorer consumes the same enumeration with its own async loop |
|
|
109
|
+
| `rank-variations.ts`, `rank-declare.ts` | The two halves `rank.ts` walks rather than hand-codes: the enumeration TABLES (declaration order is published behaviour — it breaks a score tie), and the declarations a candidate needs to compile outside the project's headers, including which names one must REFUSE to claim |
|
|
110
|
+
| `variation-{tokens,definitions,gates}.ts` | The variation REGISTRY as data: every variation a candidate's name can carry, what each one MEANS for a reader, and the admission tables a definition names. Keyed by the registry in both directions, so a variation with no definition and a definition naming no variation are each a type error |
|
|
111
|
+
| `codegen-flags.ts` | Compiler flags parsed into the codegen profile a function was compiled at — each option that can change what the compiler emits, as a named slot. One flag set reaches both the compile and the decompile |
|
|
112
|
+
| `symbols.ts`, `declare.ts`, `macros.ts` | The address→symbol map seam (core consumes the VALUE; the providers that read files live in `@asmlift/cli`), the declaration block a self-declaring candidate carries, and the address-cast macro spelling — a `#define` emits a NUMERIC pool word where an `extern` emits a relocated one, so the two are not interchangeable in the bytes |
|
|
113
|
+
| `target.ts` | `TargetDescription` (ABI + capabilities + compilerBehaviors as data — no `arch ==` in shared code); toolchain paths live in `@asmlift/toolchains` |
|
|
114
|
+
| `contracts.ts`, `proto.ts`, `mangle.ts` | Boundary contracts; prototype tables; the CodeWarrior mangler |
|
|
96
115
|
|
|
97
116
|
Scoring and ranking live across the package seam in [`@asmlift/cli`](../cli/README.md):
|
|
98
117
|
`score.ts` + `objdiff.ts` (toolchain compiles → in-process pinned `objdiff-wasm`, fail-closed)
|
|
@@ -103,12 +122,16 @@ and `rank.ts` (ranked type candidates re-ranked by the differ).
|
|
|
103
122
|
Recovered today: straight-line, if/else diamonds, natural loops (`while` / `do-while` / `for`,
|
|
104
123
|
properly nested, in-body `break`/early-`return`), comparison-tree and jump-table switches,
|
|
105
124
|
direct calls, constant-offset and variable-index memory (`*p`, `p[n]`, `a[i]`, struct fields),
|
|
106
|
-
magic-number and soft division, short-circuit booleans, width casts
|
|
125
|
+
magic-number and soft division, short-circuit booleans, width casts, PowerPC small-data access
|
|
126
|
+
through its `R_PPC_EMB_SDA21` relocation, a call's outgoing stack arguments on agbcc, where the
|
|
127
|
+
declaration and the code agree (`stagesOutgoingArgsInFrame`; MIPS still declines the same shape),
|
|
128
|
+
and a MIPS likely branch's nullified delay slot. Still DECLINED (loud, never
|
|
107
129
|
wrong code): **local stack frames** (address-taken locals / sp-as-data; MIPS models word `sp`
|
|
108
130
|
slots and PPC elides callee-saved save slots, so a spill/reload pair is modelled on those two —
|
|
109
131
|
anything the narrow models cannot honour declines),
|
|
110
132
|
**cross-block condition flags** on PPC (a `cmpw` whose branch lands in another block — the
|
|
111
|
-
capability gap behind the mwcc switch stubs), computed tail calls, PIC
|
|
133
|
+
capability gap behind the mwcc switch stubs), computed tail calls, PIC and `gp`-relative access outside PowerPC's
|
|
134
|
+
small-data relocation — MIPS refuses `R_MIPS_GOT16`/`R_MIPS_CALL16`/`R_MIPS_GPREL16` alike —
|
|
112
135
|
switch fall-through, multi-latch/irreducible loops, floats, and 64-bit memory ops. Prototypes
|
|
113
136
|
(callee arities, void-ness) come from a caller-supplied map, as a real project takes them from
|
|
114
137
|
headers.
|
|
@@ -130,9 +153,10 @@ Landmarks (not exhaustive — suites are named for what they pin):
|
|
|
130
153
|
that keeps every already-matching function matching. How to add a fixture — and when a
|
|
131
154
|
matching test is the right tool at all, vs a benchmark row — is under
|
|
132
155
|
[`@asmlift/cli` › Tests](../cli/CONTRIBUTION.md#tests).
|
|
133
|
-
- `contract-invariant
|
|
134
|
-
-
|
|
135
|
-
|
|
156
|
+
- `contract-invariant`, and `contracts` (in `../cli/test/matching/`) — the loud-fail contract,
|
|
157
|
+
mutation-proven.
|
|
158
|
+
- `structure-guard`, and `structure-soundness` / `audit-regression` (both in
|
|
159
|
+
`../cli/test/matching/`) — the adversarial-audit repro locks.
|
|
136
160
|
- `one-version` — a WORKSPACE-scope invariant, like `offline-list`: every dependency that two or
|
|
137
161
|
more workspace packages declare must be declared AND resolved at one version. It exists because
|
|
138
162
|
`packages/cli` scored with `objdiff-wasm` 3.7.3 while `apps/web` scored with 3.7.0, so the
|
package/package.json
CHANGED
package/src/backend/pascal.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// (`rshift` is arithmetic on a signed Integer → `sra`.) This is the concrete difference from
|
|
10
10
|
// Turbo/Delphi/FreePascal.
|
|
11
11
|
import { IrType, typeToString } from '../ir/types';
|
|
12
|
-
import { BinOp, Expr, LanguageBackend, SFn, Stmt } from '../l3/ast';
|
|
12
|
+
import { BinOp, Expr, LanguageBackend, SFn, Stmt, isLoop } from '../l3/ast';
|
|
13
13
|
import { orderSlotLocals } from '../l3/slotorder';
|
|
14
14
|
import { type VarTypes, declaredTypes, derefStrideOk, exprCType, writesNonPointerIntoPointer } from '../l3/typing';
|
|
15
15
|
|
|
@@ -240,7 +240,7 @@ function makePrinter(vt: VarTypes) {
|
|
|
240
240
|
(st) =>
|
|
241
241
|
st.k === 'return' ||
|
|
242
242
|
(st.k === 'if' && (hasReturn(st.then) || hasReturn(st.else))) ||
|
|
243
|
-
((st
|
|
243
|
+
(isLoop(st) && hasReturn(st.body)) ||
|
|
244
244
|
(st.k === 'switch' && (st.cases.some((c) => hasReturn(c.body)) || hasReturn(st.default ?? []))),
|
|
245
245
|
);
|
|
246
246
|
if (s.cases.some((c) => hasReturn(c.body)) || hasReturn(s.default ?? [])) {
|