Files
BFM-decomp/cookbook/C0048.md
T

4.7 KiB
Raw Blame History

§45 — The flagship func_80133CD4 crack (399 ins ×134): the merged-variable permutation-breaker + the 1-death local-alloc gate (Phase 25 task A giant escalation, Fable5 gdb-on-cc1, 2026-07-11)

The 399-ins flagship — a "whole-function register permutation" that walled the directed permuter (masked-172) and had been tagged intrinsic for ~22 phases — fell PIN-FREE (×134-clean) to a Fable5 gdb-on-cc1 crack (whole-binary byte-gate BYTE-IDENTICAL d19c9580, banked ×134). Four reusable, byte-proven levers (worked example .run/giants/func_80133CD4.fable.c; dumps + gdb oracle in .run/giants/fable_cd4/):

Lever A — MERGED ACCUMULATOR VARIABLES break a "whole-function permutation" (the headline: 378→147 mismatches). When the target holds ONE $sN across disjoint value-regions (e.g. $s0 = {call-3 result → denominator → loop-accumulator}), gcc-2.7.2 global-alloc has no coalescing (K8), so one hard reg spanning disjoint regions can only come from one reused source variable. Merge the disjoint C variables into one → the allocno becomes call-crossing (K4, global.c:917) with a high merged ref-count → top density (K2, global.c:594 allocno_compare) → it allocates FIRST → plain regno first-fit (K3) reproduces the ENTIRE callee-saved permutation (the arg0→$s7/$fp end is §43's K&R double-copy). AUDIT for reused-variable chains BEFORE calling a whole-function permutation "unsteerable" — it is the original C reusing one variable per accumulator chain, not a compiler mystery. Retires the "N-callee vs N−1-callee permutation" giant-wall class.

Lever B — the 1-death local-alloc gate + the in-out-asm fix (67→13; found by a gdb ORACLE). A shared read-temp serialized through one register (target: lh; lh into the same reg separated by a byte-visible nop) is a 2-SET variable, which local-alloc REJECTS: reg_n_deaths != 1 (local-alloc.c:472) forces it to a GLOBAL allocno, allocated after every block-local qty → it loses the low-scratch first-fit and the whole caller-saved block permutes. No pure-C spelling yields 2-sets/1-death (flow emits REG_DEAD per region flow.c:2533; combine's 2-insn merges undo, its split path needs i1 = 3-insn combos only combine.c:1737; cse dissolves every 1-set spelling — all byte-tested). The escape (flow.c:2511): no REG_DEAD when a reg is SET in the same insn it last USES — expressible ONLY as an in-out asm __asm__("lh %0, off(%2)" : "=r"(h) : "0"(h), "r"(p) : "memory") (the "0"(h) input-tie makes read-2's lh use+set h in one insn) → 1 death → LOCAL qty → wins $v0 by qty-birth tie-break → the rest cascade by first-fit. PIN-FREE / ×134-safe (generic constraints, real opcode, no hard-reg names — NOT a register __asm__("$N") pin → no §42e sibling-TU SIGABRT). The "memory" clobber doubles as a delay-slot fence.

  • THE METHOD — the gdb ORACLE (§34 flywheel). When a hypothesis reduces to ONE compiler-internal quantity, patch it mid-compile and diff the output (break *local_alloc; set reg_n_deaths[h]=1). One run turns "plausible root cause" into "proven," licensing the (expensive) hunt for the C form that induces it. -dS/-dR dump sched1/sched2 with per-insn dependence lists on reload-born insns — read those before hand-modeling. In the shipped i386 cc1, qty_first_reg lives at 0x82c5404 (the info address symbol is stale for this binary).

Lever C — offset-0 /s store asymmetry (last 5 diffs). p[0] = x expands non-/s (mem (reg)) while p[k≥1] are mem/s → a fixed-address (reload-born) load keeps its true-dep ONLY against the offset-0 store (sched.c:820 drop-clause needs /s+varying on one side, non-/s+fixed on the other). ((struct { s32 w; } *)p)->w = x; /s-ifies the offset-0 store → dep dropped → the load floats to the earlier delay gap. Store-side twin of §37's load-side /s lever.

Lever D — goto-shared-return isolates the exit li (tail). A common return 1 reached by goto ret1: gets its OWN basic block → stops sched1 hoisting the exit li v0,1 into a last-element load-delay slot cross-BB (freeing $v0 for a trailing temp); dbr still steals the li into the branch delay slot. Use when a return-constant materializes one instruction too early.

Transfer caveat (the §44 meta-lesson holds): each giant is its own class — Levers A/B are regalloc-permutation tools; apply them to a walled giant only when its residual IS a merged-variable or 2-set-temp permutation (read the .greg/.lreg tell first). The Phase-25 flywheel applies A–D via cheap-Opus to the sibling walled giants (func_8014D820 RC-6, func_801670E4, func_8016CBC0), escalating to Fable5 only for a genuinely new class.