mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-26 13:33:34 -04:00
197470ff5f
gate_stage: a binary on .run/baseline_red.txt (+ fleet_red.txt) refuses its drafts with class
BASELINE-RED before any build — a RED binary rejects every draft gated against it, and 174 of the
resolver's 245 doubly-verified drafts were refused exactly that way (negative-controlled both
directions: RED refused without a build, GREEN still reaches the ladder).
jtbl_pads_fix: (1) OBJ_ERR required the make bracket to start with build/src but make prints
[Makefile:687: build/src/...] — find_drift returned None over failing builds ('no pad-count drift',
six binaries); (2) only the 'consumed N but M' phrasing was handled — the 'more rodata .align
directives than pad specs' direction (a NEW table from a banked switch) now searches declared+1/+2;
(3) write_pads split its line on ':' but ':=' contains a colon, appending a second ': JTBL_PADS'
per write, and wrote via a raw truncating open() — the fifth un-converted mk write site; now
path-split-once + mk_write.
mk_write: refuses a parse-poisoned registry line (R43) — one such line kills EVERY build of EVERY
binary at make parse time, strictly worse than the wipes the line-count floor guards.
rtu_shadow: a 0-bank wave has no commit — gatedness now comes from the ox ledger; baseline-RED
binaries are excluded from the prediction metrics with their count reported (R41).
integration_resolver: stub_removals() nets out carve moves (72 gross -> 63 real in the first pass).
226 lines
10 KiB
Python
226 lines
10 KiB
Python
#!/usr/bin/env python3
|
|
"""rtu_shadow.py — shadow-mode rtu_match beside the wave gate, to decide whether to INVERT the gate.
|
|
|
|
WHY (P31 S61, frontier-analysis-s60 §3). The gate ladder spends ~3 whole-binary builds per FAILING
|
|
draft (stage 0 raw, stage 1 after transforms, stage 2 after sig_unify) and ~1 per banking draft, so
|
|
at 1-6% conversion almost every build is spent confirming a failure. `rtu_match` compiles the real
|
|
split TU with the draft spliced in — no build tree, no locks, ~2 s, parallel-safe — and could be the
|
|
gate's stage 0, so that whole-binary builds are spent only on rtu-MATCH drafts (builds proportional
|
|
to BANKS, not drafts). That is a ~20x reduction at current conversion — IF rtu-MATCH predicts
|
|
banking and rtu-non-MATCH does not throw away banks. This tool measures exactly that, with zero
|
|
behavioural change to the gate (G3/P9 untouched: the SHA gate stays the sole arbiter).
|
|
|
|
PROSPECTIVE (run BEFORE the wave is gated — no peeking; and NOT while a gate is in flight, because
|
|
a gate transiently splices bodies into the very src/ TUs rtu reads):
|
|
tools/rtu_shadow.py --wave fa --wave fb -j 32
|
|
-> .run/rtu_shadow/<wave>.jsonl one row per draft: rtu verdict, ndiff, reloc pre-filter status
|
|
JOIN (run AFTER the gate committed):
|
|
tools/rtu_shadow.py --join fa --join fb
|
|
-> confusion matrix rtu-verdict x outcome, the false-negative rate (banks rtu would have refused),
|
|
and the build accounting (current ladder vs inverted), every figure with its denominator (R41).
|
|
|
|
Drafts are mapped to their binary through the SHARD'S targets file, never by bare function name — a
|
|
wave drafts siblings under the same name in several binaries (54 of wave fa's 470 cards).
|
|
"""
|
|
import argparse
|
|
import collections
|
|
import glob
|
|
import json
|
|
import os
|
|
import re
|
|
import sys
|
|
import time
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
import corpus # noqa: E402
|
|
import integration_resolver as ir # noqa: E402
|
|
|
|
REPO = ir.REPO
|
|
OUT = ".run/rtu_shadow"
|
|
|
|
|
|
def wave_drafts(w):
|
|
"""[(shard, card, draft_path)] with the card taken from that shard's own targets file."""
|
|
rows, unmapped, ambiguous = [], 0, 0
|
|
for tp in sorted(glob.glob(f".run/wave_{w}_targets.*.json"),
|
|
key=lambda p: int(p.rsplit(".", 2)[1])):
|
|
i = int(tp.rsplit(".", 2)[1])
|
|
try:
|
|
targets = json.load(open(tp))
|
|
except Exception:
|
|
continue
|
|
by_name = collections.defaultdict(list)
|
|
for c in targets:
|
|
by_name[c.get("name") or c.get("fn")].append(c)
|
|
for d in glob.glob(f".run/wave_{w}/shard{i}/*.c"):
|
|
fn = os.path.basename(d)[:-2]
|
|
cs = by_name.get(fn) or []
|
|
if not cs:
|
|
unmapped += 1
|
|
continue
|
|
if len(cs) > 1:
|
|
ambiguous += 1
|
|
continue
|
|
rows.append((i, cs[0], d))
|
|
return rows, unmapped, ambiguous
|
|
|
|
|
|
def reloc_status(w):
|
|
out = {}
|
|
if not os.path.exists(ir.REJECTS):
|
|
return out
|
|
for line in open(ir.REJECTS, errors="replace"):
|
|
try:
|
|
r = json.loads(line)
|
|
except Exception:
|
|
continue
|
|
if r.get("wave") == w and r.get("fn") and r.get("binary"):
|
|
out[(r["binary"], r["fn"])] = (r.get("status"), r.get("shape"))
|
|
return out
|
|
|
|
|
|
def shadow(w, jobs):
|
|
rows, unmapped, ambiguous = wave_drafts(w)
|
|
rl = reloc_status(w)
|
|
head = ir.sh(["git", "rev-parse", "--short", "HEAD"], 30).stdout.strip()
|
|
ir.say(f"wave {w}: {len(rows)} drafts mapped via shard targets (unmapped {unmapped}, ambiguous-name {ambiguous}); "
|
|
f"reloc pre-filter rows for this wave: {len(rl)}; HEAD {head}")
|
|
|
|
def one(item):
|
|
i, c, d = item
|
|
b, fn = c["binary"], c.get("name") or c.get("fn")
|
|
row = {"wave": w, "shard": i, "binary": b, "fn": fn, "draft": d, "draft_sha": ir.sha(d),
|
|
"nins_card": c.get("nins"), "reloc": rl.get((b, fn), ("AGREE?", None))[0],
|
|
"reloc_shape": rl.get((b, fn), (None, None))[1], "head": head,
|
|
"ts": time.strftime("%Y-%m-%d %H:%M:%S")}
|
|
rb = ir.real_binary(b)
|
|
if rb is None or rb == "main":
|
|
row.update(rtu="UNSUPPORTED", ndiff=None, nins=None, note="main / unknown binary")
|
|
return row
|
|
idx, err = ir.stub_index(rb)
|
|
if err:
|
|
row.update(rtu="HARNESS-CORPUS", ndiff=None, nins=None, note=err)
|
|
return row
|
|
stub = idx.get(fn)
|
|
if stub is None:
|
|
row.update(rtu="NOT-A-STUB", ndiff=None, nins=None, note="already banked")
|
|
return row
|
|
work = os.path.join(OUT, "work", w, f"{b}__{fn}")
|
|
v = ir.rtu(rb, fn, stub, d, work, corpus.is_o0(stub.path))
|
|
row.update(rtu=v["verdict"], ndiff=v.get("ndiff"), nins=v.get("nins"), note=v.get("note", "")[:160])
|
|
return row
|
|
|
|
t0 = time.time()
|
|
with ThreadPoolExecutor(max_workers=jobs) as ex:
|
|
res = list(ex.map(one, rows))
|
|
os.makedirs(OUT, exist_ok=True)
|
|
with open(os.path.join(OUT, f"{w}.jsonl"), "w") as f:
|
|
for r in res:
|
|
f.write(json.dumps(r) + "\n")
|
|
c = collections.Counter(r["rtu"] for r in res)
|
|
ir.say(f"wave {w}: shadow verdicts in {time.time() - t0:.0f}s: " + " ".join(f"{k}={v}" for k, v in sorted(c.items())))
|
|
cg = collections.Counter(r["rtu"] for r in res if r["reloc"] == "AGREE?")
|
|
ir.say(f"wave {w}: among drafts the pre-filter will pass (no reject row): " + " ".join(f"{k}={v}" for k, v in sorted(cg.items())))
|
|
return res
|
|
|
|
|
|
def wave_commits(w):
|
|
out = []
|
|
for line in ir.sh(["git", "log", "--format=%h %s", "-600"], 60).stdout.splitlines():
|
|
h, _, s = line.partition(" ")
|
|
if re.search(r"ox wave %s(\b|$)" % re.escape(w), s):
|
|
out.append(h)
|
|
return out
|
|
|
|
|
|
def join(w):
|
|
p = os.path.join(OUT, f"{w}.jsonl")
|
|
if not os.path.exists(p):
|
|
ir.say(f"wave {w}: no shadow ledger at {p}")
|
|
return
|
|
rows = [json.loads(l) for l in open(p) if l.strip()]
|
|
# the ox ledger is the authority on whether the wave was GATED: a 0-bank wave has NO commit
|
|
# (ox_campaign.commit() returns None on a clean tree), so "no commit" must not read as "not gated"
|
|
led = [json.loads(l) for l in open(".run/ox_campaign_ledger.jsonl", errors="replace") if l.strip()]
|
|
gated_rows = [r for r in led if r.get("wave") == w and r.get("gated") is not None]
|
|
if not gated_rows:
|
|
ir.say(f"wave {w}: no gate row in the ox ledger — not gated yet")
|
|
return
|
|
commits = [r["commit"] for r in gated_rows if r.get("commit")] or wave_commits(w)
|
|
banked = set()
|
|
pat = re.compile(r'^-.*INCLUDE_ASM\("(asm/[^"]+)",\s*(\w+)\)')
|
|
for h in commits:
|
|
for line in ir.sh(["git", "show", "--unified=0", h, "--", "src/"], 120).stdout.splitlines():
|
|
m = pat.match(line)
|
|
if m:
|
|
parts = m.group(1).split("/")
|
|
banked.add((parts[1] if parts[1] != "nonmatchings" else "main", m.group(2)))
|
|
# RED binaries make every verdict about the BINARY, not the draft (S61 fleet audit): report them
|
|
# separately and exclude them from the prediction metrics (R41: say what the denominator is).
|
|
red = set()
|
|
try:
|
|
red = {l.strip() for l in open(".run/baseline_red.txt") if l.strip()}
|
|
except OSError:
|
|
pass
|
|
n_red = sum(1 for r in rows if r["binary"] in red)
|
|
if n_red:
|
|
ir.say(f"wave {w}: {n_red} of {len(rows)} shadowed drafts sit in {len(red & {r['binary'] for r in rows})} "
|
|
f"baseline-RED binaries — excluded from the prediction metrics below")
|
|
rows = [r for r in rows if r["binary"] not in red]
|
|
outcome = collections.Counter()
|
|
table = collections.Counter()
|
|
for r in rows:
|
|
k = (r["binary"], r["fn"])
|
|
if r["rtu"] == "NOT-A-STUB":
|
|
o = "ALREADY-BANKED"
|
|
elif k in banked:
|
|
o = "BANKED"
|
|
elif r["reloc"] != "AGREE?":
|
|
o = "PREFILTER-DROPPED"
|
|
else:
|
|
idx, err = ir.stub_index(r["binary"])
|
|
o = "GATE-REJECTED" if (idx and r["fn"] in idx) else "BANKED-ELSEWHERE"
|
|
outcome[o] += 1
|
|
table[(r["rtu"], o)] += 1
|
|
ir.say(f"wave {w}: gate commit(s) {commits}; outcomes: " + " ".join(f"{k}={v}" for k, v in sorted(outcome.items())))
|
|
print(" %-16s %-8s %-14s %-18s %-16s %s" % ("rtu verdict", "BANKED", "GATE-REJECTED", "PREFILTER-DROPPED", "BANKED-ELSEWHERE", "ALREADY"))
|
|
for v in sorted({r["rtu"] for r in rows}):
|
|
print(" %-16s %-8d %-14d %-18d %-16d %d" % (v, table[(v, "BANKED")], table[(v, "GATE-REJECTED")],
|
|
table[(v, "PREFILTER-DROPPED")], table[(v, "BANKED-ELSEWHERE")],
|
|
table[(v, "ALREADY-BANKED")]))
|
|
gated = [r for r in rows if r["reloc"] == "AGREE?" and r["rtu"] not in ("NOT-A-STUB", "UNSUPPORTED", "HARNESS-CORPUS")]
|
|
g_b = [r for r in gated if (r["binary"], r["fn"]) in banked]
|
|
g_r = [r for r in gated if (r["binary"], r["fn"]) not in banked]
|
|
m_b = sum(1 for r in g_b if r["rtu"] == "MATCH")
|
|
m_r = sum(1 for r in g_r if r["rtu"] == "MATCH")
|
|
n_m = m_b + m_r
|
|
print(f" gated drafts {len(gated)}: banked {len(g_b)}, rejected {len(g_r)}")
|
|
print(f" P(bank | rtu MATCH) = {m_b}/{n_m}" + (f" = {100.0 * m_b / n_m:.1f}%" if n_m else ""))
|
|
print(f" false-negative (banks rtu would refuse) = {len(g_b) - m_b}/{len(g_b)}"
|
|
+ (f" = {100.0 * (len(g_b) - m_b) / len(g_b):.1f}%" if g_b else ""))
|
|
cur = len(g_b) * 1 + len(g_r) * 3
|
|
inv = m_b * 1 + m_r * 3
|
|
print(f" whole-binary builds, current ladder ~ {cur} (1/bank + 3/reject) vs inverted ~ {inv} (rtu-MATCH only)"
|
|
+ (f" -> {cur / max(1, inv):.1f}x fewer" if inv else ""))
|
|
dropped_match = sum(1 for r in rows if r["reloc"] != "AGREE?" and r["rtu"] == "MATCH")
|
|
print(f" pre-filter-dropped drafts that rtu-MATCH the real TU (never gated): {dropped_match}")
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
ap.add_argument("--wave", action="append", default=[], help="prospective shadow over this wave's drafts")
|
|
ap.add_argument("--join", action="append", default=[], help="join a shadowed wave with its gate outcome")
|
|
ap.add_argument("-j", "--jobs", type=int, default=32)
|
|
a = ap.parse_args()
|
|
os.chdir(REPO)
|
|
for w in a.wave:
|
|
shadow(w, a.jobs)
|
|
for w in a.join:
|
|
join(w)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|