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.