3.5 KiB
§223 — READING A jal DELAY SLOT: the value in it was produced BEFORE the call, so it is NEVER that call's return (P31 S58)
§194-M owns the direction "a store in a CONDITIONAL branch's slot ⇒ its statement dominates the
branch", and §194-M BOUND 1 explicitly excludes jal slots. §176-A/L17616 and L17636 own the
placement lever ("move the store above the call"). Nothing owned the READING rule, and four cards in
this harvest lost a draft to it.
THE RULE. A delay slot executes BEFORE its jal transfers control. Therefore whatever the slot
reads was live at the SLOT, not at the call: it is the PRECEDING call's $v0, or a constant
materialised before the call, or the completion of a preceding statement. It is never the return of
the call whose slot it occupies. Ask "which value is live at the slot", not "which call is on the
line above".
Instance 1 — the preceding call's return. (✔ byte-checked:
asm/ov_SC06_029/.../func_80186DA0.s idx 6-13.)
jal func_8012B744
addiu $a0, $a0, 0x4
...
jal func_8012B2CC
sh $v0, 0x12($v1) <- $v0 is func_8012B744's return
The naive reading ("store func_8012B2CC's return through *(state+0x20)+0x12") drifted by exactly
one instruction everywhere after the slot. The correct C nests func_8012B744 directly as the stored
value and leaves func_8012B2CC as a separate void statement.
Instance 2 — a pre-call CONSTANT. (✔ byte-checked: asm/ov_SC06_000/.../func_80184524.s
idx 3-6.)
addiu $v0, $zero, 0x4 <- the constant
sw $ra, 0x14($sp)
jal func_8012B200
sw $v0, 0x1C($s0) <- stores 4, NOT the call's return
Drafting x = func_8012B200(...) mis-schedules the whole tail. func_801871EC (ov_SC03_028) is the
same shape twice over — both sw $v0,k($s0) sit in the FOLLOWING jal's slot with $v0 pre-loaded
by li (0xF and 0x23) — and both callees' results are discarded. Tell: a li/addiu …,$zero,K
above the jal and a sw $v0 in its slot ⇒ a constant store, written before the call.
Instance 3 — the completion of a preceding statement. func_80188C18 (ov_SC03_006): target line
28 sh $v0, 0x1A($sp) sits in the jal func_80133784 slot and is the scheduler-hoisted completion
of out[1] += 8, not a store of the call result. (✔ byte-checked above in §210 — lhu 0x1A(sp) /
addiu $v0,8 / jal / sh $v0,0x1A(sp).)
THE COROLLARY THAT SAVES A DRAFT — an EMPTY slot with an untouched $a0 means the incoming
argument passes straight through. func_80184524 again: lw $a0,0xD0($s0) / beqz $a0 /
jal func_80184AD4 with a nop slot ⇒ the callee's argument is the loaded pointer, which
survived the branch, not the entity. func_801887CC (ov_SC02_005) reports the same thing as a
missed call: a jal was invisible on first read because its slot held an sh and there was no
arg-setup insn at all — $a0 still carried the incoming argument. func_8018BF88 (ov_SC06_029),
func_80184B18 (ov_SC03_029) and func_801884C8 (ov_SC02_005) are three more of the same family;
on the last one, the absent a1/a2 setup means the call passes garbage in those registers,
which is expressible only through a casted function pointer.
BOUNDARY. This is a reading rule and it is one-way. It tells you what the source must have
said; it does not promise that writing the statement there will refill the slot — that is
fill_eager_delay_slots' problem and §199-F BOUND 2 / §194-M BOUND 3 own its failure modes.