mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-26 13:33:34 -04:00
feat(phase-29 Task-12): structured failure telemetry + durable permuter-winner save
The permuter-autopsy prerequisite (Drew-directed). Captures the WHAT/WHERE of every
non-match so the classifier/autopsy (Task-13) can mine it, not just a scalar closeness:
- masked_diff.structured_diff(): the per-instruction masked residual [(idx,mine,tgt),...]
- match_one --json: emits {status,closeness,nins,residual} (refactored to share the primitive)
- backlog FIELDS += residual, passes_tried
- gate_stage.match_one_closeness returns + logs the residual on every near/fail (verified
end-to-end: a near-miss's structured residual now lands in backlog.jsonl)
- grinder: durably save the winning C to .run/permuter-winners/ BEFORE gate_stage banks
(this session's lesson — 3 permuter wins were lost to a mid-flight revert)
This commit is contained in:
+8
-1
@@ -37,7 +37,14 @@ DRAFTS = os.path.join(REPO, ".run/backlog_drafts")
|
||||
SRC_GLOB = os.path.join(REPO, "src/ov_SC01_077/ov_SC01_077*.c")
|
||||
STUB_RE = re.compile(r"INCLUDE_ASM\([^,]+,\s*(\w+)\)")
|
||||
FIELDS = ("ts", "addr", "name", "reach", "klass", "nins", "status",
|
||||
"closeness", "where_stuck", "best_draft", "binary", "source")
|
||||
"closeness", "where_stuck", "best_draft", "binary", "source",
|
||||
# Phase-29 Task-12: structured failure telemetry for the permuter-autopsy loop.
|
||||
# residual = masked_diff.structured_diff()[:cap] — [(idx, mine, target), ...]; the WHAT/WHERE
|
||||
# of the residual (reg-alloc swap / scheduled-order flip / extra-absent insn). passes_tried =
|
||||
# the permuter perm_* passes exercised (from permuter_weights) before the plateau — so the
|
||||
# autopsy can tell "the right lever was tried and failed" (wall) from "the lever was never
|
||||
# tried" (missing-transform → extend the ILS). Both optional; absent on legacy records.
|
||||
"residual", "passes_tried")
|
||||
|
||||
|
||||
def append_record(rec):
|
||||
|
||||
+19
-9
@@ -112,16 +112,25 @@ def match_one_closeness(fn, cpath, asm, binary=None):
|
||||
if hit is not None:
|
||||
asm = hit.asm_dir
|
||||
if not asm:
|
||||
return ("fail", None)
|
||||
return ("fail", None, None)
|
||||
try:
|
||||
r = sh([PY, "tools/match_one.py", fn, "--c", cpath, "--asm-subdir", asm], timeout=180)
|
||||
r = sh([PY, "tools/match_one.py", fn, "--c", cpath, "--asm-subdir", asm, "--json"], timeout=180)
|
||||
except subprocess.TimeoutExpired:
|
||||
return ("fail", None)
|
||||
first = (r.stdout.strip().splitlines() or ["?"])[0]
|
||||
if first.startswith("MATCH"):
|
||||
return ("match", 0)
|
||||
m = re.search(r"(\d+) mismatch", first)
|
||||
return ("near", int(m.group(1))) if m else ("fail", None)
|
||||
return ("fail", None, None)
|
||||
# Task-12: parse the JSON result line (structured residual for the autopsy); text-fallback on any issue.
|
||||
try:
|
||||
j = json.loads([l for l in r.stdout.strip().splitlines() if l.strip()][-1])
|
||||
if j.get("status") == "match":
|
||||
return ("match", 0, [])
|
||||
if j.get("status") == "near":
|
||||
return ("near", int(j["closeness"]), j.get("residual"))
|
||||
return ("fail", None, None)
|
||||
except Exception:
|
||||
first = (r.stdout.strip().splitlines() or ["?"])[0]
|
||||
if first.startswith("MATCH"):
|
||||
return ("match", 0, [])
|
||||
m = re.search(r"(\d+) mismatch", first)
|
||||
return ("near", int(m.group(1)), None) if m else ("fail", None, None)
|
||||
|
||||
|
||||
def run_gate(drafts, binary=OV, src=None, asm=None, out=None, good_sha=None,
|
||||
@@ -251,7 +260,7 @@ def _run_gate_locked(drafts, binary, src, asm, out, good_sha, propagate, source_
|
||||
for fn in [f for f in draft_fns if f not in verified]:
|
||||
cpath = os.path.join(REPO, d, fn + ".c")
|
||||
body = open(cpath).read() if os.path.exists(cpath) else ""
|
||||
kind, close = match_one_closeness(fn, cpath, asm, binary) if body else ("fail", None)
|
||||
kind, close, resid = match_one_closeness(fn, cpath, asm, binary) if body else ("fail", None, None)
|
||||
meta = _manifest_class(fn)
|
||||
cm = re.search(r"//\s*@class:\s*(.+)", body)
|
||||
sm = re.search(r"//\s*@stuck:\s*(.+)", body)
|
||||
@@ -272,6 +281,7 @@ def _run_gate_locked(drafts, binary, src, asm, out, good_sha, propagate, source_
|
||||
backlog.append_record({"addr": meta.get("addr"), "name": fn, "reach": meta.get("reach"),
|
||||
"klass": rclass or meta.get("class"), "nins": meta.get("nins"), "status": status,
|
||||
"closeness": close, "where_stuck": where, "best_draft": draft_path,
|
||||
"residual": (resid[:24] if resid else None), # Task-12 structured residual telemetry
|
||||
"binary": binary, "source": source_tag}) # binary: lets the grinder gate non-077 near-misses
|
||||
backlog.render()
|
||||
|
||||
|
||||
+8
-2
@@ -187,8 +187,14 @@ def main():
|
||||
continue
|
||||
win = p16_permute.run_permuter(pd, a.permute_secs, a.j)
|
||||
if win:
|
||||
open(os.path.join(REPO, DRAFTS, fn + ".c"), "w").write(
|
||||
p16_permute.winner_to_draft(open(win).read()))
|
||||
_wtext = p16_permute.winner_to_draft(open(win).read())
|
||||
open(os.path.join(REPO, DRAFTS, fn + ".c"), "w").write(_wtext)
|
||||
# Task-12: durably persist the winning C BEFORE gate_stage banks/propagates/commits, so a
|
||||
# mid-flight interrupt (kill or a revert of the uncommitted bank) never loses a hard-won
|
||||
# permuter crack (this session's lesson: 3 wins lost to exactly that). permuter-winners/ is
|
||||
# the recovery source — winner_to_draft(...) form, re-gateable any time.
|
||||
_wdir = os.path.join(REPO, ".run", "permuter-winners"); os.makedirs(_wdir, exist_ok=True)
|
||||
open(os.path.join(_wdir, fn + ".c"), "w").write(_wtext)
|
||||
won.append((fn, binary)); log(f"permuter WON {fn} @ {binary} (close was {r.get('closeness')})")
|
||||
except Exception as e:
|
||||
log(f"{fn}: {e}")
|
||||
|
||||
@@ -166,6 +166,28 @@ def insns_from_s(s_path):
|
||||
return insns
|
||||
|
||||
|
||||
def structured_diff(mine, tgt):
|
||||
"""The per-instruction relocation-masked mismatch list: [(idx, mine_str, tgt_str), ...] over
|
||||
two insns_from_* streams. Each entry is a mismatching instruction position; mine_str/tgt_str are
|
||||
'<word_hex> <mnemonic>' (or '--' past the end of the shorter stream). len() == the masked mismatch
|
||||
count (the `closeness`). This is the STRUCTURED RESIDUAL telemetry the permuter-autopsy classifier
|
||||
reads (Phase-29 Task-12): idx localizes the residual, the word+mnem pair says what codegen differs
|
||||
(a reg-alloc swap, a scheduled-order flip, an extra/absent instruction). Shared by match_one +
|
||||
gate_stage's near-record so the same residual an agent reads is the one the autopsy mines."""
|
||||
n = max(len(mine), len(tgt))
|
||||
diffs = []
|
||||
for i in range(n):
|
||||
mw = mine[i]['word'] if i < len(mine) else None
|
||||
mask = mask_for(mine[i]['word'], mine[i]['reloc_kind']) if i < len(mine) else 0xFFFFFFFF
|
||||
me = (mw & mask) if mw is not None else None
|
||||
tg = (tgt[i]['word'] & mask) if i < len(tgt) else None
|
||||
if me != tg:
|
||||
diffs.append((i,
|
||||
('%08x %s' % (mine[i]['word'], mine[i]['mnem'])) if i < len(mine) else '--',
|
||||
('%08x %s' % (tgt[i]['word'], tgt[i]['mnem'])) if i < len(tgt) else '--'))
|
||||
return diffs
|
||||
|
||||
|
||||
def diff_object_object(cand, tgt):
|
||||
"""masked mismatch count between two objdump'd objects (permuter scorer). Mask driven by the
|
||||
TARGET's relocs; at masked reloc/jal positions also require reloc-operand (symbol+addend) equality.
|
||||
|
||||
+14
-12
@@ -18,6 +18,7 @@ longer under-counted (func_80132784 now reads its true 400 ins, not 384).
|
||||
"""
|
||||
import subprocess, re, sys, os, argparse
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
import json
|
||||
import masked_diff
|
||||
|
||||
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
@@ -31,6 +32,9 @@ ap.add_argument('--work', default=None,
|
||||
ap.add_argument('--o0', action='store_true',
|
||||
help='compile at -O0 (for the _o0 split subsegments: ov_SC01_077_o0.c, whale _o0b — '
|
||||
'their target bytes are -O0; an -O2 compile can never match them, Makefile:445)')
|
||||
ap.add_argument('--json', action='store_true',
|
||||
help='emit one JSON result line {status,closeness,nins,residual} (Task-12 structured '
|
||||
'residual telemetry for the permuter-autopsy). Still exits 0 on MATCH, 1 otherwise.')
|
||||
a = ap.parse_args()
|
||||
|
||||
# PRIVATE SCRATCH BY DEFAULT (Phase-28 T5). This tool's own docstring promises "Fully isolated (own
|
||||
@@ -91,21 +95,19 @@ tgt = masked_diff.insns_from_s('%s/%s.s' % (a.asm_subdir, a.fn))
|
||||
if not mine:
|
||||
print('FAIL: my object has no function', a.fn, '(compile produced nothing?)'); sys.exit(1)
|
||||
|
||||
n = max(len(mine), len(tgt))
|
||||
diffs = []
|
||||
for i in range(n):
|
||||
mw = mine[i]['word'] if i < len(mine) else None
|
||||
mask = masked_diff.mask_for(mine[i]['word'], mine[i]['reloc_kind']) if i < len(mine) else 0xFFFFFFFF
|
||||
me = (mw & mask) if mw is not None else None
|
||||
tg = (tgt[i]['word'] & mask) if i < len(tgt) else None
|
||||
if me != tg:
|
||||
diffs.append((i,
|
||||
('%08x %s' % (mine[i]['word'], mine[i]['mnem'])) if i < len(mine) else '--',
|
||||
('%08x %s' % (tgt[i]['word'], tgt[i]['mnem'])) if i < len(tgt) else '--'))
|
||||
# structured residual (shared with gate_stage's near-record + the Task-12 autopsy telemetry)
|
||||
diffs = masked_diff.structured_diff(mine, tgt)
|
||||
|
||||
if not diffs and len(mine) == len(tgt):
|
||||
print('MATCH (%d ins) %s' % (len(mine), a.fn))
|
||||
if a.json:
|
||||
print(json.dumps({"status": "match", "closeness": 0, "nins": len(mine), "residual": []}))
|
||||
else:
|
||||
print('MATCH (%d ins) %s' % (len(mine), a.fn))
|
||||
sys.exit(0)
|
||||
if a.json:
|
||||
print(json.dumps({"status": "near" if diffs else "fail", "closeness": len(diffs),
|
||||
"nins": len(mine), "residual": [[i, me, tg] for i, me, tg in diffs[:48]]}))
|
||||
sys.exit(1)
|
||||
print('DIFF %s mine=%d ins, target=%d ins, %d mismatched' % (a.fn, len(mine), len(tgt), len(diffs)))
|
||||
print(' idx | MINE | TARGET')
|
||||
for i, me, tg in diffs[:40]:
|
||||
|
||||
Reference in New Issue
Block a user