#!/usr/bin/env python3 """delever_oracle.py — the fast byte oracle for a single-translation-unit source edit (Phase 36 T2, 2026-09-09). tools/delever_oracle.py --recipes [-j N] # capture every object's exact build command via `make -n -W` -> .run/P36/delever/recipes.json tools/delever_oracle.py --calibrate ov_SC04_011 main # compile every TU of the named binaries UNTOUCHED: 100 % object equality with build/, # twin == primary, then the POSITIVE control (a nop injected -> DIFFERS); exit 1 otherwise tools/delever_oracle.py --status # is the calibration current for this HEAD / Makefile? tools/delever_oracle.py --snapshot-baseline # build/**/*.o + every binary's link inputs -> .run/P36/delever/baseline/ (after a GREEN check-all) tools/delever_oracle.py --linked-control # Phase 37 T3: the f3 known-true (reloc spelling: whole-object DIFFERS, linked IDENTICAL) + its negative THE QUESTION IT ANSWERS: "does this candidate text for TU X still produce the byte-identical object?" — and only that. The whole-binary gate (`make check BINARY=…`, and the clean fleet run, R22) remains the arbiter of every batch; this oracle is the inner loop that decides one lever site at a time in ~0.3–5 s instead of ~90 s. HOW (the design settled at gate 1 + the plan agent's review, R57: the instrument's own path is part of the instrument): * the recipe of every object is the Makefile's OWN command, printed by `make -n -W BINARY=` (0.04–0.15 s each; captured once per Makefile/config state, ~4,300 objects at -j16 in ~30 s). It carries the per-object -O0 overrides, the jtbl_rodata_pads stage of main/module objects, the reorder island, the twin rule (the primary's source into the twin's object). tools/compile_only.py's re-typed pipeline omits the pad stage and is NOT used. * a candidate compiles IN PLACE at the real path (objects embed the source path as an STT_FILE symbol) with `-o` and `-MF` redirected to scratch — build/ is never written, so the baseline object of the fleet run stays the reference; the TU's text is restored from the caller's in-memory snapshot (never `git checkout`, R102). Workers own whole TUs: no two candidates of one TU at once. * verdict = whole-object byte equality with build/ (R56: build/ must come from a clean fleet run of THIS tree — --calibrate proves it). * CALIBRATION (R39/R57): every sampled TU compiled untouched reproduces its object byte for byte; a twin's object equals its primary's; a deliberately altered body reports DIFFERS. `.run/P36/delever/calibration.json` records HEAD, the Makefile/config mtimes and the per-object seconds (the scheduler's cost model); an --apply of the de-lever tool refuses without a current calibration. * THE LINKED MODE (Phase 37 T3, 2026-09-12 — the T2 finding (h)): a correct global-block edit can be whole-object DIFFERS while the LINKED bytes are identical, because only the RELOCATION SPELLING changed (`D_801F8872` vs `D_801F8870`+2 resolve to one address; the immediate field and the symbol differ in the .o). `judge_linked` therefore runs the build's OWN tail on the candidate object: the binary's snapshot `.ld` with the one object pointed at the candidate and every other object at the snapshot's copy, the Makefile's exact `ld … -T -T [-T build/psyq/*_externals.ld] --no-check-sections`, `objcopy -O binary`, the <=3-byte end-align trim, and SHA1 against config/check..sha — nothing of HI16/LO16 pairing is re-implemented (R110: the tree's own bytes through the build's own tail). ~0.3 s per judgement; run only AFTER a whole-object DIFFERS, when the edit is flagged relocation-spelling or `reloc_only_diff` says the .text words agree modulo relocation operands. The snapshot (`--snapshot-baseline`) therefore also carries every binary's link inputs (`_link//`, `links.json`), so a clean fleet rebuild can run beside a live linked judgement (R112). """ import argparse import hashlib import json import os import pathlib import re import subprocess import sys import time from concurrent.futures import ThreadPoolExecutor REPO = pathlib.Path(__file__).resolve().parent.parent sys.path.insert(0, str(REPO / "tools")) RUN = REPO / ".run" / "P36" / "delever" RECIPES = RUN / "recipes.json" CALIB = RUN / "calibration.json" SCRATCH = RUN / "obj" RECIPE_RE = re.compile(r"^set -o pipefail; (.*) -o (build/\S+\.o)$") SIGNAL_LINE = re.compile(r"^\s*\d+\s+(Aborted|Segmentation fault|Illegal instruction|Floating point exception|Bus error|Killed)[^\n]*", re.M) JOB_STATUS_LINE = re.compile(r"^(bash: line \d+: )?\s*\d+\s+(Done|Exit \d+)\b") CPP_CONTEXT_LINE = re.compile(r"^\s*(\d+\s*\|.*|\|\s*)$") # modern cpp's source-context lines under a redefinition warning # ---------------------------------------------------------------------------------------------------------------------- # enumeration: (alias, src, obj) for every object of every binary — derived from the Makefile's own rules (R33) # ---------------------------------------------------------------------------------------------------------------------- def src_dirs(): import corpus return dict(corpus.src_dirs()) def twin_of_map(): import share_census return share_census.twin_of_map() def objects(aliases=None): """[(alias, src_rel, obj_rel)] — the pattern rule build/src/%.o: src/%.c, and for a twin the primary's sources renamed into the twin's own object names (Makefile twin block).""" import compile_only dirs = src_dirs() twins = twin_of_map() out = [] for a in sorted(dirs): if aliases and a not in aliases: continue prim = twins.get(a) for p in compile_only.tus_of(a, dirs): src = p.relative_to(REPO).as_posix() if prim: # src//.c -> build/src//.o stem = p.name[:-2] if not stem.startswith(prim): continue obj = f"build/src/{a}/{a}{stem[len(prim):]}.o" else: obj = "build/" + src[:-2] + ".o" out.append((a, src, obj)) return out def config_stamp(): files = [REPO / "Makefile"] + sorted((REPO / "config").glob("*.mk")) h = hashlib.sha1() for f in files: st = f.stat() h.update(f"{f.name}|{int(st.st_mtime)}|{st.st_size}\n".encode()) return h.hexdigest()[:12] def head(): return subprocess.run(["git", "rev-parse", "--short", "HEAD"], cwd=REPO, capture_output=True, text=True).stdout.strip() # ---------------------------------------------------------------------------------------------------------------------- # recipes # ---------------------------------------------------------------------------------------------------------------------- def capture_recipe(alias, src, obj): r = subprocess.run(["make", "-n", "-W", src, obj, f"BINARY={alias}"], cwd=REPO, capture_output=True, text=True) if r.returncode != 0: return None, f"make -n rc={r.returncode}: {r.stderr.strip()[:200]}" lines = [ln for ln in r.stdout.splitlines() if ln.startswith("set -o pipefail;")] if len(lines) != 1: return None, f"expected ONE pipeline line, got {len(lines)}" m = RECIPE_RE.match(lines[0]) if not m or m.group(2) != obj: return None, f"unparsed recipe: {lines[0][:160]}" return dict(alias=alias, src=src, obj=obj, pipeline=m.group(1)), None def build_recipes(jobs, aliases=None): objs = objects(aliases) recipes, errors = {}, [] t0 = time.time() with ThreadPoolExecutor(max_workers=jobs) as ex: for (a, src, obj), (rec, err) in zip(objs, ex.map(lambda t: capture_recipe(*t), objs)): if rec: recipes[obj] = rec else: errors.append(f"{a} {src} {obj}: {err}") return dict(stamp=config_stamp(), head=head(), generated=time.strftime("%Y-%m-%d %H:%M"), n=len(recipes), seconds=round(time.time() - t0, 1), recipes=recipes, errors=errors) def load_recipes(refresh=False, jobs=16): if RECIPES.exists() and not refresh: d = json.loads(RECIPES.read_text()) if d.get("stamp") == config_stamp(): return d d = build_recipes(jobs) RUN.mkdir(parents=True, exist_ok=True) RECIPES.write_text(json.dumps(d, indent=1) + "\n") return d # ---------------------------------------------------------------------------------------------------------------------- # compile + compare # ---------------------------------------------------------------------------------------------------------------------- def compile_obj(recipe, text=None, tag="x", write_path=None): """Compile the recipe's source (with `text` written IN PLACE first — to `write_path` when the edited file is a header the source includes, else to the source itself; the CALLER restores it) to a scratch object. Returns (bytes | None, seconds, errors).""" src = REPO / (write_path or recipe["src"]) obj = recipe["obj"] out = SCRATCH / (obj[len("build/"):].replace("/", "__")[:-2] + f".{tag}.o") out.parent.mkdir(parents=True, exist_ok=True) dep = str(out)[:-2] + ".d" pipeline = recipe["pipeline"] pipeline = re.sub(r"-MF \S+", f"-MF {dep}", pipeline, count=1) cmd = f"set -o pipefail; {pipeline} -o {out}" if text is not None: src.write_text(text, errors="surrogateescape") t0 = time.time() r = subprocess.run(["bash", "-c", cmd], cwd=REPO, capture_output=True, text=True) dt = time.time() - t0 if r.returncode != 0 or not out.exists(): # a pipeline member killed by a signal (cc1 2.7.2 aborts on some candidates — e.g. a variable whose only definition was a # launder that got deleted): bash prints the job-status block ("Done" / "Aborted (core dumped)" / "Segmentation fault") # and the pipeline's rc is >= 128 — that is a CRASH verdict, not a diagnostic (R103: the real message forms) sig = SIGNAL_LINE.search(r.stderr) if sig or r.returncode >= 128: return None, dt, "CRASH: " + (sig.group(0).strip() if sig else f"rc={r.returncode}") # gcc 2.7.2 prints errors without the word "error" and the assembler floods stderr with `$at` warnings: keep every # non-warning line (R103), then the tail errs = [ln for ln in r.stderr.splitlines() if ln.strip() and not re.search(r"\bwarning:", ln, re.I) and not ln.startswith(("In file included", " " * 16)) and ": In function" not in ln and not JOB_STATUS_LINE.match(ln) and not CPP_CONTEXT_LINE.match(ln) and ": note:" not in ln] # cpp's `NNN | #define …` / `|` context + notes (P37 T3) # gcc 2.7.2's `At top level:` is a PREFIX line; keep the message lines ahead of it in the cap errs.sort(key=lambda ln: ln.rstrip().endswith("At top level:")) return None, dt, ("\n".join(errs[:8]) or r.stderr[-400:]) data = out.read_bytes() try: out.unlink() pathlib.Path(dep).unlink(missing_ok=True) except OSError: pass return data, dt, "" BASELINE = RUN / "baseline" # the snapshot `make clean` cannot reach (see baseline_path) def baseline_path(obj): """The object a score is compared against — the SNAPSHOT when one exists, else `build/`. Everything in this engine scores a candidate against the fleet run's object under `build/`, and the R22 gate begins with `make clean`, which deletes exactly that. With agents scoring in parallel (T7's burst, S102) a fleet gate would make every live `--try` compare against a missing or half-written baseline and report nonsense in the agent's own voice. The snapshot decouples the two: the baseline is the ORIGINAL game's bytes, which never change as we bank (a bank is byte-identical by construction), so a copy taken from any green fleet is valid until the fleet stops being green. Refresh it with `--snapshot-baseline` after a green `check-all`; a missing object simply falls back to `build/`, so nothing silently scores against half a snapshot.""" if obj.startswith("build/"): p = BASELINE / obj[len("build/"):] if p.exists(): return p return REPO / obj def snapshot_baseline(): """Copy every object under `build/` into the snapshot. Run it after a GREEN `check-all` — the caller states that; this records the HEAD it was taken at so a reader can tell what it is. THE GUARD (T3, 2026-09-12): every object whose bytes CHANGE in the snapshot is re-derived — its TU compiled UNTOUCHED through its own recipe must reproduce the new bytes — else the previous snapshot copy is restored and the refresh is REFUSED, naming the object. Why: a `make build BINARY=x` run with a candidate in place (T2's relocation control) writes build/ with an object that links identically but is not the tree's; a refresh then carried it into the snapshot, and the linked-mode control read `IDENTICAL` for a spelling the tree does not have. build/ is a build product, the snapshot is an oracle input (R56/R112) — the guard is what makes the second true.""" import shutil, subprocess as sp n, changed = 0, [] BASELINE.mkdir(parents=True, exist_ok=True) # THE EMPTY/PARTIAL GUARD (T4, 2026-09-12; R68): an empty or shrunken build/ (a `make clean` with no fleet run behind it — S107's # stopped t4_D21 left exactly that) is REFUSED before anything is written. The first form of this function stamped TAKEN_AT.txt # with HEAD and "0 objects" over a snapshot whose objects were another tree's — a false "fresh". found = sorted((REPO / "build").glob("**/*.o")) prev_n = None stamp = BASELINE / "TAKEN_AT.txt" if stamp.exists(): m = re.search(r"^(\d+) objects", stamp.read_text(), re.M) prev_n = int(m.group(1)) if m else None if not found or (prev_n and len(found) < prev_n): print(f"delever_oracle --snapshot-baseline: REFUSED — build/ holds {len(found)} object(s), the previous snapshot {prev_n}: " f"not a complete fleet run (run the clean fleet gate first, R22/R56); nothing written") return 0 for src in found: rel = src.relative_to(REPO / "build") dst = BASELINE / rel dst.parent.mkdir(parents=True, exist_ok=True) if not dst.exists() or dst.stat().st_mtime < src.stat().st_mtime or dst.stat().st_size != src.stat().st_size: old = dst.read_bytes() if dst.exists() else None new = src.read_bytes() if old != new: changed.append(("build/" + rel.as_posix(), old)) shutil.copy2(src, dst) n += 1 refused = [] if changed: by_obj = load_recipes()["recipes"] for obj, old in changed: r = by_obj.get(obj) if r is None: continue # asm/asset/psyq objects: no TU recipe to re-derive from v, dt, err = judge(r, None, tag="snap") if v != "IDENTICAL": refused.append((obj, v, err[-120:])) dst = BASELINE / obj[len("build/"):] if old is not None: dst.write_bytes(old) else: dst.unlink(missing_ok=True) if refused: print(f"delever_oracle --snapshot-baseline: REFUSED — {len(refused)} changed object(s) do not reproduce from an untouched compile " f"of their TU (build/ is not the tree's; run the clean fleet gate first, R22/R56):") for obj, v, err in refused: print(f" {obj}: {v} {err}") return 0 head = sp.run(["git", "rev-parse", "HEAD"], cwd=REPO, capture_output=True, text=True).stdout.strip() (BASELINE / "TAKEN_AT.txt").write_text(f"{head}\n{n} objects\n{len(changed)} changed since the previous snapshot\n") table = snapshot_links() ok = sum(1 for v in table.values() if not v.get("error") and v.get("ld_script_snap")) print(f"delever_oracle --snapshot-baseline: {n} object(s) under {BASELINE.relative_to(REPO)} at {head[:9]}; link inputs for " f"{ok}/{len(table)} binaries under {LINKDIR.relative_to(REPO)} (links.json)") return n def baseline_bytes(obj): p = baseline_path(obj) return p.read_bytes() if p.exists() else None # ---------------------------------------------------------------------------------------------------------------------- # the linked mode (Phase 37 T3): the build's own link on the candidate object # ---------------------------------------------------------------------------------------------------------------------- LINKDIR = BASELINE / "_link" LINKS = BASELINE / "links.json" _MAKE_QUERY = "__q:\n\t@echo \"$(BINARY)|$(LD)|$(OBJCOPY)|$(LD_SCRIPT)|$(UNDEF_SYMS)|$(UNDEF_FUNCS)|$(EXE)|$(CHECK_SHA)|$(OUT)\"\n" OBJ_IN_LD = re.compile(r'"?((?:build|asm|assets)/[^\s"()]+\.o)"?') def link_vars(alias): """The Makefile's OWN link variables for one binary (R33: asked of make, never re-typed): dict(ld, objcopy, ld_script, undef_syms, undef_funcs, exe, check_sha, out, syms_frags, exe_size). syms_frags = main's `-T build/psyq/*_externals.ld` list in the Makefile's order, the files that exist (the recipe's `[ -f ]` tests); empty for every other binary (the ifeq skips them).""" r = subprocess.run(["make", "-s", "-f", "Makefile", "-f", "-", f"BINARY={alias}", "__q"], cwd=REPO, input=_MAKE_QUERY, capture_output=True, text=True) if r.returncode != 0 or "|" not in r.stdout: raise RuntimeError(f"link_vars({alias}): make query failed rc={r.returncode}: {r.stderr.strip()[:200]}") f = r.stdout.strip().splitlines()[-1].split("|") d = dict(alias=f[0], ld=f[1], objcopy=f[2], ld_script=f[3], undef_syms=f[4], undef_funcs=f[5], exe=f[6], check_sha=f[7], out=f[8]) frags = [] if alias == "main": mk = (REPO / "Makefile").read_text() for m in re.finditer(r"^(\w+_SYMS)\s*:=\s*(build/psyq/\S+)\s*$", mk, re.M): if (REPO / m.group(2)).exists(): frags.append(m.group(2)) d["syms_frags"] = frags exe = REPO / d["exe"] d["exe_size"] = exe.stat().st_size if exe.exists() else None d["want_sha"] = (REPO / d["check_sha"]).read_text().split()[0] if (REPO / d["check_sha"]).exists() else None return d def snapshot_links(aliases=None): """Copy every binary's link inputs into the snapshot (`_link//`; main's externals under `_link/psyq/`) and write links.json. Called by snapshot_baseline; the .ld's object paths stay `build/…` — judge_linked redirects them.""" import shutil dirs = src_dirs() table = {} for a in sorted(dirs): if aliases and a not in aliases: continue try: v = link_vars(a) except RuntimeError as ex: table[a] = dict(error=str(ex)) continue dst = LINKDIR / a dst.mkdir(parents=True, exist_ok=True) for key in ("ld_script", "undef_syms", "undef_funcs"): src = REPO / v[key] if src.exists(): shutil.copy2(src, dst / pathlib.Path(v[key]).name) v[key + "_snap"] = str((dst / pathlib.Path(v[key]).name).relative_to(REPO)) snap_frags = [] for fr in v["syms_frags"]: (LINKDIR / "psyq").mkdir(parents=True, exist_ok=True) shutil.copy2(REPO / fr, LINKDIR / "psyq" / pathlib.Path(fr).name) snap_frags.append(str((LINKDIR / "psyq" / pathlib.Path(fr).name).relative_to(REPO))) v["syms_frags_snap"] = snap_frags table[a] = v LINKS.write_text(json.dumps(dict(head=head(), stamp=config_stamp(), generated=time.strftime("%Y-%m-%d %H:%M"), links=table), indent=1) + "\n") return table _links_cache = None def links(): """The snapshot's link table (alias -> link_vars + snapshot paths); refuses when the snapshot has none (R43).""" global _links_cache if _links_cache is None: if not LINKS.exists(): raise RuntimeError("no links.json in the baseline snapshot — run `tools/delever_oracle.py --snapshot-baseline` after a GREEN check-all") _links_cache = json.loads(LINKS.read_text())["links"] return _links_cache def judge_linked(recipe, cand_bytes, tag="x"): """(verdict, seconds, err) for a candidate OBJECT: the binary linked with this object in place of the recipe's, everything else from the snapshot, through the Makefile's own ld/objcopy/trim, SHA1 against config/check..sha. verdict in IDENTICAL | DIFFERS | LINK-ERROR | NO-LINK-INPUTS.""" alias, obj = recipe["alias"], recipe["obj"] lv = links().get(alias) if not lv or lv.get("error") or not lv.get("ld_script_snap") or lv.get("exe_size") is None or not lv.get("want_sha"): return "NO-LINK-INPUTS", 0.0, f"the snapshot carries no link inputs for {alias}" flat = obj[len("build/"):].replace("/", "__")[:-2] + f".{tag}" SCRATCH.mkdir(parents=True, exist_ok=True) cand = SCRATCH / (flat + ".lnk.o") ld_s = SCRATCH / (flat + ".ld") elf = SCRATCH / (flat + ".elf") binp = SCRATCH / (flat + ".bin") t0 = time.time() try: cand.write_bytes(cand_bytes) script = (REPO / lv["ld_script_snap"]).read_text() def redirect(m): path = m.group(1) if path == obj: return str(cand) snap = BASELINE / path[len("build/"):] if path.startswith("build/") else None return str(snap) if snap is not None and snap.exists() else path script2 = OBJ_IN_LD.sub(redirect, script) if str(cand) not in script2: return "LINK-ERROR", time.time() - t0, f"{obj} is not named in {lv['ld_script_snap']}" ld_s.write_text(script2) cmd = [lv["ld"], "-T", str(ld_s)] for key in ("undef_syms_snap", "undef_funcs_snap"): if lv.get(key): cmd += ["-T", lv[key]] for fr in lv.get("syms_frags_snap", []): cmd += ["-T", fr] cmd += ["--no-check-sections", "-o", str(elf)] r = subprocess.run(cmd, cwd=REPO, capture_output=True, text=True) if r.returncode != 0: errs = [ln for ln in r.stderr.splitlines() if ln.strip() and "warning" not in ln.lower()] return "LINK-ERROR", time.time() - t0, "\n".join(errs[:6])[-400:] r = subprocess.run([lv["objcopy"], "-O", "binary", str(elf), str(binp)], cwd=REPO, capture_output=True, text=True) if r.returncode != 0: return "LINK-ERROR", time.time() - t0, r.stderr[-300:] data = binp.read_bytes() d = len(data) - lv["exe_size"] if 0 < d <= 3: data = data[:lv["exe_size"]] # the Makefile's shrink-only end-align trim got = hashlib.sha1(data).hexdigest() if got == lv["want_sha"]: return "IDENTICAL", time.time() - t0, "" return "DIFFERS", time.time() - t0, f"linked sha {got[:12]} != {lv['want_sha'][:12]} (size {len(data)} vs {lv['exe_size']})" finally: for p in (cand, ld_s, elf, binp): try: p.unlink() except OSError: pass def reloc_only_diff(cand_bytes, base_path, tag="x"): """True when the two objects' .text instruction streams agree word for word under the relocation mask and differ only in relocation OPERANDS (which symbol/addend a link-time field names) — the shape a global-block respelling produces, and the cheap pre-check (~60 ms) before paying for a linked judgement. False on any masked-word or length difference.""" import masked_diff as md SCRATCH.mkdir(parents=True, exist_ok=True) tmp = SCRATCH / (hashlib.sha1(cand_bytes).hexdigest()[:12] + f".{tag}.ro.o") tmp.write_bytes(cand_bytes) try: mine = md.insns_from_object(str(tmp)) tgt = md.insns_from_object(str(base_path)) finally: tmp.unlink(missing_ok=True) if not mine or not tgt or len(mine) != len(tgt): return False ops_differ = False for c, t in zip(mine, tgt): m = md.mask_for(t["word"], t["reloc_kind"]) if (c["word"] & m) != (t["word"] & m) or md._j_mismatch(c, t): return False if (c["reloc_op"] or "") != (t["reloc_op"] or "") or (c["word"] != t["word"]): ops_differ = True return ops_differ def judge(recipe, text, tag="x", write_path=None): """(verdict, seconds, err): verdict in IDENTICAL | DIFFERS | COMPILE-ERROR | NO-BASELINE.""" base = baseline_bytes(recipe["obj"]) if base is None: return "NO-BASELINE", 0.0, "" data, dt, err = compile_obj(recipe, text, tag, write_path) if data is None: return ("COMPILE-CRASH" if err.startswith("CRASH:") else "COMPILE-ERROR"), dt, err return ("IDENTICAL" if data == base else "DIFFERS"), dt, "" def recipes_by_src(recipes): """src rel -> [recipe] (a twin binary compiles the primary's source into its own object: two recipes, one source).""" out = {} for r in recipes.values(): out.setdefault(r["src"], []).append(r) for v in out.values(): v.sort(key=lambda r: r["obj"]) return out def judge_all(recipes, text, tag="x", write_path=None): """Every recipe of one written file judged in turn (the file is written once, by the first compile; the CALLER restores it). Returns (verdict, seconds, err): IDENTICAL only if every object is identical; otherwise the first non-identical verdict.""" total, first = 0.0, None for i, r in enumerate(recipes): v, dt, err = judge(r, text if i == 0 else None, tag=tag, write_path=write_path) total += dt if v != "IDENTICAL" and first is None: first = (v, err) return (first[0] if first else "IDENTICAL"), total, (first[1] if first else "") def linked_control(): """The linked mode's known-true and negative (R39/R110), on the T2 finding (h)'s own body — md_SC07_004 `func_801A4258`, whose `s16` globals D_801F8870/72/74 are one array walked through a pointer in the original: spelling `D_801F8872` as `p[1]` changes the relocation (`D_801F8870`+2 for `D_801F8872`+0) and NOTHING else, so the object DIFFERS and the linked binary is IDENTICAL; `p[2]` for D_801F8872 (a wrong addend) must DIFFER in both. Exit 0 only when all four verdicts are as predicted.""" tu = "src/md_SC07_004/md_SC07_004.c" recs = recipes_by_src(load_recipes()["recipes"]).get(tu, []) if not recs: print("linked-control: no recipe for", tu); return 1 r = recs[0] path = REPO / tu raw = path.read_text(errors="surrogateescape") st = path.stat() body_a = raw.index("void func_801A4258(void) {") body_z = raw.index("\n}\n", body_a) + 3 body = raw[body_a:body_z] if body.count("D_801F8872") != 3 or body.count("D_801F8874") != 3: print("linked-control: the control body has changed shape (expected 3 mentions each of D_801F8872/D_801F8874)"); return 1 good = raw[:body_a] + body.replace("D_801F8872", "p[1]").replace("D_801F8874", "p[2]") + raw[body_z:] bad = raw[:body_a] + body.replace("D_801F8872", "p[2]").replace("D_801F8874", "p[3]") + raw[body_z:] base = baseline_bytes(r["obj"]) out, ok = [], True try: for name, text, want_whole, want_ro, want_link in (("known-true p[1]/p[2]", good, "DIFFERS", True, "IDENTICAL"), ("negative p[2]/p[3]", bad, "DIFFERS", True, "DIFFERS")): data, dt, err = compile_obj(r, text, tag="lc") restore = path.write_text(raw, errors="surrogateescape") if data is None: out.append(f" {name}: COMPILE-ERROR {err[:120]}"); ok = False; continue whole = "IDENTICAL" if data == base else "DIFFERS" ro = reloc_only_diff(data, baseline_path(r["obj"]), tag="lc") lv, dt2, lerr = judge_linked(r, data, tag="lc") line = f" {name}: whole-object {whole} · reloc-only {ro} · linked {lv} ({dt2:.2f} s){' ' + lerr if lerr else ''}" good_row = (whole == want_whole and ro == want_ro and lv == want_link) out.append(line + (" ✓" if good_row else f" ✗ expected {want_whole}/{want_ro}/{want_link}")) ok = ok and good_row finally: path.write_text(raw, errors="surrogateescape") os.utime(path, ns=(st.st_atime_ns, st.st_mtime_ns)) print("delever_oracle --linked-control (md_SC07_004 func_801A4258, the f3 shape):") print("\n".join(out)) print("linked-control:", "OK" if ok else "FAIL") return 0 if ok else 1 # ---------------------------------------------------------------------------------------------------------------------- # calibration # ---------------------------------------------------------------------------------------------------------------------- def calibrate(aliases, jobs, recipes): """Every object of the named binaries compiled UNTOUCHED -> must equal build/; twin == primary; then the positive control.""" recs = [r for r in recipes["recipes"].values() if r["alias"] in aliases] if not recs: sys.exit(f"delever_oracle: no recipes for {aliases} (R43)") twins = twin_of_map() results = [] def one(r): v, dt, err = judge(r, None, tag="cal") return dict(alias=r["alias"], obj=r["obj"], verdict=v, seconds=round(dt, 3), err=err[-200:]) with ThreadPoolExecutor(max_workers=jobs) as ex: results = list(ex.map(one, recs)) ok = sum(1 for x in results if x["verdict"] == "IDENTICAL") bad = [x for x in results if x["verdict"] != "IDENTICAL"] # twin == primary on the objects both build twin_checks = [] for a in aliases: prim = twins.get(a) if not prim: continue for r in recipes["recipes"].values(): if r["alias"] == a: pobj = r["obj"].replace(f"build/src/{a}/{a}", f"build/src/{prim}/{prim}") same = baseline_bytes(r["obj"]) == baseline_bytes(pobj) twin_checks.append(dict(twin=r["obj"], primary=pobj, equal=same)) twin_bad = [t for t in twin_checks if not t["equal"]] # the positive control: a nop injected into a REAL function body (the first definition the shared scanner finds in the # first recipe's source with a body of >= 3 lines) must DIFFER — a verifier that cannot fail is no verifier (R39/R53) import share_census as sc ctl = recs[0] src = REPO / ctl["src"] st0 = src.stat() orig = src.read_text(errors="surrogateescape") pos_verdict = "SKIPPED" defs = [r for r in sc.scan_text(orig, ctl["src"], shared_defs=None) if r["form"] == "def" and r.get("nlines", 0) >= 3 and not r.get("empty")] if defs: d = defs[0] line_starts = [0] for ln in orig.split("\n"): line_starts.append(line_starts[-1] + len(ln) + 1) # at the END of the body (C89: a statement before the declarations is a parse error) o = orig.rfind("}", line_starts[d["end"] - 1], line_starts[d["end"]]) if o >= 0: cand = orig[:o] + ' __asm__("nop");\n' + orig[o:] try: pos_verdict, _, err = judge(ctl, cand, tag="ctl") pos_verdict = pos_verdict if pos_verdict != "COMPILE-ERROR" else f"COMPILE-ERROR ({err[-100:]})" finally: src.write_text(orig, errors="surrogateescape") os.utime(src, ns=(st0.st_atime_ns, st0.st_mtime_ns)) # the census's src stamp is stat-based: a restore is not a change by_alias = {} for x in results: d = by_alias.setdefault(x["alias"], dict(objects=0, identical=0, seconds=0.0)) d["objects"] += 1 d["identical"] += x["verdict"] == "IDENTICAL" d["seconds"] += x["seconds"] return dict(head=head(), stamp=config_stamp(), generated=time.strftime("%Y-%m-%d %H:%M"), aliases=sorted(aliases), objects=len(results), identical=ok, failures=bad[:20], twin_checks=len(twin_checks), twin_mismatch=twin_bad[:10], positive_control=dict(obj=ctl["obj"], verdict=pos_verdict), per_alias={a: dict(v, mean_s=round(v["seconds"] / max(v["objects"], 1), 3)) for a, v in by_alias.items()}, per_object_seconds={x["obj"]: x["seconds"] for x in results}, ok=(not bad and not twin_bad and pos_verdict == "DIFFERS")) def calibration_current(): if not CALIB.exists(): return False, "no calibration" d = json.loads(CALIB.read_text()) if d.get("head") != head(): return False, f"calibrated at {d.get('head')}, HEAD is {head()}" if d.get("stamp") != config_stamp(): return False, "the Makefile/config changed since the calibration" return bool(d.get("ok")), ("ok" if d.get("ok") else "the calibration FAILED") def main(): ap = argparse.ArgumentParser(description=__doc__.split("\n")[0]) ap.add_argument("--recipes", action="store_true", help="(re)capture every object's recipe") ap.add_argument("--calibrate", nargs="*", help="binaries to calibrate on (e.g. ov_SC04_011 ov_SC03_015 main)") ap.add_argument("--status", action="store_true") ap.add_argument("--snapshot-baseline", action="store_true", help="copy build/**/*.o into the snapshot every score compares against (run after a GREEN check-all)") ap.add_argument("--linked-control", action="store_true", help="the linked mode's known-true + negative on md_SC07_004 func_801A4258 (T3)") ap.add_argument("-j", "--jobs", type=int, default=16) a = ap.parse_args() RUN.mkdir(parents=True, exist_ok=True) if a.linked_control: sys.exit(linked_control()) if a.snapshot_baseline: sys.exit(0 if snapshot_baseline() else 1) if a.status: ok, why = calibration_current() print(f"delever_oracle: calibration {'CURRENT' if ok else 'STALE/MISSING'} — {why}") sys.exit(0 if ok else 1) if a.recipes: d = load_recipes(refresh=True, jobs=a.jobs) print(f"delever_oracle: {d['n']} recipes captured in {d['seconds']} s at -j{a.jobs}; errors {len(d['errors'])}") for e in d["errors"][:10]: print(" ", e) sys.exit(1 if d["errors"] else 0) if a.calibrate is not None: recipes = load_recipes(jobs=a.jobs) t0 = time.time() d = calibrate(a.calibrate, a.jobs, recipes) d["seconds"] = round(time.time() - t0, 1) CALIB.write_text(json.dumps(d, indent=1) + "\n") print(f"delever_oracle --calibrate {' '.join(d['aliases'])}: {d['identical']}/{d['objects']} objects byte-identical untouched; " f"twin checks {d['twin_checks']} ({len(d['twin_mismatch'])} mismatch); positive control {d['positive_control']['verdict']} " f"on {d['positive_control']['obj']}; {d['seconds']} s — {'OK' if d['ok'] else 'FAIL'}") for a_, v in d["per_alias"].items(): print(f" {a_:14s} {v['identical']}/{v['objects']} identical, mean {v['mean_s']} s per object") for f in d["failures"][:10]: print(" FAIL", f["alias"], f["obj"], f["verdict"], f["err"][-120:]) sys.exit(0 if d["ok"] else 1) ap.print_help() if __name__ == "__main__": main()