5.2 KiB
§232 — WHEN THE jr DELAY SLOT'S STORE STORES THE RETURN VALUE, TIE THEM WITH ONE PSEUDO (single observation — not yet cross-confirmed) (P31 S58)
func_801A7CDC (md_SC07_004, 15/15). The target tail is:
addiu $v0, $v0, 0x5
jr $ra
sh $v0, 0x2($a0) <- the final store consumes the RETURN VALUE
THE TRAP. store; return 5; — and the store-equals-return and pinned-temp respellings, all
three byte-identical to each other — make gcc materialise the constant TWICE: once for the store,
once for the return copy. The dead second li v0,5 then steals the jr delay slot, giving
sh ; jr ; li(dead) — near 3, LENGTH-DRIFT.
THE FIX. A §30 #3 anonymous-asm re-tie forces ONE real pseudo set shared by the store and the
return:
__asm__("" : "=r"(r) : "0"(5));
The return copy becomes an identity, the jr slot stays empty through reorg, and dbr fills it with
the sh.
REFUTED ON THE SAME CARD. register s32 r __asm__("$2") — pinning r to $2 removes it from
the scratch pool and perturbs allocation for the whole body (near 14, WIDTH cascade). Do not
reach for the hard pin here.
ADDENDA TO EXISTING SECTIONS (P31 S58 wave ab–ag harvest)
Appended rather than inserted, per the file's append-only rule. Each entry belongs to the section named in its heading.
§30 addendum (P31 S58) — the /s grant closes a SCHEDULING residual and a REGISTER residual with one edit
§30 establishes that a bare/zero-offset deref never gets MEM_IN_STRUCT_P while an anonymous-struct
member ref always does. func_801824D4 (ov_SC06_000, 35/35, 9 oracle calls) adds the composite:
granting /s fixed both halves of a 4-insn tail knot at once. Plain source orders all failed —
a bare += last gave the lhu BELOW the aliasing store with v0=load / v1=const (wrong
registers); a temp-load-first gave the right position but v1=load / v0=const (REGALLOC-PERM).
Writing the increment as an anonymous struct member ref,
((struct { u8 pad[2]; u16 f; } *)a0)->f += 1;, granted /s: the lhu hoisted above the
fixed-symbol store and the pseudo ordering flipped so the constant 1 landed in $v1 and the
load in $v0. The anonymous struct is required — dedup_propagate rejects inline named structs.
(Also on that card: declaring D_801AEAFC as u16 truncates a 0x1310000 constant to the sh-only
path; it must be s32/u32.)
func_801831E4 (ov_SC06_000, 30 ins) is the same law read forwards, plus a composite §30 does not
mention: the reload idiom composes with direct-param-register walking, and the local-copy variant
silently changes the loop counter's register even when the reload itself is correct
(param_1 = (s32*)((s32)param_1 + 0xCC) then param_1++ keeps the giv chain on $a0 so the counter
lands in $a1; copying into a fresh local pushed it to $a2, near 11). And on the same card, named
locals vs anonymous nested derefs flip the $v0/$v1 colouring in a double-indirect RMW.
§194-B addendum (P31 S58) — BOUND 2 is byte-wrong; see §209 Direction A
§194-B's bound "fewer than two surviving consumers ⇒ no copy" would have steered func_801A8738
away from its winning spelling. One sb store plus a compare is enough to keep the
addu $rA,$rB,$zero. Full evidence, the ablation list and three corroborating cards are in §209.
§176-B addendum (P31 S58) — the misdiagnosis direction
§176-B says a 1-4 instruction REGALLOC-PERM residual is usually not register allocation.
func_801A9810 shows the strongest form of that: it was a wrong call argument. Re-read the target's
own addu SOURCES before believing a swap signature. See §231.5.
§165-40 addendum (P31 S58) — the barrier goes at the COPY SITE, not inside the region it protects
§165-40 states the placement of a bare __asm__ __volatile__("") but not this scope constraint.
func_801836B0 (ov_SC02_027, 27/27): gcc floated an addu $a0,$s0,$zero argument copy into a
beqz delay slot where the target keeps a nop. The barrier had to sit immediately before the
defining statement (the call) — i.e. at the copy's own site — to deny the upward hoist. Placing it
inside the if-body, i.e. inside the guarded region whose branch it was meant to protect, did
nothing.
§164-63 addendum (P31 S58) — the interposed asm works when the SECOND value is an ordinary assignment
§164-63's evidence table shows the zero-byte non-volatile input asm placed between two pin sets.
func_80186F9C (ov_SC03_029, 45 ins) shows it works equally when the second set is a plain
assignment after the declaration block — no register-pinned declaration is required for the second
value. The residual was SCHEDULE-REORDER/4 in the prologue pair only (sw $s1/move $s1,a0 had to
precede sw $s0/move $s0,a1); declaration order alone did nothing (A/B'd), and
__asm__("" :: "r"(s1)); before s0 = a1; is what flipped the pair.
§193-A / §194-E addendum (P31 S58) — where the twin's body actually lives
See §214: seven cards in this harvest lost time because the twin's C is a DEFINE_func_* macro
in src/shared/engine_core.h rather than a body in its overlay .c, and one because the twin's .s
had been pruned from nonmatchings/ on banking. Add both greps to the twin-reading step.