From ce7780b91f4254e19e021e4e71b0677879c37842 Mon Sep 17 00:00:00 2001 From: Drew T <50529377+Druthulu@users.noreply.github.com> Date: Tue, 28 Jul 2026 14:53:47 -0600 Subject: [PATCH] =?UTF-8?q?feat(phase-29):=20T46=20=E2=80=94=20reg=5Frenum?= =?UTF-8?q?ber-swap=20oracle=20built=20+=20validated;=20it=20REFUTES=20the?= =?UTF-8?q?=20func=5F80176734=20framing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- phase-ends/CURRENT_PHASE.md | 49 ++++++++++++++++++++ tools/oracle/reg_renumber_swap.sh | 74 +++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 tools/oracle/reg_renumber_swap.sh diff --git a/phase-ends/CURRENT_PHASE.md b/phase-ends/CURRENT_PHASE.md index d833f4def..dc7a4a953 100644 --- a/phase-ends/CURRENT_PHASE.md +++ b/phase-ends/CURRENT_PHASE.md @@ -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 )` 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. diff --git a/tools/oracle/reg_renumber_swap.sh b/tools/oracle/reg_renumber_swap.sh new file mode 100644 index 000000000..48552edc6 --- /dev/null +++ b/tools/oracle/reg_renumber_swap.sh @@ -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 (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 <&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