Repository navigation
v0.4.0 β Thumb fidelity, and a HUD counter that took four rounds
@asmlift/core@0.4.0 and @asmlift/cli@0.4.0 are live on npm.
npm i @asmlift/cli # pulls @asmlift/core ^0.4.0Where the board stands
asmlift 363 matches vs m2c 342, over 743 rows β up from 343 vs 340 over 675 rows at 0.3.0. The corpus grew by 68 rows while the gap widened from +3 to +21.
| match | nonmatch | declined | noncompile | |
|---|---|---|---|---|
| asmlift | 363 | 173 | 202 | 5 |
| m2c | 342 | 202 | 42 | 157 |
Across five toolchains (agbcc, ido7.1, gcc2.7.2, gcc2.7.2kmc, mwcc_242_81). Explore it in the benchmark report.
The headline: one function, four rounds, six general capabilities
kleod:UpdateHUDCounterDisplay could not even be compiled at 0.3.0. Four rounds took it noncompile β diff:81 β diff:60 β diff:18 β MATCH. Each round was chosen by bisecting the gap β mutating the byte-exact reference one axis at a time and scoring each β before any code was written, so what shipped is general capability rather than a fix shaped like one row:
- Control-flow short-circuits (#11) β
if (a || b) X else Yproduces no value at all, just twocond_brblocks sharing a target. Nothing recognised that, so the structurer tail-duplicated the shared block: correct C, but not the C the compiler compiled. - Scope-aware base hoist + address numbering (#12) β and it retired a wrong call from the round before, which had written off a
+= 0;in the reference as an unmatchable source quirk. A named base does the same thing to codegen, and it is an ordinary decomp idiom. - Common-tail merge, scoped base clusters, ranked live-range coalescing (#13) β which pair of locals the allocator coalesced is not derivable from the L3 tree, and first-fit gets it wrong, so every legal merge is emitted as a candidate and the differ referees.
- Def-site anchored copies + bitfield members (#21) β SSA destruction places a merge copy where its CFG edge is, but the asm often materialised the constant earlier. The
/defsiteaxis alone flipped five more synthetic rows carrying thev = 0; if (β¦) v = 1;idiom.
Several of these ship as differ-refereed candidate axes (/scopebase, /coalesce, /defsite): the alternative spelling is emitted alongside the default and the objdiff byte-diff picks the winner, so an axis can win a row and by construction cannot cost one. The passes that do run unconditionally β short-circuit recovery, common-tail merge β earn it structurally instead; mergeCommonTails moves a statement that ends every arm below the if, which runs it on the same paths in the same order, no liveness analysis required.
Thumb fidelity
- Legacy mnemonic spellings (#25) β
ldsh,ldsb,ldm,stm,ldmfdare not similar instructions toldrsh/ldrsb/ldmia/stmia; they are the same instructions with two accepted spellings, and a disassembler emitting the legacy ones got a decline. Verifying that against ARM's own documentation turned up two semantic bugs, one of them a silent miscompile whereldm rN, {rD}deleted the load entirely. Measured over all 46 functions in the Klonoa: Empire of Dreams corpus containing an affected mnemonic: 13 gained, 0 lost. &&short-circuit returns (#26) βif (a && b) return X; return Y;had silently stopped matching byte-exact back in #11: branch fusion runs before return-sinking and destroyed the CFG signal it gates on. Neither hosted CI nor the benchmark covers that shape, so it survived twelve merged PRs. Found while cutting this release.
Ported from m2c
The pin was bumped to upstream ad5c529 (#22) and its commits worked in two passes β nice-to-haves (#23), then the four worth porting (#24). Net +3 matches, and three silent miscompiles found and fixed, two of them pre-existing and unrelated to the ports.
Two commits were verified as nothing to port and two were deliberately discarded, with the reason pinned as tests so the decision does not quietly rot. The one that paid most had been scanned ARM-only: seven MIPS rows carry xori β¦, 1, because MIPS has no set-on-greater-equal.
Also in #24: jump-table support turned out to exist but only recognised a spelling agbcc rarely emits β the m2c port was the smallest of three gaps standing between asmlift and six functions that dispatch through a table.
Correctness
Silent miscompiles are the worst failure a decompiler has, because the output looks fine. Fixed this release: the logical-shift miscompile (#9), ldm rN, {rD} deleting its load (#25), and three more found while porting from m2c (#23). Two noncompile rows also closed (#9).
Benchmark and webapp
- 240 real rows, up from 192 (#15) β chosen to close a measured size/feature blind spot
- A feature vocabulary that is machine-checked rather than hand-asserted (#16, #18, #19), and surfaced in the webapp so each tag explains itself (#20)
Also
docs/releasing.md(#27) now records the release procedure β including the two gates hosted CI cannot run, both of which caught real problems while cutting 0.4.0
Full changelog: v0.3.0...v0.4.0