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.