Skip to content

v0.6.0

Choose a tag to compare

@macabeus macabeus released this 07 Sep 02:38
· 248 commits to main since this release
0b30aeb

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 volatile scalar 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/core ships raw TypeScript. The asmlift binary is bundled and runs on stock Node,
    but importing the library needs a TypeScript loader such as tsx.

Install

npm i -g @asmlift/cli     # the binary
npm i @asmlift/core       # the library (needs a TS loader)