1.9 KiB
§243 — SPELL THE CSE BARRIER AS A REASSIGNED POINTER; AND THE nop-AFTER-EVERY-lh SIGNATURE (P31 S58b)
THE SIGNATURE. When a target reloads the same narrow global before every store, with an
unfilled load-delay nop after each load, the source did not read the symbol directly and did
not mark it volatile. It read through a pointer variable that is reassigned before each use —
the address pseudo is redefined between loads, so cse cannot common them, and no opcode changes.
func_80189B74 (ov_SC06_029, 16/16). Measured alternatives on the same card:
| spelling | result |
|---|---|
*(s16*)&D_x direct cast |
cse folds four reads to two loads + hoisted stores — 10 ins vs 16 |
volatile on the extern |
24 ins, lhu+sll/sra — wrong width, wrong count |
| reassigned block-scope pointer | MATCH 16/16 |
§176-B1 documents the narrow-symbol alias problem for schedule permutations; this is its inverse — a deliberate CSE barrier that costs zero bytes.
THE COMPLEMENTARY LEVER (same mechanism, opposite direction). When the target reloads a
pointer-typed field after an intervening store, the alias is already conservative and plain
sequential C reproduces it for free — do not hoist (func_8017EC00, ov_SC07_000, which also
explains why: gcc cannot prove *(arg0+0x20)+0x2C does not alias arg0+0x20). And when the target
reloads a global's address three times, the fix is the opposite again: take the address once into
a local (func_801A7B08, md_SC07_004; func_8017DB44, ov_SC01_084 — where sw $s0/lw $s0 with no
other $s0 use is the demand for a pointer value live across a call).
READING RULE. Count the reloads in the target and compare with the number of textual reads your draft has. Equal ⇒ leave it alone. Target has more ⇒ you need a barrier (reassigned pointer, or §244). Target has fewer ⇒ you need a name (§208/§253-adjacent).