feat(phase-29): T46 — reg_renumber-swap oracle built + validated; it REFUTES the func_80176734 framing

Item 1's remaining half. Oracle mechanized and reusable at tools/oracle/reg_renumber_swap.sh: break
at reload entry (cc1 unstripped: reg_renumber @0x82d4330, reload @0x815d4d7), swap two hard regs
across reg_renumber, finish the compile, re-score with masked_diff REUSED not reimplemented (R33).

NEGATIVE CONTROL: a no-op swap (31<->31) reproduces exactly the baseline 13 mismatches, so the
harness faithfully reproduces the pinned compile.

RESULT: both contested swaps are far WORSE — <-> (17 pseudos) = 345 mismatches +1 insn;
<-> (31 pseudos) = 97. Baseline 13.

WHY, AND IT REFUTES THE FRAMING: reading .greg for cluster B's own insn shows
  (set (reg/v:SI 6 a2) (plus:SI (reg/v:SI 5 a1) (const_int 60)))
— the destination is a HARD register, not a pseudo. reg_renumber only maps pseudos (>=
FIRST_PSEUDO_REGISTER = 68), so that value is structurally unreachable by this oracle. The draft has
NO register __asm__ pins (header says so, grep confirms), so  is hard because it is an incoming
PARAMETER register that local-alloc reused as a destination.

VERDICT for func_80176734 (51,198 ins): the residual is NOT global-allocation 2-colouring. It is the
LOCAL-alloc hard-reg reuse / tying class — combine_regs (2.7.2 local-alloc.c:1722) +
qty_phys_copy_sugg, i.e. regalloc.md K8/RC-4, whose lever is C-level LIFETIME SHAPING, not the
permuter and not reg_renumber. That also explains the flat permuter: it was mutating a dial that
does not control this residual.

MAP REFINEMENT OWED: §H presents the swap oracle as THE way to discriminate RC-6 from S3 in one gdb
run. It has an unstated PRECONDITION — the contested registers must be held by PSEUDOS. Check .greg
first; if they appear as (reg/v:SI N ...) with N < 68 they are already hard, and a coarse swap
returns a large meaningless number (345 here) that looks like a verdict and is not one.

