Files
BFM-decomp/tools/recover_giant.py
T
Drew T 0375cd529b docs(phase-22): T3 — distill §28 canonical-extern recovery + recover_giant.py + decomp.wiki patterns
- cookbook §28: the 4-way triage of a close=0 gate-rejected giant (pure-extern plumbing /
  masked-residual / struct-walled / regressed-draft) + the canonical-extern recovery (the NEW
  lever that banked func_8015126C, which Phase-21 left as a close=0 wall) + the coalescing pin
  (register __asm__ + lazy in-&& assign). §28a: PS1-applicable decomp.wiki GCC patterns
  (negative-offset loops, branch-duplication, load-coalescing, div-magic table, slti-0)
- tools/recover_giant.py: canonicalize a draft's engine_core.h-callee externs to def-sigs +
  move all externs block-scope (validated func_8015126C -> MATCH)
- R14: close=0-giant fan-out NOT uniformly near-free — only func_8015126C was pure-extern;
  the rest are struct-walled / masked-residual / regressed -> genuine per-giant hand-work
2026-06-26 03:06:54 -06:00

83 lines
3.8 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
"""recover_giant.py — the canonical-extern recovery for a "close=0 gate-rejected" giant (cookbook §28).
A giant draft that is match_one-MATCH but fails the WHOLE-binary gate with `conflicting types for
func_X` is a PURE-EXTERN PLUMBING wall: its self-contained file-scope externs guessed sigs that
clash with engine_core.h's canonical decls of the same callees (the giant calls already-matched
engine functions). sig_unify/cast_call_sites don't canonicalize these. This tool does:
1. strip // comments + scalar-typedef redefinitions; uppercase func_<hex> to the splat convention.
2. for each callee that has a `DEFINE_func_X()` in engine_core.h, rewrite the draft's extern to
that macro's exact definition signature (`extern <ret> func_X(<params>);`).
3. move ALL externs (callee + D_ data) BLOCK-scope, just inside the function body's opening brace,
so find_site/compiles_standalone/dedup_propagate lift the body + its internal externs as ONE
unit (file-scope externs are excluded from the lifted body -> false "not self-contained" skip).
Then gate it: `harvest_verify` (banks whole-binary, the sole arbiter G3/P9) -> `dedup_propagate` ×134.
This ONLY fixes the pure-extern-plumbing wall (§28 case 1). A struct-type collision (`conflicting
types for S8`, case 3), a masked residual (compiles but wrong bytes, case 2 -> permuter), or a
regressed draft (match_one DIFF, case 4 -> re-derive) are NOT addressed here — the gate decides.
Usage: tools/recover_giant.py <func_NAME> <in_draft.c> <out_draft.c>
"""
import re, sys, os
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
EC = os.path.join(REPO, "src/shared/engine_core.h")
def canon_sig(name, ec):
"""The canonical definition signature `<ret> func_X(<params>)` from DEFINE_func_X in engine_core.h."""
m = re.search(r'#define DEFINE_%s\(\)(.*?)\{' % re.escape(name), ec, re.S)
if not m:
return None
for raw in m.group(1).split('\\\n'):
line = raw.strip().rstrip('\\').strip()
if not line or line.startswith('extern') or line.startswith('#'):
continue
mm = re.match(r'([A-Za-z_].*\b%s\s*\(.*\))\s*\{?\s*$' % re.escape(name), line)
if mm:
return mm.group(1).strip()
return None
def recover(fn, src, ec):
s = re.sub(r'^[ \t]*//.*\n', '', src, flags=re.M)
s = re.sub(r'^[ \t]*typedef\b.*\b(u8|u16|u32|u64|s8|s16|s32|s64|f32|f64)[ \t]*;[ \t]*\n', '', s, flags=re.M)
s = re.sub(r'func_([0-9A-Fa-f]+)', lambda m: 'func_' + m.group(1).upper(), s)
lines = s.split('\n')
externs = [l for l in lines if l.strip().startswith('extern')]
rest = [l for l in lines if not l.strip().startswith('extern')]
def canon_extern(line):
m = re.search(r'\b(func_[0-9A-F]+)\b', line)
if not m:
return line.strip() # D_ data extern — keep as-is
cs = canon_sig(m.group(1), ec)
return ('extern ' + cs + ';') if cs else line.strip()
externs = [canon_extern(l) for l in externs]
di = next((i for i, l in enumerate(rest)
if re.match(r'^[A-Za-z_][\w \*]*\b' + re.escape(fn) + r'\s*\(', l) and ';' not in l), None)
if di is None:
raise SystemExit(f"recover_giant: could not find the definition of {fn} in the draft")
bi = di
while bi < len(rest) and '{' not in rest[bi]:
bi += 1
new = rest[:bi + 1] + [' ' + e for e in externs] + rest[bi + 1:]
return '\n'.join(new), len(externs)
def main():
if len(sys.argv) != 4:
raise SystemExit(__doc__)
fn, inp, outp = sys.argv[1], sys.argv[2], sys.argv[3]
out, n = recover(fn, open(inp).read(), open(EC).read())
os.makedirs(os.path.dirname(outp) or ".", exist_ok=True)
open(outp, 'w').write(out)
print(f"recovered {fn}: {n} externs canonicalized + block-scoped -> {outp}")
if __name__ == "__main__":
main()