mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-29 07:10:32 -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")
|
SRC_GLOB = os.path.join(REPO, "src/ov_SC01_077/ov_SC01_077*.c")
|
||||||
STUB_RE = re.compile(r"INCLUDE_ASM\([^,]+,\s*(\w+)\)")
|
STUB_RE = re.compile(r"INCLUDE_ASM\([^,]+,\s*(\w+)\)")
|
||||||
FIELDS = ("ts", "addr", "name", "reach", "klass", "nins", "status",
|
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):
|
def append_record(rec):
|
||||||
|
|||||||
+19
-9
@@ -112,16 +112,25 @@ def match_one_closeness(fn, cpath, asm, binary=None):
|
|||||||
if hit is not None:
|
if hit is not None:
|
||||||
asm = hit.asm_dir
|
asm = hit.asm_dir
|
||||||
if not asm:
|
if not asm:
|
||||||
return ("fail", None)
|
return ("fail", None, None)
|
||||||
try:
|
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:
|
except subprocess.TimeoutExpired:
|
||||||
return ("fail", None)
|
return ("fail", None, None)
|
||||||
first = (r.stdout.strip().splitlines() or ["?"])[0]
|
# Task-12: parse the JSON result line (structured residual for the autopsy); text-fallback on any issue.
|
||||||
if first.startswith("MATCH"):
|
try:
|
||||||
return ("match", 0)
|
j = json.loads([l for l in r.stdout.strip().splitlines() if l.strip()][-1])
|
||||||
m = re.search(r"(\d+) mismatch", first)
|
if j.get("status") == "match":
|
||||||
return ("near", int(m.group(1))) if m else ("fail", None)
|
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,
|
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]:
|
for fn in [f for f in draft_fns if f not in verified]:
|
||||||
cpath = os.path.join(REPO, d, fn + ".c")
|
cpath = os.path.join(REPO, d, fn + ".c")
|
||||||
body = open(cpath).read() if os.path.exists(cpath) else ""
|
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)
|
meta = _manifest_class(fn)
|
||||||
cm = re.search(r"//\s*@class:\s*(.+)", body)
|
cm = re.search(r"//\s*@class:\s*(.+)", body)
|
||||||
sm = re.search(r"//\s*@stuck:\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"),
|
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,
|
"klass": rclass or meta.get("class"), "nins": meta.get("nins"), "status": status,
|
||||||
"closeness": close, "where_stuck": where, "best_draft": draft_path,
|
"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
|
"binary": binary, "source": source_tag}) # binary: lets the grinder gate non-077 near-misses
|
||||||
backlog.render()
|
backlog.render()
|
||||||
|
|
||||||
|
|||||||
+8
-2
@@ -187,8 +187,14 @@ def main():
|
|||||||
continue
|
continue
|
||||||
win = p16_permute.run_permuter(pd, a.permute_secs, a.j)
|
win = p16_permute.run_permuter(pd, a.permute_secs, a.j)
|
||||||
if win:
|
if win:
|
||||||
open(os.path.join(REPO, DRAFTS, fn + ".c"), "w").write(
|
_wtext = p16_permute.winner_to_draft(open(win).read())
|
||||||
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')})")
|
won.append((fn, binary)); log(f"permuter WON {fn} @ {binary} (close was {r.get('closeness')})")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log(f"{fn}: {e}")
|
log(f"{fn}: {e}")
|
||||||
|
|||||||
@@ -166,6 +166,28 @@ def insns_from_s(s_path):
|
|||||||
return insns
|
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):
|
def diff_object_object(cand, tgt):
|
||||||
"""masked mismatch count between two objdump'd objects (permuter scorer). Mask driven by the
|
"""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.
|
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
|
import subprocess, re, sys, os, argparse
|
||||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
import json
|
||||||
import masked_diff
|
import masked_diff
|
||||||
|
|
||||||
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
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',
|
ap.add_argument('--o0', action='store_true',
|
||||||
help='compile at -O0 (for the _o0 split subsegments: ov_SC01_077_o0.c, whale _o0b — '
|
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)')
|
'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()
|
a = ap.parse_args()
|
||||||
|
|
||||||
# PRIVATE SCRATCH BY DEFAULT (Phase-28 T5). This tool's own docstring promises "Fully isolated (own
|
# 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:
|
if not mine:
|
||||||
print('FAIL: my object has no function', a.fn, '(compile produced nothing?)'); sys.exit(1)
|
print('FAIL: my object has no function', a.fn, '(compile produced nothing?)'); sys.exit(1)
|
||||||
|
|
||||||
n = max(len(mine), len(tgt))
|
# structured residual (shared with gate_stage's near-record + the Task-12 autopsy telemetry)
|
||||||
diffs = []
|
diffs = masked_diff.structured_diff(mine, tgt)
|
||||||
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 '--'))
|
|
||||||
|
|
||||||
if not diffs and len(mine) == len(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)
|
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('DIFF %s mine=%d ins, target=%d ins, %d mismatched' % (a.fn, len(mine), len(tgt), len(diffs)))
|
||||||
print(' idx | MINE | TARGET')
|
print(' idx | MINE | TARGET')
|
||||||
for i, me, tg in diffs[:40]:
|
for i, me, tg in diffs[:40]:
|
||||||
|
|||||||
Reference in New Issue
Block a user