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;).