Files
BFM-decomp/tools/restruct.py
T

973 lines
50 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
"""tools/restruct.py — the struct rewrite engine (Phase 37). T2 form: `--try` (rung 1 on one body), `--probe` (the T2 probe),
`--audit-layouts` (the layout engine against cc1), `--fanout-cost` (one canonical-header edit over every includer).
RUNG 1 (body-local, no signature change): every raw cast on a typed base becomes a typed cast-member access,
*(u16 *)(a0 + 0x3C) -> ((P37S_xxxxxxxx *)a0)->unk3C
with the type declared once, at file scope, right before the function (a struct built from the struct map's layout for that
base's cluster: `unk<HEX>` fields at the offsets the sites use, `u8 pad<HEX>[n]` between them). Rung 1's RTL is a COMPONENT_REF —
the same MEM_IN_STRUCT_P flag the final `a0->unk3C` carries — so it decides the byte question (the `/s` model, cse_expr.md §4)
without touching a declaration in any other TU. The oracle is tools/delever_oracle.py: the TU compiled IN PLACE through the
build's own recipe against the snapshot baseline; a header body is judged on EVERY includer; the file is restored from this
process's own snapshot (R102). S+A: a body whose member spelling DIFFERS and that carries marked levers is judged again with the
levers stripped (delever_search.lever_free_body) — the struct hypothesis. The residual is classified by delever_search.classify
(REG / COUNT / ORDER / MIXED) over the masked instruction words.
The probe (T2) draws a stratified sample of bodies from the census (.run/P37/census/sites.jsonl + body_base_type.json + the
struct map), runs rung 1 (+ S+A) on each, and writes .run/P37/probe/probe_rows.jsonl + probe_table.md with every denominator.
Nothing here writes to the tree except the in-place compile, which is restored before the function returns.
"""
import argparse
import collections
import hashlib
import json
import os
import pathlib
import random
import re
import subprocess
import sys
import time
from concurrent.futures import ProcessPoolExecutor
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent))
import share_census as sc # noqa: E402
import type_census as tc # noqa: E402
import delever as dl # noqa: E402
import delever_oracle as oracle # noqa: E402
import delever_search as ds # noqa: E402
import masked_diff as md # noqa: E402
REPO = pathlib.Path(__file__).resolve().parent.parent
CENSUS = REPO / ".run/P37/census"
PROBE = REPO / ".run/P37/probe"
RAW_FORMS = ("P", "I", "X", "M")
TYPE_OF = {(1, "u"): "u8", (1, "s"): "s8", (2, "u"): "u16", (2, "s"): "s16", (4, "u"): "u32", (4, "s"): "s32",
(8, "u"): "u64", (8, "s"): "s64", (4, "f"): "f32", (8, "f"): "f64"}
# ----------------------------------------------------------------------------------------------------------------------
# the census's view of a body
# ----------------------------------------------------------------------------------------------------------------------
_map_cache = None
def struct_map():
global _map_cache
if _map_cache is None:
p = CENSUS / "struct_map.json"
if not p.exists():
sys.exit("restruct: no struct map — run `tools/type_census.py -j 16 --sites` first")
_map_cache = {t["name"]: t for t in json.loads(p.read_text())["types"]}
return _map_cache
def body_sites(tu, fn):
"""The census's site records for one body, RE-WALKED on the current file (positions on the raw text via a same-length mask)."""
raw = (REPO / tu).read_text(errors="surrogateescape")
masked = dl.same_len_mask(raw)
line_of = lambda idx: masked.count("\n", 0, idx) + 1
recs = sc.scan_text(raw, tu, shared_defs=None)
defs = [r for r in recs if r["form"] == "def"]
span_of_line = {}
for d in defs:
for ln in range(d["line"], d["end"] + 1):
span_of_line[ln] = d
params_of = {}
for d in defs:
head_start = dl.line_starts(masked)[d["line"] - 1]
brace = masked.find("{", head_start)
head = masked[head_start:brace] if brace != -1 else ""
m = re.search(r"\b" + re.escape(d["name"]) + r"\s*\(", head)
if m:
inner, close = tc._paren_body(head, m.end() - 1)
params_of[d["name"]] = tc._param_names(inner or "", head[close + 1:] if close != -1 else "")
sites, _ = tc.find_sites(masked, tu, span_of_line, line_of, params_of)
d = next((x for x in defs if x["name"] == fn), None)
return raw, masked, d, [s for s in sites if s.get("fn") == fn]
def field_type(w, sg, ctypes):
if sg == "p":
# a pointer field: the sites' pointee when they agree, else void *
pt = collections.Counter(c.rstrip("* ").strip() + " *" for c in ctypes if c and c.count("*") >= 2)
return pt.most_common(1)[0][0] if len(pt) == 1 else "void *"
return TYPE_OF.get((w, sg))
def build_type(tname, layout, ctypes_by_off):
"""The struct text for a cluster's layout [(off, w, sign, n)]: fields at their offsets, pads between, overlaps skipped.
Returns (text, fields{off: (w, sign, name)}, skipped)."""
fields, skipped, lines, cur = {}, [], [], 0
for (off, w, sg, n) in sorted(layout, key=lambda x: (x[0], -x[3])):
if off < cur or w is None or w == 0:
skipped.append((off, w, sg))
continue
if off % (8 if w == 8 else min(w, 4)) != 0:
# an access at an offset its width cannot be aligned to (an `lh` at +0x79): not a field of THIS base — a cross-base
# contamination of the cluster, or an unaligned lwl/lwr pair; gcc would realign it and shift every later field
skipped.append((off, w, "misaligned"))
continue
t = field_type(w, sg, ctypes_by_off.get(off, []))
if t is None:
skipped.append((off, w, sg))
continue
if off > cur:
lines.append(f" /* {cur:#06x} */ u8 pad{cur:X}[{off - cur:#x}];")
name = f"unk{off:X}"
lines.append(f" /* {off:#06x} */ {t} {name};")
fields[off] = (w, sg, name, t)
cur = off + w
text = f"typedef struct {{\n" + "\n".join(lines) + f"\n}} {tname}; /* P37 rung 1 — from the struct map (size >= {cur:#x}) */\n"
return text, fields, skipped
def rung1_edits(raw, d, sites, tname, fields, base_key):
"""The positional edits of rung 1 for one body/base: [(pos, end, replacement, site)], skips{reason: n}."""
edits, skips = [], collections.Counter()
bcls, bname = base_key.split(":", 1)
for s in sites:
if s["form"] not in RAW_FORMS or s.get("bclass") != bcls or str(s.get("base")) != bname:
continue
if s.get("index") is not None or s.get("refused"):
skips["index"] += 1
continue
off = s.get("off") or 0
if off < 0:
skips["negative-offset"] += 1
continue
f = fields.get(off)
if f is None:
skips["no-field"] += 1
continue
w, sg, name, ftype = f
if s.get("width") != w:
skips["width"] += 1
continue
if s.get("sign") != sg and not (s.get("access") in ("store",) and {s.get("sign"), sg} <= {"s", "u"}):
skips["sign"] += 1
continue
if sg == "p":
# a pointer field: the site's pointee must be the field's (a `void *` field cannot be indexed or dereferenced at the site)
site_pointee = (s.get("ctype") or "").rstrip("* ").strip() + " *"
if site_pointee != ftype:
skips["pointee"] += 1
continue
base_expr = ("&" + bname) if bcls == "gaddr" else bname
edits.append((s["pos"], s["end"], f"(({tname} *){base_expr})->{name}", s))
edits.sort(key=lambda e: e[0])
kept, last = [], 0
for e in edits:
if e[0] < last:
skips["overlap"] += 1 # a nested site's inner span inside an outer's — the outer was skipped as `nested`
continue
kept.append(e); last = e[1]
return kept, skips
def splice(raw, d, tname, edits):
"""The TU text with the given edits applied and the typedef inserted before the function (file scope)."""
out, last = [], 0
for (a, b, rep, _) in sorted(edits, key=lambda e: e[0]):
out.append(raw[last:a]); out.append(rep); last = b
out.append(raw[last:])
text = "".join(out)
ins = dl.line_starts(raw)[d["line"] - 1]
return text[:ins] + _typedef_cache[tname] + text[ins:]
def rung1_text(raw, d, sites, tname, fields, base_key):
edits, skips = rung1_edits(raw, d, sites, tname, fields, base_key)
if not edits:
return None, 0, skips
return splice(raw, d, tname, edits), len(edits), skips
def greedy_casts(tu, raw, d, tname, edits, tag):
"""Rung S2: the member spelling DIFFERS — find a minimal set of sites that must stay casts (delever's rung B shape):
leave-one-out first (n compiles), else cumulative revert in source order until IDENTICAL, then drop every revert not needed.
Returns (kept_sites[list of site dicts], compiles, verdict) — verdict IDENTICAL when a set was found."""
n = len(edits)
compiles = 0
for i in range(n):
sub = edits[:i] + edits[i + 1:]
v, _, _, _ = judge_text(tu, splice(raw, d, tname, sub), tag)
compiles += 1
if v == "IDENTICAL":
return [edits[i][3]], compiles, "IDENTICAL"
K = []
for i in range(n):
K.append(i)
sub = [e for j, e in enumerate(edits) if j not in K]
v, _, _, _ = judge_text(tu, splice(raw, d, tname, sub), tag)
compiles += 1
if v == "IDENTICAL":
break
else:
return [], compiles, "NO-SET" # even every site reverted differs: the typedef alone or the mask moved bytes — a tool defect
# minimise
for j in list(K):
trial = [k for k in K if k != j]
sub = [e for i2, e in enumerate(edits) if i2 not in trial]
v, _, _, _ = judge_text(tu, splice(raw, d, tname, sub), tag)
compiles += 1
if v == "IDENTICAL":
K = trial
return [edits[i][3] for i in K], compiles, "IDENTICAL"
_typedef_cache = {}
# ----------------------------------------------------------------------------------------------------------------------
# the oracle wrapper (in place, restored)
# ----------------------------------------------------------------------------------------------------------------------
_recipes = None
_by_src = None
_inc = None
def recipes():
global _recipes, _by_src, _inc
if _recipes is None:
_recipes = oracle.load_recipes()["recipes"]
_by_src = oracle.recipes_by_src(_recipes)
_inc = dl.includers()
return _recipes, _by_src, _inc
def recs_for(tu):
_, by_src, inc = recipes()
return ds.recs_for(tu, by_src, inc)
def judge_text(tu, text, tag):
"""(verdict, seconds, err, candidate_object_bytes_of_first_recipe) — the file written in place and RESTORED (R102)."""
recs = recs_for(tu)
if not recs:
return "NO-RECIPE", 0.0, f"no recipe compiles {tu}", None
path = REPO / tu
raw = path.read_text(errors="surrogateescape")
st = path.stat()
write_path = tu if tu.endswith(".h") else None
try:
total, first, data0 = 0.0, None, None
for i, r in enumerate(recs):
base = oracle.baseline_bytes(r["obj"])
if base is None:
first = first or ("NO-BASELINE", "")
continue
data, dt, err = oracle.compile_obj(r, text if i == 0 else None, tag=tag, write_path=write_path)
total += dt
if i == 0:
data0 = data
if data is None:
v = "COMPILE-CRASH" if err.startswith("CRASH:") else "COMPILE-ERROR"
first = first or (v, err)
elif data != base:
first = first or ("DIFFERS", "")
return (first[0] if first else "IDENTICAL"), total, (first[1] if first else ""), data0
finally:
dl.restore_file(path, raw, st)
def classify_residual(tu, fn, cand_bytes):
"""delever_search's classifier over the body's instructions: candidate object vs the baseline object of the first recipe."""
recs = recs_for(tu)
if not recs or cand_bytes is None:
return None
base = oracle.baseline_path(recs[0]["obj"])
tmp = PROBE / "obj" / (hashlib.sha1((tu + fn).encode()).hexdigest()[:12] + ".o")
tmp.parent.mkdir(parents=True, exist_ok=True)
tmp.write_bytes(cand_bytes)
try:
mine = md.insns_from_object(str(tmp), fn)
tgt = md.insns_from_object(str(base), fn)
except Exception as e: # noqa: BLE001
return dict(kind="UNSCORED", err=str(e)[:80])
finally:
tmp.unlink(missing_ok=True)
if not mine or not tgt:
return dict(kind="UNSCORED", err="function not found in one object")
c = ds.classify(mine, tgt)
return dict(kind=c["kind"], score=c["score"], n_mine=c["n_mine"], n_tgt=c["n_tgt"], pairs=c["pairs"][:4])
# ----------------------------------------------------------------------------------------------------------------------
# rung 1 on one body
# ----------------------------------------------------------------------------------------------------------------------
def try_body(tu, fn, base_key=None, tname_hint=None, tag="p37", want_sa=True, want_greedy=True):
raw, masked, d, sites = body_sites(tu, fn)
if d is None:
return dict(tu=tu, fn=fn, verdict="NO-BODY")
bbt = json.loads((CENSUS / "body_base_type.json").read_text()) if base_key is None or tname_hint is None else None
if base_key is None:
ent = (bbt or {}).get(tu + "|" + fn, {})
if not ent:
return dict(tu=tu, fn=fn, verdict="NO-TYPED-BASE")
base_key = max(ent, key=lambda k: ent[k]["sites"])
tname = tname_hint or (bbt or {}).get(tu + "|" + fn, {}).get(base_key, {}).get("type")
if not tname:
return dict(tu=tu, fn=fn, base=base_key, verdict="NO-TYPE")
t = struct_map().get(tname)
if not t:
return dict(tu=tu, fn=fn, base=base_key, verdict="NO-TYPE-IN-MAP")
ctypes_by_off = collections.defaultdict(list)
for s in sites:
if s.get("bclass") + ":" + str(s.get("base")) == base_key and s.get("ctype"):
ctypes_by_off[s.get("off") or 0].append(s["ctype"])
local_name = f"P37S_{hashlib.sha1(tname.encode()).hexdigest()[:8]}"
ttext, fields, skipped = build_type(local_name, t["layout"], ctypes_by_off)
_typedef_cache[local_name] = ttext
edits, skips = rung1_edits(raw, d, sites, local_name, fields, base_key)
n = len(edits)
row = dict(tu=tu, fn=fn, base=base_key, type=tname, type_sites=t["sites"], type_bodies=t["bodies"], fields=len(fields),
rewritten=n, skips=dict(skips), header=tu.endswith(".h"), levers=None)
if not edits:
row["verdict"] = "NO-SITE-CONVERTIBLE"
return row
text = splice(raw, d, local_name, edits)
v, dt, err, cand = judge_text(tu, text, tag)
row.update(verdict=v, seconds=round(dt, 2), err=err[:160])
if v == "DIFFERS":
row["residual"] = classify_residual(tu, fn, cand)
if want_greedy:
kept, compiles, gv = greedy_casts(tu, raw, d, local_name, edits, tag + "g")
row["s2"] = dict(verdict=gv, kept=len(kept), compiles=compiles, members=n - len(kept),
kept_sites=[dict(off=k.get("off"), ctype=k.get("ctype"), access=k.get("access"), line=k.get("line")) for k in kept[:8]])
# S+A: the body's marked levers stripped as well
lever_sites = [s for s in ds.sites_by_body().get((tu, fn), []) if s.get("cls") in "AB" and s.get("kind") in ds.AB_KINDS]
row["levers"] = len(lever_sites)
if want_sa and lever_sites:
try:
free = ds.lever_free_body(tu, raw, fn, lever_sites)
except ds.Unstrippable as u:
row["sa"] = dict(verdict="UNSTRIPPABLE", why=str(u.args[0])[:120])
return row
# the lever-free TU: re-walk its sites (positions moved) and rewrite the same base
m2 = dl.same_len_mask(free)
recs2 = sc.scan_text(free, tu, shared_defs=None)
defs2 = [r for r in recs2 if r["form"] == "def"]
d2 = next((x for x in defs2 if x["name"] == fn), None)
if d2 is None:
row["sa"] = dict(verdict="NO-BODY-AFTER-STRIP")
return row
span2 = {}
for x in defs2:
for ln in range(x["line"], x["end"] + 1):
span2[ln] = x
line_of2 = lambda idx: m2.count("\n", 0, idx) + 1
pnames = tc._param_names(*(lambda h: (h[0] or "", h[1]))(_head_parts(m2, d2, fn)))
sites2, _ = tc.find_sites(m2, tu, span2, line_of2, {fn: pnames})
sites2 = [s for s in sites2 if s.get("fn") == fn]
text2, n2, skips2 = rung1_text(free, d2, sites2, local_name, fields, base_key)
if text2 is None:
row["sa"] = dict(verdict="NO-SITE-CONVERTIBLE")
return row
v2, dt2, err2, cand2 = judge_text(tu, text2, tag + "sa")
row["sa"] = dict(verdict=v2, seconds=round(dt2, 2), rewritten=n2, err=err2[:120])
if v2 == "DIFFERS":
row["sa"]["residual"] = classify_residual(tu, fn, cand2)
# the lever-free text ALONE (no struct): the control that says whether the struct did anything
if v2 == "IDENTICAL":
v3, _, _, _ = judge_text(tu, free, tag + "a")
row["sa"]["levers_off_alone"] = v3
return row
def _head_parts(masked, d, fn):
ls = dl.line_starts(masked)
head_start = ls[d["line"] - 1]
brace = masked.find("{", head_start)
head = masked[head_start:brace] if brace != -1 else ""
m = re.search(r"\b" + re.escape(fn) + r"\s*\(", head)
if not m:
return "", ""
inner, close = tc._paren_body(head, m.end() - 1)
return inner, (head[close + 1:] if close != -1 else "")
# ----------------------------------------------------------------------------------------------------------------------
# the probe (T2)
# ----------------------------------------------------------------------------------------------------------------------
def draw_sample(n, seed, strata):
"""Bodies stratified by the base's kind, from body_base_type.json: returns [(tu, fn, base_key, type, stratum)]."""
bbt = json.loads((CENSUS / "body_base_type.json").read_text())
smap = struct_map()
rng = random.Random(seed)
# every (body, base) with a type; classify the stratum
cands = collections.defaultdict(list)
lever_bodies = {(k[0], k[1]) for k in ds.sites_by_body()}
for key, bases in bbt.items():
tu, fn = key.split("|", 1)
for bk, ent in bases.items():
t = smap.get(ent["type"])
if not t or ent["sites"] < 2:
continue
bcls = bk.split(":", 1)[0]
if t["sites"] >= 50000:
stratum = "A-entity(0x24C)" if t["size_hint"] >= 0x200 else "B-large-ptr-globals"
elif bcls in ("gaddr", "global") and t["bodies"] >= 5:
stratum = "D-record-tables"
elif t["size_hint"] in (8, 32, 48, 16) and t["bodies"] >= 20:
stratum = "C-matrix/vector-shaped"
elif tu.startswith("src/shared/"):
stratum = "E-shared-header-bodies"
elif t["bodies"] >= 2:
stratum = "F-other-multi-body-types"
else:
stratum = "G-single-body-types"
lev = (tu, fn) in lever_bodies
cands[stratum].append((tu, fn, bk, ent["type"], stratum, ent["sites"], lev))
out = []
for stratum, want in strata.items():
pool = cands.get(stratum, [])
rng.shuffle(pool)
# half of each stratum from lever-bearing bodies where available (the struct hypothesis needs them)
lev = [c for c in pool if c[6]]
non = [c for c in pool if not c[6]]
take = lev[: want // 2] + non[: want - min(len(lev), want // 2)]
if len(take) < want:
take = (lev + non)[:want]
out.extend(take)
return out, {k: len(v) for k, v in cands.items()}
def _probe_worker(args):
tu, fn, bk, tname, stratum, nsites, lev = args
try:
row = try_body(tu, fn, base_key=bk, tname_hint=tname, tag="pr" + hashlib.sha1(tu.encode()).hexdigest()[:6])
except Exception as e: # noqa: BLE001
row = dict(tu=tu, fn=fn, base=bk, type=tname, verdict="TOOL-ERROR", err=repr(e)[:200])
row.update(stratum=stratum, base_sites=nsites, lever_body=lev)
return row
def run_probe(a):
PROBE.mkdir(parents=True, exist_ok=True)
strata = {"A-entity(0x24C)": 40, "B-large-ptr-globals": 25, "C-matrix/vector-shaped": 25, "D-record-tables": 25,
"E-shared-header-bodies": 20, "F-other-multi-body-types": 20, "G-single-body-types": 10}
if a.sample:
scale = a.sample / sum(strata.values())
strata = {k: max(1, int(round(v * scale))) for k, v in strata.items()}
sample, pool_sizes = draw_sample(a.sample or sum(strata.values()), a.seed, strata)
# workers own whole FILES: group by tu; headers run serially at the end (their includers are other workers' files)
by_tu = collections.defaultdict(list)
for c in sample:
by_tu[c[0]].append(c)
c_files = [v for k, v in by_tu.items() if not k.endswith(".h")]
h_files = [v for k, v in by_tu.items() if k.endswith(".h")]
t0 = time.time()
rows = []
recipes() # warm the caches in the parent before forking
with ProcessPoolExecutor(max_workers=a.jobs) as ex:
for res in ex.map(_probe_file, c_files):
rows.extend(res)
for group in h_files:
rows.extend(_probe_file(group))
dt = time.time() - t0
with (PROBE / "probe_rows.jsonl").open("w") as fh:
for r in rows:
fh.write(json.dumps(r) + "\n")
table = render_probe(rows, pool_sizes, dt, a.seed)
(PROBE / "probe_table.md").write_text(table)
print(table)
def _probe_file(group):
return [_probe_worker(c) for c in group]
def render_probe(rows, pool_sizes, dt, seed):
L = [f"# T2 probe (i) — rung 1 (struct spelling) on a stratified sample; seed {seed}; {len(rows)} bodies; {dt:.0f} s wall", ""]
L.append(f"Pool sizes (bodies × typed bases in the census): {pool_sizes}")
L.append("")
L.append("| stratum | bodies | IDENTICAL | DIFFERS | COMPILE-ERROR/CRASH | no site / other | sites rewritten | DIFFERS closed by S2 (casts kept) | of DIFFERS with levers: S+A IDENTICAL | levers-off alone IDENTICAL | residual kinds (DIFFERS) |")
L.append("|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---|")
tot = collections.Counter()
for stratum in sorted({r["stratum"] for r in rows}):
rs = [r for r in rows if r["stratum"] == stratum]
v = collections.Counter(r["verdict"] for r in rs)
rew = sum(r.get("rewritten", 0) or 0 for r in rs)
diff_lev = [r for r in rs if r["verdict"] == "DIFFERS" and r.get("levers")]
sa_ok = sum(1 for r in diff_lev if (r.get("sa") or {}).get("verdict") == "IDENTICAL")
alone_ok = sum(1 for r in diff_lev if (r.get("sa") or {}).get("levers_off_alone") == "IDENTICAL")
kinds = collections.Counter((r.get("residual") or {}).get("kind", "?") for r in rs if r["verdict"] == "DIFFERS")
ce = v["COMPILE-ERROR"] + v["COMPILE-CRASH"]
other = len(rs) - v["IDENTICAL"] - v["DIFFERS"] - ce
s2 = [r for r in rs if r["verdict"] == "DIFFERS" and (r.get("s2") or {}).get("verdict") == "IDENTICAL"]
s2k = sum(r["s2"]["kept"] for r in s2)
L.append(f"| {stratum} | {len(rs)} | {v['IDENTICAL']} | {v['DIFFERS']} | {ce} | {other} | {rew} | {len(s2)}/{v['DIFFERS']} ({s2k} casts kept) | {sa_ok}/{len(diff_lev)} | {alone_ok}/{len(diff_lev)} | {dict(kinds)} |")
tot.update(dict(n=len(rs), ident=v["IDENTICAL"], diff=v["DIFFERS"], ce=ce, rew=rew, dl=len(diff_lev), sa=sa_ok, alone=alone_ok, s2=len(s2), s2k=s2k))
L.append(f"| **all** | {tot['n']} | {tot['ident']} | {tot['diff']} | {tot['ce']} | {tot['n'] - tot['ident'] - tot['diff'] - tot['ce']} | {tot['rew']} | {tot['s2']}/{tot['diff']} ({tot['s2k']} casts kept) | {tot['sa']}/{tot['dl']} | {tot['alone']}/{tot['dl']} | |")
L.append("")
judged = tot["ident"] + tot["diff"]
if judged:
L.append(f"**Rung 1 is byte-neutral on {tot['ident']}/{judged} judged bodies ({100.0 * tot['ident'] / judged:.1f} %), moving {tot['rew']} sites; "
f"rung S2 (a minimal set of sites kept as casts, delever's rung-B shape) closes {tot['s2']} of the {tot['diff']} DIFFERS bodies keeping {tot['s2k']} casts "
f"({tot['ident'] + tot['s2']}/{judged} = {100.0 * (tot['ident'] + tot['s2']) / max(1, judged):.1f} % of judged bodies byte-identical with members everywhere but {tot['s2k']} sites); "
f"of the {tot['dl']} DIFFERS bodies that carry marked levers, {tot['sa']} become IDENTICAL with the struct AND the levers off "
f"(the struct hypothesis), of which {tot['alone']} were already identical with the levers off alone (the struct did not decide those).**")
errs = collections.Counter(r.get("err", "")[:70] for r in rows if r["verdict"] in ("COMPILE-ERROR", "COMPILE-CRASH", "TOOL-ERROR"))
if errs:
L.append("")
L.append("Compile errors / tool errors (top): " + " · ".join(f"{k!r} ×{n}" for k, n in errs.most_common(8)))
skips = collections.Counter()
for r in rows:
for k, n in (r.get("skips") or {}).items():
skips[k] += n
L.append(f"\nSites skipped by the rewrite (not converted; counted): {dict(skips)}")
secs = [r.get("seconds", 0) for r in rows if r.get("seconds")]
if secs:
L.append(f"Seconds per body judgement: mean {sum(secs) / len(secs):.2f}, max {max(secs):.2f} (headers judge every includer).")
L.append("")
L.append("Rows: `.run/P37/probe/probe_rows.jsonl` (per body: tu, fn, base, type, rewritten, skips, verdict, residual, sa).")
return "\n".join(L) + "\n"
# ----------------------------------------------------------------------------------------------------------------------
# (ii) the declaration probe: the in-scope declaration replaced by the definition's ANSI signature, per TU and per callee
# ----------------------------------------------------------------------------------------------------------------------
NARROW = re.compile(r"\b(s16|u16|s8|u8|short|char)\b")
def _decl_repair_text(raw, rows, real_ret=None):
"""Rewrite every lying extern of the given rows (same TU, same callee) to the definition's parameter list."""
lines = raw.split("\n")
changed = 0
for r in rows:
i = r["line"] - 1
if i >= len(lines):
continue
pat = re.compile(r"\b" + re.escape(r["callee"]) + r"\s*\(([^;)]*)\)")
new, n = pat.subn(lambda m: f"{r['callee']}({r['real_params']})", lines[i], count=1)
if n:
lines[i] = new
changed += 1
return "\n".join(lines), changed
def probe_decls(a):
import argcheck
defs = argcheck.definitions()
rows = argcheck.scan(defs, only_needed_argpins=False)
by_tu = collections.defaultdict(lambda: collections.defaultdict(list))
for r in rows:
by_tu[r["tu"]][r["callee"]].append(r)
rng = random.Random(a.seed)
# stratify TUs by kind
kinds = collections.defaultdict(list)
for tu in by_tu:
k = "shared-header" if tu.startswith("src/shared/") else "main" if tu.count("/") == 1 else "md" if tu.startswith("src/md_") else "ov"
kinds[k].append(tu)
want = {"ov": 200, "main": 40, "md": 30, "shared-header": 30}
if a.sample:
scale = a.sample / sum(want.values())
want = {k: max(1, int(round(v * scale))) for k, v in want.items()}
sample = []
for k, n in want.items():
pool = kinds.get(k, [])
rng.shuffle(pool)
sample.extend((tu, k) for tu in pool[:n])
recipes()
work = [(tu, k, dict(by_tu[tu]), defs) for tu, k in sample]
out = []
t0 = time.time()
with ProcessPoolExecutor(max_workers=a.jobs) as ex:
for res in ex.map(_decl_worker, [w for w in work if not w[0].endswith(".h")]):
out.extend(res)
for w in work:
if w[0].endswith(".h"):
out.extend(_decl_worker(w))
dt = time.time() - t0
PROBE.mkdir(parents=True, exist_ok=True)
with (PROBE / "decl_rows.jsonl").open("w") as fh:
for r in out:
fh.write(json.dumps(r) + "\n")
print(render_decls(out, dt, a.seed, sum(len(v) for v in by_tu.values()), len(by_tu)))
(PROBE / "decl_table.md").write_text(render_decls(out, dt, a.seed, sum(len(v) for v in by_tu.values()), len(by_tu)))
def _decl_worker(args):
tu, kind, by_callee, defs = args
out = []
try:
raw = (REPO / tu).read_text(errors="surrogateescape")
except OSError:
return out
for callee, rows in by_callee.items():
text, changed = _decl_repair_text(raw, rows)
if not changed:
out.append(dict(tu=tu, kind=kind, callee=callee, rows=len(rows), verdict="NO-EDIT"))
continue
real = defs.get(callee)
v, dt, err, _ = judge_text(tu, text, "dc" + hashlib.sha1((tu + callee).encode()).hexdigest()[:6])
cause = None
if v != "IDENTICAL":
if "too few arguments" in err or "too many arguments" in err:
cause = "arity (the call passes a different count)"
elif "conflicting types" in err:
cause = "conflicting declaration elsewhere in the TU"
elif v.startswith("COMPILE"):
cause = "compile: " + err.split("\n")[0][-70:]
elif NARROW.search(real[1] or ""):
cause = "narrow parameter width (caller-side conversion)"
else:
cause = "other (bytes moved with no narrow width)"
out.append(dict(tu=tu, kind=kind, callee=callee, rows=len(rows), declared_kinds=sorted({r["kind"] for r in rows}),
real_params=real[1] if real else None, verdict=v, cause=cause, err=err[:120], seconds=round(dt, 2)))
return out
def render_decls(rows, dt, seed, n_rows_all, n_tus_all):
L = [f"# T2 probe (ii) — the canonical prototype (the definition's ANSI signature) per TU × callee; seed {seed}; {len(rows)} judgements; {dt:.0f} s wall", "",
f"Population: {n_rows_all} lying declarations in {n_tus_all} TUs (argcheck). Sample: per TU, every lying callee repaired ALONE (its declarations in that TU), judged on the object.", "",
"| TU kind | judgements | IDENTICAL | DIFFERS | COMPILE-ERROR | no edit | causes (non-identical) |", "|---|---:|---:|---:|---:|---:|---|"]
tot = collections.Counter()
for kind in sorted({r["kind"] for r in rows}):
rs_ = [r for r in rows if r["kind"] == kind]
v = collections.Counter(r["verdict"] for r in rs_)
causes = collections.Counter((r.get("cause") or "")[:45] for r in rs_ if r["verdict"] not in ("IDENTICAL", "NO-EDIT"))
ce = sum(n for k, n in v.items() if k.startswith("COMPILE"))
L.append(f"| {kind} | {len(rs_)} | {v['IDENTICAL']} | {v['DIFFERS']} | {ce} | {v['NO-EDIT']} | {dict(causes.most_common(4))} |")
tot.update(dict(n=len(rs_), ident=v["IDENTICAL"], diff=v["DIFFERS"], ce=ce, noedit=v["NO-EDIT"]))
L.append(f"| **all** | {tot['n']} | {tot['ident']} | {tot['diff']} | {tot['ce']} | {tot['noedit']} | |")
judged = tot["ident"] + tot["diff"] + tot["ce"]
if judged:
L.append("")
L.append(f"**The definition's signature is a free replacement for {tot['ident']}/{judged} judged (TU, callee) pairs ({100.0 * tot['ident'] / judged:.1f} %); "
f"{tot['diff']} move bytes, {tot['ce']} refuse to compile (arity contradictions and conflicting declarations).** Per callee, the non-free "
f"cases are the declaration solver's input (T3): the definition's width or return axis, or a byte-proven K&R call.")
kr = [r for r in rows if r["verdict"] in ("DIFFERS",) and "K&R-empty" in (r.get("declared_kinds") or [])]
narrow = [r for r in rows if r["verdict"] == "DIFFERS" and "narrow parameter" in (r.get("cause") or "")]
L.append(f"\nOf the DIFFERS: {len(narrow)} have a narrow parameter in the definition (`s16/u16/s8/u8`: the caller now converts — the width rule); "
f"{len(kr)} were `()` declarations.")
L.append("\nRows: `.run/P37/probe/decl_rows.jsonl`.")
return "\n".join(L) + "\n"
# ----------------------------------------------------------------------------------------------------------------------
# (iii) the definition fold: a TU's file-scope definition that duplicates a canonical type (same layout, tier 1/2) becomes a
# typedef alias of the canonical name (or is deleted when it IS the canonical name) — judged on the object
# ----------------------------------------------------------------------------------------------------------------------
def probe_defs(a):
import lever_census as lc
aliases, dirs = sc.fleet_and_dirs()
tu_aliases, headers, _ = lc.enumerate_files(aliases, dirs)
canon = tc._walk_worker("src/shared/engine_types.h")
cdefs = {}
for d in canon["definitions"]:
for n in d["names"] + ([d["tag"]] if d["tag"] else []):
cdefs.setdefault(n, d)
for al in canon["typedef_aliases"]:
cdefs.setdefault(al["name"], dict(kind="alias", alias_of=al["alias_of"], alias_stars=al["alias_stars"], alias_dims=al["alias_dims"]))
cres = tc.Resolver(dict(cdefs))
canon_by_lhash = collections.defaultdict(list)
for d in canon["definitions"]:
if d["kind"] == "enum" or not d["fields"]:
continue
lay = cres.layout_of_fields(d["fields"], d["kind"], packed=("packed" in d["attrs"]))
if lay:
d["lhash"] = tc.layout_hash(lay)
canon_by_lhash[d["lhash"]].append(d)
def sig(d):
return tuple(("*" if re.match(r"^(?:unk|f|field_?|pad|p|a|b|c|d|w|v|m|t|x|s|u|_|arr|buf|data|val|word|byte|half|tmp|r|q|n)?[0-9A-Fa-fx_]*$", f.get("name") or "") else f["name"]) for f in d["fields"])
# candidate TUs: overlay TUs including the prelude with the most file-scope definitions
cands = []
for tu in sorted(tu_aliases):
if not tu.startswith("src/ov_"):
continue
raw = (REPO / tu).read_text(errors="surrogateescape")
if "engine_prelude.h" not in raw:
continue
n = len(re.findall(r"^(?:typedef\s+)?(?:struct|union)\b[^;]*\{", raw, re.M))
if n:
cands.append((n, tu))
cands.sort(reverse=True)
rng = random.Random(a.seed)
top = [tu for _, tu in cands[:60]]
rng.shuffle(top)
sample = top[:20]
rows = []
for tu in sample:
r = tc._walk_worker(tu)
raw = (REPO / tu).read_text(errors="surrogateescape")
scope = dict(cdefs)
for d in r["definitions"]:
for n in d["names"] + ([d["tag"]] if d["tag"] else []):
scope.setdefault(n, d)
res = tc.Resolver(scope)
ls = dl.line_starts(raw)
edits, kinds = [], collections.Counter()
for d in r["definitions"]:
if d["kind"] == "enum" or d["scope"] != "file" or not d["fields"]:
continue
lay = res.layout_of_fields(d["fields"], d["kind"], packed=("packed" in d["attrs"]))
if not lay:
kinds["unresolved"] += 1
continue
lh = tc.layout_hash(lay)
cs = canon_by_lhash.get(lh, [])
names = d["names"] + ([d["tag"]] if d["tag"] else [])
same_name = [c for c in cs if set(c["names"] + ([c["tag"]] if c["tag"] else [])) & set(names)]
if same_name:
# the canonical definition IS visible under this name: the local copy is a redefinition the TU tolerates only because
# cdecl/strip left it — delete it (R43: identical text or refuse)
if any(c["text_hash"] == d["text_hash"] for c in same_name):
edits.append((ls[d["line"] - 1], ls[d["end"]], "")); kinds["deleted-same"] += 1
else:
kinds["same-name-different-text"] += 1
continue
tier = [c for c in cs if sig(c) == sig(d) or all(x == "*" for x in sig(d) + sig(c))]
if not tier:
kinds["no-canonical-twin"] += 1 if not cs else 0
kinds["layout-twin-kept-apart"] += 1 if cs else 0
continue
c = tier[0]
cname = c["names"][0] if c["names"] else f"struct {c['tag']}"
if not d["typedef"] and d["tag"] and not d["names"]:
kinds["tag-only-local"] += 1
continue
alias_lines = "".join(f"typedef {cname} {n};\n" for n in d["names"])
edits.append((ls[d["line"] - 1], ls[d["end"]], alias_lines)); kinds["aliased"] += 1
if not edits:
rows.append(dict(tu=tu, folds=0, kinds=dict(kinds), verdict="NO-EDIT"))
continue
out, last = [], 0
for (a0, b0, rep) in sorted(edits):
out.append(raw[last:a0]); out.append(rep); last = b0
out.append(raw[last:])
text = "".join(out)
v, dt, err, _ = judge_text(tu, text, "df" + hashlib.sha1(tu.encode()).hexdigest()[:6])
rows.append(dict(tu=tu, folds=len(edits), kinds=dict(kinds), verdict=v, err=err[:160]))
print(f"{v:14s} {len(edits):3d} folds {dict(kinds)} {tu} {err[:80]}")
PROBE.mkdir(parents=True, exist_ok=True)
(PROBE / "defs_rows.jsonl").write_text("".join(json.dumps(r) + "\n" for r in rows))
v = collections.Counter(r["verdict"] for r in rows)
folds = sum(r["folds"] for r in rows)
print(f"restruct --probe-defs: {len(rows)} TUs, {folds} definitions folded onto the canonical header: {dict(v)}")
# ----------------------------------------------------------------------------------------------------------------------
# (iv) the layout engine audited against cc1
# ----------------------------------------------------------------------------------------------------------------------
def audit_layouts(a):
"""For every definition the census parsed: a probe TU asserting sizeof and every top-level named field's offset with the
negative-array-size idiom, compiled through the pinned cc1 (cdecl's oracle). 0 rejections = the engine agrees with the compiler."""
import cdecl
files = {}
defs_all = []
aliases, dirs = sc.fleet_and_dirs()
import lever_census as lc
tu_aliases, headers, _ = lc.enumerate_files(aliases, dirs)
todo = sorted(tu_aliases) + list(headers) + sorted(p.relative_to(REPO).as_posix() for p in (REPO / "include").glob("*.h"))
with ProcessPoolExecutor(max_workers=a.jobs) as ex:
for r in ex.map(tc._walk_worker, todo, chunksize=8):
files[r["rel"]] = r
# the census's resolvers, per file (its own definitions first, then the canonical header)
global_defs = {}
for r in files.values():
for d in r["definitions"]:
if d["file"].startswith("include/") or d["is_canon"]:
for n in d["names"] + ([d["tag"]] if d["tag"] else []):
global_defs.setdefault(n, d)
for al in r["typedef_aliases"]:
if al["file"].startswith("include/") or al["file"] in tc.CANON_HEADERS:
global_defs.setdefault(al["name"], dict(kind="alias", alias_of=al["alias_of"], alias_stars=al["alias_stars"], alias_dims=al["alias_dims"]))
probes, n_defs, n_fields, unresolved = [], 0, 0, 0
for rel, r in files.items():
scope = dict(global_defs)
for d in r["definitions"]:
for n in d["names"] + ([d["tag"]] if d["tag"] else []):
scope.setdefault(n, d)
for al in r["typedef_aliases"]:
scope.setdefault(al["name"], dict(kind="alias", alias_of=al["alias_of"], alias_stars=al["alias_stars"], alias_dims=al["alias_dims"]))
res = tc.Resolver(scope)
for d in r["definitions"]:
if d["kind"] == "enum" or not d["fields"] or d["scope"] != "file":
continue
lay = res.layout_of_fields(d["fields"], d["kind"], packed=("packed" in d["attrs"]))
if lay is None:
unresolved += 1
continue
n_defs += 1
offs = field_offsets(res, d)
if not d["names"] and not d["tag"]:
continue # an anonymous, tagless definition (a member's inline type) cannot be named by a probe
spans = [(x["line"], x["end"]) for x in r["definitions"] if x["scope"] == "file" and x["line"] <= d["line"]]
tname = d["names"][0] if d["names"] else (f"struct {d['tag']}" if d["kind"] == "struct" else f"union {d['tag']}")
fnspans = [(f["line"], f["fn_end"]) for f in r["fndefs"] if f.get("fn_end")]
probes.append((rel, d["line"], tname, lay[0], offs, spans, fnspans))
n_fields += len(offs)
# compile in batches: each probe is its own tiny TU (a definition may depend on its file's other types: include the file's
# own file-scope definitions verbatim before it — the masked text, in order — plus its scalar typedefs and common.h)
rej, checked = [], 0
with ProcessPoolExecutor(max_workers=a.jobs) as ex:
for res_ in ex.map(_audit_one, probes, chunksize=16):
checked += 1
if not res_[4]:
rej.append(res_)
PROBE.mkdir(parents=True, exist_ok=True)
(PROBE / "layout_audit.json").write_text(json.dumps(dict(checked=checked, definitions=n_defs, fields=n_fields, unresolved=unresolved, rejected=rej[:200]), indent=1))
print(f"restruct --audit-layouts: {checked} file-scope definitions ({n_fields} named fields) asserted against cc1; rejected {len(rej)}; "
f"unresolved layouts {unresolved}")
for r in rej[:12]:
print(" REJECT", r[0], r[1], r[2], r[3])
return 0 if not rej else 1
def cc1_accepts(body):
"""cdecl._cc1_accepts with a scratch path keyed by the body (R111: cdecl's fixed `cc1probe.c` would collide across workers)."""
import cdecl
import tempfile
d = PROBE / "cc1"
d.mkdir(parents=True, exist_ok=True)
fd, cpath = tempfile.mkstemp(dir=str(d), prefix="p", suffix=".c") # unique per call (R111): identical bodies from the
os.close(fd) # cross-address copies raced on a shared hash name
c = pathlib.Path(cpath); i = pathlib.Path(cpath[:-2] + ".i")
c.write_text(body, errors="surrogateescape")
p = subprocess.run(cdecl.CPP + ["-Isrc", str(c)], capture_output=True, text=True, cwd=REPO)
if p.returncode:
c.unlink(missing_ok=True)
return None
i.write_text(p.stdout)
r = subprocess.run([cdecl.CC1] + cdecl.CC1FLAGS + [str(i), "-o", "/dev/null"], capture_output=True, text=True, cwd=REPO)
ok = not (r.returncode or cdecl._ERR.search(r.stderr or ""))
if ok:
c.unlink(missing_ok=True); i.unlink(missing_ok=True)
else:
c.with_suffix(".err").write_text(r.stderr[-2000:])
return ok
def _audit_one(args):
rel, dline, tname, size, offs, spans, fnspans = args
raw = (REPO / rel).read_text(errors="surrogateescape")
masked = tc._blank_dead_guards(sc.mask_text(raw), raw, rel)
ls = dl.line_starts(masked)
upto = ls[spans[-1][1]] if spans else len(masked) # the target definition's end (the last span is the target)
# the TU's real environment IN ORDER: #include lines (shared BODY headers dropped — code, not types), file-scope typedef statements
# and struct/union definitions, each at its own position, up to the target; function bodies and externs left out
items = []
for m in sc.INCLUDE_LINE.finditer(masked):
if m.start() >= upto:
break
target = os.path.normpath(os.path.join(os.path.dirname(rel), m.group(1)))
if re.search(r"/func_[0-9A-Fa-f]{8}", target):
continue
inc = os.path.relpath(REPO / target, REPO / "include") if (REPO / target).exists() else m.group(1)
items.append((m.start(), f'#include "{inc}"'))
def_spans = [(ls[a0 - 1], ls[b0]) for (a0, b0) in spans]
for (a0, b0) in def_spans:
txt = masked[a0:b0].rstrip()
items.append((a0, txt if txt.endswith(";") else txt + ";"))
fn_pos = [(ls[a0 - 1], ls[b0]) for (a0, b0) in fnspans if a0 - 1 < len(ls) and b0 < len(ls)]
for m in re.finditer(r"(?<![\w.])typedef\s+[^;{}]+?;", masked):
if m.start() >= upto:
break
if any(a0 <= m.start() < b0 for (a0, b0) in def_spans):
continue # inside a definition's span already emitted
if any(a0 <= m.start() < b0 for (a0, b0) in fn_pos):
continue # a block-scope typedef inside a function body: not the file scope's
items.append((m.start(), masked[m.start():m.end()]))
items.sort()
body = "\n".join(t for _, t in items) + "\n"
if '#include "common.h"' not in body:
body = '#include "common.h"\n' + body
body += f"char p37_size[(sizeof({tname}) == {size}) ? 1 : -1];\n"
for (fname, off) in offs:
body += f"char p37_{fname}[((int)&((({tname} *)0)->{fname}) == {off}) ? 1 : -1];\n"
ok = cc1_accepts(body)
return (rel, dline, tname, size, ok)
def field_offsets(res, d):
"""Top-level NAMED, non-bitfield fields with their byte offsets under the engine's rules — the audit's assertions."""
out, off, kind = [], 0, d["kind"]
packed = "packed" in (d.get("attrs") or "")
for f in d["fields"]:
if f.get("nested"):
nd = f["nested"]
r = res.layout_of_fields(nd["fields"], nd["kind"], packed=("packed" in (nd.get("attrs") or ""))) if nd["kind"] != "enum" else (4, 4, [])
else:
r = res.layout(f["type"], f["stars"], f["dims"])
if r is None:
return out
fsz, fal, _ = r
if f.get("dims") and f["nested"]:
for dd in reversed(f["dims"]):
fsz *= (dd or 0)
fa = f.get("attrs") or ""
ma = re.search(r"aligned\s*\(\s*(\d+)\s*\)", fa)
if packed:
fal = 1
if ma:
fal = max(fal, int(ma.group(1)))
if f.get("bits") is not None:
return out # bit-field layouts are not asserted (the unit rule is compiler-specific); stop here
if kind == "union":
if f["name"]:
out.append((f["name"], 0))
continue
off = (off + fal - 1) // fal * fal
if f["name"]:
out.append((f["name"], off))
off += fsz
return out
# ----------------------------------------------------------------------------------------------------------------------
# (vi) the fan-out cost of one canonical-header edit
# ----------------------------------------------------------------------------------------------------------------------
def fanout_cost(a):
hdr = "src/shared/engine_types.h"
_, by_src, inc = recipes()
tus = inc.get(hdr, [])
recs = [r for t in tus for r in by_src.get(t, [])]
path = REPO / hdr
raw = path.read_text(errors="surrogateescape")
st = path.stat()
text = raw.replace("#endif", "typedef struct { s32 p37_probe_unused; } P37_FANOUT_PROBE;\n#endif", 1) if raw.rstrip().endswith("#endif") else raw + "\ntypedef struct { s32 p37_probe_unused; } P37_FANOUT_PROBE;\n"
t0 = time.time()
verdicts = collections.Counter()
try:
path.write_text(text, errors="surrogateescape")
def one(r):
base = oracle.baseline_bytes(r["obj"])
data, dt, err = oracle.compile_obj(r, None, tag="fo" + hashlib.sha1(r["obj"].encode()).hexdigest()[:6])
if data is None:
return "COMPILE-ERROR", dt
return ("IDENTICAL" if data == base else "DIFFERS"), dt
from concurrent.futures import ThreadPoolExecutor
cpu = 0.0
with ThreadPoolExecutor(max_workers=a.jobs) as ex:
for v, dt in ex.map(one, recs):
verdicts[v] += 1
cpu += dt
finally:
dl.restore_file(path, raw, st)
wall = time.time() - t0
print(f"restruct --fanout-cost: {hdr} reaches {len(tus)} TUs / {len(recs)} objects; one harmless typedef added: {dict(verdicts)}; "
f"wall {wall:.0f} s at -j{a.jobs} (cpu {cpu:.0f} s, {cpu / max(1, len(recs)):.3f} s/object)")
# ----------------------------------------------------------------------------------------------------------------------
def main():
ap = argparse.ArgumentParser(description=__doc__.split("\n")[0])
ap.add_argument("--try", dest="try_", nargs=2, metavar=("TU", "FN"))
ap.add_argument("--base", help="bclass:name (default: the body's largest typed base)")
ap.add_argument("--probe", action="store_true")
ap.add_argument("--sample", type=int, default=0)
ap.add_argument("--seed", type=int, default=37)
ap.add_argument("--audit-layouts", action="store_true")
ap.add_argument("--fanout-cost", action="store_true")
ap.add_argument("--rerun-differs", action="store_true", help="re-run only the DIFFERS rows of the last probe (with S2) and re-render")
ap.add_argument("--probe-decls", action="store_true", help="probe (ii): the definition's signature as the in-scope declaration, per TU x callee")
ap.add_argument("--probe-defs", action="store_true", help="probe (iii): fold a TU's duplicate file-scope definitions onto the canonical header")
ap.add_argument("-j", "--jobs", type=int, default=16)
a = ap.parse_args()
if a.try_:
row = try_body(a.try_[0], a.try_[1], base_key=a.base)
print(json.dumps(row, indent=1))
return
if a.probe:
run_probe(a)
return
if a.rerun_differs:
rows = [json.loads(l) for l in (PROBE / "probe_rows.jsonl").read_text().splitlines() if l.strip()]
out = []
for r in rows:
if r["verdict"] == "DIFFERS":
r2 = _probe_worker((r["tu"], r["fn"], r["base"], r["type"], r["stratum"], r.get("base_sites", 0), r.get("lever_body", False)))
out.append(r2)
else:
out.append(r)
with (PROBE / "probe_rows.jsonl").open("w") as fh:
for r in out:
fh.write(json.dumps(r) + "\n")
table = render_probe(out, {}, 0, a.seed)
(PROBE / "probe_table.md").write_text(table)
print(table)
return
if a.probe_decls:
probe_decls(a)
return
if a.probe_defs:
probe_defs(a)
return
if a.audit_layouts:
sys.exit(audit_layouts(a))
if a.fanout_cost:
fanout_cost(a)
return
ap.print_help()
if __name__ == "__main__":
main()