3.0 KiB
§226 — FRAME PADS: FOUR WAYS §162i1/§2429's DEAD-LOCAL LEVER MISFIRES (P31 S58)
The pad lever ("declare a dead s32 pad[N≥2] to reserve var_size") fired correctly on five cards
this harvest (func_8018ED84 Δ0x10, func_8017D118 Δ8, func_801871EC Δ8, func_8018BA38 Δ0x10,
func_801815A8 Δ8). These are the four cards where reaching for it was wrong:
1. IT OVERSHOOTS WHEN REAL NARROW LOCALS ALREADY EXIST. func_8018B830 (ov_SC04_011, 36 ins):
the 0x8 phantom frame (addiu sp,-8, no saves, no spills) is not pad-induced — an
address-taken pad gave 0x10, and combined with the s16 locals gcc rounded to 0x10. The halfword
locals s16 a2; u16 temp alone reserve exactly 8 bytes of var_size. Count the real locals'
contribution to get_frame_size() before adding anything.
2. A DEAD ARRAY ELEMENT SURVIVES WHERE A SEPARATE PAD ARRAY IS TRIMMED. func_801870B8
(ov_SC03_029, 24 ins): frame 0x28 with ra at 0x20 needs a 16-byte locals block for 3 live words.
A plain s32 pad[N] would have been trimmed; growing the real array to a dead 4th element did
the job. Prefer widening an existing aggregate over adding a new one.
3. THE DEAD LOCAL IS SOMETIMES LOAD-BEARING, NOT PADDING. func_801AA210 (md_SC07_004, 58/58):
frame 0x38 requires a dead third local at sp+0x20 whose address is cached in $s0 across the
three RotTransSV calls — declare it even though nothing reads it. That is not a size lever; it is
a register lever that happens to change the size.
4. THE PAD CAN BE A DECOY FOR A HOISTING RESIDUAL. func_8017E424 (ov_SC07_007): frame 0x28 vs
0x20 was "fixed" by pad[2] — and that was wrong. The real cause was hoisting a narrow read into a
local; running §167-10 BACKWARDS (un-hoisting the else-arm re-read *(s16*)(s0+8) - 1) regenerated
both the addu $v1,$v0,$zero copy AND the orphan 8-byte slot, and deleting the pad was then
required, not optional. §167-10 documents only the cure direction ("hoist to delete") and its gate
table's "short, read hoisted into int n ⇒ no copy" row reads as if hoisting were always the fix.
THE INVERSE LAW, worth stating: a target showing copy + frame + no pad is EVIDENCE OF THE
UN-HOISTED SOURCE FORM.
5. AND NEVER COPY THE TWIN'S PAD. func_8017D684 (ov_SC07_007, 48 ins, sim 0.85): the twin's
dead-local pad existed to push ITS buffer to sp+0x18; this target's buffer is the FIRST local at
sp+0x10, so copying the pad would have shifted it and broken all four swl/swr offsets.
6. ONE MORE FRAME CAUSE THAT IS NOT A PAD AT ALL. func_80183C7C (ov_SC02_005): LENGTH-DRIFT −4
because gcc keeps $s0 live across the early-return path — s0 = &D_80126614 is set before the
guard, reused as the call's third argument and as the receiver of the return value — which forces
the full 4-slot frame even though the fast path returns immediately. Return-value reuse of a
pre-call address register is a frame-size cause; it looks like a pad problem and is not.