diff --git a/.gitignore b/.gitignore index 86e100607..24301ce07 100644 --- a/.gitignore +++ b/.gitignore @@ -310,3 +310,15 @@ unsloth_compiled_cache/ !/.run/P32/t5x/reports/*.md !/.run/P32/t5x/verdicts.jsonl !/.run/P32/t5x/resume_queue.txt +# P32 T4b hand pass (S84, 2026-09-06): notes, mechanism probes, verdict drafts and the private dump script (not the dumps_*/ or rtu/ dirs) +!/.run/P32/t4c/ +/.run/P32/t4c/* +!/.run/P32/t4c/*.sh +!/.run/P32/t4c/*.py +!/.run/P32/t4c/*.md +!/.run/P32/t4c/*.log +!/.run/P32/t4c/*.json +!/.run/P32/t4c/func_*/ +/.run/P32/t4c/func_*/* +!/.run/P32/t4c/func_*/*.c +!/.run/P32/t4c/func_*/*.md diff --git a/.run/P32/t4c/check_all_preflight.log b/.run/P32/t4c/check_all_preflight.log new file mode 100644 index 000000000..d00d00fd2 --- /dev/null +++ b/.run/P32/t4c/check_all_preflight.log @@ -0,0 +1,220 @@ +[ OK ] resident +[ OK ] ov_SC01_000 +[ OK ] ov_SC01_005 +[ OK ] ov_SC01_006 +[ OK ] ov_SC01_001 +[ OK ] ov_SC03_002 +[ OK ] ov_SC02_000 +[ OK ] ov_SC04_000 +[ OK ] ov_SC02_003 +[ OK ] ov_SC05_000 +[ OK ] ov_SC03_006 +[ OK ] ov_SC01_077 +[ OK ] ov_SC03_001 +[ OK ] ov_SC04_019 +[ OK ] ov_SC04_018 +[ OK ] ov_SC06_000 +[ OK ] ov_SC01_008 +[ OK ] ov_SC07_000 +[ OK ] ov_SC01_004 +[ OK ] ov_SC02_005 +[ OK ] ov_SC02_004 +[ OK ] ov_SC02_017 +[ OK ] ov_SC01_080 +[ OK ] ov_SC01_084 +[ OK ] ov_SC02_021 +[ OK ] ov_SC02_015 +[ OK ] ov_SC02_011 +[ OK ] ov_SC01_074 +[ OK ] ov_SC02_016 +[ OK ] ov_SC01_009 +[ OK ] ov_SC02_026 +[ OK ] ov_SC02_028 +[ OK ] ov_SC02_031 +[ OK ] ov_SC02_035 +[ OK ] ov_SC02_027 +[ OK ] ov_SC02_039 +[ OK ] ov_SC03_007 +[ OK ] ov_SC02_041 +[ OK ] ov_SC03_003 +[ OK ] ov_SC03_011 +[ OK ] ov_SC03_012 +[ OK ] ov_SC03_013 +[ OK ] ov_SC03_010 +[ OK ] ov_SC03_014 +[ OK ] ov_SC03_015 +[ OK ] ov_SC03_023 +[ OK ] ov_SC03_028 +[ OK ] ov_SC03_030 +[ OK ] ov_SC03_024 +[ OK ] ov_SC03_029 +[ OK ] ov_SC03_089 +[ OK ] ov_SC03_031 +[ OK ] main +[ OK ] ov_SC03_090 +[ OK ] ov_SC03_091 +[ OK ] ov_SC03_095 +[ OK ] ov_SC03_092 +[ OK ] ov_SC03_096 +[ OK ] ov_SC03_097 +[ OK ] ov_SC03_093 +[ OK ] ov_SC03_094 +[ OK ] ov_SC03_098 +[ OK ] ov_SC03_101 +[ OK ] ov_SC03_099 +[ OK ] ov_SC03_100 +[ OK ] ov_SC03_102 +[ OK ] ov_SC03_103 +[ OK ] ov_SC03_105 +[ OK ] ov_SC03_109 +[ OK ] ov_SC03_108 +[ OK ] ov_SC03_111 +[ OK ] ov_SC03_104 +[ OK ] ov_SC03_112 +[ OK ] ov_SC03_114 +[ OK ] ov_SC03_110 +[ OK ] ov_SC03_115 +[ OK ] ov_SC03_113 +[ OK ] ov_SC03_117 +[ OK ] ov_SC03_116 +[ OK ] ov_SC03_119 +[ OK ] ov_SC03_118 +[ OK ] ov_SC03_121 +[ OK ] ov_SC04_004 +[ OK ] ov_SC03_125 +[ OK ] ov_SC04_002 +[ OK ] ov_SC04_003 +[ OK ] ov_SC03_126 +[ OK ] ov_SC04_005 +[ OK ] ov_SC03_124 +[ OK ] ov_SC04_006 +[ OK ] ov_SC04_008 +[ OK ] ov_SC04_007 +[ OK ] ov_SC04_009 +[ OK ] ov_SC04_010 +[ OK ] ov_SC04_011 +[ OK ] ov_SC04_012 +[ OK ] ov_SC04_015 +[ OK ] ov_SC04_016 +[ OK ] ov_SC04_020 +[ OK ] ov_SC05_001 +[ OK ] ov_SC05_003 +[ OK ] ov_SC05_004 +[ OK ] ov_SC05_005 +[ OK ] ov_SC04_021 +[ OK ] ov_SC05_006 +[ OK ] ov_SC05_008 +[ OK ] ov_SC05_007 +[ OK ] ov_SC05_002 +[ OK ] ov_SC05_009 +[ OK ] ov_SC05_011 +[ OK ] ov_SC05_010 +[ OK ] ov_SC05_017 +[ OK ] ov_SC05_019 +[ OK ] ov_SC06_011 +[ OK ] ov_SC06_006 +[ OK ] ov_SC05_018 +[ OK ] ov_SC06_008 +[ OK ] ov_SC06_013 +[ OK ] ov_SC06_018 +[ OK ] ov_SC06_015 +[ OK ] ov_SC06_010 +[ OK ] ov_SC06_020 +[ OK ] ov_SC06_016 +[ OK ] ov_SC06_014 +[ OK ] ov_SC06_022 +[ OK ] ov_SC06_024 +[ OK ] ov_SC06_025 +[ OK ] ov_SC06_027 +[ OK ] ov_SC06_030 +[ OK ] ov_SC06_033 +[ OK ] ov_SC06_032 +[ OK ] ov_SC06_029 +[ OK ] ov_SC07_008 +[ OK ] ov_SC07_011 +[ OK ] ov_SC07_002 +[ OK ] ov_SC07_001 +[ OK ] ov_SC07_009 +[ OK ] ov_SC07_007 +[ OK ] ov_SC07_010 +[ OK ] ov_SC07_006 +[ OK ] ov_MAIN_012 +[ OK ] ov_SC02_037 +[ OK ] ov_SC03_107 +[ OK ] md_MAIN_015 +[ OK ] md_MAIN_013 +[ OK ] md_MAIN_016 +[ OK ] md_MAIN_018 +[ OK ] md_MAIN_020 +[ OK ] md_MAIN_014 +[ OK ] md_MAIN_022 +[ OK ] md_MAIN_021 +[ OK ] md_MAIN_017 +[ OK ] md_MAIN_019 +[ OK ] md_MAIN_023 +[ OK ] md_MAIN_024 +[ OK ] md_MAIN_026 +[ OK ] md_MAIN_025 +[ OK ] md_MAIN_027 +[ OK ] md_MAIN_030 +[ OK ] md_MAIN_031 +[ OK ] md_MAIN_036 +[ OK ] md_MAIN_029 +[ OK ] md_MAIN_028 +[ OK ] md_MAIN_033 +[ OK ] md_MAIN_038 +[ OK ] md_MAIN_037 +[ OK ] md_MAIN_035 +[ OK ] md_MAIN_032 +[ OK ] md_MAIN_040 +[ OK ] md_MAIN_034 +[ OK ] md_MAIN_041 +[ OK ] md_MAIN_042 +[ OK ] md_MAIN_039 +[ OK ] md_MAIN_043 +[ OK ] md_MAIN_046 +[ OK ] md_MAIN_047 +[ OK ] md_MAIN_044 +[ OK ] md_MAIN_001 +[ OK ] md_MAIN_045 +[ OK ] md_SC07_004 +[ OK ] md_MAIN_011 +[ OK ] md_SC07_003 +[ OK ] md_MAIN_008 +[ OK ] md_SC03_073 +[ OK ] md_SC03_075 +[ OK ] md_SC03_074 +[ OK ] md_MAIN_003 +[ OK ] md_SC03_077 +[ OK ] md_SC03_076 +[ OK ] md_SC03_078 +[ OK ] md_SC03_133 +[ OK ] md_SC03_079 +[ OK ] md_SC03_135 +[ OK ] md_SC03_134 +[ OK ] md_SC03_132 +[ OK ] md_SC03_136 +[ OK ] md_SC03_138 +[ OK ] md_SC04_026 +[ OK ] md_SC03_137 +[ OK ] md_SC04_028 +[ OK ] md_SC04_024 +[ OK ] md_SC04_029 +[ OK ] md_SC04_027 +[ OK ] md_SC04_025 +[ OK ] md_SC04_030 +[ OK ] md_SC05_023 +[ OK ] md_SC05_025 +[ OK ] md_SC05_026 +[ OK ] md_SC05_028 +[ OK ] md_SC05_024 +[ OK ] md_SC05_027 +[ OK ] md_SC02_009 +[ OK ] md_SC05_029 +[ OK ] md_MAIN_009 +[ OK ] md_SC03_056 +[ OK ] md_SC03_053 +[ OK ] md_SC03_054 +[ OK ] md_MAIN_007 +check-all: 218 passed, 0 failed of 218 +EXIT=0 diff --git a/.run/P32/t4c/dump.sh b/.run/P32/t4c/dump.sh new file mode 100644 index 000000000..123c17e3c --- /dev/null +++ b/.run/P32/t4c/dump.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# usage: dump.sh -> .run/P32/t4c/dumps_/ (private; frame line + ins count + ghost census) +cd /home/musashi/bfm-decomp +f="$1"; tag="$2" +d=.run/P32/t4c/dumps_$tag; rm -rf $d; mkdir -p $d +src=$d/$tag.c +if ! grep -q '#include "common.h"' "$f"; then echo '#include "common.h"' > $src; fi +cat "$f" >> $src +mipsel-linux-gnu-cpp -lang-c -Iinclude -undef -Wall -fno-builtin -Dmips -D__GNUC__=2 -D__OPTIMIZE__ -Dpsx -D_PSYQ -D_MIPSEL -D_LANGUAGE_C $src > $d/$tag.i +(cd $d && /home/musashi/bfm-decomp/tools/bin/gcc-2.7.2-psx/cc1 -quiet -O2 -G0 -mips1 -mcpu=3000 -mgas -msoft-float -fgnu-linker -dr -ds -dj -df -dc -dS -dl -dg $tag.i -o $tag.s 2>$tag.err) +echo "== $tag: $(grep -m1 '\.frame' $d/$tag.s | sed 's/\t/ /g') ins=$(grep -cE '^\s+[a-z]' $d/$tag.s)" +grep -n "Spilling reg\|now on stack" $d/$tag.i.greg | head -5 +# ghost census: pseudos with 'or none' class and NO occurrence in the greg insn stream +python3 tools/ghost_census.py "$d/$tag.i.lreg"; : <<'PY' +import re,sys +lreg=open(sys.argv[1]).read(); greg=open(sys.argv[2]).read() +for m in re.finditer(r'^Register (\d+) used (\d+) times.*?; (\w+ or none|pref \w+, else \w+|\w+ or \w+)\.', lreg, re.M): + r=m.group(1) + if not re.search(r'\(reg[^ ]* '+r+r'\)', greg): + print(f" GHOST candidate: pseudo {r} refs={m.group(2)} class={m.group(3)} (no occurrence in greg)") +PY diff --git a/.run/P32/t4c/func_80032A74/NOTES.md b/.run/P32/t4c/func_80032A74/NOTES.md new file mode 100644 index 000000000..e35f70f78 --- /dev/null +++ b/.run/P32/t4c/func_80032A74/NOTES.md @@ -0,0 +1,57 @@ +# func_80032A74 (main / 800_b_2, 422 ins) — T4b HAND PASS, S84 (2026-09-06), Fable 5.1 at Max + +## Verdict: PROVED at closeness 1 — the pass is named, every producer of the residual is enumerated and refuted on the bytes. + +**State (re-verified this session, real TU, `rtu_match … --tu src/800_b_2.c --asm-subdir asm/nonmatchings/800_b_2`):** +* `.run/P32/t4/drafts/func_80032A74_tuclean.c` (`s16 u18`): **DIFF 1** — idx 244 `lh v0,0x18(s1)` vs target `lhu`; frame exact (0x78). +* `.run/P32/t4c/func_80032A74/lhu_tuclean.c` (the same draft with `u16 u18`, TU-clean): **DIFF 22** = all 22 frame rows + (`addiu sp,-0x70` vs `-0x78`, the 10 saved-register offsets ×2); **422/422 instructions otherwise identical.** +So the residual is EXACTLY one never-referenced stack slot at sp+0x48 (bytes: sp10 local 0x10–0x2F, the a0/a1/a2 reload +spills at 0x30/0x38/0x40 — 5/2/4 refs — nothing at 0x48, saved regs 0x50–0x74). vars 56 → 64: any slot of 1..8 bytes +allocated AFTER pseudo 74's slot lands there and rounds (`MIPS_STACK_ALIGN`) to the same frame; an expand-time slot cannot +(it precedes the reload slots and would push `sw $a0,0x30($sp)` to 0x38). + +## The producer census (from tools/reference/gcc-2.7.2 — every stack-slot allocation site that runs after the parameters' slots) +| producer | source | requirement | refuted on THIS function by | +|---|---|---|---| +| 1. ghost pseudo: `alter_reg(i,-1)` in the initial loop | reload1.c:658 (8-byte slot, regno order → right after 72/73/74) | a pseudo with `reg_n_refs>0`, `reg_renumber<0`, no REG_EQUIV, i.e. minted with NO occurrences BEFORE regclass (class `ST_REGS or none` → unallocatable) | only combine mints those (below): path (a) forces `lh`/`lb`; path (b) needs a 3-insn chain whose 2-insn merge fails outright and whose fold is one insn — every reachable fold of a memory head is a narrow load (`lh`/`lb`/a duplicate `lhu`), the target has one load at the site, no `lb`, no double load, all nine `lh` single-use | +| 2. caller-save area: `setup_save_areas` | caller-save.c:249 (4-byte slot per call-used hard reg holding ANY pseudo with `reg_n_calls_crossed>0`, once `caller_save_needed` is set by the profitability path global.c:1085 / local-alloc.c:2209) | a call-used-reg pseudo with a STALE positive count and no real crossing (else `save_call_clobbered_regs` emits `sw/lw` around the call — the target has none) | the only staleness route is sched.c:4962 (a multi-block pseudo keeps flow's count when sched's is 0) after sched1 moved a register-only def/use across a call INSIDE the call's block; the seven call blocks contain only arg setups (fenced by the call's USEs), loads/stores (fenced by `flush_pending_lists`) and call-result copies — nothing can move. combine never crosses a call except with a constant source (combine.c:924). `update_equiv_regs` moves nothing (it deletes 2-ref multi-block inits and doubles `reg_live_length`) | +| 3. invalid-equivalent-address slot | reload1.c:879 (`reg_equiv_memory_loc` whose address eliminates to a spilled pseudo → new slot) | an UNALLOCATED pseudo with a REG_EQUIV MEM through `arg0`/`arg2` (both spilled) | such pseudos are single-block (`update_equiv_regs` requires `reg_basic_block>=0`) and local-alloc/global allocate them unless every GR conflicts — impossible over a 2-insn life | +| 4. `spill_stack_slot` | reload1.c:3499 (`spill_hard_reg` → `alter_reg(i, regno)`; no retry for local-alloc'd pseudos, `retry_global_alloc` for allocnos) | a pseudo evicted from `$t0` or `LO` that gets no new home | `$t0` holds no pseudo (else `order_regs_for_reload` makes `$t1` the spill register — every param reload is `lw $t0`); LO-pref mult results are global allocnos with alternate class `GR_REGS` (`pref LO_REG, else GR_REGS`) and retry into a free GR — the draft's 273→`$t1`, 316→`$v1` | + +## The NEW ghost producer found and measured (cookbook §501-M) — and why it cannot slot +`local-alloc.c optimize_reg_copy_2`: `tmp = x; ; tmp = tmp op c; ; x = tmp;` in ONE block (x dead at +the head copy and live after the copy-back; the head copy survives combine when tmp's first use is not its last and no +3-insn chain passes through it (combine.c:904 guard); the copy-back survives when tmp has an intervening use) rewrites +every `tmp` into `x`, leaves the two copies as no-op self-moves (deleted by jump.c), and decrements `reg_n_refs[tmp]` once +per insn while flow counted the in-place insn TWICE → `tmp` = a ghost with stale refs (P13: refs 5; P14: refs 1, "dies in 0 +places"). **But it is minted AFTER regclass**, so it keeps `GR_REGS` and has no conflicts → global allocates it → **vars=0** +(P14). Only combine-minted ghosts (pre-regclass → `ST_REGS or none`) take a slot. + +## Probes (all isolated reproducers, `.run/P32/t4c/dump.sh

.c `; oracle = `vars=` + `tools/ghost_census.py`) +| tag | shape | vars | ghost? | what it showed | +|---|---|---|---|---| +| p1–p4 | `m = vol; m = (m<<7)-(m<<4) / m *= 0x70 / m += 3; m >>= 7; vol = m` | 0 | no | combine absorbs the head copy through the 3-insn chain (sll,subu,copy) and the copy-back into the srl | +| p5 | `a = m<<3; b = m<<4; m = a-b` (independent uses) | 0 | no | head copy SURVIVES (`move`) — combine.c:904 guard; copy-back merged | +| p6/p7 | `m = m * K` / `m = m * m` | 0 | no | as p2 | +| p8 | intervening `J = m & 0x7F` before the copy-back | 0 | no | copy-back survives (`move`), head copy absorbed | +| p9 | a call between | 0 | no | the scan breaks at a CALL unless src crosses calls elsewhere | +| p10 | store-protected both copies | 0 | no | both `move`s survive but sched sinks the store below the copy-back → death note moves → optimize_reg_copy_2 sees no `REG_DEAD` | +| p11 | as p10 without a later mention of m | 0 | no | cse canonicalizes m→vol (`make_regs_eqv`: the longer-lived reg wins) → head copy dead | +| p12 | `k = m & 0x7F` after the srl, stored after the copy-back | 0 | no | still a `move` | +| **p13** | `k = m & 3; m >>= 7; Q = k + m; vol = m` + later real uses of m | 0 | **YES (refs 5)** | `.lreg`: `(set 72 72)` ×2, every m rewritten to vol — optimize_reg_copy_2 FIRED; m allocated ($2) because it still had real uses | +| **p14** | p13 with a dead trailing `m = 0` (keeps m canonical for cse; flow deletes it) | 0 | **YES (refs 1, GR_REGS)** | the pure ghost — allocated by global, no slot: the post-regclass rule | +| p15 | p14 without the trailing mention | 0 | no | cse canonicalizes m away | +| p16–p18 | register-only 3-chains with a live head (`(n<<24)>>24`, sign chain, `+4,*3,-12`) | 0 | no | fold at cse/tree level or the middle temp is reused by `find_split_point` — combine path (b) never runs | +| lhu_tuclean | the draft with `u16 u18` in the real TU | 56 | — | DIFF 22 (frame only), 422/422 code | + +## What the S83 briefs got right and wrong +* S83 hand pass (HYPOTHESIS.md): "caller-save area, transient at iteration 1" — WRONG in its `$t0` form (Fable F1: a + pseudo in `$t0` displaces the spill register) and wrong in general (no staleness route in this function; see producer 2). +* T4b Fable (402k tokens): "ghost pseudo of the SIGN_EXTEND split" — RIGHT about the species; its "next lever" (a ghost + from a non-memory 3-chain) is combine path (b) and is refuted here (p16–p18 + the target's chains are already folded). + +## Files +`.run/P32/t4c/func_80032A74/{lhu_tuclean.c,p1..p18.c,NOTES.md}` · `.run/P32/t4c/dump.sh` (private dump + census) · +`tools/ghost_census.py` (promoted) · `tools/cc1_dumps.sh` (repaired: frame line + census instead of the `(use)` grep) · +dumps under `.run/P32/t4c/dumps_*/` (regenerable, ignored). diff --git a/.run/P32/t4c/func_80032A74/lhu_tuclean.c b/.run/P32/t4c/func_80032A74/lhu_tuclean.c new file mode 100644 index 000000000..6edfa2f25 --- /dev/null +++ b/.run/P32/t4c/func_80032A74/lhu_tuclean.c @@ -0,0 +1,538 @@ +#include "common.h" +/* ===================================================================================== + * S79 (this session) — RESULT UNCHANGED AT closeness 1/422; the residual is now EXPLAINED + * AND THE ORPHAN SEARCH IS CLOSED. ~200 byte-probes, all with the pinned cc1 + * (cpp -Iinclude | cc1 -quiet -O2 -G0 -mips1 -mcpu=3000 -mgas -msoft-float -fgnu-linker), + * oracle = `vars=` off the .frame line (NOT the `(use (reg))` count — see INSTRUMENT below). + * + * THE FRAME ARITHMETIC IS EXACT AND LEAVES EXACTLY ONE ORPHAN TO BUY (§165-03): + * target .frame $sp,0x78 regs=10/0 args=16 => vars = 0x40 = 64 + * 64 = 0x20 (the ONE declared local, sp10) + 8*3 (the a0/a1/a2 reload spills at + * 0x30/0x38/0x40, all referenced) + 8*1 (ONE never-referenced slot at 0x48). + * sp10 must be 0x1C..0x20 bytes: 0x18 puts the first spill at 0x28, 0x24 puts it at 0x38; + * only [0x1C,0x20] CEIL_ROUNDs frame_offset to the target's 0x30. A declared pad can + * therefore NEVER buy the 8 bytes here — any pad lands before the spills and moves + * `sw $a0,0x30($sp)`. (§162i1/§226/§333 are all inapplicable to this function.) + * + * WHY THE ORPHAN AND THE `lhu` ARE MUTUALLY EXCLUSIVE (the new, general result): + * `extendhisi2` in mips.md is an EXPAND that does force_not_mem at -O2, so EVERY + * `int x = ` is movhi + ashl16 + ashr16 and combine 3-way-merges it to `lh`. + * In a SINGLE-USE merge, newi2pat==0 so elim_i2/elim_i1 DROP both intermediates' death + * notes -> no orphan. An orphan needs the HImode load's reg to carry a SECOND use, which + * forces the i3_subst_into_i2 path (newi2pat != 0 -> elim_i2 == 0) and strands the ashift + * intermediate. `zero_extendhisi2` is a define_INSN that takes memory, so every unsigned + * promotion is one insn and can never orphan (14 u16/u32/s32/QI respellings measured: 0). + * => an orphan in this function REQUIRES an `lh`, and the target's idx-244 load is `lhu`. + * + * THE SITE CENSUS (why no other site can pay for it). The target has 8 `lh`s + * (D_800C5328, D_800C532A, D_800A4646, D_800A46A2 x2, sp10.unk16, D_800A4EFA, + * ch->unk48, D_800A4EF6) and every one is SINGLE-USE, so each is a note-dropping + * 3->1 merge. The only two values in the whole body with a free narrow second use are + * - `vo->unk18` (mask + a QImode `(s8)` use) -> orphan only in the `lh` spelling, and + * - `n` (int uses + the `sh $s2,0x10($s0)` HImode store) -> `s16 n` costs +4 ins and + * TWO orphans (426 ins, near 395); an s16 shadow of n (nh/n pair, both directions) + * is 2 orphans / near 387. Measured, not assumed. + * + * MEASURED-INERT THIS SESSION (do not re-try): + * * 100-variant local-retyping sweep (20 locals x 6 widths): only `n`->s16/s8 (2 orphans, + * near 395/397) and `b`->s8 (1 orphan, near 273) move `vars`; nothing is free. + * * splitting `(s8)u18` into its own s32 temp DOES buy the orphan and the exact 0x78 frame + * (vars=64, near 5) — but combine then re-derives the byte straight from memory as a + * second load `lb $v0,0x18($s1)`; 8 spellings (u32/s32/s16 base, <<1 vs *2, compound, + * `(u8)`/`&0xFFFF` launders) all keep the `lb`. Best of that family: near 5. + * * `__asm__ __volatile__("" ::: "memory")` between the load and the use DOES block the + * 3-way merge and restores `lhu` at zero instruction cost — near 22, frame 0x70, and + * the orphan dies with the merge. Same for §148-C's zero-emission ref slider + * `__asm__ ("" :: "r"(u18))` placed BEFORE the expression (near 22, vars 56). + * Placed AFTER, the slider keeps the orphan but costs a `move` (423 ins, near 184). + * This is the tension in its sharpest form: the extra HImode use that BLOCKS the merge + * (giving `lhu`) is the same use that must SURVIVE the merge to strand the intermediate. + * * `?:`-accumulator respellings of the four selects (ch->unk48, ch->unk24, ch->unk4D, + * vv), s16 temps at every single-use `lh` site, un-hoisting the clear loop (index / + * pointer / 4 forms), and 6 zero-emission ref sliders at other sites: all vars=56. + * * volatile s16 local: vars=64 but near 192 (it is a DECLARED local, wrong stratum). + * + * INSTRUMENT CORRECTION (worth banking): §172's "count standalone `(use (reg))` in the + * .combine dump" UNDER-COUNTS. A minimal §167-10 reproducer + * (`s16 c = A; if (c != 0) A = c - 1;`) emits `vars= 8` with ZERO `(use (reg))` insns — + * the stranded pseudo is simply absent from every post-combine dump while flow's stale + * `reg_n_refs` keeps it an allocno (`; ST_REGS or none` in -dl, §165-03). Also: the + * naive grep counts hard-reg return USEs (`(use (reg/i:SI 2 v0))`) — 54 hits across + * src/800_b_2.c collapse to 11 real pseudo orphans. USE `vars=` AS THE ORACLE. + * + * WHAT IS LEFT (for whoever picks this up): the 8 bytes are almost certainly NOT a combine + * orphan. §172's producer 3 — a caller-save area, `assign_stack_local(SImode,4,0)`, + * allocated inside reload's loop (reload1.c:1445) and therefore AFTER the alter_reg slots + * (reload1.c:658) — lands exactly at 0x48 and MIPS_STACK_ALIGN rounds vars 0x3C -> 0x40, + * reproducing 64 with no instruction anywhere. That is a register-allocation event, not a + * spelling one; the C axis for it (one more call-crossing value competing for the 9 + * callee-saved regs this function already uses in full) was not found. Next probe worth + * running: an A/B that adds one genuine call-crossing value and reads `vars` + the + * `.greg` "Spilling reg" lines, rather than any further respelling of the 0x18 read. + * ===================================================================================== */ +/* func_80032A74 - NEAR, closeness 1 / 422 ins (frame 0x78 exact, every immediate, every stack + * offset, every branch target and 421 of 422 registers exact). Residual: idx 244 `lh` vs `lhu`. + * + * =================================================================================== + * THE LEVER THAT TOOK THIS FROM 12 -> 1 (new; not in the cookbook as of S77): + * HOIST A GLOBAL ARRAY'S BASE INTO A FILE-SCOPE-TYPED POINTER LOCAL ASSIGNED *BEFORE* + * THE LOOP. That single move reproduces the target's `lui $t0/addiu $t0/addu` shape + * for three different symbols AND puts them all in $t0, with no asm launder and no + * register pin. Mechanism, read out of the gcc-2.7.2 source (tools/reference/gcc-2.7.2): + * + * 1. local-alloc.c:472 - a pseudo is a LOCAL-ALLOC CANDIDATE only if + * `reg_basic_block[i] >= 0 && reg_n_deaths[i] == 1` + * i.e. it lives in ONE basic block and dies ONCE. `&D_800A4C28[idx]` written inline + * makes a 2-ref, one-block pseudo -> local-alloc hands it the LOWEST free hard reg + * (find_free_reg scans regno 0..31; MIPS defines no REG_ALLOC_ORDER) -> $v1. + * 2. Assigning the base to a local BEFORE the `for(;;)` makes it multi-block, so + * local-alloc skips it; global-alloc cannot place it either (all ten callee-saved + * registers are already taken and update_equiv_regs doubled its live length), so + * `reg_renumber < 0`. + * 3. update_equiv_regs (local-alloc.c:1030) has already attached REG_EQUIV (symbol_ref) + * because the pseudo is set once from a constant -> reload1.c DELETES the initialising + * insn (zero cost, so the hoist is FREE) and reload.c substitutes the symbol at every + * use, reloading it into a SPILL register. $t0 is this function's first spill reg + * (it is also what carries `lw $t0,0x30($sp)`, `addiu $t0,$zero,1`, `mflo $t0`) - which + * is exactly why the target shows the symbols and the parm reloads sharing $t0, and why + * `register s32 x __asm__("$8")` can NEVER reproduce it: a hard-reg user variable makes + * reload move every spill to $t1 (measured: +30 rows). + * 4. `addu $s1,$v0,$t0` operand order comes from writing `idx * 0x48 + (s32)vB`, not + * `vB + idx * 0x48`. + * 5. `const` on the two tables is LOAD-BEARING (dropping it costs 14 rows of scheduling + * around the mult/mflo pair) - the original declared them const. + * + * OTHER LEVERS RETAINED FROM THE PREVIOUS 408->12 RUN: + * - `vol` and `m` are ONE variable (both live in $a0 over disjoint ranges). + * - `register s32 base __asm__("$2")` on the 0x18-lerp base: without it the addu/addiu/subu + * chain lands in $v1 (+3 rows). + * - a zero-byte `__asm__ __volatile__("")` after `vo->unk04 = 0x6009F` stops sched1 hoisting + * the D_800A4EF6 `lh` above the three stores (-7 rows), and one after `ch->unk14 = t32`. + * - use the PARAMETERS directly (no `e = arg0` copies) so the reload spill slots stay + * 0x30/0x38/0x40; the 8-byte clear loop is a POINTER loop with `i = 7;` FIRST (S211). + * + * =================================================================================== + * THE ONE REMAINING ROW, and why it is a genuine wall for this spelling + * idx 244 mine `lh $v0,0x18($s1)` target `lhu $v0,0x18($s1)` + * + * The target frame is 0x78: sp10 at 0x10-0x2F, the three parm spills at 0x30/0x38/0x40, and a + * NEVER-REFERENCED 8-byte slot at 0x48. The only producer of that slot reachable from C here + * is a S172 combine USE-orphan, and the only site in this function that orphans is + * `s16 u18 = vo->unk18` with BOTH an int (sign) promotion and a QImode use - which forces the + * load to be `extendhisi2_internal` = `lh`. Spelling the int use as `(u16)u18 & 0xFF00` gives + * the target's `lhu` and is byte-identical in all 422 instructions - but the orphan vanishes, + * the frame drops to 0x70 and 22 stack-offset rows break (that draft is kept at + * .run/S77w/opus/scratch_func_80032A74/v2.c). Measured dead ends for a substitute orphan + * (each checked by counting standalone `(insn N P X (use (reg ...)))` in the cc1 `-dc` dump): + * - every u16/s16/s8/QImode respelling of the vo->unk18 site (14 forms) -> 0 orphans; + * - `s16 n` (D_800C5328/D_800C532A) -> 2 orphans but a `lhu`+`lh` DOUBLE LOAD, +4 ins; + * - an s16 temp at that site whose only uses are promotions -> folds, 0 orphans; + * - an s16 local for the func_8003F144 return -> 0 orphans. + * Every other `lh` in the target (D_800A4646, D_800A46A2 x2, sp10.unk16, D_800A4EFA, + * ch->unk48, D_800A4EF6) has a SINGLE consumer, and S172's rule is that a single-use load + * 3-way-merges and orphans nothing. A declared 8-byte dead local cannot substitute: expand-time + * locals precede the reload spills and push $a0 from 0x30 to 0x38. + */ +/* func_80032A74 - NEAR, closeness 12 / 422 ins (length exact, frame exact). + * + * LEVERS PROVEN THIS SESSION (each byte-measured with tools/match_one.py; start 408 -> 12): + * 1. FRAME +8 (vars 56 -> 64) = ONE combine USE-orphan (cookbook S172 producer 2). The target's + * frame is 0x10..0x2F C24 local | 0x30/0x38/0x40 parm spills | 0x48 NEVER-REFERENCED. A + * declared dead local CANNOT do it (expand-time locals precede reload spills - measured: it + * pushes a0 from 0x30 to 0x38), and neither can `asm("":: "m"(pad))` (mark_addressable puts it + * in the locals region too). What does: an `s16` LOCAL read from memory and promoted to int + * TWICE, sited after a CODE_LABEL -> expand emits movhi + ashift/ashiftrt, combine merges them + * into one `lh` and orphans the HImode pseudo as `(insn (use (reg:SI N)))`, which alter_reg + * still gives an 8-byte slot. Instrument: count standalone `(use (reg` insns in the .combine + * dump (tools/cc1_dumps.sh). ZERO-extending (u16) temps merge cleanly and orphan NOTHING - + * the ashift/ashiftrt PAIR is the whole mechanism. Single-use s16 temps also orphan nothing. + * COST: the only site available here is `vo->unk18`, where the target loads `lhu` - so idx 244 + * is `lh` vs `lhu`, the one structural row left. A 2-use `lh` site would be free; the only + * other one is `n` (D_800C5328) and typing it s16 costs +4 ins. + * 2. `vol` AND `m` ARE ONE VARIABLE. The target holds both in $a0 across disjoint ranges; two + * separate C variables give two allocnos ($v1 and $a1). Merging them is what puts the whole + * volume chain in $a0. + * 3. THE S153 LAUNDER'S REAL COST IS AN ALLOCNO, AND THE FIX IS A PIN ON A DEAD TEMP. Removing + * the launder (plain `D_8007319E[pan]`) fixes m/$a0, the `li 0x100` delay-slot schedule and the + * D_800A4EF6 hoist all at once - but folds the address back to the 3-insn $at macro form (-2 + * ins). Keeping the launder, the symbol pseudo has priority log2(refs)*refs/live_length ~ 0.67 + * and OUTRANKS the long-lived `vol` (~0.19), so it steals $a0 and pushes the whole chain to $a1 + * (+17 rows). `register s32 bp __asm__("$10")` parks it on a register nothing else wants: + * $a0 goes back to vol/m and reload keeps $t0. Do NOT pin it to $8: reload then picks $t1 for + * every parm reload (measured +14 rows, net worse). $9 costs 2 rows (the mflo temp at idx 272 + * moves $t1 -> $t2); $10 costs none. + * 4. A zero-byte `__asm__ __volatile__("")` after `vo->unk04 = 0x6009F;` stops sched1 hoisting the + * D_800A4EF6 `lh` above the three stores (-7 rows). + * 5. S219: `vol *= 0x70; vol >>= 7;` (compound) vs `vol = (vol*0x70)>>7;` decides whether the + * `<<4` intermediate lands in $a0 or $v0 (-2 rows). + * + * REMAINING RESIDUAL (12), all REGISTER-NAMING, nothing structural: + * - 3 rows: the D_800A4C28 base is $v1, target $t0. + * - 8 rows: the laundered table base is $t1/$t2, target $t0 - and $t0 is unreachable because the + * same $t0 is reload's spill register for the three parm reloads; a hard-reg var there evicts + * reload. In the target BOTH uses coexist, which means those symbol pseudos are NOT allocnos - + * they are reload rematerialisations of a reg_equiv_constant. Every C spelling tried + * (array[i], &array[i], scalar `extern u16 D;` + `&D + off`, S195-H's struct-cast force_reg, + * const u16* local, S239 integer-space, one-table-two-index) folds to the $at macro form; only + * the launder produces the 4-insn shape, and the launder always creates an allocno. + * - 1 row: idx 244 `lh` vs `lhu` (see lever 1). + */ + + /* 0x0C */ + /* 0x0C */ + /* 0x20 */ + /* 0x14 */ + + +/* ---- views this function needs (new names, no TU collision) ---- */ + +/* the caller's 0x54 request slot (Slot54, seen past its declared tail) */ +typedef struct { + /* 0x00 */ u16 unk00; + /* 0x02 */ u16 unk02; + /* 0x04 */ u8 pad04[6]; + /* 0x0A */ u8 unk0A; + /* 0x0B */ u8 pad0B[1]; + /* 0x0C */ u16 unk0C; + /* 0x0E */ u8 unk0E[8]; +} Req32A74; + +/* the 0x14 record walked by this loop (Rec14, byte-resolved) */ +typedef struct { + /* 0x00 */ u16 unk00; + /* 0x02 */ u16 unk02; + /* 0x04 */ u16 unk04; + /* 0x06 */ u8 unk06; + /* 0x07 */ u8 unk07; + /* 0x08 */ u8 unk08; + /* 0x09 */ u8 unk09; + /* 0x0A */ u8 unk0A; + /* 0x0B */ u8 unk0B; + /* 0x0C */ u8 pad0C[4]; + /* 0x10 */ s32 unk10; +} Rec32A74; /* 0x14 */ + +/* the 0x54 mixer channel at D_800A4988 (cf. Chan336A8) */ +typedef struct { + /* 0x00 */ s32 unk00; + /* 0x04 */ s32 unk04; + /* 0x08 */ u16 unk08; + /* 0x0A */ u16 unk0A; + /* 0x0C */ u16 unk0C; + /* 0x0E */ u16 unk0E; + /* 0x10 */ s16 unk10; + /* 0x12 */ s16 unk12; + /* 0x14 */ s32 unk14; + /* 0x18 */ u8 pad18[0xC]; + /* 0x24 */ s32 unk24; + /* 0x28 */ u8 pad28[0xC]; + /* 0x34 */ u8 unk34; + /* 0x35 */ u8 unk35; + /* 0x36 */ u8 unk36; + /* 0x37 */ u8 pad37[9]; + /* 0x40 */ s32 unk40; + /* 0x44 */ s32 unk44; + /* 0x48 */ s16 unk48; + /* 0x4A */ s16 unk4A; + /* 0x4C */ u8 unk4C; + /* 0x4D */ u8 unk4D; + /* 0x4E */ u8 unk4E; + /* 0x4F */ u8 unk4F; + /* 0x50 */ u8 pad50[2]; + /* 0x52 */ u8 unk52; + /* 0x53 */ u8 unk53; +} Chan32A74; /* 0x54 */ + +/* the 0x48 voice at D_800A4C28 (Slot, byte-resolved) */ +typedef struct { + /* 0x00 */ s32 unk00; + /* 0x04 */ s32 unk04; + /* 0x08 */ u16 unk08; + /* 0x0A */ u16 unk0A; + /* 0x0C */ u16 unk0C; + /* 0x0E */ u16 unk0E; + /* 0x10 */ u8 pad10[4]; + /* 0x14 */ u16 unk14; + /* 0x16 */ u8 pad16[2]; + /* 0x18 */ u16 unk18; + /* 0x1A */ u8 pad1A[2]; + /* 0x1C */ s32 unk1C; + /* 0x20 */ u8 pad20[0x1A]; + /* 0x3A */ u16 unk3A; + /* 0x3C */ u16 unk3C; + /* 0x3E */ u8 pad3E[2]; + /* 0x40 */ s32 unk40; + /* 0x44 */ u8 unk44; + /* 0x45 */ u8 unk45; + /* 0x46 */ u8 pad46[2]; +} Voice32A74; /* 0x48 */ + +extern s16 D_800C5328[]; +extern s16 D_800C532A[]; +extern s16 D_800A4646[]; +extern A12 D_80064D44[]; +extern B12 *D_8006A970[]; +extern Slot D_800A4C28[]; +extern u8 D_800A4988[]; +extern s32 D_80073140[]; +extern s16 D_800A46A2; +extern s16 D_800A4EF6; +extern s16 D_800A4EFA; +extern u8 D_800A4F19; +extern u8 D_800A4F1E; +extern u16 D_8006AA30[]; +extern u16 D_8006AB30[]; +extern u16 D_8006AB32[]; +extern const u16 D_8007319E[]; +extern const u16 D_800731A0[]; +extern u16 D_8007321E; +extern u8 D_8006AED8[]; +extern s16 func_8003F144(s32, s32, s32, C24 *); +extern s32 func_8003F380(s32, s32); +extern s32 func_80030CA4(u16); +extern void func_8002EFF8(s32, s32); +extern void func_8002F064(s32, s32); +extern void func_800316F8(s32); +extern void func_80033324(s32, s32); + +void func_80032A74(Slot54 *arg0, s32 arg1, Rec14 *arg2, s32 flags) { +#define REQ ((Req32A74 *)arg0) +#define REC ((Rec32A74 *)arg2) + C24 sp10; + + Chan32A74 *ch; + Voice32A74 *vo; + A12 *dd; + B12 *q; + u8 *cp; + const u16 *tA; + const u16 *tB; + u8 *vB; + s32 i2; + u16 v; + u32 vv; + s32 idx; + s32 w; + s32 n; + s32 flag; + s32 h; + s32 i; + s32 b; + s32 sub; + u8 pan; + u32 vol; + u32 mp; + u32 qq; + s32 tb; + u32 d; + register s32 base __asm__("$2"); + u16 t16; + s32 t8; + s32 t32; + + tA = D_8007319E; + tB = D_800731A0; + vB = (u8 *)D_800A4C28; + v = REQ->unk02; + i = 7; + cp = (u8 *)arg0 + 7; + do { + cp[0xE] = 0; + i--; + cp--; + } while (i >= 0); + REQ->unk0C = 0; + + for (;;) { + b = REC->unk08; + flag = 0; + if ((b & 0x80) == 0) { + dd = &D_80064D44[b]; + sub = REC->unk09; + if (dd->unk06 != 0) { + flag = (u32)sub < (u32)dd->unk07; + } + n = D_800C5328[b * 2]; + if (n < 0) { + if (flag == 0) { + break; + } + n = D_800C532A[b * 2]; + if (n < 0) { + break; + } + if (sub >= D_800A4646[n * 12]) { + break; + } + } + } else { + n = 4; + } + if ((flags & 0x1000) && (flags & 0x7F) < 0x30U) { + v >>= 1; + flags = (flags & 0xFF80) | (0x2F - ((0x2F - (flags & 0x7F)) >> 1)); + } + idx = func_80030CA4(v); + if (idx != 0) { + idx--; + vo = (Voice32A74 *)(idx * 0x48 + (s32)vB); + if ((b & 0x80) == 0) { + q = &D_8006A970[n][REC->unk09]; + vo->unk1C = q->unk00; + vo->unk18 = q->unk04; + vo->unk3A = q->unk06; + vo->unk3C = q->unk08; + } else { + if (func_8003F144(D_800A46A2, b & 0x7F, REC->unk09, &sp10) != 0) { + goto next; + } + h = func_8003F380(D_800A46A2, sp10.unk16); + if (h < 0) { + goto next; + } + tb = sp10.unk04; + vo->unk1C = h; + vo->unk18 = tb << 8; + vo->unk3A = sp10.unk10; + vo->unk3C = sp10.unk12; + } + w = idx + 0x10; + vo->unk00 = D_80073140[w]; + ch = (Chan32A74 *)(D_800A4988 + idx * 0x54); + ch->unk4C = 0; + ch->unk4A = 0x7FFF; + if (REQ->unk00 & 0x80) { + ch->unk4D = 0; + } else { + ch->unk4D = 1; + } + t16 = REC->unk02; + ch->unk04 = 0; + ch->unk08 = v; + ch->unk0A = w; + ch->unk00 = t16; + t8 = REC->unk09; + ch->unk0E = b; + ch->unk36 = flag; + ch->unk10 = n; + ch->unk12 = 0; + ch->unk0C = t8; + t32 = REC->unk10; + ch->unk4F = 0; + ch->unk4E = 0x85; + ch->unk14 = t32; + __asm__ __volatile__(""); + vol = REC->unk06; + if (D_800A4F1E != 0) { + vol *= 0x70; + vol >>= 7; + } + if (flags & 0x1000) { + ch->unk48 = flags & 0x7F; + } else { + ch->unk48 = 0x7F; + } + ch->unk34 = vol & 0x7F; + pan = REC->unk07; + ch->unk35 = pan; + if (flags & 0x8000) { + ch->unk24 = REC->unk04 - 0x80; + } else { + ch->unk24 = REC->unk04; + } + ch->unk40 = (s32)func_80033324; + ch->unk44 = arg1; + if ((flags & 0x2000) && pan != 0) { + if ((flags & 0x3000) == 0x3000) { + ch->unk53 = D_8006AED8[(u32)(flags & 0xF00) >> 8]; + } else { + ch->unk53 = flags & 0x7F; + } + } else { + ch->unk53 = 0; + } + if (REC->unk02 == 0) { + func_800316F8(ch); + } + { + u16 u18 = vo->unk18; + base = (u18 & 0xFF00) + (s8)u18 * 2; + } + base -= 0x3C00; + d = ch->unk24; + d -= base; + if (d >= 0x5300) { + vo->unk14 = 0x3FFF; + } else { + qq = D_8006AB30[d >> 8]; + qq = qq * (0x100 - (d & 0xFF)); + vo->unk14 = (qq + D_8006AB32[d >> 8] * (d & 0xFF)) >> 8; + } + vol = D_8006AA30[ch->unk34]; + mp = vol * D_800A4EFA; + vol = mp >> 7; + mp = vol * ch->unk48; + vol = mp >> 7; + if (pan != 0) { + if (ch->unk53 != 0) { + pan += ch->unk53; + if (pan >= 0x42) { + pan -= 0x40; + if (pan >= 0x80) { + pan = 0x7F; + } + } else { + pan = 1; + } + } + if (D_800A4F19 != 0) { + vv = (vol * tA[pan]) >> 14; + vo->unk0A = vv; + vv = (vol * tB[0x7F - pan]) >> 14; + vo->unk08 = vv; + } else { + vv = (vol * D_8007321E) >> 14; + vo->unk0A = vv; + vo->unk08 = vv; + } + } else { + vv = vol; + vo->unk08 = vv; + vo->unk0A = vv; + } + ch->unk52 = pan; + vo->unk0C = 0; + vo->unk0E = 0; + vo->unk04 = 0x6009F; + __asm__ __volatile__(""); + if (D_800A4EF6 > REC->unk0A) { + func_8002F064(1, vo->unk00); + } else { + func_8002F064(0, vo->unk00); + } + t16 = ch->unk0A; + vo->unk45 = 0; + vo->unk44 = 1; + vo->unk40 = t16; + if (REC->unk02 == 0) { + func_8002EFF8(1, vo->unk00); + } + REQ->unk0C++; + REQ->unk0E[idx] = 1; + } + next: + if (REC->unk0B == 0) { + break; + } + arg2++; + v = REC->unk00; + } + if (REQ->unk0C == 0) { + REQ->unk00 = 0; + } else { + REQ->unk0A = 4; + } +} + +#undef REQ +#undef REC diff --git a/.run/P32/t4c/func_80032A74/p1.c b/.run/P32/t4c/func_80032A74/p1.c new file mode 100644 index 000000000..da60fcb06 --- /dev/null +++ b/.run/P32/t4c/func_80032A74/p1.c @@ -0,0 +1,15 @@ +extern u32 G, H; +extern u8 F; +void g(u32 vol) { + u32 m; + G = vol; + if (F != 0) { + m = vol; + m = (m << 7) - (m << 4); + m >>= 7; + vol = m; + } + H = vol; + m = H * 3; + G = m >> 14; +} diff --git a/.run/P32/t4c/func_80032A74/p10.c b/.run/P32/t4c/func_80032A74/p10.c new file mode 100644 index 000000000..cb63fc167 --- /dev/null +++ b/.run/P32/t4c/func_80032A74/p10.c @@ -0,0 +1,17 @@ +extern u32 G, H, J, Q; +extern u8 F; +void g(u32 vol) { + u32 m; + G = vol; + if (F != 0) { + m = vol; + J = m; + m >>= 7; + Q = m; + vol = m; + } + H = vol; + m = H; + m++; + G = m; +} diff --git a/.run/P32/t4c/func_80032A74/p11.c b/.run/P32/t4c/func_80032A74/p11.c new file mode 100644 index 000000000..2bd0c47a5 --- /dev/null +++ b/.run/P32/t4c/func_80032A74/p11.c @@ -0,0 +1,14 @@ +extern u32 G, H, J, Q; +extern u8 F; +void g(u32 vol) { + u32 m; + G = vol; + if (F != 0) { + m = vol; + J = m; + m >>= 7; + Q = m; + vol = m; + } + H = vol; +} diff --git a/.run/P32/t4c/func_80032A74/p12.c b/.run/P32/t4c/func_80032A74/p12.c new file mode 100644 index 000000000..9e80de22a --- /dev/null +++ b/.run/P32/t4c/func_80032A74/p12.c @@ -0,0 +1,18 @@ +extern u32 G, H, J, Q; +extern u8 F; +void g(u32 vol) { + u32 m, k; + G = vol; + if (F != 0) { + m = vol; + J = m; + m >>= 7; + k = m & 0x7F; + vol = m; + Q = k; + } + H = vol; + m = H; + m++; + G = m; +} diff --git a/.run/P32/t4c/func_80032A74/p13.c b/.run/P32/t4c/func_80032A74/p13.c new file mode 100644 index 000000000..fc6a49f53 --- /dev/null +++ b/.run/P32/t4c/func_80032A74/p13.c @@ -0,0 +1,17 @@ +extern u32 G, H, J, Q; +extern u8 F; +void g(u32 vol) { + u32 m, k; + G = vol; + if (F != 0) { + m = vol; + k = m & 3; + m >>= 7; + Q = k + m; + vol = m; + } + H = vol; + m = H; + m++; + G = m; +} diff --git a/.run/P32/t4c/func_80032A74/p14.c b/.run/P32/t4c/func_80032A74/p14.c new file mode 100644 index 000000000..e793ef25f --- /dev/null +++ b/.run/P32/t4c/func_80032A74/p14.c @@ -0,0 +1,15 @@ +extern u32 G, H, Q; +extern u8 F; +void g(u32 vol) { + u32 m, k; + G = vol; + if (F != 0) { + m = vol; + k = m & 3; + m >>= 7; + Q = k + m; + vol = m; + } + H = vol; + m = 0; +} diff --git a/.run/P32/t4c/func_80032A74/p15.c b/.run/P32/t4c/func_80032A74/p15.c new file mode 100644 index 000000000..4f1a29e54 --- /dev/null +++ b/.run/P32/t4c/func_80032A74/p15.c @@ -0,0 +1,14 @@ +extern u32 G, H, Q; +extern u8 F; +void g(u32 vol) { + u32 m, k; + G = vol; + if (F != 0) { + m = vol; + k = m & 3; + m >>= 7; + Q = k + m; + vol = m; + } + H = vol; +} diff --git a/.run/P32/t4c/func_80032A74/p16.c b/.run/P32/t4c/func_80032A74/p16.c new file mode 100644 index 000000000..b4f0f85d4 --- /dev/null +++ b/.run/P32/t4c/func_80032A74/p16.c @@ -0,0 +1,11 @@ +/* path (b) attempt: head N live later, middle T single-use, composition folds to one insn */ +extern u32 G, H, J; +u32 g(u32 r, u32 *p) { + u32 n, t, x; + n = r & 0xFF; /* I1: N = r & 0xFF, live later */ + t = n << 24; /* I2: T = N << 24 */ + x = t >> 24; /* I3: X = T >> 24 == r & 0xFF == n? (combine: (lshiftrt (ashift (and r 0xFF) 24) 24) -> (and r 0xFF)) */ + G = x; + p[0] = n; + return n + 1; +} diff --git a/.run/P32/t4c/func_80032A74/p17.c b/.run/P32/t4c/func_80032A74/p17.c new file mode 100644 index 000000000..d7204e01a --- /dev/null +++ b/.run/P32/t4c/func_80032A74/p17.c @@ -0,0 +1,11 @@ +/* path (b) attempt with a sign-extend chain on a register value: N = r & 0xFF (live), T = N << 24, X = T >>a 24 */ +extern u32 G, H, J; +s32 g(u32 r, u32 *p) { + u32 n; s32 t, x; + n = r & 0xFF; + t = n << 24; + x = t >> 24; + G = x; + p[0] = n; + return n + 1; +} diff --git a/.run/P32/t4c/func_80032A74/p18.c b/.run/P32/t4c/func_80032A74/p18.c new file mode 100644 index 000000000..2e0654648 --- /dev/null +++ b/.run/P32/t4c/func_80032A74/p18.c @@ -0,0 +1,11 @@ +/* path (b) attempt: N = r + 4 (live), T = N * 3, X = T - 12 (== r*3 : one mult) */ +extern u32 G, H, J; +u32 g(u32 r, u32 *p) { + u32 n, t, x; + n = r + 4; + t = n * 3; + x = t - 12; + G = x; + p[0] = n; + return n + 1; +} diff --git a/.run/P32/t4c/func_80032A74/p2.c b/.run/P32/t4c/func_80032A74/p2.c new file mode 100644 index 000000000..2e7c24c37 --- /dev/null +++ b/.run/P32/t4c/func_80032A74/p2.c @@ -0,0 +1,15 @@ +extern u32 G, H; +extern u8 F; +void g(u32 vol) { + u32 m; + G = vol; + if (F != 0) { + m = vol; + m *= 0x70; + m >>= 7; + vol = m; + } + H = vol; + m = H * 3; + G = m >> 14; +} diff --git a/.run/P32/t4c/func_80032A74/p3.c b/.run/P32/t4c/func_80032A74/p3.c new file mode 100644 index 000000000..8f3936053 --- /dev/null +++ b/.run/P32/t4c/func_80032A74/p3.c @@ -0,0 +1,13 @@ +extern u32 G, H; +extern u8 F; +void g(u32 vol) { + u32 m; + G = vol; + if (F != 0) { + m = vol; + m = (m << 7) - (m << 4); + m >>= 7; + vol = m; + } + H = vol; +} diff --git a/.run/P32/t4c/func_80032A74/p4.c b/.run/P32/t4c/func_80032A74/p4.c new file mode 100644 index 000000000..78b8a43c5 --- /dev/null +++ b/.run/P32/t4c/func_80032A74/p4.c @@ -0,0 +1,15 @@ +extern u32 G, H; +extern u8 F; +void g(u32 vol) { + u32 m; + G = vol; + if (F != 0) { + m = vol; + m += 3; + m >>= 7; + vol = m; + } + H = vol; + m = H * 3; + G = m >> 14; +} diff --git a/.run/P32/t4c/func_80032A74/p5.c b/.run/P32/t4c/func_80032A74/p5.c new file mode 100644 index 000000000..0f82bfa29 --- /dev/null +++ b/.run/P32/t4c/func_80032A74/p5.c @@ -0,0 +1,17 @@ +extern u32 G, H; +extern u8 F; +void g(u32 vol) { + u32 m, a, b; + G = vol; + if (F != 0) { + m = vol; + a = m << 3; + b = m << 4; + m = a - b; + m >>= 7; + vol = m; + } + H = vol; + m = H * 3; + G = m >> 14; +} diff --git a/.run/P32/t4c/func_80032A74/p6.c b/.run/P32/t4c/func_80032A74/p6.c new file mode 100644 index 000000000..6a679d189 --- /dev/null +++ b/.run/P32/t4c/func_80032A74/p6.c @@ -0,0 +1,16 @@ +extern u32 G, H; +extern u8 F; +extern u16 K; +void g(u32 vol) { + u32 m; + G = vol; + if (F != 0) { + m = vol; + m = m * K; + m >>= 7; + vol = m; + } + H = vol; + m = H * 3; + G = m >> 14; +} diff --git a/.run/P32/t4c/func_80032A74/p7.c b/.run/P32/t4c/func_80032A74/p7.c new file mode 100644 index 000000000..64c281362 --- /dev/null +++ b/.run/P32/t4c/func_80032A74/p7.c @@ -0,0 +1,16 @@ +extern u32 G, H; +extern u8 F; +extern u16 K; +void g(u32 vol) { + u32 m; + G = vol; + if (F != 0) { + m = vol; + m = m * m; + m >>= 7; + vol = m; + } + H = vol; + m = H * 3; + G = m >> 14; +} diff --git a/.run/P32/t4c/func_80032A74/p8.c b/.run/P32/t4c/func_80032A74/p8.c new file mode 100644 index 000000000..924250b40 --- /dev/null +++ b/.run/P32/t4c/func_80032A74/p8.c @@ -0,0 +1,18 @@ +extern u32 G, H, J; +extern u8 F; +void g(u32 vol) { + u32 m; + G = vol; + if (F != 0) { + m = vol; + m = (m << 3) - m; + m <<= 4; + m >>= 7; + J = m & 0x7F; + vol = m; + } + H = vol; + m = H; + m++; + G = m; +} diff --git a/.run/P32/t4c/func_80032A74/p9.c b/.run/P32/t4c/func_80032A74/p9.c new file mode 100644 index 000000000..b20f5463e --- /dev/null +++ b/.run/P32/t4c/func_80032A74/p9.c @@ -0,0 +1,21 @@ +extern u32 G, H, J; +extern u8 F; +extern void ext(u32); +void g(u32 vol) { + u32 m; + G = vol; + ext(vol); + if (F != 0) { + m = vol; + m = (m << 3) - m; + m <<= 4; + m >>= 7; + ext(m); + vol = m; + } + H = vol; + ext(vol); + m = H; + m++; + G = m; +} diff --git a/.run/backlog.jsonl b/.run/backlog.jsonl index 4409aff88..2aa686cd3 100644 --- a/.run/backlog.jsonl +++ b/.run/backlog.jsonl @@ -3,3 +3,4 @@ {"ts": "2026-09-05 19:09:22", "addr": "0x80185810", "name": "func_80185810", "reach": null, "klass": "SCHED", "nins": 489, "status": "near", "closeness": 13, "where_stuck": "S83 Fable: 35 -> 13 at exact length; 3 of 4 windows closed (P_TAG bitfield OT link + integer add for the addu operand order; sched1 flush_pending_lists at the 33rd memory op explains the load order -> HI temps; hard-reg destinations are not birthing-boosted -> pins uu $4 / mode $5 / ot16 $6 give the LUID order; shf pin $3). Residual ONE cause idx 363-380: `cl &= 0xFFFF` is an unboosted 2nd set \u2014 the fence after p[7]|= is needed (else its two reads float to the block head, 43/51) yet it blocks sched2 fillers crossing into the tpage/code window. NEXT: a spelling in which cl is single-set (its high half cleared at birth: cl = *(u16*)... or the shift form) so no fence is needed, or the two cl reads consume a fresh single-set copy that combine cannot fold (nonzero_bits defeats a plain andi copy; try a subreg/HI-mode temp)", "best_draft": ".run/P32/t5x/fable/func_80185810.c", "binary": "ov_SC03_105", "source": "P32-T4b S83 Fable agent (471k tokens, 26 min, ~3,400 compiles); report .run/P32/t5x/reports/func_80185810.md", "residual": null, "passes_tried": null} {"ts": "2026-09-05 23:33:21", "addr": "0x800cf408", "name": "func_800CF408", "reach": null, "klass": "SCHED+REGALLOC", "nins": 178, "status": "near", "closeness": 3, "where_stuck": "S83 Fable: 49 -> 3, zero pins (the \u00a7501-H shape + a dead arg1 kill against cse re-association + the P_TAG OT write + a named mhi born before block 1). Residual idx 10-12 = \u00a7501-H verbatim (the unboosted tag load blocks one cycle behind the tpage sw; the empty cycle eats the highest-LUID floater ori $s5,0x96) COUPLED to the $t2/$t3 qty_compare contest (2389 vs 2400): every cure of one re-opens the other. NEXT: fill the OT-chain lhu gap with an UNBOOSTED `p & m24` as the target does (needs a 2-set a3 that combine does not re-merge \u2014 combine.c:2309 decrements reg_n_sets on the merge \u2014 e.g. a second set through a different width/mode or a volatile-qualified temp), or move the contest margin by one ref elsewhere (an extra ob use in a block that does not touch the tag load)", "best_draft": ".run/P32/t5x/fable/func_800CF408.c", "binary": "md_MAIN_007", "source": "P32-T4b S83 Fable agent (499k tokens, 21 min, 135-variant sweep); report .run/P32/t5x/reports/func_800CF408.md", "residual": null, "passes_tried": null} {"ts": "2026-09-05 12:06:54", "addr": "0x80032a74", "name": "func_80032A74", "reach": null, "klass": "WALL-CANDIDATE", "nins": 422, "status": "near", "closeness": 1, "where_stuck": "WALL candidate CONFIRMED in the real TU (S83): 422/422, sole residual idx 244 `lh v0,0x18(s1)` vs target `lhu` \u2014 extendhisi2 is a force_not_mem EXPAND (the orphan frame slot is minted only at an lh; \u00a7172 producer 3 caller-save area, reload1.c:1445), so lhu loses the 8 frame bytes; ~200 byte-probes + 100-variant retyping sweep (S79) + permuter_ils 8x150s null (S80). Citation current (\u00a7172, reload1.c:1445). Draft synced to the TU (typedefs stripped via cdecl.strip_provided_typedefs; D_80064D44/D_8006A970/func_8003F144/func_800316F8 spelled as the TU)", "best_draft": ".run/P32/t4/drafts/func_80032A74_tuclean.c", "binary": "main", "source": "P32-T4 S83 re-probe (R40): the S79w Opus draft was a CC1 FAIL in src/800_b_2.c only for PLUMBING (7 typedefs the TU provides via 800_shared.h + 4 decl spellings); stripped/synced copy re-run in the REAL TU: DIFF 1 (idx 244 lh vs lhu) \u2014 the recorded residual, confirmed in TU context", "residual": null, "passes_tried": null} +{"ts": "2026-09-06 11:41:02", "addr": "0x80032a74", "name": "func_80032A74", "reach": null, "klass": "WALL-PROVED", "nins": 422, "status": "near", "closeness": 1, "where_stuck": "T4b HAND PASS S84 (2026-09-06, Fable Max): PROVED at 1 by producer census. Residual = ONE reload-time slot at sp+0x48 (the u16 lhu draft is 422/422 with DIFF 22 = frame rows only; the s16 lh draft is DIFF 1 at idx 244 with the frame exact). The four post-parameter slot producers (reload1.c:658 ghost alter_reg / caller-save.c:249 area / reload1.c:879 invalid-equiv address / reload1.c:3499 spill_stack_slot) each refuted on the bytes: the site loads lhu and the function has no lb and no double load (combine newi2pat ghosts re-derive a narrow load); no register-only insn shares a block with a call (no sched.c:4962 staleness, so no save area without sw/lw); no unallocated single-block equiv pseudo; $t0 holds no pseudo (else $t1 would be the spill reg) and LO mult results retry into GR_REGS. NEW mechanism measured: optimize_reg_copy_2 ghosts (tmp = x; tmp op= c; x = tmp) are minted AFTER regclass -> GR_REGS, allocated, vars=0 (P14). 18 isolated reproducers, 0 draft variants; cookbook \u00a7501-M; notes .run/P32/t4c/func_80032A74/NOTES.md", "best_draft": ".run/P32/t4/drafts/func_80032A74_tuclean.c", "binary": "main", "source": null, "residual": null, "passes_tried": null} diff --git a/config/wave_exclude.txt b/config/wave_exclude.txt index 163aa2143..b1cce3f7b 100644 --- a/config/wave_exclude.txt +++ b/config/wave_exclude.txt @@ -2,4 +2,4 @@ # 1 still-valid of 2; 1 dropped as stale (banked / linked / blocker-since-fixed). # An exclude list records what the TOOLING could not do — regenerate it as # part of every tool fix, or it becomes a list of work you decided not to do. -main:func_80032A74 # WALL: candidate: 422/422, frame/offsets/27 symbols exact, sole residual idx 244 `lh` vs `lhu` — extendhisi2 is a force_not_mem EXPAND (an orphan frame slot is minted only at an lh), the target's 8 extra frame bytes are §172 producer 3 (caller-save area, reload1.c:1445); ~200 byte-probes incl. a 100-variant retyping sweep (S79 Opus) + permuter_ils 8x150s null (S80); closeness 1 | T4 S83: the S79w draft was a CC1 FAIL only for PLUMBING (7 header typedefs + 4 decl spellings); synced copy .run/P32/t4/drafts/func_80032A74_tuclean.c re-run in the real TU DIFF 1 (idx 244 lh vs lhu) — CANDIDATE, unchanged | T4b S83 Fable (402k tokens): the 0x48 slot is a GHOST pseudo (combine.c:2306-2313 stranded temp → alter_reg 8-byte slot); caller-save route REFUTED; the only ghost species from a memory value is the SIGN_EXTEND narrow-load split = lh; closeness 1 STANDS — CANDIDATE→near-PROVED +main:func_80032A74 # WALL: candidate: 422/422, frame/offsets/27 symbols exact, sole residual idx 244 `lh` vs `lhu` — extendhisi2 is a force_not_mem EXPAND (an orphan frame slot is minted only at an lh), the target's 8 extra frame bytes are §172 producer 3 (caller-save area, reload1.c:1445); ~200 byte-probes incl. a 100-variant retyping sweep (S79 Opus) + permuter_ils 8x150s null (S80); closeness 1 | T4 S83: the S79w draft was a CC1 FAIL only for PLUMBING (7 header typedefs + 4 decl spellings); synced copy .run/P32/t4/drafts/func_80032A74_tuclean.c re-run in the real TU DIFF 1 (idx 244 lh vs lhu) — CANDIDATE, unchanged | T4b S83 Fable (402k tokens): the 0x48 slot is a GHOST pseudo (combine.c:2306-2313 stranded temp → alter_reg 8-byte slot); caller-save route REFUTED; the only ghost species from a memory value is the SIGN_EXTEND narrow-load split = lh; closeness 1 STANDS — CANDIDATE→near-PROVED | T4b HAND PASS S84 (2026-09-06): PROVED at 1 — producer census (§501-M): the 0x48 slot can only be a combine-minted ghost (newi2pat split), which re-derives a narrow load (lh/lb); the site is lhu, no lb, no double load; caller-save area needs sched staleness and no register-only insn shares a block with a call; invalid-equiv and spill_stack_slot producers refuted; optimize_reg_copy_2 ghosts are post-regclass and allocatable (measured). Best drafts: s16 = DIFF 1 (frame exact), u16 = 422/422 code with DIFF 22 frame rows diff --git a/docs/SETUP.md b/docs/SETUP.md index f50ea2b9b..0b139d076 100644 --- a/docs/SETUP.md +++ b/docs/SETUP.md @@ -808,7 +808,7 @@ Every script under `tools/` (plus the two report make-targets), grouped by purpo | | `tools/recover_integration.py` | **(Phase 24 T6; extended Phase 29 SESSION-16/17, cookbook §65/§66)** The stranded-draft recovery DRIVER — consumes a wave dir (`--draft-dir`, repeatable; the backlog is the wrong source: unreliable `closeness`, overlay-specific drafts), runs the declared `--stages` (`demacroize` T1 / `arity` T2), then gates in TWO passes (**gate all → exact snapshot-restore → re-stage winners only**), so a non-bank never leaves an edit behind. `--run-id` puts all scratch + `verified_out`/`failed_out` under `.run/recover//` (closes §55b trap 4); bank truth is `banked_from_source()` (the stub is GONE from src), never a gate report; `stub_map` derives from `corpus.stubs` (R33). **Blast-radius tiers are ENFORCED** (`--max-tier`, write-set assertion), and **propagation is itself fleet-tier**: it needs `--max-tier fleet` AND `--r22`, and is refused outright after `demacroize` (those banks are ×1 by construction; `--auto-from` would re-macroize and undo them) — both refusals negative-control-tested, exit 1. `--probe-only` / `--report`. Success path verified end-to-end by the §66 free re-bank test. | | | `tools/lift_types.py` · `tools/uniquify_type.py` | **(Phase 29 SESSION-14, cookbook §64/§64a)** Fleet-wide type lift into `src/shared/engine_types.h`, and the camp-uniquify that must precede it for VARIANT names (same identifier, different layouts in different TUs — reconciling them merges two layouts and breaks the minority camp). Both are **T2**: dry-run by default, and **R22 clean-fleet is the arbiter, not the per-binary gate**. | | | `tools/fix_header_decl.py` | **(Phase 29 SESSION-13, cookbook §63 + its UPDATE)** Rewrites a shared `engine_core.h` caller decl to a draft's byte-true sig. **Effectively retired**: its `[SAFE]` verdict inspects one caller and is structurally blind to the other ~137 overlays the shared decl reaches — 3/3 banked per-binary, then R22 **139/140**. Use `demacroize.py` (T1) instead. | -| | `tools/backlog.py` | Near-miss ledger (`.run/backlog.jsonl` + `docs/backlog.md`); **fleet-aware** `load_best` (a 077-matched-but-stuck-local fn surfaces via its overlay record; Phase 23). | +| | `tools/backlog.py` | Near-miss ledger (`.run/backlog.jsonl` + `docs/backlog.md`); **fleet-aware** `load_best` (a 077-matched-but-stuck-local fn surfaces via its overlay record; Phase 23). | **S84 repair (2026-09-06):** `load_best` now keeps the LATEST record at equal closeness (its docstring's contract; the `(cscore, ts) <= …` compare had kept the EARLIEST, so a re-verdict at the same closeness never rendered — func_80032A74's PROVED row). | | `tools/lora_grind.py` | Mass-run driver: rotate binaries → draft open ≤N-ins stubs with the served model → gate → propagate. `--min-reach N` (Phase 23) targets shared fns (sig-reach oracle == `dedup_propagate`). | | | `tools/bulk_harvest.py` | **(Phase 23 / T10)** The phase-separated + parallel-gate harvester (throughput rebuild of `lora_grind`): **(A)** bulk-draft K fresh ≤N-ins stubs (GPU) → **(B)** `ProcessPoolExecutor --workers` byte-gate over DISTINCT binaries (`build//**` isolated; `run_gate` per-binary lock + per-worker scratch, `propagate=False`/`commit=False`/`compute_fleet=False`) → **(C)** dedupe-once + ONE commit. Round-robin fuel spread; STOP-sentinel; on-demand/bounded. Measured 2026-07-01: gate **0.4s/fn** (8 workers, ~75× the serial gate) ⇒ drafting is the bottleneck (→ vLLM next). Run: `API_BASE=… MODEL=bfm-match-7b-v3 tools/bulk_harvest.py --binary-glob 'ov_SC03_*' --count 80 --workers 8 --measure`. `lora_grind` kept as the serial fallback. | | | `tools/grinder.py` | Token-free decomp-permuter daemon on the backlog near-misses; **per-binary** (Phase-23 fix). `auto_supervisor.sh`/`auto_stop.sh` keep-alive + STOP sentinel. **Phase-24 T5:** auto-threads the residual `klass`/`where_stuck` into `p16_permute.setup` (§31-directed weights) + **input-changed idle gating** (`draft_sig`) replacing the blind `tried.clear()` churn. **Phase-29 T13A TARGETING:** `candidates()` filters on the MEASURED residual bucket from `autopsy.verdicts()` (1,303 → 78) and takes its directed profile from the measured class, not the logged label (91% of records have none, so the search silently ran on gcc defaults). Measured: of the 972 records this filter admitted, only **75 (7.7%) were permuter-shaped** — ~92% of the daemon's CPU was going where a search-closer provably cannot win, which is why it banked 0 after Phase 21. Degrades to undirected if the corpus is absent and says which mode it is in; `--no-targeting` A/Bs it. | @@ -1021,6 +1021,8 @@ fills fast). Nothing is leaking — but the host does not get the memory back on | `tools/agent_drafts_restore.py` (P32 T3, 2026-09-05) | REBUILDS a subagent's final deliverable draft from its transcript by replaying every Write/Edit/heredoc/cp/mv op (` --out

[--pattern REGEX]`); one line per agent, NO-DELIVERABLE for agents with none (R32), and a count of unreplayable ops (an edit applied after a shell-side change → the rebuilt file may be STALE: verify with `rtu_match`, prefer an on-disk copy). Written when one agent's `find … -exec mv` swept 11 sibling deliverables out of the shared `.run/P32/t3/opus/` (cookbook §500-E; playbook §S80 addendum-2) | when a wave's deliverable file is missing but its transcript exists — the recovery route, never the primary | | `tools/agent_reports.py` (P32 T3, 2026-09-05) | writes each Agent-tool subagent's FINAL message — the full prose report (closers, INERT levers, residual mechanism, TU-plumbing warnings) — to `/____.md`; `agent_verdicts.py` keeps only the JSON line. T3's 31 reports live in `.run/P32/t3/reports/` (tracked) | after any single-agent drafting pass, before routing NEARs or banking MATCHes; the successor's first step after a dead session | | `tools/transcript_dump.py` (P32 T3, 2026-09-05) | condenses a session transcript (`~/.claude/projects//.jsonl`, MBs of JSON) into readable text — assistant text, every tool call (Bash in full), truncated results, timestamps — so a successor can find the last checkpoint/commit and what was in flight | when a session died without a checkpoint; pair with agent_verdicts / agent_reports / agent_drafts_restore | +| `tools/cc1_dumps.sh` (P30 §172; repaired P32 T4b hand pass, 2026-09-06) | `cc1_dumps.sh ` → `.run/c294/dumps_/.i.{rtl,jump,cse,loop,flow,combine,sched,lreg,greg}` + `.s` with the pinned cc1 (`-dr -ds -dj -df -dc -dS -dl -dg`); prints the `.frame` line (`vars=` is the frame-residue oracle), the instruction count, reload's `Spilling reg` / `now in` lines and the ghost census. The old standalone-`(use)` grep it printed UNDER-COUNTED (§172 note) and is gone | the first artefact of any residual: attribute it to a pass and a dump line before the first probe (accelerators (11)); edit the cc1 line for `-O0` modules | +| `tools/ghost_census.py` (P32 T4b hand pass, 2026-09-06; cookbook §501-M) | `ghost_census.py .i.lreg …` — every `Register N used …` header whose pseudo has NO occurrence in the post-sched insn stream (a ghost with stale `reg_n_refs`), with its class: `ST_REGS or none` ⇒ reload gives it an 8-byte `alter_reg` slot (combine-minted, pre-regclass); `GR_REGS …` ⇒ allocatable, no slot (`optimize_reg_copy_2`-minted, post-regclass). A census, exit 0 | frame-residue diagnosis (`vars=` off by 8·k); run by `cc1_dumps.sh` | | `tools/parallel_gate.py` (S80, task #10) | **banked-but-not-merged is loud**: the worker's raw `git status` + scope ride in each result, a binary whose bank oracle fired but whose files were neither adopted nor refused prints `!! [pgate] BANKED-BUT-NOT-MERGED` and the run exits 2 (S80: `ov_SC03_107` banked 1 / merged 0 / exit 0, the bank died with the worktree — cause unrecovered because the fixed-path results JSON was overwritten); every run now also writes `.run/pgate_runs/.json`. Recovery = `rtu_match` MATCH → in-tree splice → `make build` (exit code) → commit | every `parallel_gate` run | | `tools/verbatim_to_stub.py` (S80 usage note) | refuses to GUESS the asm subdir when the TU has no sibling `INCLUDE_ASM` left (a fully-decompiled overlay): pass `--asm-subdir asm//nonmatchings/` (main: `asm/nonmatchings/`); `--apply --gate` re-extracts, rebuilds and compares the SHA. Used in #10 to revert S79 #7's assembly "bank" (cookbook §495) | any verbatim → stub conversion | | `tools/p16_permute.py` + `tools/permuter_ils.py` (S80, task #9c) | **Pinned seeds are permutable now** (cookbook §493 S80 correction): `hide_asm` carries `asm(`/`__asm(` as well as `__asm__(` into the b64 pragma (keyword must be followed by `(`/`volatile`, so `INCLUDE_ASM("asm/…")` path strings are not statements — R39-controlled over 5,311 drafts); `permuter_ils` RE-HIDES every warm-restart waypoint (the permuter serializes pins raw), asserts `defines_fn` survived, aborts exit-2 on a refusal (R61a), and flushes its log (R55); `defines_fn` accepts K&R-style definitions. Any pre-S80 "permuter plateau" on a pinned seed was 1 cycle + silent no-ops — re-measure before citing it | every `permuter_ils` / `permuter_sweep` run; the S79 NEAR ledger re-run | diff --git a/docs/accelerators.md b/docs/accelerators.md index 81b064fdb..d7add68fc 100644 --- a/docs/accelerators.md +++ b/docs/accelerators.md @@ -792,3 +792,13 @@ measured nothing. The cracks came from one dump each: the `7f000001` birthing bo "not desirable" line (§501), the `-dl` quantity priorities (§501-B/E), the `.greg` "Register N in M" (§501-B/E), the `-dR` hazard walk (§501-G). Accelerator: a residual's first artefact is the dump line that owns it — `tools/cc1_dumps.sh` gives all of them in one run; a wall verdict without a pass and a dump line is a hypothesis, not a proof (extends (9) and R40). + +**(12) Enumerate the artefact's PRODUCERS from the compiler source before probing a single spelling (P32 T4b hand pass, +`func_80032A74`, S84).** Two sessions (S79 ~200 byte-probes + a 100-variant sweep, S83 22 spellings, a 402k-token Fable agent) +had chased the 8 phantom frame bytes as a spelling problem. Reading every stack-slot allocation site in reload1.c / +caller-save.c / combine.c / local-alloc.c gave a four-row producer census, and each row died on a fact already in the bytes +or a dump — the site's `lhu`, the call blocks' contents, the spill register's identity (`lw $t0` ⇒ no pseudo lived in `$t0`), +the mult results' alternate class — without compiling a variant of the draft (18 isolated reproducers, 0 draft variants). +It also found a new ghost producer (`optimize_reg_copy_2`, §501-M) and measured why it cannot slot. Accelerator: a frame +residual gets a producer table first (`tools/cc1_dumps.sh` + `tools/ghost_census.py`), a spelling sweep last — and a +"PROVED" verdict is the table with every row refuted, not a sweep that came back empty (extends (9), (11), R40). diff --git a/docs/backlog.md b/docs/backlog.md index b22f168f2..ad6323d71 100644 --- a/docs/backlog.md +++ b/docs/backlog.md @@ -2,11 +2,11 @@ > Generated by `tools/backlog.py render` from `.run/backlog.jsonl`. These are functions the Phase-21 automation got **close** on but did NOT byte-match. The whole-binary byte-gate is the sole arbiter (G3/P9): **byte-matches bank and are NOT listed here** — only genuine near-misses/blockers are. Ranked by hand-session priority: **reach** (×N propagation leverage) → **closeness** (match_one mismatch count, lower = closer) → **size**. Each row's `best_draft` is the closest C the machine reached — resume from there. -**Open near-misses:** 5 · by status {'near': 4, 'failed': 1} · by class {'WALL-CANDIDATE': 1, 'SCHED+REGALLOC': 1, 'FRAME+SCHED': 1, 'SCHED': 1, None: 1} +**Open near-misses:** 5 · by status {'near': 4, 'failed': 1} · by class {'WALL-PROVED': 1, 'SCHED+REGALLOC': 1, 'FRAME+SCHED': 1, 'SCHED': 1, None: 1} | # | addr | reach | class | nins | status | closeness | where it stuck | best draft | |--:|------|------:|-------|-----:|--------|----------:|----------------|------------| -| 1 | func_80032A74 | None | WALL-CANDIDATE | 422 | near | 1 | WALL candidate CONFIRMED in the real TU (S83): 422/422, sole residual idx 244 `lh v0,0x18(s1)` vs target `lhu` — extendhisi2 is a force_not_mem EXPAND (the orphan frame slot is minted only at an lh; §172 producer 3 caller-save area, reload1.c:1445), so lhu loses the 8 frame bytes; ~200 byte-probes + 100-variant retyping sweep (S79) + permuter_ils 8x150s null (S80). Citation current (§172, reload1.c:1445). Draft synced to the TU (typedefs stripped via cdecl.strip_provided_typedefs; D_80064D44/D_8006A970/func_8003F144/func_800316F8 spelled as the TU) | `.run/P32/t4/drafts/func_80032A74_tuclean.c` | +| 1 | func_80032A74 | None | WALL-PROVED | 422 | near | 1 | T4b HAND PASS S84 (2026-09-06, Fable Max): PROVED at 1 by producer census. Residual = ONE reload-time slot at sp+0x48 (the u16 lhu draft is 422/422 with DIFF 22 = frame rows only; the s16 lh draft is DIFF 1 at idx 244 with the frame exact). The four post-parameter slot producers (reload1.c:658 ghost alter_reg / caller-save.c:249 area / reload1.c:879 invalid-equiv address / reload1.c:3499 spill_stack_slot) each refuted on the bytes: the site loads lhu and the function has no lb and no double load (combine newi2pat ghosts re-derive a narrow load); no register-only insn shares a block with a call (no sched.c:4962 staleness, so no save area without sw/lw); no unallocated single-block equiv pseudo; $t0 holds no pseudo (else $t1 would be the spill reg) and LO mult results retry into GR_REGS. NEW mechanism measured: optimize_reg_copy_2 ghosts (tmp = x; tmp op= c; x = tmp) are minted AFTER regclass -> GR_REGS, allocated, vars=0 (P14). 18 isolated reproducers, 0 draft variants; cookbook §501-M; notes .run/P32/t4c/func_80032A74/NOTES.md | `.run/P32/t4/drafts/func_80032A74_tuclean.c` | | 2 | func_800CF408 | None | SCHED+REGALLOC | 178 | near | 3 | S83 Fable: 49 -> 3, zero pins (the §501-H shape + a dead arg1 kill against cse re-association + the P_TAG OT write + a named mhi born before block 1). Residual idx 10-12 = §501-H verbatim (the unboosted tag load blocks one cycle behind the tpage sw; the empty cycle eats the highest-LUID floater ori $s5,0x96) COUPLED to the $t2/$t3 qty_compare contest (2389 vs 2400): every cure of one re-opens the other. NEXT: fill the OT-chain lhu gap with an UNBOOSTED `p & m24` as the target does (needs a 2-set a3 that combine does not re-merge — combine.c:2309 decrements reg_n_sets on the merge — e.g. a second set through a different width/mode or a volatile-qualified temp), or move the contest margin by one ref elsewhere (an extra ob use in a block that does not touch the tag load) | `.run/P32/t5x/fable/func_800CF408.c` | | 3 | func_80039308 | None | FRAME+SCHED | 518 | near | 4 | S83 Fable: 17 -> 4 in the real TU. Closed the 11-row alias block (natural spelling; tail via a pointer so the li follows the addu in RTL) and rows 390/391 (p = r + a dead reset). Residual 4 = two causes: rows 49/50 the hoisted constant 2 vs the pinned vbase preheader order (move_movables splices after source preheader code); rows 412/415 a PHANTOM 8-byte frame slot with no traffic at sp+8 between the arg1 HImode spill (sp+0) and cnt (sp+0x10) — `lhu $s7` is reload's spill register; storing arg1 directly reproduces sh $a1/lhu $s7 but not the slot (frame 0x38 vs 0x40). NEXT: induce the phantom slot — a hard-reg spill_stack_slot (reload1.c spill_hard_reg on LO or $s7 during retry_global_alloc), cf. §501-E (pins forbid regs at retry) and the func_80032A74 ghost-pseudo finding (a stranded combine temp -> alter_reg 8-byte slot in regno order) | `.run/P32/t5x/fable/func_80039308.c` | | 4 | func_80185810 | None | SCHED | 489 | near | 13 | S83 Fable: 35 -> 13 at exact length; 3 of 4 windows closed (P_TAG bitfield OT link + integer add for the addu operand order; sched1 flush_pending_lists at the 33rd memory op explains the load order -> HI temps; hard-reg destinations are not birthing-boosted -> pins uu $4 / mode $5 / ot16 $6 give the LUID order; shf pin $3). Residual ONE cause idx 363-380: `cl &= 0xFFFF` is an unboosted 2nd set — the fence after p[7]/= is needed (else its two reads float to the block head, 43/51) yet it blocks sched2 fillers crossing into the tpage/code window. NEXT: a spelling in which cl is single-set (its high half cleared at birth: cl = *(u16*)... or the shift form) so no fence is needed, or the two cl reads consume a fresh single-set copy that combine cannot fold (nonzero_bits defeats a plain andi copy; try a subreg/HI-mode temp) | `.run/P32/t5x/fable/func_80185810.c` | diff --git a/docs/matching-cookbook.md b/docs/matching-cookbook.md index 62057ea5a..a47b2e08d 100644 --- a/docs/matching-cookbook.md +++ b/docs/matching-cookbook.md @@ -37401,3 +37401,36 @@ nop (179 ins). **Law:** when the same ready-list slot decides both a scheduling oscillate between two closeness floors (here 3 and 13/18); decouple by adding a filler that changes neither count (an unboosted temp combine cannot re-merge — a different mode/width, or a `volatile` temp) or by moving the contest margin with a reference in a block that does not touch the slot. 135-variant sweep floor 3 (×24). Next lever recorded in `docs/backlog.md`. + +**§501-M — THE PHANTOM-SLOT PRODUCER CENSUS, and the ghost that cannot slot (P32 T4b hand pass, S84 2026-09-06; +`main:func_80032A74` PROVED at 1).** A never-referenced stack slot that sits AFTER the parameter spill slots (sp+0x48 here; +the params at 0x30/0x38/0x40 are `alter_reg` slots in regno order, so any expand-time local would displace them) can only +come from four reload-time sites, all read out of `tools/reference/gcc-2.7.2`: (1) `reload1.c:658` `alter_reg(i,-1)` for a +GHOST pseudo — `reg_n_refs>0`, no occurrence, no REG_EQUIV, class `ST_REGS or none` because regclass never saw it; (2) +`caller-save.c:249 setup_save_areas` — a 4-byte area per call-used hard reg holding ANY pseudo with `reg_n_calls_crossed>0`, +once `caller_save_needed` is set by the profitability retry (global.c:1085, local-alloc.c:2209; `4*calls < refs`); it leaves no +`sw/lw` only when the count is STALE-HIGH, and the sole staleness route is sched.c:4962 (a multi-block pseudo keeps flow's +count when sched's is 0 — the comment says why) after sched1 moved a register-only def/use across a call inside the call's +own block (combine never crosses a call except with a constant source, combine.c:924; `update_equiv_regs` moves nothing); +(3) `reload1.c:879` — a `reg_equiv_memory_loc` whose address eliminates to a SPILLED pseudo gets a fresh slot, but only an +UNALLOCATED pseudo qualifies and those equivalences are single-block (`update_equiv_regs`), so local-alloc takes them; (4) +`reload1.c:3499 spill_stack_slot` — a pseudo evicted from a spilled hard reg with no retry (local-alloc'd) or a failed +`retry_global_alloc`; `$t0` can hold no pseudo at all (`order_regs_for_reload` lists zero-use call-used regs first, so a +pseudo in `$t0` moves every param reload to `$t1`), and LO-pref mult results carry alternate class `GR_REGS` and re-home. +**The one producer reachable from C at zero code cost is combine's `newi2pat` split** (combine.c:1887 SIGN_EXTEND-of-narrow- +load, combine.c:1963 two-independent-SETs) whose `i2dest` vanishes with `reg_n_refs` kept (the zeroing at combine.c:2306 is +skipped whenever `newi2pat != 0`); both re-derive a NARROW LOAD (`lh`/`lb`, or a duplicate `lhu`) from the chain's memory +head — a register head folds at tree/cse level or has its middle temp re-used by `find_split_point`, so path (b) never runs +(18 reproducers). Hence a phantom slot whose site loads `lhu`, has no `lb` and no double load is unreachable: PROVED. +**The NEW ghost producer, measured, and why it does not slot:** `local-alloc.c optimize_reg_copy_2` on +`tmp = x; ; tmp = tmp op c; ; x = tmp;` (one block; x dead at the head copy, live after the copy-back; +the head copy survives combine when tmp's FIRST use is not its last and no 3-insn chain passes through it — combine.c:904; +the copy-back survives when tmp has an intervening use that sched keeps above it) rewrites every `tmp` to `x`, leaves two +no-op self-moves, and decrements `reg_n_refs[tmp]` once per insn while flow counted the in-place insn twice → a ghost with +stale refs (P13 refs 5, P14 refs 1). **It is minted AFTER regclass, keeps `GR_REGS` with no conflicts, and global simply +allocates it: vars=0.** Only pre-regclass (combine) ghosts take slots. **Instrument:** `tools/ghost_census.py .i.lreg` +(headers with no occurrence, class → SLOT / allocatable), now run by `tools/cc1_dumps.sh` in place of its `(use)` grep (which +under-counted, §172 note); `vars=` on the `.frame` line remains the arbiter. **Law:** before probing spellings for a frame +residual, enumerate the artefact's PRODUCERS from the source and refute each on the bytes — the site's load width (`lh` vs +`lhu`), the call blocks' contents, the spill register's identity and the mult results' alternate class each kill one +producer without a compile. Probes and notes: `.run/P32/t4c/func_80032A74/`. diff --git a/phase-ends/CURRENT_PHASE.md b/phase-ends/CURRENT_PHASE.md index 558a58acc..5afe96b8e 100644 --- a/phase-ends/CURRENT_PHASE.md +++ b/phase-ends/CURRENT_PHASE.md @@ -99,7 +99,7 @@ Scale estimate: 3–5 sessions. `func_80011380` → `.run/m3/opus/func_80011380.c`; `func_801834A4` → `.run/S71_gate14/ov_SC03_105*/`), `exclude_audit --write`, `backlog.py render`; a T3 idiom naming a wall's mechanism re-opens that row (bounded: one attempt, permuter first). Wall table into this file; cookbook §496+; decision log; accelerators. -- [~] **T4b — crack and bank the final 15 (ADDED 2026-09-05 by Drew's directive; supersedes the kill gate: "anything that isn't the original hand-written asm or the PsyQ libs needs to be cracked and banked before we finish")** — **FIRST PASS COMPLETE 2026-09-05 (S83): 11 of 15 BANKED byte-identical, 4 carried to the NEXT session's HAND pass (Drew: no second agent round; hand-crack the remaining ones).** Hand pass first (22 spellings, 0 banks, every blocker refined to a mechanism in `.run/P32/t4b//NOTES.md`), then one Fable agent per row (permission, not requirement — Drew), resumed 3-at-a-time through three usage-limit outages. Banked (each: coordinator `rtu_match` in the CURRENT real TU → `gate_main` / `bank.sh` byte-identical → one commit; pins dropped by `exclude_audit --write`; mechanism in cookbook §501–§501-L): `main:func_800391D4` `commit:3956` · `main:func_80039DEC` `commit:3959` · `md_MAIN_009:func_800CD674` `commit:3964` · `ov_SC06_022:func_8017DF28` `commit:3966` · `main:func_80020DA4` `commit:3969` · `ov_SC03_105:func_801834A4` `commit:3972` · `md_MAIN_003:func_800CF3E8` `commit:3976` · `md_MAIN_009:func_800CD92C` `commit:3979` · `ov_SC07_002:func_8017DC80` `commit:3983` · `main:func_80011380` `commit:3990` (the §474 "PROVED" wall) · `md_MAIN_007:func_800CF6D0` `commit:3992`. Ten of the eleven were T4 "walls" or long-standing NEARs. **Carried (4, all exact length, in `docs/backlog.md` with next levers):** `main:func_80032A74` 1 (the last pin; ghost pseudo — near-proved) · `md_MAIN_007:func_800CF408` 3 · `main:func_80039308` 4 · `ov_SC03_105:func_80185810` 13. Close: fleet R22 **218 passed / 0 failed, exits 0/0/0** (23:33–23:36 MDT, `.run/P32/t4b/r22_full.log`); `make report`: instr 13,486,890 / 13,488,497 = 100.0% · distinct 5,814,982 / 5,816,589 = 100.0% (90,982 / 90,984 unique) · fn-count 363,210 / 363,214 = 100.00% · **INCLUDE_ASM 4**; main REAL 787 · LINKED 1,256 · VERBATIM 3 · stubs 2 · 2,089 / 2,091 = 99.90% · `143dbb89…`; census `.run/P32/frontier_t4b_close.json` 4 stubs / 1,607 ins; decision-log P32 S83 T4b (R31); accelerators (10)–(11); tools-health OK (`.run/P32/t4b/tools_health.log`). +- [~] **T4b — crack and bank the final 15 (ADDED 2026-09-05 by Drew's directive; supersedes the kill gate: "anything that isn't the original hand-written asm or the PsyQ libs needs to be cracked and banked before we finish")** — **FIRST PASS COMPLETE 2026-09-05 (S83): 11 of 15 BANKED byte-identical, 4 carried to the NEXT session's HAND pass (Drew: no second agent round; hand-crack the remaining ones).** Hand pass first (22 spellings, 0 banks, every blocker refined to a mechanism in `.run/P32/t4b//NOTES.md`), then one Fable agent per row (permission, not requirement — Drew), resumed 3-at-a-time through three usage-limit outages. Banked (each: coordinator `rtu_match` in the CURRENT real TU → `gate_main` / `bank.sh` byte-identical → one commit; pins dropped by `exclude_audit --write`; mechanism in cookbook §501–§501-L): `main:func_800391D4` `commit:3956` · `main:func_80039DEC` `commit:3959` · `md_MAIN_009:func_800CD674` `commit:3964` · `ov_SC06_022:func_8017DF28` `commit:3966` · `main:func_80020DA4` `commit:3969` · `ov_SC03_105:func_801834A4` `commit:3972` · `md_MAIN_003:func_800CF3E8` `commit:3976` · `md_MAIN_009:func_800CD92C` `commit:3979` · `ov_SC07_002:func_8017DC80` `commit:3983` · `main:func_80011380` `commit:3990` (the §474 "PROVED" wall) · `md_MAIN_007:func_800CF6D0` `commit:3992`. Ten of the eleven were T4 "walls" or long-standing NEARs. **HAND PASS S84 (2026-09-06, Fable Max, no agents): row (a) `main:func_80032A74` → PROVED at 1 (§501-M producer census: the 0x48 slot can only be a combine-minted ghost, which needs an `lh`/`lb`; the pin stays with its final verdict; ledger + `tools/ghost_census.py` + `cc1_dumps.sh` repair; rows (b)(c)(d) next).** Carried (4, all exact length, in `docs/backlog.md` with next levers): `main:func_80032A74` 1 (the last pin; ghost pseudo — near-proved) · `md_MAIN_007:func_800CF408` 3 · `main:func_80039308` 4 · `ov_SC03_105:func_80185810` 13. Close: fleet R22 **218 passed / 0 failed, exits 0/0/0** (23:33–23:36 MDT, `.run/P32/t4b/r22_full.log`); `make report`: instr 13,486,890 / 13,488,497 = 100.0% · distinct 5,814,982 / 5,816,589 = 100.0% (90,982 / 90,984 unique) · fn-count 363,210 / 363,214 = 100.00% · **INCLUDE_ASM 4**; main REAL 787 · LINKED 1,256 · VERBATIM 3 · stubs 2 · 2,089 / 2,091 = 99.90% · `143dbb89…`; census `.run/P32/frontier_t4b_close.json` 4 stubs / 1,607 ins; decision-log P32 S83 T4b (R31); accelerators (10)–(11); tools-health OK (`.run/P32/t4b/tools_health.log`). - [ ] **T5 — PhaseEnd** (Max, Tier 1 — prompt R27): P7 checkbox walk; milestone demo (R22 fleet N/N, tools-health, `verbatim_check --strict`, final census, wall ledger, parked-5 dispositions via `make audit-disc`, `make report` all three metrics + main `143dbb89…` with/without SDK dirs, corrected denominators); **WAIT @@ -135,6 +135,7 @@ cookbook before the next drafting step (R16/R30) · no `Co-Authored-By` trailer needed for T0–T2b; if T2d needs it: `tools/ghidra_mcp_start.sh` → pause → Drew runs `/mcp` (R29) → G2 ping. ## Log +- 2026-09-06 (S84, session 72d8b4b1, Max, Fable 5.1) — **T4b hand pass, row (a) `main:func_80032A74` CLOSED: PROVED at 1 (verdict, not a bank).** Preflight: tree clean, `verbatim_check --strict` 5==5, `make check-all` 218 passed / 0 failed rc 0 (`.run/P32/t4c/check_all_preflight.log`). Re-verified in the real TU (`rtu_match … --tu src/800_b_2.c --asm-subdir asm/nonmatchings/800_b_2`): the `s16 u18` draft DIFF 1 (idx 244 `lh` vs `lhu`, frame exact); the `u16 u18` TU-clean variant DIFF 22 = the 22 frame rows only (422/422 code). The residual is ONE reload-time slot at sp+0x48. Instead of a spelling sweep, enumerated every post-parameter stack-slot producer from the 2.7.2 source (reload1.c:658 ghost `alter_reg` · caller-save.c:249 area · reload1.c:879 invalid-equiv address · reload1.c:3499 `spill_stack_slot`) and refuted each on the bytes/dumps: combine's `newi2pat` ghosts re-derive a narrow load (`lh`/`lb`) and the site is `lhu` with no `lb`/double load; a save area without `sw/lw` needs sched.c:4962 staleness and no register-only insn shares a block with any of the 7 calls; unallocated single-block equiv pseudos cannot exist; `$t0` holds no pseudo (`order_regs_for_reload`) and LO mult results retry into `GR_REGS`. Found and measured a NEW ghost producer — `local-alloc.c optimize_reg_copy_2` on `tmp = x; tmp op= c; x = tmp;` (P13 refs 5, P14 refs 1) — which cannot slot because it is minted after regclass (class `GR_REGS`, no conflicts → allocated, vars=0). 18 isolated reproducers, 0 draft variants. Deliverables: cookbook **§501-M**, `tools/ghost_census.py` (new) + `tools/cc1_dumps.sh` (repaired: frame line + census, the under-counting `(use)` grep gone) + SETUP rows (R21), accelerators (12), backlog row (WALL-PROVED) + `tools/backlog.py` tie-break repair (kept the EARLIEST record at equal closeness — the S84 row never rendered), `config/wave_exclude.txt` annotated (`exclude_audit --assert-fresh` 1/1), notes `.run/P32/t4c/func_80032A74/NOTES.md`. NEXT = row (b) `md_MAIN_007:func_800CF408` (3). - 2026-09-05 13:05–23:40 MDT (S83, continued) — **T4b first pass COMPLETE: 11 of 15 banked, 4 carried.** Fifteen Fable agents launched from `.run/P32/t5x/` (BRIEF + packs + the hand-pass NOTES); three usage-limit outages killed every run (resumed each time via SendMessage with context intact; "write deliverables early" saved one crack from a dead run); from the second outage on, resumed 3 at a time (Drew). Verdicts: 11 MATCH (each re-verified by the coordinator in the CURRENT real TU and banked byte-identical — main rows via `gate_main` slates, overlays/modules via `SPLIT=… DRAFT_DIR=.run/P32/t5x/fable bank.sh`), 4 NEAR at exact length (1 / 3 / 4 / 13) with pass-attributed residuals and next levers ledgered. Every crack came from READING a pass dump against the 2.7.2 source; cookbook §501–§501-L (12 new laws: cascades, cross-jump-after-alloc, dying-input vs birthing boost, hard-reg sets count, pins forbid retry regs, CSE-quantity split, three-passes-three-dials, constants as floaters, manufactured orphans, a proved tree wall is not an RTL wall, sched2's /s exemption, coupled dials); decision-log P32 S83 T4b; accelerators (10)–(11). Instrument defects: bank.sh (empty fn list → built the unchanged tree, exit 0; `_jr_` TU split; draft dir) hardened, and two premature "banked" ledger messages corrected in the next commit (memory: write the message from the tool's output). Pins 7 → 1. Fleet R22 218/218 at the close; `make report` fleet 100.0/100.0/100.00, 4 stubs. **Drew: no second agent round; hand-crack the remaining four next session.** NEXT = the hand pass on the four (see the 🛑 block), then T5. - 2026-09-05 12:05–12:40 MDT (S83, continued) — **T4 DONE.** Preflight: tree clean, verbatim 5==5, R22 218/218 (12:00), `exclude_audit --assert-fresh` 7/7. Every pinned wall's best draft re-run with `rtu_match` in its CURRENT real TU: `func_80011380` DIFF 6 (`--o0`), `func_80020DA4` DIFF 2, `func_8017DF28` DIFF 2, `func_801834A4` DIFF 6 ×3 variants; the three CC1-FAIL rows re-probed after their plumbing was understood — `func_80032A74` (7 TU-provided typedefs + 4 decl spellings → `cdecl.strip_provided_typedefs` + the TU's lines → DIFF 1 in the real TU), `func_80039DEC` and `func_800391D4` (a sandbox TU copy under `.run/P32/t4/tu/` with the declaration edited THERE → DIFF 2 / DIFF 3) — no `src/` edit, no byte-neutral commit spent on rows that will not bank. Leaf `match_one` re-measured all three (1 / 2 (permuter) / 3). **No verdict changed: 1 PROVED (§474) + 6 CANDIDATE**, citations current (§474, §172 reload1.c:1445, loop.md L4 2.7.2:1529, cse_expr.md [A23-2], the K&R promotion laws). Deliverables: the wall table (above), `config/wave_exclude.txt` per-row S83 lines, backlog rows for all 7 (+ the two path-less rows fixed, R62; `docs/backlog.md` 16 open), cookbook §500-I, accelerators (8), decision-log. NEXT = **T5 (Max, Tier 1 — prompt R27, WAIT for gate 2)**. - 2026-09-05 11:30–12:30 MDT (S83, continued) — **T3 steps 8–9 DONE → T3 CLOSED.** Step 8: `permuter_ils` 8×150 s -j3 on the two REGALLOC-PERM seeds — `func_800CD674` plateau (best waypoint = the same 2-row `$a3↔$t1` pair; ledgered with cost), `func_8001BC6C` reached masked 1; the R63 read showed three mutations, one of them a WRONG-WIDTH `& 0xFF` (lhu→lbu); the two sound ones (idx after color; an early `tag`/`k` birth) = leaf MATCH, re-spelled well-defined as `k = 0; tag = (a1 << 8) | k;` (11 spellings measured), rtu MATCH in src/800.c, **gate_main BANKED 143dbb89** (`commit:3948`) — main 7 → 6 open. gate_main's first rebuild died on a concurrent agent's `src/.masked_diff_probe..c` (present at parse, gone at compile) → **Makefile `C_SRCS` find now `-not -name '.*'`** (`commit:3949`, byte-neutral, control on `make -pn`). One bounded Opus second look at `func_800CF3E8` (245k tokens, 29 min): 27 holds; §500-D1's mechanism corrected to `cse.c find_best_addr` (fold_rtx MEM; COST pseudo 0 vs hard reg 1), the alias lever refuted 5/5, a new zero-byte pinned-pointer launder found (79 @ 470, structurally closer) — cookbook **§500-H**, backlog row updated. Step 9: `make report` (fleet instr 100.0% · distinct 99.9% · fn 100.00% · 15 stubs), `make report BINARY=main`, census `.run/P32/frontier_t3_close.json` (15 / 3,758), twin_rescan 0 free, cookbook §500-G/H + index, this file; R22 → see the 🛑 block. **Kill gate:** the session banked 29 and produced 3 new verdicts; the tail's three bounded attempts are spent — T3 closes on the evidence. NEXT = T4. @@ -149,67 +150,61 @@ cookbook before the next drafting step (R16/R30) · no `Co-Authored-By` trailer - 2026-09-05 — **T1a DONE — `resident:func_800D128C` BANKED (243 ins, byte-identical 8e17e02f, R22 213/213).** The stored S71 closeness-0 draft was byte-correct all along; the whole task was three instrument defects the resident (the fleet's one `common.h`-only, `--pre`-sandwich binary) exposed in overlay-only assumptions: (1) `jr_isolate_all` dropped a file-local typedef whose name engine_types.h also defines (§496 — fixed: provided types derived from the TU's own includes); (2) `jtbl_carve` regenerated `JTBL_INTERLEAVE` without the `--pre hdr.rodata.o` clause → extract refused → the gate linked a stale script and booked the byte-correct draft as DIFF; `harvest_verify` ignored that extract's rc (§498 — both fixed, R49/R61); (3) `interleave_check` read a `--pre` line as n=0 (false DRIFT; fixed). R38 then found two more stored MATCH bodies for T1b/T1c (see their rows). Effort stayed Max. - 2026-09-05 — **T0 DONE.** Baseline reads all green (`.run/P32/t0_baseline.log`): `verbatim_check --strict` 5 bodies == 5 rows; `exclude_audit --assert-fresh` 8 entries, 8 WALL, 0 stale; `frontier_classify` → 21 rows = the S80 census exactly; `make tools-health` OK (sigs fresh, corpus(+resident), cdecl, audit-binaries 213/213, report lint+dedup, cookbook-index, split_indicator 213 OK); `make check-all` 213 passed / 0 failed, rc 0. Harness task list #1–#11 built (R28). NEXT = T1a. -## 🛑 SESSION CHECKPOINT — T4b FIRST PASS COMPLETE: 4 rows left, HAND-CRACK NEXT (no agents), then T5 (2026-09-05 23:45 MDT; written by session 491895ad "S83"; SUPERSEDES the 13:40/15:30 blocks) +## 🛑 SESSION CHECKPOINT — T4b HAND PASS IN PROGRESS: row (a) PROVED at 1, rows (b)(c)(d) NEXT, then T5 (2026-09-06 ~12:00 MDT; written by session 72d8b4b1 "S84"; SUPERSEDES the 2026-09-05 23:45 block) ### 0. How to use this block You are a FRESH SESSION that has read `PROJECT_CONTEXT.md`, `phase-ends/DIGEST.md`, `PhaseEnd_Phase29/30/31.md` and this file, and nothing else (CLAUDE.md protocol, R64 candidate). Replay this block verbatim into your chat, state phase / done / NEXT / effort, -list the rules from the digest, then WAIT for Drew. **NEXT is the HAND pass on the four remaining rows — Drew (2026-09-05 23:2x MDT): -"we will hand crack the remaining ones next session instead of using agents."** That is non-obvious root-cause work: recommend -**effort: Max** (R7/R27 — prompt and wait for the `/effort`). No Agent tool, no Workflow. After the four (banked or honestly +list the rules from the digest, then WAIT for Drew. **NEXT is the HAND pass on rows (b) → (c) → (d) — Drew (2026-09-05 23:2x MDT): +"we will hand crack the remaining ones next session instead of using agents."** Non-obvious root-cause work: recommend +**effort: Max** (R7/R27 — prompt and wait for the `/effort`). No Agent tool, no Workflow. After the three (banked or honestly ledgered), T5 = the PhaseEnd (Tier 1, Max, WAIT for gate 2). ### 1. Where we are **Phase 32 — the last 21 + the parked 5 (short, kill-gated) — extended by Drew's T4b directive: every function that is not original hand-asm or a PsyQ object must be cracked and banked before the phase closes.** Gate 1 approved 2026-09-05; R44–R63 ratified then; -R64 candidate. Tasks: **T0 ✓ T1a ✓ T1b ✓ T1c ✓ T2a–c ✓ (T2d not needed) T3 ✓ T4 ✓** (commits in the task rows above) · **T4b first -pass ✓ — 11 of 15 banked** (`commit:3956` `commit:3959` `commit:3964` `commit:3966` `commit:3969` `commit:3972` `commit:3976` `commit:3979` -`commit:3983` `commit:3990` `commit:3992`), **4 carried** (below) · **T5 pending.** Harness tasks #1–#13 (#13 = T4b in_progress). -Fleet **218 binaries**. **Last fleet R22: `make clean && make extract-all && make check-all` → 217+main extracted, 218 passed / -0 failed, exits 0/0/0 at 23:36 MDT** (`.run/P32/t4b/r22_full.log`) — AFTER every bank of the session. `make report` at the close: -**instr 13,486,890 / 13,488,497 = 100.0% · distinct 5,814,982 / 5,816,589 = 100.0% (90,982 / 90,984 unique fns) · fn-count -363,210 / 363,214 = 100.00% · INCLUDE_ASM 4**; main REAL 787 · LINKED 1,256 · VERBATIM 3 · stubs 2 · byte-identical 2,089 / -2,091 = 99.90% · sha `143dbb89…`. `verbatim_check --strict` 5 == 5. `config/wave_exclude.txt`: **1 entry** (`main:func_80032A74`), -`exclude_audit --assert-fresh` OK. Census `.run/P32/frontier_t4b_close.json`: **4 stubs / 1,607 ins**. `make tools-health` → **OK** at the close (`.run/P32/t4b/tools_health.log`: sigs fresh; corpus(+resident) + cdecl + binaries + -report(lint+dedup) + cookbook-index green). Non-ghidra -tree clean at HEAD; the `ghidra/` churn is R23 restart-noise — never stage it. Claude commits, Drew pushes (R6; ~100 unpushed). -100% C after today: resident, md_SC03_053/054/056, md_MAIN_003, md_MAIN_009, ov_SC06_022, ov_SC07_002. +R64 candidate. Tasks: **T0 ✓ T1a ✓ T1b ✓ T1c ✓ T2a–c ✓ (T2d not needed) T3 ✓ T4 ✓ T4b first pass ✓ (11 of 15 banked)** · +**T4b hand pass IN PROGRESS: row (a) `main:func_80032A74` CLOSED as PROVED at 1 (S84, a verdict — nothing banked this session); +rows (b)(c)(d) open** · **T5 pending.** Harness tasks #1–#13 (#13 = T4b in_progress). +Fleet **218 binaries**. **No `src/`, `config/` (except `wave_exclude.txt` annotation) or carve state changed in S84.** Last fleet R22: +`make clean && make extract-all && make check-all` → 217+main extracted, **218 passed / 0 failed, exits 0/0/0 at 2026-09-05 23:36 MDT** +(`.run/P32/t4b/r22_full.log`); S84 preflight `make check-all` → **218 passed / 0 failed, EXIT=0** (`.run/P32/t4c/check_all_preflight.log`). +`make report` (S83 close, still current): **instr 13,486,890 / 13,488,497 = 100.0% · distinct 5,814,982 / 5,816,589 = 100.0% (90,982 / +90,984 unique fns) · fn-count 363,210 / 363,214 = 100.00% · INCLUDE_ASM 4**; main REAL 787 · LINKED 1,256 · VERBATIM 3 · stubs 2 · +byte-identical 2,089 / 2,091 = 99.90% · sha `143dbb89…`. `verbatim_check --strict` 5 == 5 (S84). `config/wave_exclude.txt`: **1 entry** +(`main:func_80032A74`, now carrying its S84 PROVED verdict), `exclude_audit --assert-fresh` 1/1 OK (S84). Census +`.run/P32/frontier_t4b_close.json`: **4 stubs / 1,607 ins**. `make tools-health` → OK at the S83 close (`.run/P32/t4b/tools_health.log`); +`cookbook_index.py --check` OK (S84). Non-ghidra tree clean at HEAD after the S84 commit; the `ghidra/` churn is R23 restart-noise — +never stage it. Claude commits, Drew pushes (R6; ~100 unpushed). 100% C: resident, md_SC03_053/054/056, md_MAIN_003, md_MAIN_009, +ov_SC06_022, ov_SC07_002. -### 2. What S83 did (session 491895ad, 2026-09-05 10:10 → 23:45 MDT) -T3 steps 0–9 (28 banks; §500-F/G/H), T4 (the walls' ledger; §500-I), then **T4b**: the hand pass (22 spellings, 0 banks, blockers -named in `.run/P32/t4b//NOTES.md`), 15 Fable agents (one per row, ~2 h budget, brief `.run/P32/t5x/BRIEF.md`, packs, the NOTES), -three usage-limit outages (every agent resumed via SendMessage with its context; from the second outage on, 3 at a time — Drew), -**11 MATCH banked + 4 NEAR ledgered**. The T4b cracks are cookbook **§501–§501-L**; the doctrine is in `docs/decision-log.md` (P32 -S83 T4b) and `docs/accelerators.md` (10)–(11): every crack came from reading a pass dump (`-dS` ready lists, `.loop` desirability, -`-dl` priorities, `.greg` dispositions, `-dR` hazard walks) against `tools/reference/gcc-2.7.2/` BEFORE the first probe; six of the -seven T4 "wall" citations named the wrong pass. Instrument fixes: `.run/P32/t3s3/bank.sh` (refuses an empty fn list; `SPLIT=` for -`_jr_` TUs; `DRAFT_DIR=`; verbatim check = instruction-bearing asm only; propagates a failed commit); Makefile `C_SRCS` dotfile -guard; `jtbl_carve --probe` names §303 for `md_*`. Two premature "banked" ledger messages (`commit:3962`, `commit:3963`) were corrected -in `commit:3964`'s message. **Nothing under `config/` changed except `wave_exclude.txt` (pins 7 → 1) and the T4b plumbing in -`src/800_c.c` (`commit:3953`, byte-neutral).** +### 2. What S84 did (session 72d8b4b1, 2026-09-06, Max, Fable 5.1, no agents) +Row (a) only. Re-verified both best drafts in the real TU; then, instead of a spelling sweep, read every post-parameter stack-slot +producer out of `tools/reference/gcc-2.7.2` and refuted each on the bytes/dumps — the producer census is cookbook **§501-M** and +`.run/P32/t4c/func_80032A74/NOTES.md`. Found a NEW ghost producer (`local-alloc.c optimize_reg_copy_2`) and measured why it cannot +slot (post-regclass → allocatable). Tooling: **`tools/ghost_census.py`** (new; ghosts with stale refs and their class), **`tools/cc1_dumps.sh` +repaired** (prints the `.frame` line + ins count + spill lines + the census; its `(use)` grep under-counted), **`tools/backlog.py` +tie-break repaired** (kept the EARLIEST record at equal closeness — the S84 PROVED row never rendered), SETUP rows (R21), accelerators +(12), backlog row `WALL-PROVED`, the pin annotated. 18 isolated reproducers (`.run/P32/t4c/func_80032A74/p1..p18.c`), 0 draft +variants. **Verdict for (a): PROVED at 1** — the honest final disposition the plan allows for this row (P9; nothing redefined). -### 3. THE FOUR REMAINING ROWS — the hand-crack briefs (everything a fresh session needs; all at EXACT length, all rtu-clean) -Verify any draft with `rtu_match` in the REAL TU (main: `--tu src/.c`); bank via `gate_main` (main) or -`SPLIT= DRAFT_DIR= .run/P32/t3s3/bank.sh "" <fn>` (it refuses on red). RTL -dumps: `tools/cc1_dumps.sh <self-contained draft> <tag>` → `.run/c294/dumps_<tag>/<tag>.i.{rtl,cse,loop,combine,lreg,greg,sched,...}` -(the draft must carry its own typedefs/externs; add `-O0` by editing the script's cc1 line for boot). Each row's Fable report +### 3. THE THREE REMAINING ROWS — the hand-crack briefs (all at EXACT length, all rtu-clean; unchanged from the 23:45 block) +Verify any draft with `rtu_match` in the REAL TU (main: `--tu src/<sub>.c --asm-subdir asm/nonmatchings/<sub>`; overlays/modules: +`--split <tu-basename> --source <binary>`); bank via `gate_main` (main) or `SPLIT=<tu-basename> DRAFT_DIR=<dir> .run/P32/t3s3/bank.sh +<binary> <tu> <asmdir> <arm> "<title>" <fn>` (it refuses on red). RTL dumps: `tools/cc1_dumps.sh <self-contained draft> <tag>` → +`.run/c294/dumps_<tag>/<tag>.i.{rtl,jump,cse,loop,flow,combine,sched,lreg,greg}` + the `.frame`/ins/spill/ghost summary (the draft +must carry its own typedefs/externs; add `-O0` by editing the script's cc1 line for boot). Each row's Fable report (`.run/P32/t5x/reports/<fn>.md`) carries the dump citations and the measured-inert list — READ IT FIRST, do not repeat its sweeps. +**Do the producer census BEFORE any spelling probe (§501-M, accelerators (12)): name the pass and the dump line that owns the residual.** -**(a) `main:func_80032A74` — 422 ins, closeness 1, the LAST pin.** TU `src/800_b_2.c`, sub `800_b_2`, asm `asm/nonmatchings/800_b_2/`. -Best drafts: `.run/P32/t4/drafts/func_80032A74_tuclean.c` (TU-clean, rtu DIFF 1: idx 244 `lh` vs target `lhu`) and the code-exact -`lhu` respelling `.run/P32/t4b/func_80032A74/lhu.c` (422/422 identical EXCEPT the frame: vars 56 vs 64 → 22 offset rows); the -self-contained ancestor `.run/S79w/opus/func_80032A74.c` for dumps. **Mechanism (Fable, 402k tokens, `.run/P32/t5x/reports/ -func_80032A74.md`):** the target's never-referenced 8-byte slot at 0x48 is a GHOST pseudo — a stranded middle temp of a 3-insn -combine whose refs-zeroing is skipped when `newi2pat != 0` (combine.c:2306–2313); zero occurrences → regclass "ST_REGS or none" → -reload1.c:658 `alter_reg` 8-byte slot in regno order = right after the three param slots (reproducer `ghost1`: vars=8, no code). -The S83 caller-save-area hypothesis is REFUTED (order_regs_for_reload picks zero-use regs first; an area needs `caller_save_needed` -and saves at every live call). The only ghost species from a memory value is the SIGN_EXTEND narrow-load split (combine.c:1887– -1930) whose signature IS `lh`; a jump-target second promotion is folded by cse follow-jumps; a fall-through one needs a register -sign_extend MIPS lacks; the generic two-SETs split (combine.c:1963–2020) has no candidate (cse pre-folds constant offsets). **Next -lever:** a ghost from a NON-memory value — a 3-insn combine over a register-only expression whose middle temp is stranded -(`newi2pat != 0` path) — e.g. a shift/mask/add chain on an already-loaded value whose intermediate has a second use in a LATER -block; enumerate the target's `and/sll/addu` triples on register operands and test each for the `(use)` orphan in `.combine` -(count vars= in the `.frame` line). If none, the row is PROVED at 1 (write the proof: "no C spelling mints an orphan without an lh"). +**(a) `main:func_80032A74` — CLOSED: PROVED at 1 (S84).** Best drafts: `.run/P32/t4/drafts/func_80032A74_tuclean.c` (`s16 u18`, rtu +DIFF 1 = idx 244 `lh` vs `lhu`, frame exact) and `.run/P32/t4c/func_80032A74/lhu_tuclean.c` (`u16 u18`, 422/422 code, DIFF 22 = the +frame rows: vars 56 vs 64). Proof: the residual is one reload-time slot at sp+0x48; its only zero-code producer is a combine-minted +ghost (`newi2pat` split, combine.c:1887/1963 → reload1.c:658), which re-derives a narrow signed load; the site is `lhu`, the function +has no `lb` and no double load, all nine `lh` are single-use. Caller-save area (caller-save.c:249) needs sched.c:4962 staleness and no +register-only insn shares a block with any call; reload1.c:879 needs an unallocated single-block equiv pseudo; `spill_stack_slot` +needs a pseudo in `$t0` (impossible: `lw $t0` param reloads) or a failed LO retry (mult results have alternate `GR_REGS`). +`optimize_reg_copy_2` ghosts are post-regclass and allocatable (P14 vars=0). Do NOT reopen without a new producer. **(b) `md_MAIN_007:func_800CF408` — 178 ins, closeness 3, zero pins.** TU `src/md_MAIN_007/md_MAIN_007.c`, sub `md_MAIN_007`, asm `asm/md_MAIN_007/nonmatchings/md_MAIN_007/`. Draft `.run/P32/t5x/fable/func_800CF408.c`; report `.run/P32/t5x/reports/func_800CF408.md`; @@ -228,11 +223,12 @@ asm `asm/nonmatchings/800_c/`. Draft `.run/P32/t5x/fable/func_80039308.c`; repor two causes:** rows 49/50 — the hoisted constant `2` vs the pinned `vbase` preheader order (move_movables splices after source preheader code; unpinning/moving vbase → 515/495; a named `cst2` → 520/286); rows 412/415 — `lhu $s7` is reload's SPILL REGISTER: storing `arg1` directly spills the HImode parameter and reproduces `sh $a1 / lhu $s7` exactly, but the target frame is [arg1 spill -@0][8-byte slot with NO traffic @8][cnt @0x10] and the phantom slot could not be induced (frame 0x38 vs 0x40). **Next lever:** the -phantom slot is a hard-reg `spill_stack_slot` (reload1.c `spill_hard_reg` on LO or `$s7` during `retry_global_alloc`) or a ghost -pseudo (as in (a)) — read the target's frame layout against `.greg` "Spilling reg N" lines; a pseudo that global parks in LO and -reload respills (as `func_80020DA4`'s m13 did) mints the 8 bytes; cf. §501-E. Corrected law from this row: at an equal-priority -load/store tie sched2 issues the STORE via `potential_hazard`, not LUID (sched.c:2616–2680). +@0][8-byte slot with NO traffic @8][cnt @0x10] and the phantom slot could not be induced (frame 0x38 vs 0x40). **Next lever:** run the +§501-M producer census on THIS frame first — the phantom slot sits BETWEEN the arg1 spill (regno-lowest) and `cnt`, i.e. it is an +`alter_reg` slot of a pseudo numbered between them (or a `spill_stack_slot`): read the `.greg` "Spilling reg N" / "now on stack" lines +and the `.lreg` headers (`tools/ghost_census.py`) before any spelling; the S83 hypotheses (a hard-reg `spill_stack_slot` on LO or `$s7` +during `retry_global_alloc`, or a ghost as in (a)) are now testable with the census; cf. §501-E. Corrected law from this row: at an +equal-priority load/store tie sched2 issues the STORE via `potential_hazard`, not LUID (sched.c:2616–2680). **(d) `ov_SC03_105:func_80185810` — 489 ins, closeness 13.** TU `src/ov_SC03_105/ov_SC03_105_jr_80181C84.c`, sub `ov_SC03_105_jr_80181C84`, asm `asm/ov_SC03_105/nonmatchings/ov_SC03_105_jr_80181C84/`. Draft `.run/P32/t5x/fable/func_80185810.c`; @@ -250,39 +246,47 @@ temp defeats `nonzero_bits`). 3,360-variant region-2 sweep best 14 — do not re ### 4. NEXT — in order 0. **Preflight:** `git status --short | grep -v ghidra/` (empty) · `verbatim_check --strict` (5 == 5) · `make check-all` → 218/218 (R56 baseline). Prompt `/effort max` (R27) and WAIT. -1. **The hand pass on (a)–(d), one row at a time, closest first (a → b → c → d):** read the report, reproduce the residual with - `rtu_match`, dump the RTL (`cc1_dumps.sh`), attribute the residual to a PASS and a dump line before the first probe (§501, - accelerators (11)), then the row's next lever; ≤ ~10 probes per row before writing the verdict. A MATCH → bank + commit + pin - drop (`exclude_audit --write config/wave_exclude.txt config/wave_exclude.txt`) + `backlog.py render` + cookbook §501-M…; a - plateau → `backlog.py log` with closeness, class, best draft, cost; for (a) at 1, a written PROOF that names the pass is an - acceptable final verdict (P9 — never redefine). +1. **The hand pass on (b) → (c) → (d), one row at a time:** read the report, reproduce the residual with `rtu_match`, dump the RTL + (`cc1_dumps.sh`), run the producer census where the residual is a frame/slot (§501-M), attribute the residual to a PASS and a dump + line before the first probe (§501, accelerators (11)/(12)), then the row's next lever; ≤ ~10 draft probes per row before writing the + verdict. A MATCH → bank + commit + `backlog.py render` + cookbook §501-N…; a plateau → `backlog.py log` (closeness, class, best draft, + cost) — write "banked" only from the tool's printed success line; a PROVED verdict names the pass and the refuted producers (P9). 2. **T4b close:** `make clean && make extract-all && make check-all` (quote 218/218) · `make report` + `make report BINARY=main` · `frontier_classify --json .run/P32/frontier_t4b_final.json` · refresh this block · commit · harness #13 done. 3. **T5 (Max, Tier 1 — prompt R27; WAIT for gate 2):** P7 walk (T0–T4b) → milestone demo (R22 218/218 · tools-health · verbatim 5==5 · - the final census · the wall ledger — now ONE pin or none · `make audit-disc` UNCLAIMED 0 of 220 · `make report` ×3 metrics + main + the final census · the wall ledger — ONE pin, PROVED · `make audit-disc` UNCLAIMED 0 of 220 · `make report` ×3 metrics + main `143dbb89…` WITH and WITHOUT the SDK object dirs · denominators) → WAIT → `PhaseEnd_Phase32.md` (Build Log · Deviations incl. - the T4b directive and the 4-row remainder stated plainly · Commit Message · Rules Added — R64 to RATIFY + the S83 candidates + the T4b directive and the remainder stated plainly · Commit Message · Rules Added — R64 to RATIFY + the S83/S84 candidates below · Changelog v1.30.0 → v1.31.0 · Roadmap delta: P33 = verify + public flip · Plain-English Recap · believed/failed/sooner · 🛑) → DIGEST.md §2/§3 (step 3b) → `git mv phase-ends/CURRENT_PHASE.md phase-ends/logs/Phase32.md` (R19) → R23 → leave both uncommitted for Drew (R6) → "PhaseEnd file created. Commit the file and start a new Claude Code session for the next phase." → - HARD STOP (P8). **Rule candidates from S83 for the PhaseEnd table:** (i) a wall verdict must be PASS-attributed with a dump line + HARD STOP (P8). **Rule candidates for the PhaseEnd table:** (i) a wall verdict must be PASS-attributed with a dump line (R40 → "exonerate the instrument, then name the pass"); (ii) write "banked" only from the tool's printed success line; (iii) agents - write deliverables early (a dead run's draft banked); (iv) a helper must refuse an empty work list (R43 restated). + write deliverables early (a dead run's draft banked); (iv) a helper must refuse an empty work list (R43 restated); **(v) S84: a + residual gets a PRODUCER CENSUS from the compiler source before a spelling sweep, and "PROVED" means every producer refuted on the + bytes (accelerators (12)); (vi) S84: a ledger's tie-break is part of the instrument — a re-verdict that cannot surface is a silent + skip (backlog.py kept the earliest record at equal closeness; R43/R61 family).** ### 5. Files, tools, exact invocations, gotchas -- **`.run/P32/t5x/`** (tracked: BRIEF.md, targets.json, `fable/func_*.c` (15 drafts), `reports/*.md` (15), `verdicts.jsonl` (16 rows), - resume_queue.txt; untracked: packs/, work/, rtu/). **`.run/P32/t4b/`** (tracked: `<fn>/NOTES.md|HYPOTHESIS.md`, `<fn>/*.c` - variants, `*.log`; gate slates under `gate/` are ignored — recreate). **`.run/P32/t3s3/`**: `bank.sh` (env `SPLIT=`, `DRAFT_DIR=`), - `splice.py`, logs. **`.run/c294/dumps_*`**: cc1 dump dirs (regenerable). -- **Transcripts of the 15 Fable agents:** `~/.claude/projects/-home-musashi-bfm-decomp/491895ad-3c84-4037-b04f-bf7e5ee16a0c/subagents/ - agent-<id>.jsonl`; `tools/agent_verdicts.py <tasks>/a*.output` rebuilds verdicts (the S83 Haiku agents are in the same dir — filter by - `fn` against `.run/P32/t5x/targets.json`); `tools/agent_reports.py` saves full reports. -- **Gotchas that bit today:** `gate_main --assert-baseline --allow-dirty` RESTORES the working tree's TUs before building (its GREEN - measured the committed tree — use an in-tree `make extract BINARY=main && make build BINARY=main` for an uncommitted TU edit) · - `make extract BINARY=main` rewrites main's `asm/` — never while something reads those `.s` files · a bank helper called with no - function name built the unchanged tree and exited 0 (fixed, R43) · backticks inside a double-quoted `--where` argument are - command substitution (log rows were mangled twice; use single quotes) · `.run/P32/**` allowlists are per-subdir — an ignored - path silently breaks a `git add … &&` chain · an agent's masked_diff probe in `src/` is build input (Makefile guard `commit:3949`). +- **`.run/P32/t4c/`** (S84, tracked: `dump.sh` (private dump + census, writes `dumps_<tag>/` under t4c), `check_all_preflight.log`, + `func_80032A74/{NOTES.md,lhu_tuclean.c,p1..p18.c}`; ignored: `dumps_*/`, `rtu/`). **`.run/P32/t5x/`** (tracked: BRIEF.md, targets.json, + `fable/func_*.c` (15), `reports/*.md` (15), `verdicts.jsonl`; untracked: packs/, work/, rtu/). **`.run/P32/t4b/`** (tracked: + `<fn>/NOTES.md|HYPOTHESIS.md`, `<fn>/*.c`, `*.log`). **`.run/P32/t3s3/`**: `bank.sh` (env `SPLIT=`, `DRAFT_DIR=`), `splice.py`, logs. + **`.run/c294/dumps_*`**: `cc1_dumps.sh` output (regenerable). +- **`tools/ghost_census.py <tag>.i.lreg`** — headers with no occurrence in the post-sched stream: `ST_REGS or none` ⇒ an 8-byte slot + (combine-minted); `GR_REGS …` ⇒ allocatable, no slot. `tools/cc1_dumps.sh` runs it and prints `vars=` (the arbiter). +- **Gotchas that bit in S84:** `rtu_match` on a MAIN function needs `--asm-subdir asm/nonmatchings/<sub>` (its default is the overlay + layout: "No such file … asm/ov_SC01_077/nonmatchings/…") · `.run/P32/t4b/func_80032A74/lhu.c` carries the TU-provided typedefs + (`Rec14`, `Slot54` …) and FAILS cc1 in the real TU — use the `*_tuclean.c` drafts (`cdecl.strip_provided_typedefs`) · a mechanism probe + must keep the temp's LAST mention after the variable's (cse `make_regs_eqv` canonicalizes the shorter-lived reg away) and sched can + sink a protecting store below a copy · `backlog.py` ties are now latest-wins (repaired) · the cookbook index does not index the bold + `§501-x` sub-entries (only `## §` headings) — `--check` stays green after appending one. +- **Gotchas from S83 (still live):** `gate_main --assert-baseline --allow-dirty` RESTORES the working tree's TUs before building (use an + in-tree `make extract BINARY=main && make build BINARY=main` for an uncommitted TU edit) · `make extract BINARY=main` rewrites main's + `asm/` — never while something reads those `.s` files · a bank helper called with no function name built the unchanged tree and + exited 0 (fixed, R43) · backticks inside a double-quoted `--where` argument are command substitution (use single quotes) · + `.run/P32/**` allowlists are per-subdir — an ignored path silently breaks a `git add … &&` chain · an agent's masked_diff probe in + `src/` is build input (Makefile guard `commit:3949`). ### 6. Environment Fleet 218; shas: main `143dbb89f34491258bbc27810d0a12ec8b43a8dd` · md_MAIN_007 `2ff702b605ab5cfc18474c464c4c07e5f8ffd48c` · ov_SC03_105 @@ -294,13 +298,15 @@ Drew pushes. Every bank commits before the next command that can touch `src/` (R42) · count banks from the SOURCE and quote the fleet's green count at every close (R58) · verify a build from its exit code (R53) · a masked/permuter score is not a closeness until its diff is read (R63) · an `__asm__` body that reproduces instructions is a verbatim, not a bank (R62; zero-byte fences/launders are dials) · re-verify in -the CURRENT TU before splicing (§500-F) · attribute a residual to a PASS before sweeping levers (§501, accelerators (11)) · write -"banked" only from the tool's printed success line · rules check every 4 tasks (P6) · harvest into the cookbook before the next -drafting step (R16/R30) · no `Co-Authored-By` (R5) · never stage `ghidra/` (R23). +the CURRENT TU before splicing (§500-F) · attribute a residual to a PASS before sweeping levers (§501, accelerators (11)) · census the +PRODUCERS of a frame residual before any spelling (§501-M, accelerators (12)) · write "banked" only from the tool's printed success +line · rules check every 4 tasks (P6) · harvest into the cookbook before the next drafting step (R16/R30) · no `Co-Authored-By` (R5) · +never stage `ghidra/` (R23). -**Plain-English recap (R18).** Today the crack pass banked twenty-eight functions, the walls got their written verdicts, and then the -owner moved the finish line: nothing may remain but the original hand-written assembly and Sony's library. A hand pass named every -blocker; fifteen deep agents then took one function each, survived three quota outages, and banked eleven of them — ten of which -had been declared unmatchable, one with a written proof — each by reading the compiler's own pass dumps rather than by guessing. -Every rebuild of all 218 files is still identical, and by instruction count the game is at 100.0%. Four functions remain, each within -one to thirteen instructions with its cause written down; the next session cracks them by hand, then the phase closes. +**Plain-English recap (R18).** This session took the first of the four leftover functions — the one that was a single instruction +away — and, instead of trying more rewordings, listed every way the 1995 compiler can leave an unused slot in a function's stack +frame and struck each off against the bytes. The only way that could produce this slot at zero cost needs a signed 16-bit load, and +the original uses an unsigned one, so the function is now recorded as proved-unreachable by re-spelling, with the compiler pass named. +Along the way a genuinely new compiler quirk was found and measured (a temporary copied in and out of a variable leaves a ghost +register behind), a small measuring tool was written, an old one repaired, and a ledger bug fixed that had been hiding updated +verdicts. Three functions remain, each with its cause written down; nothing in the game's rebuilt code changed. diff --git a/tools/backlog.py b/tools/backlog.py index 5610a7685..4e2039636 100644 --- a/tools/backlog.py +++ b/tools/backlog.py @@ -205,8 +205,12 @@ def load_best(): cur = best.get((key, subkey)) c = r.get("closeness") cscore = c if isinstance(c, int) else 10 ** 9 - if cur is None or (cscore, r.get("ts", "")) <= (cur[0], cur[1]): - best[(key, subkey)] = (cscore, r.get("ts", ""), r) + # Lower closeness wins; at EQUAL closeness the LATEST record wins (the docstring's contract). + # The old `(cscore, ts) <= (cur…)` kept the EARLIEST at a tie, so a re-verdict at the same + # closeness (S84: func_80032A74 CANDIDATE -> PROVED, both 1) never reached render (R43/R61). + ts = r.get("ts", "") + if cur is None or cscore < cur[0] or (cscore == cur[0] and ts >= cur[1]): + best[(key, subkey)] = (cscore, ts, r) # fold each addr's unknown-nins record into its body when that body is unambiguous sized = collections.defaultdict(list) diff --git a/tools/cc1_dumps.sh b/tools/cc1_dumps.sh index ee650cd97..3999527d3 100644 --- a/tools/cc1_dumps.sh +++ b/tools/cc1_dumps.sh @@ -1,7 +1,10 @@ #!/bin/bash -# usage: dump.sh <draft.c> <tag> -> .run/c294/<tag>.{s,greg,lreg,combine} + orphan-slot report +# usage: cc1_dumps.sh <draft.c> <tag> -> .run/c294/dumps_<tag>/<tag>.i.{rtl,jump,cse,loop,flow,combine,sched,lreg,greg} + <tag>.s # gcc-2.7.2 cc1 writes dump files as <inputname>.<pass> in its CWD; we cd into a private dir so # the repo root never collects gccdump.* droppings again. +# The draft must be self-contained (its own typedefs/externs); edit the cc1 line for -O0 (boot) modules. +# Prints: the .frame line (vars= is THE frame-residue oracle, §172/§501-M), the instruction count, the reload +# spill lines, and the ghost census (tools/ghost_census.py — the old standalone-`(use)` grep UNDER-COUNTS, §172 note). cd /home/musashi/bfm-decomp f="$1"; tag="$2" d=.run/c294/dumps_$tag; rm -rf $d; mkdir -p $d @@ -9,18 +12,7 @@ src=$d/$tag.c if ! grep -q '#include "common.h"' "$f"; then echo '#include "common.h"' > $src; fi cat "$f" >> $src mipsel-linux-gnu-cpp -lang-c -Iinclude -undef -Wall -fno-builtin -Dmips -D__GNUC__=2 -D__OPTIMIZE__ -Dpsx -D_PSYQ -D_MIPSEL -D_LANGUAGE_C $src > $d/$tag.i -(cd $d && ../../../tools/bin/gcc-2.7.2-psx/cc1 -quiet -O2 -G0 -mips1 -mcpu=3000 -mgas -msoft-float -fgnu-linker -dr -ds -dj -dc -dl -dg $tag.i -o $tag.s 2>$tag.err) -echo "== $tag: dumps in $d" -grep -c "^(insn" $d/$tag.i.greg 2>/dev/null | sed 's/^/greg insns: /' -# orphan slots: pseudos whose ONLY appearance after reload is inside a (use ...) — count stack slots -python3 - "$d/$tag.i.greg" <<'PY' -import re, sys -t = open(sys.argv[1]).read() -# every "Register N ... in stack slot" style line differs by gcc version; instead pull the -# reload-era equivalences: (reg N) replaced by (mem (plus (reg 29) (const_int X))) -slots = {} -for m in re.finditer(r'\(insn [0-9]+ [0-9]+ [0-9]+[^\n]*\n?[^(]*\(use \(reg[^ ]* ([0-9]+)\)', t): - slots.setdefault(m.group(1), 0) -uses = re.findall(r'\(use \(reg[:A-Z]* ([0-9]+)[ )]', t) -print("USE-referenced pseudos in greg:", sorted(set(uses), key=int)) -PY +(cd $d && ../../../tools/bin/gcc-2.7.2-psx/cc1 -quiet -O2 -G0 -mips1 -mcpu=3000 -mgas -msoft-float -fgnu-linker -dr -ds -dj -df -dc -dS -dl -dg $tag.i -o $tag.s 2>$tag.err) +echo "== $tag: $(grep -m1 '\.frame' $d/$tag.s | sed 's/\t/ /g') ins=$(grep -cE '^\s+[a-z]' $d/$tag.s) (dumps in $d)" +grep -n "Spilling reg\|now on stack\|now in" $d/$tag.i.greg | head -8 +python3 tools/ghost_census.py "$d/$tag.i.lreg" diff --git a/tools/ghost_census.py b/tools/ghost_census.py new file mode 100644 index 000000000..d7f514bfc --- /dev/null +++ b/tools/ghost_census.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +"""ghost_census.py — the frame-residue oracle for gcc-2.7.2 dumps (P32 T4b hand pass, 2026-09-06; cookbook §501-M). + +A GHOST is a pseudo that still carries flow's `reg_n_refs > 0` but has NO occurrence left in the insn stream. +Reload gives every ghost with `reg_renumber < 0` and no REG_EQUIV an 8-byte `alter_reg` slot (reload1.c:658, +align -1 = BIGGEST_ALIGNMENT) — the "never-referenced frame slot" residual (§172, §501-M). Whether a ghost +actually gets the slot depends on WHEN it was minted: + + * minted by combine (the `newi2pat` split paths, combine.c:1887 / 1963) — BEFORE regclass, so regclass sees no + occurrence and gives it `ST_REGS or none`: unallocatable → slot. [these are the ones that matter] + * minted by local-alloc's `optimize_reg_copy_2` (`tmp = x; tmp = tmp op c; x = tmp;`) — AFTER regclass, so it + keeps `GR_REGS` and no conflicts: global allocates it → NO slot. (byte-measured, P14 in + .run/P32/t4c/func_80032A74/) + +So the census reads the `.lreg` dump (post-sched, pre-alloc: the register headers are flow's counts as updated by +combine/sched/update_equiv_regs) and reports every header whose pseudo does not occur in the insn stream, with its +class — `ST_REGS or none` ⇒ this ghost WILL take a slot; `GR_REGS …` ⇒ it will not. Use `vars=` on the `.frame` +line as the final arbiter (tools/cc1_dumps.sh prints both). + +usage: ghost_census.py <tag>.i.lreg [<tag>.i.lreg ...] +exit 0 always (a census, not a gate); prints one line per ghost, nothing when there are none. +""" +import re +import sys + + +def census(path): + text = open(path, errors="replace").read() + first_insn = text.find("\n(") + headers, body = (text[:first_insn], text[first_insn:]) if first_insn >= 0 else (text, "") + regs = re.findall(r"^Register (\d+) used (\d+) times[^\n]*?;\s*([^\n]*)$", headers, re.M) + occurrences = set(re.findall(r"\(reg[^ ]* (\d+)\)", body)) + out = [] + for regno, refs, tail in regs: + if regno not in occurrences: + cls = tail.strip().rstrip(".") + slot = "SLOT" if "ST_REGS" in cls or "NO_REGS" in cls else "allocatable, no slot" + out.append((int(regno), int(refs), cls, slot)) + return out + + +def main(argv): + if len(argv) < 2 or argv[1] in ("-h", "--help"): + print(__doc__) + return 0 + for path in argv[1:]: + rows = census(path) + tag = path if len(argv) > 2 else "" + for regno, refs, cls, slot in rows: + print(f" GHOST{(' ' + tag) if tag else ''}: pseudo {regno} refs={refs} [{cls}] -> {slot}") + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv))