Files
BFM-decomp/cookbook/C0241.md
T

4.4 KiB

§220 — THE PARAMETER ITSELF IS A REGALLOC DIAL: use it directly, prefer s32 to void*, and place the save-copy AFTER the first call (P31 S58)

Four cards, four overlays, one direction.

1. DO NOT COPY THE PARAMETER INTO A LOCAL. func_801877FC (ov_SC02_005, 49 ins): naming the parameter s0 and using it directly matched; s32 s0 = arg0; made gcc double-buffer through $s1 and add a spill/restore pair. Same on func_8018251C (ov_SC02_000): drafting the twin's u8 *s0 = a0 prologue forced an early move+sw and drifted 28 → 31 ins.

2. PREFER PLAIN s32 TO void* WHEN THE ALLOCATOR SPLITS ON POINTER-vs-INT. func_8017E424 (ov_SC07_007): the last diff was sh $v0,8($a0) vs 8($s0). A register __asm__("$16") pin lost the delay-slot fill; a void* spelling made the same $a0 choice; typing the parameter plain s32 won — one pseudo, no pointer/int allocno split, and regalloc keeps $a0 live through the tail store. func_801878A8 (ov_SC03_028) reports the identical requirement independently.

3. THE s0 = a0 COPY'S POSITION RELATIVE TO THE FIRST CALL DECIDES THE WHOLE PROLOGUE. func_80189E1C (ov_SC03_006, 27 ins), the cleanest measurement in this harvest:

copy written result
before the first call (the naive s32 s0 = a0; first) gcc computes the copy eagerly → move s0,a0 before the jal, then burns a SECOND callee-saved reg ($s1) for the body copy — 29 ins, frame 0x20, 25 mismatches
after the call statement (the twin's order: call, then copy) the copy's live range starts post-call, local-alloc keeps it solely in $s0, and sched2 sinks addu $s0,$a0,$zero into the jal's delay slot — MATCH, frame 0x18, saves s0+ra only

One-line reorder was the entire residual, and the twin's statement order encoded it without saying so. Compare func_80183968 (ov_SC03_029), where the target births s0←a0 at idx 2, before sw ra, and plain C hoisted the copy too late — an unpinned inline-asm launder at the top fixed the prologue order and freed $a0 so the first pointer load landed there like the target. The two cards are the two ends of the same dial.

4. AND THE COPY CAN BE THE PROBLEM. func_80184C20 (ov_SC02_005): a naive cbptr = *(s32*)(s0+0xCC) let local-alloc's optimize_reg_copy_1 forward-substitute $a0 for $s0 and defer move s0,a0 into the beqz delay slot. Fix per §11402: make the use insn also SET the source (a0 = *(s32 *)(s0 + 0xCC);) so reg_set_p aborts the substitution scan.

Addendum (P31 S64 t5j-t5m, func_8018AAE4): the RESIDUAL SIGNATURE for this addendum's pin case, and the closeness trap that hides it. func_8018AAE4 (75 ins) runs the whole ladder in three match_one calls: register s32 s0 __asm__("$16"); s0 = a0; → near, closeness 1, one masked residual at idx 65, mine 00000000 nop vs target 02002021 addu $a0,$s0,$zero, klass DELAY-SLOT / profile schedule / sig DELAY-SLOT/1; the plain s32 s0 = a0; neighbour-house-style copy → closeness 64, nins 78 vs 75, 02008821 move $s1,$s0 at idx 8 plus the extra sw $s1 (§161b's +3, to the instruction); deleting the alias entirely and referencing a0 at every site → MATCH 75/75, closeness 0. Two things to carry. (1) The pin's failure is a bare nop where the target has a call-site re-materialize addu $aM,$sN,$zero. The pinned local coalesces onto the same physical register but leaves the incoming-parameter pseudo simultaneously live with an identical value, and in this function's else-arm copy-propagation substitutes the parameter pseudo for the pinned one and deletes the copy — §220 card 4's optimize_reg_copy_1 forward-substitution, one block later than card 4 measures it. (2) That is the exact MIRROR of §263, and the polarity is the discriminator. §263 reads mine-has-move $aN,$sM / target-has-nop as a callee-arity error; mine-has-nop / target-has-the-copy is an aliasing defect and the cure is this addendum's (delete the alias), not §263's (flip the arity). Check which side the copy is on before flipping any callee's arity. Same caution as the §195 DELAY-SLOT/1 misdiagnosis note: the schedule profile is wrong here too. (3) Do not let closeness rank the candidate shapes. The pin reads 1 and the plain copy reads 64, yet the MATCH is neither of them — a closeness-1 pinned draft on a parameter that must survive calls is a signal to delete the alias, not to grind the last instruction.