Files
BFM-decomp/cookbook/C0236.md
T

4.9 KiB
Raw Blame History

§215 — PIN ECONOMY: the twin's pins are NOT part of the shape, and §17's "pin every call-crossing value" is over-broad (P31 S58)

THE DEFAULT IS PLAIN LOCALS. Run that A/B first. Five cards in this harvest copied a twin's register T v __asm__("$16"/"$17"/"$18") triple and were made worse by it:

card binary with the twin's pins with plain locals
func_80182A30 ov_SC06_000 every pin permutation left a 4-insn SCHEDULE-REORDER in bb0; statement order controlled which pairs permuted (i=0 last → s2,s1,s0; i=0 mid → s0,s2,s1) but never reached the target's s2,s0,s1 s32 i; s32 *ptr; s32 a0; → the exact target weave, first try; statement order became irrelevant
func_8018B684 ov_SC03_006 the ≥0.9 twin's __asm__ barrier gave gcc nothing to hoist; a fresh local + barrier gave 24 ins / wrong fills duplicate the call per arm with literal constants → cross_jump merges the tail, MATCH 25
func_80184840 ov_SC04_011 the twin matched without any pin, so the twin was actively misleading on the regalloc axis this card needed register s32 p __asm__("$4") — the opposite conclusion from the same family
func_801AD5B4 md_SC07_004 the twin's pins were entirely unnecessary for this relative plain locals sufficed once statement order was right
func_8017E424 ov_SC07_007 the TU-neighbours' house hints (register pins, casted calls) were counterproductive for this body typing the parameter plain s32 won — one pseudo, no pointer/int allocno split

AND §17's ABSOLUTE IS WRONG. func_8018130C (ov_SC03_124, wave af): §17 prescribes pinning EVERY call-crossing value. Pinning arg0 to $17 alone → near 21. Pinning only $s0 to $16, leaving s2/s3 unpinned so gcc keeps its own density ranking → near 4. Pinning s2/s3 as well → near 47 LENGTH-DRIFT (the pins on the mult-chain temps broke gcc's negu/mflo scheduling). Only the values that genuinely cross a jal boundary need pins.

WHEN THE PIN IS THE ANSWER — four shapes, all "pin exactly one thing":

  1. A caller-saved colour rotation where the target register IS the incoming-arg register. func_801AE908 (md_SC07_004): target colours p→$a0 / t→$v0 / test→$v1 against first-fit's $v0/$v1/$a0. Tried and inert: s32-vs-u16 on the temp; §13259/§15519's copy-preference respellings (both void* and s32 parameter — the $4 preference did not survive); field-based spelling with no temp. register s32 p __asm__("$4") → MATCH 34. This is the explicit complement of §2425's "no register __asm__ pins needed".
  2. A lone lw in the wrong GPR whose only use is a branch. func_801A2364 (md_SC07_004): a plain s32 temp put the 0xD4 field load in $v1; register s32 flag __asm__("$4") forced lw $a0,0xD4($s0) + beqz $a0. MATCH 39.
  3. Pin the CONSTANT interloper, not the pointer. func_8018CAF0 (ov_SC04_011): unpinned, the 0x80000000 constant landed in $a1 and the pointer/value in $v0/$v1; target wants constant=$v1. register s32 mask __asm__("$3"), with the mask statement placed before the pointer load, closed all 5. (§176-B2's exemplar contested a load against a constant; this is the same fix on a different contested pair — a bare lui constant feeding an OR mask.)
  4. A zero-byte pin to raise a use count, placed BEFORE the branch. func_801852C8 (ov_SC03_006, 15 ins): ONE __asm__ __volatile__("" :: "r"(hw)) immediately after the halfword's birth raised its use count enough for allocno priority to hand it $v1 while the long-lived pointer kept $a1. Placement is load-bearing: inside the then-block it did nothing.

AND THE DIRECTION IS OFTEN INVERTED FROM INTUITION. func_80188104 (ov_SC02_017): the residual was two early loads reading the restored $a0 where the target reads $s0. (a) Pinning the SAVED value to $16 fails — the allocator gives the plain parameter its own callee-saved home ($s1), adds an sw/lw pair (+3 ins), and still serves the early loads off $a0. (b) Pinning the TAIL web to $4 snaps it: since that value occupies $4 across the call, the parameter must survive the call elsewhere → it stays in $s0, the early loads read $s0, and the copy materialises as the target's addu $a0,$s0,$zero in the beqz slot. (c) Ordering trap: writing the copy FIRST lets dbr thread it into the delay slot before the two loads, so cse serves them from a fresh $a0 again — the copy must stay after the store pairs. Read (a)/(b) as the rule: pin the register you want OCCUPIED, not the value you want MOVED.

PROVENANCE. Ten cards, six overlays, waves ab–ag. Also confirms §176-C practically: the $4 pin scheduled fine around the call on func_80188104 despite the sched.c:1704 hard-reg anti-dep bug (that bug only adds false deps, and first-fit had no better choice than $4 anyway).