mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-27 22:45:39 -04:00
a4bc49a23d
- 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.
91 lines
4.9 KiB
Python
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)")
|