3.0 KiB
§74 — Auditing a pinned draft: the §72 hazard is CALLER-SAVED pins spanning a call, and only the disassembly can tell you (Phase 29 SESSION-19, func_8017D960 b2, 5 pins)
§72 established that a register __asm__("$N") pin is a preference, not a reservation — gcc will
happily put another value in $N while your variable is notionally live. The banked corpus is safe
from this by construction (the whole-binary byte-gate rejects any miscompile), but un-gated drafts
are not, and a behemoth draft can carry pins for days before it ever reaches a gate. This is the
cheap audit.
Two distinct failure modes, only one of which is real most of the time:
- CALLER-SAVED PIN SPANNING A CALL — the one that silently corrupts.
$25($t9) and the$t0–$t9range are call-clobbered. gcc-2.7.2 does not save/restore an explicit-register variable across a call, so if the pinned variable's live range crosses ajal, the value is destroyed with no diagnostic. Callee-saved pins ($16–$23=$s0–$s7) are immune — the prologue/epilogue save/restore covers them. - SCRATCH REUSE OF THE PINNED REGISTER — usually benign. gcc will use
$Nas a temporary for an unrelated value before the pinned variable's own value lands there. Observed here:lui s4,..; lw s4,0(s4); addiu s4,s4,-128—$20(pinnedr1lo) carried the raw global for two insns, andaddu t9,s4,zerocopied that value out to$25(pinnedr1) on the way. Self-consistent; nothing live was clobbered.
The audit (no recompile needed if a match_one object survives — .run/match/<fn>.<pid>/<fn>/t.o;
confirm it is the draft you think it is with cmp t.c <draft>):
mipsel-linux-gnu-objdump -d <t.o> > dis.txt
# (a) does any call exist after the first pin write?
grep -nE '\bjalr?\b' dis.txt # compare addresses against the pin-write addresses
# (b) how many times is each pinned reg WRITTEN? (first operand, excluding sw/branch/jal/mult-class)
Expect writes == (assignments in the C) + 1 epilogue lw for each callee-saved pin. Anything above
that is scratch reuse — read those sites before assuming they are benign.
Worked verdict (.run/giants/s18_func_8017D960_b2.c, pins $25 $17 $19 $20 $21): 3 jals total,
all at 0x2c–0x50, and the first pin write is at 0x58 — no call after the pins are
established, so the caller-saved $25 pin never spans one and mode (1) does not arise. Modes (2)
sightings on $20/$21/$17 are the benign scratch pattern above. Draft is safe to keep building
on. (The C corroborates: everything after the three prologue calls is a macro — gte_*, BOXTEST,
ATTEN, CLAMP80 — not a function call. Verify that with a token census, not by eye: a 636-line
behemoth hides a jal easily.)
Standing rule of thumb: prefer a callee-saved register for any pin whose variable outlives a
call; if the target's register really is caller-saved and the value really does span a jal, the pin
cannot express it — that is a genuine wall verdict, not a drafting slip.