Files
Drew T 827295e241 tools+docs(phase-33.5): task 13.5 — the tools audit + the two dictionaries: tools/tool_census.py (two agreeing enumerations of 327 tool files; docstring/SETUP row/consumers/class derived from the tree; the authored half in config/tool_dictionary.tsv — phase · portability · the NEED each tool answers · what · adapts · verdict — with coverage asserted both ways) → docs/tool-index.md (need-keyed, KEEP-GEN, Reference-index row, wiki + how-to pointers), the kit's tools/MANIFEST.md regenerated (header states live 293 + superseded 28 = 321 rows), and the two verbatim corpora in-tree (Drew, confirmed S91): decomp-architect/corpus/tools/<phase>/ (302 copies + 28 superseded pointers + INDEX) and corpus/cookbook/ (the cookbook, its symptom index, the codegen map, a front page stating what transfers per compiler) — sha1-equal to their sources by tool_census --check in tools-health, regenerated by make kit-corpus; kit_lint exempts the corpus dirs (verbatim evidence) but syntax-checks them; G66 (consult the tool dictionary first) + G67 (translate an inherited idiom through its pass) + two memory seeds (34 at install); SETUP Step 6 installs docs/knowledge-corpus.md and checks the manifest against its own stated total; the ops-setup dictionary rows; the intake's Phase 7 cites G66/G67 and Phase 10 + Part C name the raw-cast → declared-symbol step; templates/layout-contract.md (the five-tool probe, a draft for the split). The review under Drew's criterion: 93 no-consumer tools (one Opus agent's draft, verified: 0 defects, every successor live, 0 live consumers, 0 collisions; four one-off verdicts overturned to STILL-NEEDED) → 34 retired by git mv to tools/sunset/ (28 superseded, 6 one-offs; README review table; SETUP rows moved; Archive-index group). Run 4 (fresh throwaway, the final kit): stopped on my Step-6 check (321 vs the live 293) → both sides derived → resumed → PASS 10/10, manifest 56 == 56, 4 commits, guardrails held (the one foreign path was the timeline regenerated by the detached tools-health). tools-health OK; doc_links --strict rc 0; audit_public OK over 6,842 paths; the purge probe PASSED (Phase 34's gate open). decision-log "P33.5 S91" + accelerators "P33.5 S91" banked; log + checkpoint (NEXT = task 14, xHigh, fresh session)
2026-09-07 22:09:15 -06:00

168 lines
6.8 KiB
Python
Raw Permalink 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
"""P31 T9 — the permuter/grinder FEEDER: warm-start drafts from banked exemplars + routed fuel.
WHY: the grinder's own audit (Phase-22) showed 92% of its CPU went where the permuter provably
cannot win; its targeting fix admits autopsy `bucket=="permuter"` and — deliberately — KEEPS
unclassified records ("unknown is not a reason to skip", R32). So a feeder that enqueues ONLY
pre-verified permuter-shaped work needs NO grinder patch: it appends backlog near-records
(source=warmstart) whose drafts are remapped PROVEN sibling bodies, and the daemon picks them up.
Modes:
--from-banked fn[,fn…] --binary B for each newly-banked exemplar: walk its h_seq family's
still-open members (the atlas group's mechanical remainder); for each, build the remapped
seed draft (family_remap.symbol_map — refuses on reloc-count mismatch) and STREAM-CLASSIFY
member-vs-seed with ZERO compiles (both are ground-truth bytes); enqueue only
bucket==permuter or LENGTH-DRIFT |Δ|<=2.
--lenmiss ingest .run/lenmiss/permuter.json (T8's |Δ|<=2 clean-drift route, drafts already
on disk) as warmstart backlog records.
The whole-binary byte-gate remains the arbiter; a wrong enqueue costs grinder CPU, never a byte.
"""
import argparse, json, os, sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import backlog
import corpus
import family_remap as FR
import family_cousins as FC
import aprop_autodraft as APD
import residual_class
import sig_image
def stream_dicts(words, vram=0x80100000):
"""words -> masked_diff-shaped [{word, mnem, reloc_kind, reloc_op}] for classify_streams
(zero compiles: both sides are ground-truth bytes)."""
rel = FR.reloc_indices(words)
out = []
pending_hi = set()
for k, w in enumerate(words):
ins = sig_image.make_insn(w, vram + 4 * k)
mnem = ins.getOpcodeName().lower() if ins.isValid() else "?"
kind = None
if k in rel:
op = w >> 26
if op in (2, 3):
kind = "R_MIPS_26"
elif op == 0x0F:
kind = "R_MIPS_HI16"
pending_hi.add((w >> 16) & 31)
else:
kind = "R_MIPS_LO16"
out.append({"word": w, "mnem": mnem, "reloc_kind": kind, "reloc_op": None})
return out
def classify_pair(seed_words, member_words):
"""-> verdict dict from residual_class over the two byte-truth streams."""
return residual_class.classify_streams(stream_dicts(seed_words), stream_dicts(member_words))
def enqueue(rec, dry):
if dry:
print(" would enqueue:", rec["binary"], rec["name"], rec.get("klass"))
return
backlog.append_record(rec)
def from_banked(fns, binary, outdir, dry):
fam = json.load(open(".run/family_hseq.json"))["families"]
by_hs = {}
sig = corpus.sig(binary)
n_enq = n_ref = 0
for fn in fns:
addr = int(fn.split("_")[1], 16)
row = sig.get(addr)
if row is None:
print(f" {fn}: not in {binary}'s sig — skipped")
continue
hs = row["h_seq"]
f = next((x for x in fam if x["h_seq"] == hs), None)
if f is None:
print(f" {fn}: no family for its h_seq — ×1 bank, nothing to seed")
continue
seed_words = FR.stream_words(binary, addr, row["nins"])
body_ref = FC.seed_body_ref(binary, addr)
if not body_ref or body_ref.get("kind") == "unresolved":
print(f" {fn}: seed body unresolved — skipped")
continue
body, seed_text, _kind = APD.seed_body(body_ref, fn)
if body is None:
print(f" {fn}: seed body unreadable — skipped")
continue
for m in f["members"]:
mb, ma = m[0], (m[1] if isinstance(m[1], int) else int(str(m[1]), 16))
if mb == binary and ma == addr:
continue
st = corpus.stubs(mb)
if ma not in st:
continue # already banked
member_words = FR.stream_words(mb, ma, f["nins"])
if member_words is None:
continue
v = classify_pair(seed_words, member_words)
klass = v.get("klass")
delta = len(member_words) - len(seed_words)
ok = (v.get("bucket") == "permuter") or (klass == "LENGTH-DRIFT" and abs(delta) <= 2)
if not ok:
n_ref += 1
continue
mfn = st[ma].symbol
try:
renames, sm_err = FR.symbol_map(addr, binary, mb, to_addr=ma)
except Exception as e:
renames, sm_err = None, repr(e)[:60]
if renames is None:
print(f" {mfn}: symbol_map refused ({sm_err})")
n_ref += 1
continue
draft, skip = APD.build_draft(body, fn, mfn, renames, seed_text, "")
if draft is None:
print(f" {mfn}: build_draft skipped ({skip})")
n_ref += 1
continue
os.makedirs(f"{outdir}/{mb}", exist_ok=True)
p = f"{outdir}/{mb}/{mfn}.c"
open(p, "w").write(draft)
enqueue({"name": mfn, "binary": mb, "status": "near", "source": "warmstart",
"klass": v.get("profile") or klass, "closeness": v.get("closeness"),
"best_draft": p, "where_stuck": f"warmstart from {binary}:{fn}"}, dry)
n_enq += 1
print(f"warmstart --from-banked: enqueued {n_enq}, refused {n_ref}")
def from_lenmiss(dry):
rows = json.load(open(".run/lenmiss/permuter.json"))
n = 0
for r in rows:
enqueue({"name": r["fn"], "binary": r["binary"], "status": "near",
"source": "warmstart", "klass": "LENGTH-DRIFT",
"closeness": abs(r.get("delta") or 0),
"best_draft": r["draft"],
"where_stuck": "lenmiss permuter-length route"}, dry)
n += 1
print(f"warmstart --lenmiss: enqueued {n}")
def main():
ap = argparse.ArgumentParser(description=__doc__.split("\n")[0])
ap.add_argument("--from-banked", default=None, help="comma-separated banked fn names")
ap.add_argument("--binary", default=None)
ap.add_argument("--lenmiss", action="store_true")
ap.add_argument("--outdir", default=".run/warmstart")
ap.add_argument("--dry-run", action="store_true")
a = ap.parse_args()
if a.lenmiss:
from_lenmiss(a.dry_run)
if a.from_banked:
if not a.binary:
sys.exit("--from-banked needs --binary")
from_banked([x.strip() for x in a.from_banked.split(",") if x.strip()],
a.binary, a.outdir, a.dry_run)
if not a.lenmiss and not a.from_banked:
sys.exit("nothing to do: pass --lenmiss and/or --from-banked")
if __name__ == "__main__":
main()