mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-29 07:10:32 -04:00
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:
@@ -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.
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user