Files
BFM-decomp/cookbook/C0003.md
T

2.5 KiB

§2 Writing matching C (what makes gcc emit X)

T1 — Loop pointer: top-of-body for addu induction, not constant-folded addiu

If you init a loop pointer before the loop from a constant index (p = base + 2), gcc constant-folds it to addiu p,base,2. The original often recomputes it from the induction variable, giving addu p,base,i at both entry and loop-back. Fix: compute the pointer at the top of the loop body (p = base + i;) so gcc keeps the induction form. Example: func_80018F20 (365→0 hinged on this).

T2 — Source statement order drives instruction scheduling

gcc 2.7.2's scheduler largely follows source order for independent setup/init statements. When pre-loop inits are mis-ordered vs the target (e.g. a counter li landing before vs after a hoisted loop-invariant), reordering the C statements moves them. When it doesn't (the op is placed by the optimizer, e.g. a hoisted invariant), it becomes a permuter job — see §3. Open example: func_80015A74 residual (counter init vs hoisted magic constant).

T3 — Types

u8/u16/u32/s8/s16/s32 from include/common.h. A u8* deref → lbu (zero-extend); s8* → lb (sign-extend). Pick the load width/sign that matches the asm, then layer & 0xff (I2) / casts as needed.

T4 — Branch polarity: invert the source condition to flip gcc's chosen branch

Two source forms can be logically identical but emit opposite branches: if (x & m) return A; return B; vs if ((x & m) == 0) return B; return A;. gcc -O2 picks one polarity (beqz vs bnez); it may be the opposite of the target. Symptom in asm-differ: the right structure but a lone beqz↔bnez flip with the two return constants swapped between the branch and its j/delay slot. Fix: rewrite the condition with the other polarity. Also: which arm of an if/else becomes the fall-through follows source order — put the target's fall-through block in the if, the branched-to block in the else (e.g. if (a != b){…} else {…} if the == block sits last). Example: CdQueueBusy (1405 → 210 via if/else order, 210 → 0 via the 0x20 polarity flip). Likewise multi-exit functions: write the success/main return LAST (it becomes the fall-through into the shared epilogue) and error cases as early returns (they branch in). Reversing this — if (ok) { … return good; } return 0; — makes return 0 the fall-through and duplicates the j epilogue/move v0,zero tail. Example: CdReadRequest (305 → 0 by flipping to if (busy) return 0; … return cdReq_result;).