Files
BFM-decomp/decomp-architect/corpus/tools/P4/decompile.py
T
Drew T 827295e241 tools+docs(phase-33.5): task 13.5 — the tools audit + the two dictionaries: tools/tool_census.py (two agreeing enumerations of 327 tool files; docstring/SETUP row/consumers/class derived from the tree; the authored half in config/tool_dictionary.tsv — phase · portability · the NEED each tool answers · what · adapts · verdict — with coverage asserted both ways) → docs/tool-index.md (need-keyed, KEEP-GEN, Reference-index row, wiki + how-to pointers), the kit's tools/MANIFEST.md regenerated (header states live 293 + superseded 28 = 321 rows), and the two verbatim corpora in-tree (Drew, confirmed S91): decomp-architect/corpus/tools/<phase>/ (302 copies + 28 superseded pointers + INDEX) and corpus/cookbook/ (the cookbook, its symptom index, the codegen map, a front page stating what transfers per compiler) — sha1-equal to their sources by tool_census --check in tools-health, regenerated by make kit-corpus; kit_lint exempts the corpus dirs (verbatim evidence) but syntax-checks them; G66 (consult the tool dictionary first) + G67 (translate an inherited idiom through its pass) + two memory seeds (34 at install); SETUP Step 6 installs docs/knowledge-corpus.md and checks the manifest against its own stated total; the ops-setup dictionary rows; the intake's Phase 7 cites G66/G67 and Phase 10 + Part C name the raw-cast → declared-symbol step; templates/layout-contract.md (the five-tool probe, a draft for the split). The review under Drew's criterion: 93 no-consumer tools (one Opus agent's draft, verified: 0 defects, every successor live, 0 live consumers, 0 collisions; four one-off verdicts overturned to STILL-NEEDED) → 34 retired by git mv to tools/sunset/ (28 superseded, 6 one-offs; README review table; SETUP rows moved; Archive-index group). Run 4 (fresh throwaway, the final kit): stopped on my Step-6 check (321 vs the live 293) → both sides derived → resumed → PASS 10/10, manifest 56 == 56, 4 commits, guardrails held (the one foreign path was the timeline regenerated by the detached tools-health). tools-health OK; doc_links --strict rc 0; audit_public OK over 6,842 paths; the purge probe PASSED (Phase 34's gate open). decision-log "P33.5 S91" + accelerators "P33.5 S91" banked; log + checkpoint (NEXT = task 14, xHigh, fresh session)
2026-09-07 22:09:15 -06:00

60 lines
2.4 KiB
Python

#!/usr/bin/env python3
"""decompile.py — m2c scaffold helper for the BFM matching loop (Phase 6+).
Given a function's splat label (e.g. func_80015A74, main, GameModeDispatch), locate
its disassembly under asm/nonmatchings/ and run m2c (target mipsel-gcc-c) to emit a C
scaffold to stdout. The scaffold is a STARTING POINT only — refine it by hand against
asm-differ until the diff score is 0 (G3), then paste the matched body into src/ in
place of the INCLUDE_ASM stub.
Usage:
tools/decompile.py <function> # m2c scaffold -> stdout
tools/decompile.py <function> --context src/800.c # seed m2c with existing C types
tools/decompile.py <function> --stack-structs # also emit stack-struct templates
Notes:
- m2c parses splat's annotated .s natively (no preprocessing needed).
- For complex functions, Ghidra's decompiler (via MCP get_code) is often a richer
starting point than m2c; use whichever is closer, then converge with asm-differ.
"""
import argparse
import glob
import os
import subprocess
import sys
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
M2C = os.path.join(REPO, "tools", "m2c", "m2c.py")
def find_asm(fn):
return glob.glob(os.path.join(REPO, "asm", "nonmatchings", "**", fn + ".s"), recursive=True)
def main():
ap = argparse.ArgumentParser(description="m2c scaffold helper for the BFM matching loop")
ap.add_argument("function", help="splat function label, e.g. func_80015A74")
ap.add_argument("--context", help="preprocessed C file to seed m2c types/structs (e.g. src/800.c)")
ap.add_argument("--target", default="mipsel-gcc-c", help="m2c target (default: mipsel-gcc-c)")
ap.add_argument("--stack-structs", action="store_true", help="emit stack-struct templates")
args = ap.parse_args()
hits = find_asm(args.function)
if not hits:
sys.exit(f"decompile.py: no asm/nonmatchings/**/{args.function}.s "
f"(is {args.function} still an INCLUDE_ASM stub in src/?)")
if len(hits) > 1:
sys.stderr.write(f"decompile.py: {len(hits)} matches, using {hits[0]}\n")
cmd = [sys.executable, M2C, "-t", args.target, "-f", args.function]
if args.context:
cmd += ["--context", args.context]
if args.stack_structs:
cmd.append("--stack-structs")
cmd.append(hits[0])
sys.exit(subprocess.run(cmd).returncode)
if __name__ == "__main__":
main()