mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-26 13:33:34 -04:00
69d83f7b61
Drew: we do want C correctness on all funcs, and log it for the story and the chart. - decl_repair --apply rewrote 3,439 units and repaired 16,759 declarations. check-all: 218 passed, 0 failed of 218. lever_census --check: 26,456 pin/asm sites, 0 UNMARKED — unchanged, as expected: this pass fixed TRUTH, not levers. - only the free set was touched: a declaration is repaired when every call to that function in the unit already passes the arguments, so the code was right and only the promise was wrong. Calls that pass too few remain R19's population, where the argument must be chosen and the bytes decide. - tools/readability_progress.py: the Gen3 series beside docs/levers.md, because levers are only one way the source is untrue. It counts lying call declarations (split by the K&R-empty and (void) forms, and how many sit in a body still holding an argument-register pin) and raw cast dereferences against struct member reads — the struct debt. Each row carries its date and commit so the chart is generated, never typed (R75). docs/readability.md renders it. First row after the repair: 94,001 lying declarations over 1,564 callees (86,701 (), 7,300 (void)), 461 in 314 pinned bodies; 414,148 raw cast dereferences against 173,286 struct member reads. - dictionary rows for decl_repair and readability_progress; kit corpus regenerated; tool_census --check OK.
116 lines
6.3 KiB
Python
116 lines
6.3 KiB
Python
#!/usr/bin/env python3
|
|
"""readability_progress — the Gen3 series: how honest and how readable the matched C is, over time.
|
|
|
|
WHY A SECOND SERIES. `docs/levers.md` (`tools/lever_progress.py`) counts LEVERS — the register pins and `asm`
|
|
statements this project inserted to force a byte match — and it is the chart of Phase 36's own goal. But Gen3 is about
|
|
the source being TRUE and readable, and the levers are only one of the ways it is not. Two more are measurable today and
|
|
neither is a lever:
|
|
|
|
* LYING CALL DECLARATIONS. A file declares `extern void f(void);` while the function really takes an argument. It
|
|
compiles, it matches, and it is false — and it was the largest single blocker in Phase 36's residue: six agents
|
|
independently reached byte-identical output by restoring an argument the decompiler had dropped, and the register
|
|
pin in each case existed only to fake the instruction the missing argument would have emitted.
|
|
* RAW CAST DEREFERENCES. `*(u16 *)(p + 0x12)` where the original wrote a struct field. A struct is not recoverable
|
|
from the binary — types are erased and a retail build carries no metadata — so this number falls only as the struct
|
|
layers are inferred from base+offset+width evidence and applied.
|
|
|
|
Each row is dated and carries the commit it was taken at, so the chart is reproducible from the tree rather than typed
|
|
(R75: a published number is generated). Append a row with `--snapshot "<label>"`; `--check` asserts the last row is
|
|
this tree's.
|
|
"""
|
|
import argparse, datetime, json, pathlib, re, subprocess, sys
|
|
|
|
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent))
|
|
import argcheck
|
|
|
|
REPO = pathlib.Path(__file__).resolve().parent.parent
|
|
TSV = REPO / "docs" / "readability-progress.tsv"
|
|
DOC = REPO / "docs" / "readability.md"
|
|
CAST = re.compile(r"\*\s*\(\s*(?:struct\s+|union\s+)?[A-Za-z_]\w*\s*\*+\s*\)\s*\(")
|
|
MEMBER = re.compile(r"->[A-Za-z_]\w*|\.[A-Za-z_]\w*\s*=")
|
|
|
|
|
|
def counts():
|
|
defs = argcheck.definitions()
|
|
rows = argcheck.scan(defs)
|
|
casts = members = 0
|
|
for f in (REPO / "src").glob("**/*.c"):
|
|
t = f.read_text(errors="surrogateescape")
|
|
casts += len(CAST.findall(t))
|
|
members += len(MEMBER.findall(t))
|
|
for f in (REPO / "src" / "shared").glob("**/*.h"):
|
|
t = f.read_text(errors="surrogateescape")
|
|
casts += len(CAST.findall(t))
|
|
members += len(MEMBER.findall(t))
|
|
pinned = [r for r in rows if r["argpin"]]
|
|
return dict(definitions=len(defs), narrow_decls=len(rows),
|
|
callees=len({r["callee"] for r in rows}),
|
|
kr_empty=sum(1 for r in rows if r["kind"] == "K&R-empty"),
|
|
narrow=sum(1 for r in rows if r["kind"] == "narrow"),
|
|
pinned_rows=len(pinned), pinned_bodies=len({(r["tu"], r["in_fn"]) for r in pinned}),
|
|
raw_casts=casts, struct_members=members)
|
|
|
|
|
|
HEAD = ("date\tlabel\tcommit\tdefinitions\tnarrow_decls\tcallees\tkr_empty\tnarrow\tpinned_rows\tpinned_bodies"
|
|
"\traw_casts\tstruct_members\n")
|
|
|
|
|
|
def render():
|
|
rows = [l.rstrip("\n").split("\t") for l in TSV.read_text().splitlines()[1:] if l.strip()]
|
|
out = ["# Readability of the matched C — the Gen3 series",
|
|
"",
|
|
"> **Generated by `tools/readability_progress.py --snapshot`; never typed (R75).** Companion to",
|
|
"> `docs/levers.md`, which counts the compiler-forcing levers. This one counts the two things that are wrong",
|
|
"> with the source even where no lever remains: call declarations that are FALSE, and memory read through raw",
|
|
"> pointer casts where the original had a struct.",
|
|
"",
|
|
"| date | label | commit | lying declarations | of which `()` | of which `(void)` | in a pinned body | raw cast derefs | struct member reads |",
|
|
"|---|---|---|---:|---:|---:|---:|---:|---:|"]
|
|
for r in rows:
|
|
out.append(f"| {r[0]} | {r[1]} | `{r[2][:9]}` | {r[4]} | {r[6]} | {r[7]} | {r[8]} | {r[10]} | {r[11]} |")
|
|
out += ["",
|
|
"**How to read it.** A *lying declaration* is a call site whose in-scope declaration names fewer parameters",
|
|
"than the callee's own definition. It is not a style problem: on this processor an argument travels in a",
|
|
"register, so a dropped argument removes an instruction, and Phase 36 found register pins inserted to fake",
|
|
"exactly that instruction. *Raw cast derefs* is the struct debt — a struct cannot be recovered from the",
|
|
"binary, only inferred from consistent base+offset+width evidence, so this figure falls as that work lands.",
|
|
""]
|
|
DOC.write_text("\n".join(out) + "\n")
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
ap.add_argument("--snapshot", metavar="LABEL")
|
|
ap.add_argument("--check", action="store_true")
|
|
a = ap.parse_args()
|
|
head = subprocess.run(["git", "rev-parse", "HEAD"], cwd=REPO, capture_output=True, text=True).stdout.strip()
|
|
if a.check:
|
|
if not TSV.exists():
|
|
print("readability_progress --check: no series yet")
|
|
return 1
|
|
last = TSV.read_text().splitlines()[-1].split("\t")
|
|
ok = last[2] == head
|
|
print(f"readability_progress --check: {'OK' if ok else 'STALE'} — last row {last[1]!r} at {last[2][:9]}, "
|
|
f"HEAD {head[:9]}")
|
|
return 0 if ok else 1
|
|
if not a.snapshot:
|
|
print(__doc__)
|
|
return 2
|
|
c = counts()
|
|
if not TSV.exists():
|
|
TSV.write_text(HEAD)
|
|
with TSV.open("a") as fh:
|
|
fh.write("\t".join([datetime.date.today().isoformat(), a.snapshot, head, str(c["definitions"]),
|
|
str(c["narrow_decls"]), str(c["callees"]), str(c["kr_empty"]), str(c["narrow"]),
|
|
str(c["pinned_rows"]), str(c["pinned_bodies"]), str(c["raw_casts"]),
|
|
str(c["struct_members"])]) + "\n")
|
|
render()
|
|
print(f"readability_progress: {a.snapshot} — {c['narrow_decls']} lying declaration(s) over {c['callees']} callee(s) "
|
|
f"({c['kr_empty']} `()`, {c['narrow']} `(void)`), {c['pinned_rows']} in {c['pinned_bodies']} pinned bodies; "
|
|
f"{c['raw_casts']} raw cast deref(s), {c['struct_members']} struct member read(s), at {head[:9]}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|