Files
BFM-decomp/cookbook/C0035.md
T

4.4 KiB
Raw Blame History

§32 — The region-a CAMERA-GIANT idiom set: struct-base hoisting + 4 sibling levers (Phase 24 T7, Fable5-cracked on func_80129CF8 191 ins, match_one MATCH; transferable to the 6 sibling giants)

The region-a (ov_SC01_077_a.c) giants Ghidra flattens into per-global lui/%lo — but the target holds global base ARRAYS in callee-saved regs across the whole function. Fable5 read the gcc-2.7.2 source and cracked func_80129CF8 in 3 edits (202→143→10→0), no pins, no permuter. The idiom set (each transferable — .run/t7/func_80129CF8.c is the worked example, .run/t7/GIANTS_SURVEY.md the target list):

  1. Hoisted $sN base = an explicit POINTER LOCAL, assigned AFTER the first call, in the target's lui order (p2 = D_800AF630; p1 = D_80126DB8; right after the first jal). gcc-2.7.2 has no cross-bb CSE, so a base living in a callee-saved reg across calls/branches can ONLY come from a source local — you cannot get it from a bare global access. No register __asm__ pins needed: allocation order = density priority floor_log2(n_refs)*n_refs/live_length (global.c:594 allocno_compare) then first-fit regno (mips.h has no REG_ALLOC_ORDER) → most-referenced ptr→$s0, next→$s1, next→$s2. The init placement survives because sched1's ascending-LUID tie-break (sched.c:2414 rank_for_schedule) keeps zero-dep sets in order. A single-set/single-use base local is SAFE from the RC-7 init-sink (its hi/lo SET_SRC is a LO_SUM → fails rtx_equal_p at local-alloc.c:1169-1172).
  2. Branch polarity is READ OFF THE TARGET OPCODE (not Ghidra's if): target beqz $v0,.Lcopy with the other arm as fall-through ⇒ write if (sel != 0) {fallthrough-arm} else {copy}. On func_80129CF8 this single inversion fixed 133 of 143 mismatches.
  3. lw/lw/nop/addu/sw per-element ladders = the S12 reused-s32-temp fence — a = p1[i]; b = cam[j]; cam[k] = a+b; reusing ONE (a,b) pair across all elements. But leave the LAST element in Ghidra's fresh-temp shape (t = p1[last] + cam[..]; cam[..] = ..; cam[..] = t;) — its unfenced load is what the scheduler hoists into the previous element's load-delay slot, and a following call-arg &cam[..] fills the gap before it, both automatically.
  4. A grouped 3-loads/3-stores copy fed by la $reg,SRC (split lui/addiu) = a 32-byte STRUCT ASSIGNMENT, never scalar copies: *(RView*)(p2 + 6) = *(RView*)D_800AE688; (RView = the GsRVIEW2-shaped 32-byte struct, now in engine_types.h). expand_block_move (mips.c:2361) emits one movstrsi_internal for ≤2*MAX_MOVE_BYTES(32) with 4 scratches; output_block_move burns the last scratch on la a1,SRC → 3 data regs → the 3+3 grouping. The dest must go through the pointer local (p2+6, folded to 0x18($s2)); a bare global dest is CONSTANT_P and degrades to 2 regs. Scalar a=src[i]; dst[i]=a; copies schedule as lw/lw/sw/sw pairs — WRONG shape.
  5. Frame bigger than args(16)+saves by a round chunk = a DEAD LOCAL AGGREGATE: add an unused RView view; (32 B) to reserve the missing 0x20 — gcc-2.7.2 assigns stack slots to local structs/arrays at expand time regardless of use, and -O2 never deletes them. Suspect this whenever a sibling's frame is 0x20/0x28 over-accountable.
  6. This class needs NO pins, NO asm fences, NO permuter — the interp-loop software-pipelining (a2/sll/lh-next/sra/sw + a3-in-jal-slot) is deterministic sched2+dbr output once the bases sit in $s0/$s1/$s2.

BANKING (Phase-24 T7b — RESOLVED, see §33): a freshly-matched giant MATCHes standalone (match_one) but its loose data decls (struct BigCopy / s32 / s32[] / u8[] / s8-vs-u8) collide in-TU with engine_core.h when you try to bank it. The fix is a decl-reconcile — declare each symbol its canonical type and cast byte-neutrally at the access site (cam = (s32*)D_80126948;, p1 = (s32*)&D_80126DB8; for a struct BigCopy base, D_801151D4 = (s32)cam;, *(u8*)&D_801150D6 to force lbu under s8) — done by HAND for func_80129CF8, now automated by tools/reconcile_decls.py (§33). R14 correction: the T7 note that "banking ×134 hits the wall" was a MISDIAGNOSIS — once the ×1 bank is reconciled, dedup_propagate --recover propagates it to all 134 overlays byte-identical for free (proven: func_80129CF8 ×134, clean fleet 136/136). The wall was only ever the ×1 reconcile of the loose draft, not the propagation.