Tree clean; nothing banked, nothing broken.
This commit is contained in:
Drew T
2026-07-28 14:53:47 -06:00
parent 5ae6409640
commit ce7780b91f
2 changed files with 123 additions and 0 deletions
+49
View File
@@ -7316,3 +7316,52 @@ with my probe instrument, and the gate was right.)*
Not recoverable by declaration work. Its residual is now honestly classed **DIFF at 3 instructions
over** — a codegen problem for the crack tier, not the integration tier. Re-file it as a crack
target, not an integration one.
## 🔬 T46 — the reg_renumber-swap ORACLE built, validated, and it REFUTES the framing for `func_80176734`
Item 1's remaining half. The oracle is now **mechanized and reusable**
(`tools/oracle/reg_renumber_swap.sh`): break at `reload` entry (cc1 is unstripped —
`reg_renumber` @ `0x82d4330`, `reload` @ `0x815d4d7`), swap two hard registers throughout
`reg_renumber`, finish the compile, and re-score with **`masked_diff` reused, not reimplemented**
(R33 — the same comparison `match_one` makes).
**NEGATIVE CONTROL FIRST:** a no-op swap (`31↔31`) reproduces **exactly the baseline 13 mismatches**,
so the harness faithfully reproduces the pinned compile and a swap result is meaningful.
### Result: both contested swaps make it far WORSE
| swap | pseudos moved | mismatches |
|---|---|---|
| control `$31↔$31` | 0 | **13** (baseline ✓) |
| `$a0(4) ↔ $a2(6)` | 17 | **345** (and +1 insn) |
| `$v1(3) ↔ $a1(5)` | 31 | **97** |
### WHY — and this REFUTES the "three register 2-swaps = pure allocation" reading
The contested registers serve **17 and 31 pseudos**, so a blanket swap destroys the many allocations
that were already correct. But the deeper reason is structural. Reading the `.greg` RTL for
cluster B's own instruction:
```
(insn 32 15 35 (set (reg/v:SI 6 a2) <-- a HARD register, not a pseudo
(plus:SI (reg/v:SI 5 a1) (const_int 60))) 3 {addsi3_internal} ...)
```
**`reg_renumber` only maps PSEUDOS (≥ `FIRST_PSEUDO_REGISTER` = 68). A value already living in a hard
register at `.greg` time is structurally unreachable by this oracle.** And the draft has **no
`register __asm__` pins at all** (its header says so, and grep confirms) — so `$a2` is hard because
it is an **incoming PARAMETER register that local-alloc reused as a destination**.
**VERDICT for `func_80176734` (51,198 ins): the residual is NOT global-allocation 2-colouring.**
It is the **local-alloc hard-reg reuse / tying class** — `combine_regs` (2.7.2 `local-alloc.c:1722`)
+ `qty_phys_copy_sugg`, i.e. **regalloc.md K8 / RC-4**, whose lever is C-level *lifetime shaping*
(which value dies where), **not** the permuter and **not** `reg_renumber`. That also explains the
flat permuter: it was mutating a dial that does not control this residual.
### 🔧 MAP REFINEMENT OWED — §H's oracle has an unstated PRECONDITION
`regalloc.md` §H presents the swap oracle as the way to "discriminate RC-6 (allocation) from S3
(scheduling) in ONE gdb run". It only works when **the contested registers are held by PSEUDOS**.
Check the `.greg` RTL first: if the diff's registers appear as `(reg/v:SI N <name>)` with N < 68,
they are hard already and the oracle cannot move them — a coarse swap will return a large,
meaningless number (345 here) that looks like a verdict and is not one.
**Harness preserved at `tools/oracle/reg_renumber_swap.sh`** (R3), with its negative control
documented. Tree clean; nothing banked, nothing broken.
+74
View File
@@ -0,0 +1,74 @@
#!/bin/bash
# swap_oracle.sh — the regalloc.md §H reg_renumber-swap oracle, mechanized.
#
# QUESTION IT ANSWERS: when a residual is "register identity AND an instruction reordering", you
# cannot tell from the diff whether the reorder is a SCHEDULING decision (S3 — C often cannot move
# it) or merely a CONSEQUENCE of the register grant (fix the regs and the schedule follows).
#
# METHOD: break at `reload` entry, swap two hard registers throughout `reg_renumber` (so every
# pseudo allocated to A gets B and vice versa), let compilation finish, and re-score the result with
# the SAME comparison match_one uses (masked_diff, reused not reimplemented — R33).
#
# diff count DROPS -> the residual is ALLOCATION; route to density/lifetime levers.
# diff count STAYS -> the reorder is not a consequence of these two registers.
#
# usage: swap_oracle.sh <hardA> <hardB> (MIPS: v0=2 v1=3 a0=4 a1=5 a2=6 a3=7)
set -u
REPO=/home/musashi/bfm-decomp
D=$REPO/.run/giants/fable_cd4/dumps_o734
A=$1; B=$2
cd "$D" || exit 1
cat > swap_${A}_${B}.gdb <<EOF
set pagination off
set confirm off
set height 0
set debuginfod enabled off
break *0x815d4d7
commands 1
silent
set \$rr = *(long *)0x082d4330
set \$mx = *(long *)0x082d3a10
set \$i = 0
set \$n = 0
while \$i < \$mx
set \$h = *(short *)(\$rr + \$i*2)
if \$h == $A
set *(short *)(\$rr + \$i*2) = $B
set \$n = \$n + 1
else
if \$h == $B
set *(short *)(\$rr + \$i*2) = $A
set \$n = \$n + 1
end
end
set \$i = \$i + 1
end
printf "SWAPPED %d pseudo(s) between hard %d and hard %d\n", \$n, $A, $B
continue
end
run -quiet -O2 -G0 -mips1 -mcpu=3000 -mgas -msoft-float -fgnu-linker t.i -o swap_${A}_${B}.s
quit
EOF
timeout 600 gdb -batch -x swap_${A}_${B}.gdb "$REPO/tools/bin/gcc-2.7.2-psx/cc1" 2>&1 | grep -E "^SWAPPED"
[ -s swap_${A}_${B}.s ] || { echo " (no output produced)"; exit 1; }
python3 "$REPO/tools/maspsx/maspsx.py" --aspsx-version=2.56 --expand-div < swap_${A}_${B}.s > swap_${A}_${B}.mas.s 2>/dev/null
# `as` must run from the REPO ROOT: the emitted .s carries a RELATIVE `.include "include/labels.inc"`.
( cd "$REPO" && mipsel-linux-gnu-as -march=r3000 -mtune=r3000 -no-pad-sections -O1 -G0 -Iinclude \
-o "$D/swap_${A}_${B}.o" "$D/swap_${A}_${B}.mas.s" ) 2>/dev/null \
|| { echo " (assemble failed)"; exit 1; }
cd "$REPO" && python3 - "$D/swap_${A}_${B}.o" "$A" "$B" <<'PY'
import sys, os
sys.path.insert(0, 'tools')
import masked_diff as M
obj, A, B = sys.argv[1], sys.argv[2], sys.argv[3]
tgt = 'asm/ov_SC01_077/nonmatchings/ov_SC01_077_jr_801734BC/func_80176734.s'
mine = M.insns_from_object(obj, 'func_80176734')
want = M.insns_from_s(tgt)
d = M.structured_diff(mine, want)
print(" swap $%s<->$%s : mine=%d ins, target=%d ins, %d mismatched (baseline 13)"
% (A, B, len(mine), len(want), len(d)))
PY