Files
BFM-decomp/cookbook/C0022.md
T

4.1 KiB
Raw Blame History

§16 Guided hand-matching the struct-heavy core (Phase 17 — beats the §15 brute-force)

Phase 16 called the loose-typing wall "fundamental." Phase 17 disproves it for the majority. The wall is a signature-CONSISTENCY problem, not a comprehension one — the §1 loop reconstructs correct bodies ~100% of the time; the work is byte-closing + sig reconciliation. Full process: docs/sunset/hand-matching-process.md (§1 loop, §2 idioms, §3a the 5-move signature-consistency playbook, §7 the Ultracode wave + canonical-sig wall).

New byte-idioms (Phase 17, §2 there):

  • mask-local (defeats lh→lhu fold). *(s16*)f & (x & 0xFFFF) inline lets gcc fold the load to lhu
    • drop the andi. Hoist the mask: s32 m = x & 0xFFFF; ... *(s16*)f & m → gcc keeps lh + emits andi.
  • shared-ret0 goto (cross-jump clustering + branch polarity). Two non-adjacent predicate tests the original routes to ONE shared return 0 block → write both as goto ret0; to a single trailing ret0: return 0;. gcc then makes ret0 a labeled block reached by branches (right polarity) + schedules the next test's constant into the delay slot. A lone if(x)return 0; inlines (wrong polarity/reg).
  • v0↔v1 result/constant coalescing + the §10 hoist-vs-remat / phantom-frame quirks = the residual hard tail — NOT a dead-end: §17 (CORRECTED) shows the call-crossing register-ORDER class is matchable with register __asm__ PINS + a scheduling barrier (byte-proven, func_8012B8E4), and array-decay cracks the hoist-vs-remat class. Hand-tier, but matchable. (Permuter can't help — it rejects register __asm__.)

Scaling = Ultracode wave (§12 pattern + §7): Ghidra pre-pass (DecompileFunctions.java, headless batch, no /mcp) → parallel draft agents (m2c+Ghidra-C+asm+actor-struct+§3a, self-validate match_one) → whole-binary gate (harvest_verify --chunk 1) → sig_unify recover → dedup_propagate --auto-from. Calibration (top-30): 60% match_one MATCH, 33% whole-binary (+0.47% fleet), 136/136. THE CANONICAL-SIG LAYER (built Phase-17 session-4; NOT the ~2× lever it first looked like). The match_one→whole-binary gap on the calibration's top-30 was SIG CONFLICTS (parallel agents declare shared callees inconsistently → conflicting types in the one-big-TU; 100% compile-errors, 0 codegen). Fix = a SURGICAL per-callee canonical-sig layer: tools/census_conflict_callees.py (the conflict predicate: undeclared-stub callee with decl_sources = n_callers + is_target >= 2) + tools/derive_canonical_sigs.py (byte-neutral s32 func_X(s32...), arity from Ghidra-C + asm read-before-write $a0-$a3) → a 20-extern block at the TOP of ov_SC01_077.c (LOCAL, not engine_core.h — reach-1 names differ across overlays). (Both census_conflict_callees.py and derive_canonical_sigs.py were DELETED in Phase 26-A — R33; the fleet-canonical-sig approach was superseded by reconcile_tu's per-TU oracle. Historical record.) gen_harvest_targets + sig_unify auto-read it; the gate pipeline is now draft → sig_unify (MANDATORY) → harvest_verify --chunk 1 (the accumulating baseline now carries the file-top block, so a raw draft's guessed extern would clash without sig_unify). SIZING CORRECTION (R14): for the remaining 270, the conflict wall is only 20 callees / 24 targets / 7% of wave reach — the "~2×" was the top-30's in-flight conflicts, since resolved by banking. The real wall is the gcc-quirk tail, not sig conflicts — the 4 highest-reach circular targets are ALL §10-hoist / regalloc / layout-bound (0 closed by hand or permuter). The layer makes a wave sig-clean; it does NOT unlock the quirk tail. → The match-% lever is understanding gcc-2.7.2 (R17 compiler-source research, Phase 18), not more brute waves. Wave deferred; infra staged (.run/harvest_wave_s4.js, 40 tractable reach-134 targets). See docs/sunset/hand-matching-process.md §8. §17 (CORRECTED) answers it: the call-crossing register-ORDER class IS matchable — with register __asm__ pins + a scheduling barrier (byte-proven). The "brute waves won't help" point stands; HAND levers (pins) do.