Files
BFM-decomp/cookbook/C0478.md
T

2.0 KiB

§429 ★★★ — EVERY HELD POINTER NEEDS ITS OWN LOCAL, AND A NEGATIVE-DISPLACEMENT BYTE STORE NEEDS ONE OF ITS OWN (P31 S72; main/CdReadStateMachine, MATCH 385/385)

Two laws from one function, both about the SAME root: gcc-2.7.2 canonicalises (mem (reg)) back to a symbol whenever the pseudo has a single reachable set, so reusing a pointer local silently changes the addressing mode of everything downstream.

1. A NEGATIVE-DISPLACEMENT BYTE STORE RE-FOLDS UNLESS IT HAS ITS OWN POINTER. Writing p[-0x10] = v (or *(u8 *)(p - 0x10) = v) where p is a symbol-derived pointer lets alias.c re-fold the address to lui %hi(sym - 0x10) — the wrong instruction pair. Give it a local of its own:

u8 *q = p - 0x10;      /* q is SET ONCE and used once: the reg survives as a reg */
*q = v;

2. EVERY HELD POINTER NEEDS A DISTINCT LOCAL. A pseudo with MULTIPLE sets defeats the (mem (reg)) -> symbol canonicalisation for every use, and the resulting reg-form load costs a load-delay nop the target does not have. If you are carrying two or three pointers through a state machine, that is two or three separate locals — never one reused cursor. This is the same mechanism as §421 (a la $tN+addiu pair is RELOAD scratch) read from the source side: what you spell as reuse, the allocator spells as a multi-set qty.

3. THE FRAME DIAL AND THE MERGE-END PIN (both already known, confirmed here). The §333 frame dial (an unused s32 pad[2] to move the frame size to 0x28) and explicit labels on the merged tails — setStateNine: / resetState: — were the other two levers. gcc's own cross_jump picks the OTHER end of a merge than you expect; naming both tails pins which one survives, which is the cheap alternative to §5a's fence and complements §428's UID barrier.

Verification standard this function met (§405-A): MATCH is .text-only, so the agent checked the reloc-symbol sequence 180/180 and the 11-entry jump table in case order before reporting. That is the bar for any switch function.