From 66bf1ebe62dd7708f479ec7e0734c96e6e9cb175 Mon Sep 17 00:00:00 2001 From: Drew T <50529377+Druthulu@users.noreply.github.com> Date: Thu, 3 Sep 2026 16:02:32 -0600 Subject: [PATCH] =?UTF-8?q?docs(cookbook):=20=C2=A7464-=C2=A7466=20?= =?UTF-8?q?=E2=80=94=20volatile=20levers,=20the=20ASPSX=20slot-hop=20gap,?= =?UTF-8?q?=20and=20main's=20-O0=20address=20law?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit §464, from func_8005DE78 (141 ins -> MATCH): a volatile QI/HI load stops combine folding the u8->s32 promotion into the lbu; ||-vs-&& selects do_jump's drop-through arm; a VOLATILE STORE can never be stolen into a delay slot (resource_conflicts_p returns 1 on any volatil resource) which is how to force a target nop after a j; and a "memory" clobber vs a volatile read are NOT interchangeable CSE-breakers — both reload, but only the clobber leaves the addu operand order alone. §465, from func_8005F830 (152/153 byte-exact): the target hops the head insn of the branch's own target block into the delay slot. Ten controlled probes show cc1's fill_slots_from_thread refuses a thread insn writing the register the branch TESTS, and a negative control shows GNU as -O2 only swaps with the PRECEDING insn. So it is the original ASPSX reorder doing what our REORDER_TUS substitute structurally cannot — an assembler gap, §182/§188 one level deeper. Also records that this function's old 'epilogue unreachable' verdicts are stale. §466, from matching main itself (509 ins, -O0): inside a MEMORY ADDRESS, base + i*K expands to a (mult reg K) that force_operand emits INDEX-first; rewriting as base + ((i*(K>>n))<> n)) << n)` materialises the index first and yields the target's BASE-first +`addu $v0,$s1,$a0` / `lui %hi; addu $at,$at,idx; sw %lo($at)`. **Value context is unaffected**, which +is why this hides: the same expression is fine everywhere except under a `MEM`. -**1. A 4-BYTE GAP IN AN OTHERWISE 4-PACKED FRAME IS A SPILL SLOT, NOT A PAD.** `sp+0xC8` / `sp+0xD0` -in this target are not struct members — they are spilled pseudos. reload's `alter_reg` calls -`assign_stack_local(mode, size, -1)`, and `align == -1` means `BIGGEST_ALIGNMENT` (8) with -`CEIL_ROUND`, so **every 4-byte spill slot occupies EIGHT bytes**. That is exactly why the target's -two slots sit 8 apart with `0xCC`/`0xD4` untouched. Reading those gaps as padding — or as fields of a -struct you then invent — is a wrong model of the frame. Worth 11 instructions here, and modelling -them as spills is also what evicts both values from local-alloc so reload picks `$t0`. - -**2. §41b's "a global load cannot float above the RTL prologue" IS NOT A WALL — it is an `$a0` -ANTI-DEPENDENCE.** The parameter copy `addu $s0, $a0, $zero` *reads* `$a0`, which pins the load -below it. Get the value out of `$a0` and make the load the first statement, and it floats to idx 0 -on its own. **Both moves are required and either alone is worthless** — statement-first by itself -measured 33 → **50** (worse); combined with law 1 (which is what frees the register) it went -22 → 4. Before treating a "load above the prologue" residual as unreachable, check what reads the -argument register. - -**3. Which ARGUMENT POSITION a guard value is passed in decides its hard register.** Passing it as -arg 1 — `func_80021120(&L.cnt, L.lp)` — gives that pseudo a `qty_phys_copy_sugg` toward `$a1`, which -local-alloc's scan-from-`$v0` can never reach on its own. The sibling guards that do *not* pass it -stay in `$v0`, which is the control proving the mechanism rather than a coincidence. - -**Banker caveat for this function:** its `INCLUDE_ASM` is at `src/800.c:11168`, but the TU's own -`MTX_80020248` typedef (`:11180`) and the `D_80074818`/`D_80075018` externs (`:11191-2`) are twelve -lines BELOW it. Hoist that block above `:11168` or drop the draft's copy, or the duplicate typedef is -a hard C89 error at bank time. - -**Verified by hand (law 1c):** 26 `jal` targets and 16 HI16/LO16 relocs identical in name and order; -the four `D_1F800020` words are the scratchpad literal, byte-identical (`3c111f80` / `26310020`). +**Supporting -O0 idioms from the same match:** +* 12-byte-strided stores need a struct `COMPONENT_REF` — only that folds to `sw …,8($v0)`. +* `s32 pad[6]` supplies the 24 bytes of dead -O0 locals that make the frame `0x38`. +* A second, unused `register u8 *q = &D_800BA118` keeps the `$s0` `lui`/`addiu` + save alive. +* `(*(u16*)x)++` emits the extra `move` that `+= 1` omits. +* `CatPrim`'s arg2 must be ` + D_80074778*4`: with a MEM as operand 0 the address is emitted + first, then operand 1, then the load, so the `addu` comes out operand-1-first.