Files
BFM-decomp/tools/wave_targets.py
T
Drew T 5fc5d64445 feat(phase-21): R2/R3 — the learning flywheel (class-grouped waves + distill)
- residual class travels with the draft: drafter stamps // @class / // @stuck; gate_stage logs
  the worker's gcc-quirk class to the backlog (not the coarse manifest class).
- wave_targets.py --class <C> / --list-classes: class-grouped re-attempt waves over the backlog's
  classified near-misses (REGALLOC/SCHEDULE/REMAT/STRUCT/IV/LOOPGUARD/LOOSE/PLUMBING).
- orchestrator prep: auto-picks a class-focused wave when a class accrues >= threshold near-misses,
  else a fresh pool harvest; clears the draft dir per wave.
- tools/workflows/distill.js: post-wave agent extracts NEW byte-verified gcc idioms -> cookbook §21
  (R16, conservative); drafters now read the LIVE cookbook so distilled idioms feed forward.
- runbook: the full prep->worker->gate->distill cycle (the Phase-18 close-rate loop, automated).
2026-06-21 13:56:59 -06:00

152 lines
6.7 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 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()
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()