Files
BFM-decomp/.run/s7_capture.py
T
Drew T a4bc49a23d feat(phase-30 S8): the x10-99 band closes 23/23; §137 makes REGALLOC-PERM arithmetic, not a permuter job
- S8-3 (23 fresh x10-99 families, 121-328 ins — the hardest band this session): draft 16/23 ->
  capture (1 PLUMBING / 6 DIFF) -> reconcile 1/1 -> redraft 6/6 => **23/23 (100%)**.
  Propagated 206 + 81 = 287 member-matches across 80+54 overlays. R22 clean-fleet 140/140.
  FLEET 95.97% fn / 93.4% instr / 87.5% distinct (77,404 uniq); dedup 1905/0; 0 NON_MATCHING.
- §136b CLOSES AT 15/15 — no function ledgered "genuine byte-DIFF" survived a redraft, all session.
- §137 (NEW, the session's most reusable result): REGALLOC-PERM — a clean 2-register swap — is a
  TWO-COMPILE ARITHMETIC PROBLEM. global.c:allocno_compare ranks by floor_log2(R)*R/L*1e4*size;
  read R and L out of `cc1 -dl -dg` for BOTH contenders AND their ranked neighbours to get the
  admissible priority WINDOW, then place a zero-byte `__asm__ __volatile__("" ::"r"(v))` so L lands
  inside it. func_801833F0: contenders ONE unit apart (1297 vs 1296), window (1228,1296), five
  placements probed, only L=219 -> pri 1232 worked. R and L are FORCED BY THE EMITTED CODE (L is
  recomputed post-sched1), which is exactly why source-reordering is a dead end for this class.
  Converts a class the permuter banked 0 from all session into a deterministic calculation.
  Companion: floor_log2 makes ref-count a STEP function (5/6/7 refs are worthless, you must reach 8)
  — func_8017EFA8 closed 30 register-name mismatches by taking a pseudo 4 refs -> 8 with a dead read.
- §136j — the failure MIX FLIPS WITH SIZE: <=120 ins fails ~70% on declarations; 121-328 ins fails
  86% on genuine codegen. Budget reconcile for the small band, redraft for the big one — and do NOT
  read 70% on a big-function wave as a broken pipeline; that is the expected shape.
- §137a — a gate verdict has a TIMESTAMP. Two "DIFF" ledger entries were STALE (draft rewritten 28
  min after the gate ran, never re-gated); both were already byte-perfect. Compare verdict time to
  draft mtime before redrafting. Plus two offline oracles an agent built: a FULL RELOCATION RESOLVE
  (catches wrong jal/%hi/%lo targets that match_one's mask hides) and a COLLATERAL CHECK (whole-TU
  objdump with/without splice). Together they discriminate all three causes of "match_one says MATCH
  but the overlay SHA differs" without running make.
- §136f addendum — the collider is often an already-banked SIBLING BELOW the splice; locate it by
  arithmetic (draft grows the file N lines, so TU line L reports at L+N).
- cookbook-index 380 -> 382 sections.
2026-08-03 16:25:06 -06:00

91 lines
4.9 KiB
Python

#!/usr/bin/env python3
"""S7 blocker capture — the any-overlay, any-draft-dir generalization of .run/uc_capture.py.
WHY THIS EXISTS (the S29 law, re-confirmed every wave since): agents CANNOT run the whole-binary
gate, so a `match_one`-MATCH draft that dies on `conflicting types for D_800A5E88` looks to them
like a codegen wall. Hand a reconcile agent the compiler's OWN error line and the lane runs 12/12;
hand it "the gate refused" and it re-derives the function from scratch and usually still fails.
Per target: splice the draft over its INCLUDE_ASM (TU derived from corpus.stubs, never asserted),
build THAT overlay, keep the error lines, REVERT the TU unconditionally.
.venv/bin/python .run/s7_capture.py .run/s7_gate4a_derived.json # the failed set
"""
import sys, os, re, json, subprocess
sys.path.insert(0, 'tools')
import cdecl, corpus
src = sys.argv[1] if len(sys.argv) > 1 else '.run/s7_gate4a_derived.json'
targets = json.load(open(src))['failed']
out = {}
for ov, fn in targets:
draft = f'.run/s8/{ov}/{fn}.c'
if not os.path.exists(draft):
out[fn] = {'ov': ov, 'blocker': '(no draft on disk)'}
print(f"{fn:<16} {ov:<14} (no draft)", flush=True)
continue
addr = int(fn.split('_')[1], 16)
st = corpus.stubs(ov)
if addr not in st:
out[fn] = {'ov': ov, 'blocker': '(not a live stub — banked since?)'}
print(f"{fn:<16} {ov:<14} (not a live stub)", flush=True)
continue
tu = st[addr].path
orig = open(tu).read()
body = cdecl.strip_provided_typedefs(open(draft).read(), cdecl.typedef_names(tu))
m = re.search(rf'INCLUDE_ASM\("[^"]*",\s*{fn}\);', orig)
if not m:
out[fn] = {'ov': ov, 'tu': tu, 'blocker': '(INCLUDE_ASM line not found in the derived TU)'}
print(f"{fn:<16} {ov:<14} (no INCLUDE_ASM in {tu})", flush=True)
continue
try:
open(tu, 'w').write(orig[:m.start()] + body + orig[m.end():])
b = subprocess.run(f"make --no-print-directory build BINARY={ov}",
shell=True, capture_output=True, text=True)
txt = b.stdout + b.stderr
# DERIVE the class from the INVARIANT (did the compile produce an object?), do NOT
# pattern-match error prose. Matching phrasings cost me twice: an exit-status branch that
# was unreachable, then a regex that missed `too many arguments to function` and filed a
# plain arity conflict as UNKNOWN. cc1's diagnostic vocabulary is open-ended; "make deleted
# the .o" is closed and true.
compile_failed = bool(re.search(r'^make: \*\*\* \[.*\] Error \d+', txt, re.M)
and 'Deleting file' in txt)
# Still collect the diagnostic lines — the reconcile agent needs them verbatim — but they
# no longer DECIDE the class. Keep the warning filter: `conflicting types` also appears
# benignly as a warning for builtins and external-decl mismatches.
err = [l for l in txt.splitlines()
if 'warning' not in l.lower()
and re.search(r'\berror\b|conflicting|undefined|previous declaration|redeclar'
r'|too (many|few) arguments|parse error|undeclared|incompatible', l)]
# CLASSIFY ON THE OUTPUT, NEVER ON THE EXIT STATUS. `make build` runs `check`, so a draft
# that COMPILES PERFECTLY and merely produces different bytes also exits non-zero — an
# `rc == 0 -> DIFF` branch is unreachable and silently mislabels every real byte-DIFF as
# "unknown". (Caught this session; the S28 law "a tool's exit status is never the oracle".)
sha_fail = re.search(r'^\s*\[FAIL\].*\n\s*got\s+(\w+)\n\s*want\s+(\w+)', txt, re.M)
if compile_failed:
verdict = (" || ".join(err[:5]) if err
else "compile FAILED but no diagnostic line matched — read the log by hand")
klass = 'PLUMBING'
elif sha_fail:
verdict = (f"compiles clean; SHA mismatch got {sha_fail.group(1)[:12]}… "
f"want {sha_fail.group(2)[:12]}… -> a GENUINE BYTE DIFF, not a declaration problem")
klass = 'DIFF'
elif b.returncode == 0:
verdict = "BUILD OK and byte-identical -> should have banked; re-gate"
klass = 'BANKED?'
else:
verdict = "(non-zero build, neither a compiler error nor a SHA mismatch matched)"
klass = 'UNKNOWN'
finally:
open(tu, 'w').write(orig) # revert ALWAYS — a killed process performs no undo
out[fn] = {'ov': ov, 'tu': tu, 'klass': klass, 'blocker': verdict}
print(f"{fn:<16} {ov:<14} [{klass}] {verdict[:130]}", flush=True)
json.dump(out, open('.run/s7_blockers.json', 'w'), indent=1)
n = {}
for v in out.values():
n[v.get('klass', 'n/a')] = n.get(v.get('klass', 'n/a'), 0) + 1
print("\nCLASSIFIED:", n)
print("-> .run/s7_blockers.json (feed each agent its OWN blocker line, verbatim)")