Files
BFM-decomp/tools/gen_engine_decls.py
T
Drew T 9062f0fcc0 docs(phase-16): PIVOT — m2c+permuter won't crack the loose-typed core; new plan + harness fixes
- docs/struct-core-pivot.md: findings + decision + new research directions. Root cause = the
  original engine is LOOSELY TYPED (K&R; same fn called with int/ptr, arg/no-arg across sites),
  so no single canonical signature exists -> m2c guesses inconsistently, permuter can't fix
  semantics, byte-gate (correctly) rejects. Yields ~3%, not the crack. New plan: emulator-recover
  the actor struct/types -> Ghidra global type propagation -> Ghidra-C -> permuter+gate.
- harness bug-fixes (REAL, kept): p16_permute output-0-only match (killed the false '42%'),
  base.c keeps callee externs, winner_to_draft line-strip; sig_unify canonicalizes m2c's
  no-extern prototypes; gen_engine_decls.py (documents why a global canonical header breaks
  loose-typed matches).
- a few byte-gated leaf matches banked in ov_SC01_077.c.
2026-06-19 10:05:58 -06:00

105 lines
4.6 KiB
Python

#!/usr/bin/env python3
"""gen_engine_decls.py — the canonical-decls header (Phase 16, Drew's "lever 1", cookbook §14c-c).
Declare EVERY engine function (from its definition signature) and EVERY data symbol ONCE, canonically,
in src/shared/engine_decls.h. The overlay .c includes it; harvest drafts then strip ALL their own
declarations -> a draft can NEVER conflict with engine_core.h's definitions or another draft's guess.
This dissolves the dominant whole-binary gate-failure (`conflicting types for func_X`) on call-heavy
functions, which m2c declares with a guessed signature (and without `extern`, so sig_unify misses them).
python3 tools/gen_engine_decls.py # regenerate src/shared/engine_decls.h
"""
import os, re
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
ECORE = os.path.join(REPO, "src/shared/engine_core.h")
OVC = os.path.join(REPO, "src/ov_SC01_077/ov_SC01_077.c")
OUT = os.path.join(REPO, "src/shared/engine_decls.h")
# a function DEFINITION signature: `<ret> func_XXXX(<params>) {` (in an engine_core.h macro or inline)
DEF_RE = re.compile(r"\b([A-Za-z_][\w \*]*\bfunc_[0-9A-Fa-f]+\s*\([^){;]*\))\s*\{")
DATA_RE = re.compile(r"extern\s+([A-Za-z_][\w\s\*]*?\bD_[0-9A-Fa-f]+\s*(?:\[\s*\])?)\s*;")
SCALARS = {"void", "u8", "u16", "u32", "s8", "s16", "s32", "u64", "s64", "f32", "f64", "char",
"int", "short", "long", "unsigned", "signed", "float", "double",
"M2C_UNK", "M2C_UNK8", "M2C_UNK16", "M2C_UNK32", "M2C_UNK64"}
def known_structs():
"""struct names declared in engine_types.h (forward decls + defs)."""
names = set()
p = os.path.join(REPO, "src/shared/engine_types.h")
if os.path.exists(p):
for m in re.finditer(r"\b(?:struct|union)\s+([A-Za-z_]\w*)", open(p).read()):
names.add(m.group(1))
return names
def resolvable(sig, structs):
"""True iff every type token in the signature is a scalar or a known shared struct/union."""
s = re.sub(r"\bfunc_[0-9A-Fa-f]+\b", "", sig) # drop the function name
s = re.sub(r"\b[a-z_]\w*\s*(?=[,)])", "", s) # drop param names (lowercase, before , or ))
for m in re.finditer(r"\b(struct|union)\s+([A-Za-z_]\w*)", s):
if m.group(2) not in structs:
return False
s2 = re.sub(r"\b(struct|union)\s+\w+", "", s) # remove resolved struct refs
for tok in re.findall(r"\b([A-Za-z_]\w*)\b", s2):
if tok not in SCALARS:
return False
return True
def def_sigs(path, structs):
"""addr -> canonical prototype string, from every RESOLVABLE function DEFINITION in `path`."""
out = {}
if not os.path.exists(path):
return out
for m in DEF_RE.finditer(open(path).read()):
sig = re.sub(r"\s+", " ", m.group(1)).strip().rstrip("\\").strip()
if not resolvable(sig, structs):
continue
a = int(re.search(r"func_([0-9A-Fa-f]+)", sig).group(1), 16)
out.setdefault(a, sig) # first definition wins (engine_core.h is authoritative)
return out
def data_decls(paths):
out = {}
for p in paths:
if not os.path.exists(p):
continue
for m in DATA_RE.finditer(open(p).read()):
decl = re.sub(r"\s+", " ", m.group(1)).strip()
sym = re.search(r"D_[0-9A-Fa-f]+", decl).group(0)
out.setdefault(sym, decl)
return out
def main():
structs = known_structs()
funcs = {}
funcs.update(def_sigs(ECORE, structs)) # engine_core.h macro defs (authoritative)
for a, s in def_sigs(OVC, structs).items(): # + inline 077-local defs
funcs.setdefault(a, s)
data = {k: v for k, v in data_decls([ECORE, OVC]).items() if resolvable(v, structs)}
lines = ["#ifndef BFM_ENGINE_DECLS_H", "#define BFM_ENGINE_DECLS_H",
"/* src/shared/engine_decls.h — GENERATED by tools/gen_engine_decls.py (Phase 16 lever 1).",
" * Canonical declaration of every engine function + data symbol, so harvest drafts strip",
" * their own decls and can never `conflicting types`. Regenerate after new matches land. */",
'#include "common.h"', '#include "engine_types.h"', ""]
lines.append(f"/* {len(funcs)} function prototypes */")
for a in sorted(funcs):
lines.append(funcs[a] + ";")
lines.append("")
lines.append(f"/* {len(data)} data symbols */")
for sym in sorted(data):
lines.append(f"extern {data[sym]};")
lines += ["", "#endif"]
open(OUT, "w").write("\n".join(lines) + "\n")
print(f"wrote {OUT}: {len(funcs)} func protos + {len(data)} data decls")
if __name__ == "__main__":
main()