Files
BFM-decomp/tools/wave_targets.py
T
Drew T 40fa76284e feat(phase-21): wave_targets skips close=0 plumbing-blocked near-misses from the pool
- self-MATCH-but-gate-rejected fns can never bank via re-drafting (the drafter reproduces the
  byte-correct body the gate rejects again) -> exclude from POOL selection so agents draft FRESH
  targets; they stay in the backlog as recovery-tooling/hand-finish fuel
- CURRENT_PHASE: sustained-loop state + pool-mode override policy (compaction-recovery anchor)
2026-06-22 00:59:06 -06:00

162 lines
7.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""wave_targets.py — select a worker-wave target batch from the fuel manifest.
Emits a JSON array of {name, addr, nins, class, asm, ghidra_c} for tools/workflows/worker_wave.js
(passed as args.targets). Filters to still-OPEN (INCLUDE_ASM) cached targets in the chosen pool,
ranked by leverage (reach*nins), skipping known walls already logged 'failed'/'stub' in the backlog.
Pools (ROI rotation): tractable (reach-134 WAVE/PINS/STRUCT <=150 ins, main region) | giants |
o0 | capped | any-reach134.
Usage: tools/wave_targets.py --pool tractable --n 24 [--region main|a|any] [--out -]
"""
import argparse, glob, json, os, re, sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import backlog
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
STUB_RE = re.compile(r"INCLUDE_ASM\([^,]+,\s*(\w+)\)")
ASM_SUBDIR = "asm/ov_SC01_077/nonmatchings/ov_SC01_077"
# Canonical gcc-quirk residual classes (the cookbook §17–20 taxonomy). A wave studies ONE of these
# at a time (the Phase-18 learning model): bank what the class's idiom reaches, distill the quirk,
# feed it forward. Keyword-matched from the worker's self-reported klass + its where_stuck note.
CLASS_KEYWORDS = [
("REGALLOC", ("regalloc", "register", "$s", " pin", "reg-order", "reg order", "swap")),
("SCHEDULE", ("schedule", "sched", "store-vs-load", "store vs load", "delay slot", "reorder", "operand-order", "operand order")),
("REMAT", ("hoist", "remat", "rematerial", "array-decay", "array decay")),
("STRUCT", ("struct", "field", " type", "layout", "%lo", "array-of-struct", "array of struct", "union")),
("IV", ("iv-combine", "iv combine", "induction", "biv", "halfword rmw")),
("LOOPGUARD", ("loop-guard", "loop guard", "get_condition", "strength-reduc")),
("LOOSE", ("loose-typing", "loose typing", "arity", "conflicting types", "narrow-param")),
("PLUMBING", ("plumbing", "declaration", "extern", "call-site cast", "callee", "no-proto", "sibling decl")),
]
def canon_class(rec):
"""Normalize a backlog record (klass + where_stuck) to a canonical residual class."""
blob = ((rec.get("klass") or "") + " " + (rec.get("where_stuck") or "")).lower()
for name, kws in CLASS_KEYWORDS:
if any(k in blob for k in kws):
return name
return "OTHER"
def live_stubs():
s = set()
for p in glob.glob(os.path.join(REPO, "src/ov_SC01_077/ov_SC01_077*.c")):
s |= set(STUB_RE.findall(open(p).read()))
return s
def backlog_walls():
"""names already logged as 'failed'/'stub' — skip (don't waste agents re-drafting known walls)."""
walls = set()
p = os.path.join(REPO, ".run/backlog.jsonl")
if os.path.exists(p):
for line in open(p):
line = line.strip()
if not line:
continue
r = json.loads(line)
if r.get("status") in ("failed", "stub") and r.get("name"):
walls.add(r["name"])
return walls
def plumbing_blocked():
"""near-misses with closeness==0 = match_one MATCH but the whole-binary gate REJECTED (DEF-side /
TU loose-typing plumbing). Re-drafting just reproduces the same byte-correct body the gate rejects
again -> it can NEVER bank via a worker wave (Phase-21 finding). Skip in POOL selection so agents
draft FRESH targets instead; these stay in the backlog as recovery-tooling / hand-finish fuel
(an improved sig_unify / recovery gate can still bank them out-of-band, as the arity-extend fix did)."""
return {r["name"] for r in backlog.load_best()
if r.get("status") == "near" and r.get("closeness") == 0 and r.get("name")}
def emit(batch, out):
s = json.dumps(batch, indent=0)
if out == "-":
sys.stdout.write(s + "\n")
else:
open(os.path.join(REPO, out), "w").write(s)
print(f"{len(batch)} targets -> {out}", file=sys.stderr)
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--pool", default="tractable",
choices=["tractable", "giants", "o0", "capped", "any-reach134"])
ap.add_argument("--class", dest="rclass", default=None,
help="CLASS-GROUPED wave: select backlog near-misses of this residual class "
"(REGALLOC/SCHEDULE/REMAT/STRUCT/IV/LOOPGUARD/LOOSE/PLUMBING/OTHER) to re-attempt")
ap.add_argument("--list-classes", action="store_true",
help="print the backlog residual-class histogram (ranked by leverage) and exit")
ap.add_argument("--n", type=int, default=24)
ap.add_argument("--region", default="main", choices=["main", "a", "any"])
ap.add_argument("--max-nins", type=int, default=150)
ap.add_argument("--include-walls", action="store_true", help="don't skip backlog failed/stub")
ap.add_argument("--out", default="-")
a = ap.parse_args()
# --- CLASS-GROUPED modes (Phase-21 flywheel): operate on the backlog's classified near-misses ---
if a.list_classes:
import collections
recs = [r for r in backlog.load_best() if r.get("status") == "near"]
cnt = collections.Counter(canon_class(r) for r in recs)
lev = collections.Counter()
for r in recs:
lev[canon_class(r)] += (r.get("reach") or 1)
for cls, _ in lev.most_common():
print(f"{cls:10} n={cnt[cls]:3} reach-weight={lev[cls]}")
return
if a.rclass:
rc = a.rclass.upper()
recs = [r for r in backlog.load_best()
if r.get("status") == "near" and canon_class(r) == rc and r.get("name")]
recs.sort(key=lambda r: (-(r.get("reach") or 1), r.get("closeness") if isinstance(r.get("closeness"), int) else 999))
batch = [{"name": r["name"], "addr": r.get("addr") or ("0x" + r["name"][5:].lower()),
"nins": r.get("nins"), "class": rc, "asm": f"{ASM_SUBDIR}/{r['name']}.s",
"ghidra_c": f".run/ghidra_c/{r['name']}.c", "prior_stuck": r.get("where_stuck"),
"prior_closeness": r.get("closeness")}
for r in recs[:a.n]]
emit(batch, a.out)
return
m = json.load(open(os.path.join(REPO, ".run/fuel_manifest.json")))
stubs = live_stubs()
walls = set() if a.include_walls else (backlog_walls() | plumbing_blocked())
def ok(t):
if t["name"] not in stubs or not t["cached"]:
return False
if t["name"] in walls:
return False
if a.region != "any" and t["region"] != a.region:
return False
if a.pool == "tractable":
return t["reach_134"] and t["class"] in ("WAVE", "PINS", "STRUCT") and (t["nins"] or 999) <= a.max_nins
if a.pool == "giants":
return t["class"] == "GIANT"
if a.pool == "o0":
return t["class"] == "O0"
if a.pool == "any-reach134":
return t["reach_134"]
return False
pool = [t for t in m["targets"] if ok(t)]
if a.pool == "capped": # the matched-but-local recovery set (not stubs)
pool = [{"name": n, "addr": "0x" + n[5:].lower(), "nins": None, "class": "CAPPED",
"reach": 134, "leverage": 0} for n in m.get("capped_recovery", [])]
pool.sort(key=lambda t: t.get("leverage") or 0, reverse=True)
pool = pool[:a.n]
batch = [{"name": t["name"], "addr": t["addr"], "nins": t["nins"], "class": t["class"],
"asm": f"{ASM_SUBDIR}/{t['name']}.s", "ghidra_c": f".run/ghidra_c/{t['name']}.c"}
for t in pool]
emit(batch, a.out)
if __name__ == "__main__":
main()