Repository navigation
v0.6.0
asmlift 0.6.0 lifts one function's assembly to source that recompiles byte-identical. This release
is a GBA release: every measured gain is on ARM/agbcc projects, and nothing regressed anywhere.
Measured against 0.5.0
Same 743 benchmark rows in both releases, same pinned m2c (ad5c529a), same vendored inputs:
| 0.5.0 | 0.6.0 | ||
|---|---|---|---|
| byte-exact matches | 363 | 411 | +48 |
| — synthetic rows | 302 | 333 | +31 |
| — real project rows | 61 | 78 | +17 |
| declined | 195 | 186 | −9 |
| noncompile | 6 | 4 | −2 |
| matches lost | 0 |
Real decompilation projects, byte-exact rows out of 40 each:
| project | 0.5.0 | 0.6.0 |
|---|---|---|
| kleod (GBA) | 17 (43%) | 21 (53%) |
| pokeemerald (GBA) | 15 (38%) | 20 (50%) |
| sa3 (GBA) | 10 (25%) | 15 (38%) |
| marioparty3 (N64) | 12 (30%) | 15 (38%) |
| af (N64) | 4 (10%) | 4 (10%) |
| snowboardkids2 (N64) | 3 (8%) | 3 (8%) |
Rows that still do not match got closer: summed objdiff residual over the 131 real rows scored in
both releases fell 3,122 → 2,189, −30%.
A second check, on 2,864 functions sampled straight from the project checkouts' own asm/ trees —
functions no benchmark row was ever drawn from, both releases run over an identical file list:
+12 newly lifted, 0 regressed.
What is new
21 candidate axes, from 3 registered ranked axes to 24, none removed. The bulk of them are one
family: where a value lives. agbcc decides register allocation from how the source spells a
value, so recovering the spelling — not the semantics — is what closes a match.
- Value and expression homes —
/expr-home,/merge-home,/addr-home,/derived-home,
/livebase,/basefold,/orderbase,/initfirst,/fresh-merge,/unmerge. A base address
held live across a loop; the value two branch arms each re-derive hoisted to one definition; an
init moved above or below a zero-trip guard. - Uninitialised locals — the IR gained a real
undef, replacing a fabricated parameter. - Device registers — a
volatilescalar value local, const-address pointer inlining, and
polled reads (/pollread,/pollguard,/inplace). - Types and widths — narrow loop counters, parameters agbcc widens in its own prologue,
member arrays, global shape recovery, an unsigned-divide L3 operator. - Control flow — switch fall-through with a shared return, jump-table dispatch shape, the
pre-update loop family, short-circuit conditions, long-branch trampolines. - Thumb frontend — alignment padding read as data or
.align, literal-pool handling,
sub-word data tables.
Benchmark
The corpus grew from 743 to 1,022 rows — 288 synthetic function specs across four toolchains
(ARM/agbcc, MIPS/IDO 7.1, MIPS/gcc 2.7.2 + KMC, PowerPC/mwcc 2.4.2) and 252 rows from six real
decompilation projects. 11 new feature tags. On the full corpus asmlift is at 581 byte-exact
against m2c's 407.
Every row added since 0.5.0 was audited before this release for provisioning symmetry between the
two decompilers, tag accuracy, stale commentary and duplication (#160), and a new gate now checks
those properties mechanically.
Known limits
- N64 is honest but narrow: two of the three MIPS projects did not move. The wall is one
instruction family — 152 occurrences of branch-likely (bnezl,beqzl,beql,blezl,
bgtzl,bnel,bltzl,bgezl,bc1fl,bc1tl) across the corpus, then hardware float.
These are loud declines, never wrong C. - GameCube / PowerPC has 168 synthetic rows and no real-project coverage at all. Treat
PPC support as unproven on real code. @asmlift/coreships raw TypeScript. Theasmliftbinary is bundled and runs on stock Node,
but importing the library needs a TypeScript loader such astsx.
Install
npm i -g @asmlift/cli # the binary
npm i @asmlift/core # the library (needs a TS loader)