Files
BFM-decomp/cookbook/C0230.md
T

4.5 KiB

§209 — THE NARROW LOCAL IS A DIAL IN TWO OPPOSITE DIRECTIONS, AND §194-B's "≥2 sh STORES" BOUND IS BYTE-WRONG (P31 S58)

Direction A — a HImode local KEEPS a truncation copy that an s32 local elides. BOUNDS §194-B.

§194-B reads: "A addu $rA,$rB,$zero copy feeding ≥2 sh stores is a SECOND, 16-BIT-DECLARED local — the width, not the clamp or the join liveness, is what keeps the copy." Its bound #2 ("fewer than two surviving consumers ⇒ no copy") is refuted:

lbu    $a1, 0x0($a0)
addiu  $v0, $zero, 0xFF
addu   $v1, $a1, $zero      <- the copy
beq    $v1, $v0, .L801A8764 <- consumer #2 is the COMPARE
...
sb     $a1, 0x0($a0)        <- consumer #1 is ONE sb, not two sh

(✔ byte-checked: asm/md_SC07_004/.../func_801A8738.s, all 13 instructions.)

func_801A8738 (md_SC07_004, wave ac, ~10 drafts): every s32 spelling compiles to 11 ins with the load landing directly in $v1 and no copy. Declaring the accumulator s16 reproduces the target exactly — the HImode declaration makes "assigned then tested" emit a deferred-truncation copy before the compare, which also hands the pseudo $a1 by copy-preference off the incoming argument. Probed and refuted on the same card: two s32 locals (copy coalesces), an s32+s16 pair (copy appears but after the increment), u16 (same as s32), a guard-style nested if (identical, per §164-55), hoisting t = v+2 above the return test (moves the copy below the branch), and opaque asm barriers (inert — reorg runs after everything).

Three more instances agree, none of which has two sh stores either:

function binary s32 gives s16/short gives
func_80184E30 ov_SC02_005 +1 ins, an extra nop, no addu $a1,$v0,$zero MATCH 36 — the s16 type blocks gcc's dead-copy elimination
func_8018B830 ov_SC04_011 the copy folds away (it is NOT a CSE duplicate-read) s16 a2 produces the copy for the compare while the raw load feeds the later add
func_801815A8 ov_SC03_006 reading straight into the if condition folds it away a named short v local produces addu $v1,$v0,$zero

THE AMENDED LAW. A addu $rA,$rB,$zero immediately before a compare or a narrow store is a 16-BIT-DECLARED local. The consumer count is irrelevant — one sb plus the compare is enough.

Direction B — for a *(s16*) load whose only consumer is a zero/equality test, the RECEIVING LOCAL'S MODE decides lh vs lhu. This is a SECOND, INDEPENDENT DIAL from §15764.

lh    $v1, 0xFE($s0)
...
bnez  $v1, .L801A44B0

(✔ byte-checked: asm/md_SC07_004/.../func_801A445C.s idx 4/7 — a bare lh feeding only a bnez.)

§15764's relaxation ("missing sra ⇒ write the lvalue *(s16*)") is necessary but not sufficient: func_801A445C wrote s16 v1 = *(s16*)(p+0xFE); if (v1 == 0) and still got lhu (WIDTH/1), because an s16 local is a HImode pseudo (§21189: s16 k and u16 k are both (reg/v:HI)) and the extension into it is elided by the same relaxation. Landing the load in an s32 variable makes the sign-extension a genuine SImode requirement fused into the load pattern, the relaxation cannot fire, and the bare lh survives. func_801A9270 (md_SC07_004) reaches the same conclusion independently: s16 g ⇒ lhu; s32 g = D_801F8E98 ⇒ lh. func_80183B20 (ov_SC06_000) is the third face — an s16 raw local rematerialised at its second use as lhu + manual sll/sra; reading the s16 field directly inside the array subscript emitted the target's plain lh.

A third lever for Direction B, single observation — not yet cross-confirmed. func_8017F300 (ov_SC06_029, 12/12) has

lh   $v0, 0x100($a0)
sll  $v0, $v0, 2
sh   $v0, 0x34($a0)

(✔ byte-checked.) The sign-extension is semantically dead under the later sh, so with the natural << 2 spelling extend-propagation weakened the load to lhu. Spelling the scale as a multiply (* 4) matched. The submitted mechanism — "gcc does not do the dead-high-bits analysis through MULT, so SIGN_EXTEND stays alive through expand" — is plausible and untested; ship the precondition (*k vs <<n is a load-width dial when the extension is dead), not the story.

⚠ THE TWO DIRECTIONS WANT OPPOSITE WIDTHS. Direction A wants the narrow local, Direction B wants the wide one; one local cannot give you both a truncation copy and an lh. Choose by the residual you actually have: a missing addu ...,$zero ⇒ narrow; an lhu where the target has lh ⇒ widen.