1.6 KiB
§387 ★★ — SPLIT-FOLD DISPATCH CLOBBER: one switch case needs a reload, another must keep the fold (P31 S69; byte-proven main/func_80030F80, 343 ins, escalation 3 → 0)
When one case needs a cse-forced RELOAD of the switch-index load while another must keep FOLDING that same value into a call argument:
- Do NOT put the invalidator in the case body —
reorg.c'sstop_search_phaltsfill_slots_from_threadat ANY asm insn (asm_noperands >= 0), so branch-delay-slot hoists into that thread die (+1 length). - Do NOT volatile-cast the load —
loop.c:339init_recog_no_volatilefailsrecogon everyDEST_ADDRgiv rewrite of a volatile mem, so its address stays biv-based forever.
Instead: name the index in a local; load the dispatch byte into an s32 local (a u8 costs an
andi); place a non-volatile __asm__("" ::: "memory") AFTER that lbu and BEFORE the switch —
zero bytes, invalidates cse's memory table on every path, and sits outside every case's delay-slot
thread. Never between a load and its first use: it eats the final-pass load-delay nop. Fold-cases
then pass the named local; reload-cases re-read memory.
Side-effect law, and it is general: adding or removing pseudos flips razor-thin global-alloc ties
among pre-loop invariant pointers — which one loses its callee-saved register and gets reload-
rematerialised as inline lui/addiu. This is INVISIBLE under match_one's HI16/LO16 mask and
surfaces as a bogus "schedule" diff at the remat site. Audit with objdump -r, and flip it back by
swapping the two invariants' init order.