mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-26 13:33:34 -04:00
8fa29f2dda
- NEW tools/rtu_match.py: real-TU-faithful, PARALLEL-SAFE per-fn match check. Compiles the WHOLE split TU (candidate spliced, INCLUDE_ASM neutralized via -DINCLUDE_ASM(a,b)= + -Isrc/<source>) in a per-fn temp dir -> no asm/, no shared overlay build. Closes match_one's isolation blind spot (in-TU decl/global-type/memcpy-builtin drift) so a MATCH HOLDS at the whole-binary gate. - Corrected Ultracode fan-out (wf_80762f2c-822, 9 xHigh workers): 7/9 real-TU MATCH -> all 7 banked byte-identical (individual + combined d19c9580), ZERO drift (vs wave-2's ~50% attrition). Banked: func_8012FCC4 func_80133AB0 func_80134A74 func_80136824 func_8014DD8C func_80168828 func_8016C188. func_8012FCC4's "irreducible" delay-slot was a wrong-callee-arity bug. - Swept x134: 266 siblings banked / 266 failed (the 2 //@EDIT families func_80133AB0/80136824 can't per-sibling-reconcile via family_sweep -> backlog: a --edit-remap enhancement). Batch = 7 exemplars + 266 = 273 fns. - func_8014DD8C: engine_core.h DEFINE_func_8014D790 extern void->s32 flip (byte-neutral fleet-wide, caller discards return) -- R22-confirmed neutral across all 136. - 7 durable levers -> cookbook §42c (callee-arity delay-slot, pointer-global *(T**)&, array-decay CSE, §17 zero-reg copy, void->s32, register-arg capture, free-floating load hoist). rtu_match -> SETUP §6. - 2 DIFF -> permuter (func_801670E4 70->48, func_80185BA4 c=65), seeds .run/crack3/wave3/. - R22 clean-fleet 136/136 BYTE-IDENTICAL from a fully clean tree; NON_MATCHING 7 (0 in default build, G4).
99 lines
5.2 KiB
Python
99 lines
5.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Real-TU-faithful per-function match check (Phase-25 wave-3, cookbook §42b).
|
|
|
|
Unlike match_one.py (which compiles ONE function in ISOLATION and is therefore blind to
|
|
in-TU declaration conflicts -- the def-side loose-typing / memcpy-builtin / read-global-type
|
|
drift class), this compiles the WHOLE split .c with the candidate spliced in and INCLUDE_ASM
|
|
neutralized (`-DINCLUDE_ASM(a,b)=`, so no asm/ is needed and no shared overlay build is
|
|
touched -> many workers run in PARALLEL, each in its own temp dir). gcc-2.7.2 -O2 compiles each
|
|
global function independently, so the neutralized whole-TU compile reproduces the exact ambient
|
|
context (types, canonical-sig layer, the `extern memcpy` builtin-disable, file-scope global
|
|
types) that the real build sees -> its MATCH holds at the whole-binary gate far more reliably
|
|
than match_one's. STILL finish on the real `make build` whole-binary SHA gate (G3/P9).
|
|
|
|
tools/rtu_match.py func_80164930 --split ov_SC01_077_after --c cand.c
|
|
# candidate may carry file-scope TU pre-edits as leading directive lines:
|
|
# //@EDIT old_text||new_text (applied to the split .c before splicing; e.g. the s16->u16 flip)
|
|
"""
|
|
import subprocess, re, sys, os, argparse
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
import masked_diff
|
|
|
|
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
ap.add_argument('fn')
|
|
ap.add_argument('--split', required=True, help='split basename, e.g. ov_SC01_077_after')
|
|
ap.add_argument('--c', required=True, help='candidate C (the function def; may lead with //@EDIT lines)')
|
|
ap.add_argument('--source', default='ov_SC01_077')
|
|
ap.add_argument('--asm-subdir', default=None)
|
|
ap.add_argument('--work', default='.run/crack3/rtu')
|
|
ap.add_argument('--o0', action='store_true')
|
|
ap.add_argument('--maxdiff', type=int, default=60)
|
|
a = ap.parse_args()
|
|
|
|
SPLIT_SRC = 'src/%s/%s.c' % (a.source, a.split)
|
|
ASM_SUBDIR = a.asm_subdir or ('asm/%s/nonmatchings/%s' % (a.source, a.split))
|
|
CPP='mipsel-linux-gnu-cpp'; CC1='tools/bin/gcc-2.7.2-psx/cc1'
|
|
MASPSX='tools/maspsx/maspsx.py'; AS='mipsel-linux-gnu-as'; PY='.venv/bin/python'
|
|
CPPFLAGS='-lang-c -Iinclude -undef -Wall -fno-builtin -Dmips -D__GNUC__=2 -D__OPTIMIZE__ -Dpsx -D_PSYQ -D_MIPSEL -D_LANGUAGE_C'.split()
|
|
CC1FLAGS=('-quiet %s -G0 -mips1 -mcpu=3000 -mgas -msoft-float -fgnu-linker' % ('-O0' if a.o0 else '-O2')).split()
|
|
ASFLAGS='-Iinclude -march=r3000 -mtune=r3000 -no-pad-sections -O1 -G0'.split()
|
|
|
|
split_txt = open(SPLIT_SRC).read()
|
|
cand_raw = open(a.c).read()
|
|
# extract //@EDIT directives (file-scope pre-edits), strip them from the spliced body
|
|
edits = []
|
|
body_lines = []
|
|
for ln in cand_raw.split('\n'):
|
|
m = re.match(r'\s*//@EDIT\s+(.*?)\|\|(.*)$', ln)
|
|
if m:
|
|
edits.append((m.group(1), m.group(2)))
|
|
else:
|
|
body_lines.append(ln)
|
|
body = '\n'.join(body_lines).rstrip('\n')
|
|
for old, new in edits:
|
|
if old not in split_txt:
|
|
print('EDIT-FAIL: text not found in split: %r' % old); sys.exit(2)
|
|
split_txt = split_txt.replace(old, new, 1)
|
|
|
|
pat = re.compile(r'INCLUDE_ASM\([^;]*\b' + re.escape(a.fn) + r'\)\s*;')
|
|
if pat.search(split_txt):
|
|
split_txt = pat.sub(lambda m: body, split_txt, count=1)
|
|
elif re.search(r'\b%s\s*\(' % re.escape(a.fn), split_txt):
|
|
pass # already defined (validation mode: compile as-is)
|
|
else:
|
|
print('ERR: no INCLUDE_ASM stub nor def for %s in %s' % (a.fn, a.split)); sys.exit(2)
|
|
|
|
wd = '%s/%s' % (a.work, a.fn); os.makedirs(wd, exist_ok=True)
|
|
open('%s/t.c' % wd, 'w').write(split_txt)
|
|
|
|
def pipe(cmd, data=None): return subprocess.run(cmd, input=data, capture_output=True)
|
|
# -Isrc/<source> so the split's relative `#include "../shared/..."` resolves from the temp dir
|
|
p = pipe([CPP]+CPPFLAGS+['-Isrc/%s'%a.source, '-DINCLUDE_ASM(a,b)=', '%s/t.c'%wd])
|
|
if p.returncode: print('CPP FAIL\n'+p.stderr.decode()[-1500:]); sys.exit(1)
|
|
p = pipe([CC1]+CC1FLAGS, p.stdout)
|
|
if p.returncode: print('CC1 FAIL\n'+p.stderr.decode()[-2000:]); sys.exit(1)
|
|
p = pipe([PY, MASPSX, '--aspsx-version=2.56', '--expand-div'], p.stdout)
|
|
if p.returncode: print('MASPSX FAIL\n'+p.stderr.decode()[-1500:]); sys.exit(1)
|
|
p = pipe([AS]+ASFLAGS+['-o', '%s/t.o'%wd], p.stdout)
|
|
if p.returncode: print('AS FAIL\n'+p.stderr.decode()[-1500:]); sys.exit(1)
|
|
|
|
mine = masked_diff.insns_from_object('%s/t.o'%wd, a.fn)
|
|
tgt = masked_diff.insns_from_s('%s/%s.s' % (ASM_SUBDIR, a.fn))
|
|
if not mine: print('FAIL: object has no function', a.fn); sys.exit(1)
|
|
n = max(len(mine), len(tgt)); diffs=[]
|
|
for i in range(n):
|
|
mw = mine[i]['word'] if i < len(mine) else None
|
|
mask = masked_diff.mask_for(mine[i]['word'], mine[i]['reloc_kind']) if i < len(mine) else 0xFFFFFFFF
|
|
me = (mw & mask) if mw is not None else None
|
|
tg = (tgt[i]['word'] & mask) if i < len(tgt) else None
|
|
if me != tg:
|
|
diffs.append((i, ('%08x %s'%(mine[i]['word'], mine[i]['mnem'])) if i<len(mine) else '--',
|
|
('%08x %s'%(tgt[i]['word'], tgt[i]['mnem'])) if i<len(tgt) else '--'))
|
|
if not diffs and len(mine) == len(tgt):
|
|
print('MATCH (%d ins) %s' % (len(mine), a.fn)); sys.exit(0)
|
|
print('DIFF %s mine=%d ins, target=%d ins, %d mismatched' % (a.fn, len(mine), len(tgt), len(diffs)))
|
|
print(' idx | MINE | TARGET')
|
|
for i, me, tg in diffs[:a.maxdiff]:
|
|
print(' %3d | %-28s | %s' % (i, me, tg))
|
|
sys.exit(1)
|