mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-28 14:59:48 -04:00
876dc7f053
Ultracode wave: 16 isolated drafters over the resident's non-jtbl stubs (the 5 jtbl deferred —
they need the rodata-island carve, §53). Drafts only; the whole-binary byte-gate arbitrated after.
- BANKED 7/16, byte-gated: func_800CEFD0(77) func_800D0D7C(45) func_800D1B80(22) func_800D1E28(37)
func_800D1FC8(62) func_800D29F8(172) func_800D2D10(39).
Resident REAL 122 -> 129, stubs 21 -> 14, byte-ident 124/145 (85.52%) -> 131/145 (90.34%).
FLEET instr 9017152 -> 9017606 (+454 ins). R22 make clean && extract-all && check-all ->
140 passed, 0 failed of 140 (the first R22 was killed by a terminal crash and RE-RUN, not assumed).
Ground truth on 14 agrees 3 ways: source grep, splat-emitted stub .s count, progress.py.
- §52b's LAW, MEASURED AGAIN INDEPENDENTLY: the agents self-reported 11 match_one MATCH; the
whole-binary gate banked 7 (64%). All 4 blocked MATCHes died on `conflicting types`
(D_8010EDEC / D_80115110 / func_800D1984 / cdFileLocTable) — the loose-typing def-side wall, NOT
codegen. gate_stage's recovery banked 0/5 on them. A match_one MATCH is a CANDIDATE (G3/P9).
- FIX — match_one's isolation was FAKE, and its own docstring was the false spec. It promises
"Fully isolated (own temp dir) so many run in PARALLEL with no shared build -- a real asm-differ
loop for an agent to iterate against", while `--work` defaulted to the SHARED '.run/match': every
concurrent caller compiled into the same t.c/t.o. FOUND BY AN AGENT MID-WAVE, the only way it can
be found — it read another agent's function out of its own scratch ("found another agent's
func_800D2650 in my t.c") and reported it. Every other agent steered by a loop that could hand it
someone else's compile: a CONFIDENT WRONG verdict, worse than a crash. Default is now a private
.run/match/<fn>.<pid>; the default IS the promise. (Some agents had already worked around it by
passing --work themselves.) The byte-gate was never at risk — it is the sole arbiter — but the
iteration loop the agents steer by absolutely was.
- The 14 remaining: 5 PLUMBING (loose-typing) + 4 DIFF (genuine codegen: func_800D2650 close=4,
func_800CFAD0 close=5, func_800D0E30 close=12, func_800D27DC close=48) + 5 jtbl deferred.
Dossier next (T5b) — the agents' per-function residual analyses are the durable asset (R30).
114 lines
6.2 KiB
Python
114 lines
6.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Per-function matching check: compile ONE function's C standalone (the pinned triple), mask
|
|
relocations (jal/j 26-bit + HI16/LO16 immediates, like psyq_identify), and compare to the target
|
|
bytes in its splat .s. Fully isolated (own temp dir) so many run in PARALLEL with no shared build --
|
|
a real asm-differ loop for an agent to iterate against.
|
|
|
|
Prints "MATCH (N ins)" on a byte-exact (relocation-masked) match, else a per-instruction diff.
|
|
gcc-2.7.2 -O2 compiles each global function independently (no cross-TU inlining of externs), so a
|
|
standalone match == the whole-binary byte-gate for that function -- MODULO in-TU declaration
|
|
conflicts (def-side loose-typing) and -O0 subsegments; ALWAYS finish on the whole-binary gate (G3/P9).
|
|
|
|
The masking + objdump live in tools/masked_diff.py (shared with the permuter's MaskedScorer). NOTE
|
|
(Phase 24 T2): objdump is now `-drz`, NOT `-dr` -- `-z` keeps runs of nops so GTE-heavy seeds are no
|
|
longer under-counted (func_80132784 now reads its true 400 ins, not 384).
|
|
|
|
python3 tools/match_one.py func_800D1DB0 # uses .run/drafts3|2|/<fn>.c
|
|
python3 tools/match_one.py func_800D1DB0 --c /path/to.c # explicit C file
|
|
"""
|
|
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('--c', help='C file (externs + the function def). Default: search .run/drafts3|2|/')
|
|
ap.add_argument('--asm-subdir', default='asm/resident/nonmatchings/resident')
|
|
ap.add_argument('--work', default=None,
|
|
help='scratch dir. Default: a PRIVATE per-invocation dir (.run/match/<fn>.<pid>). '
|
|
'It used to default to the SHARED ".run/match", which silently broke the one '
|
|
'property this tool advertises — see below.')
|
|
ap.add_argument('--o0', action='store_true',
|
|
help='compile at -O0 (for the _o0 split subsegments: ov_SC01_077_o0.c, whale _o0b — '
|
|
'their target bytes are -O0; an -O2 compile can never match them, Makefile:445)')
|
|
a = ap.parse_args()
|
|
|
|
# PRIVATE SCRATCH BY DEFAULT (Phase-28 T5). This tool's own docstring promises "Fully isolated (own
|
|
# temp dir) so many run in PARALLEL with no shared build -- a real asm-differ loop for an agent to
|
|
# iterate against." That was FALSE: --work defaulted to the shared '.run/match', so every concurrent
|
|
# caller compiled into the SAME t.c/t.o. A 16-agent wave found it the only way it can be found — one
|
|
# agent read another's function out of its own scratch file ("found another agent's func_800D2650 in
|
|
# my t.c") and said so. Every other agent in that wave iterated against a loop that could silently
|
|
# hand it someone else's compile, which is worse than a crash: it produces a CONFIDENT WRONG verdict.
|
|
# The docstring was the spec; the default contradicted it; nothing checked. Now the default IS the
|
|
# promise. (The whole-binary byte-gate was never at risk — it is the sole arbiter, G3/P9 — but the
|
|
# iteration loop agents steer by absolutely was.)
|
|
if not a.work:
|
|
a.work = os.path.join('.run/match', f'{a.fn}.{os.getpid()}')
|
|
|
|
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()
|
|
|
|
cfile = a.c
|
|
if not cfile:
|
|
for d in ('.run/drafts3', '.run/drafts2', '.run/drafts'):
|
|
if os.path.exists('%s/%s.c' % (d, a.fn)):
|
|
cfile = '%s/%s.c' % (d, a.fn); break
|
|
if not cfile or not os.path.exists(cfile):
|
|
print('FAIL: no C file for', a.fn); sys.exit(2)
|
|
|
|
wd = '%s/%s' % (a.work, a.fn); os.makedirs(wd, exist_ok=True)
|
|
src = open(cfile).read()
|
|
# strip inline scalar-typedef redefinitions (common.h provides them; C89 rejects the dup).
|
|
# The T4 primitive splits multi-typedef lines the old regex couldn't cross (42 discarded MATCHes).
|
|
src = masked_diff.strip_scalar_typedefs(src)
|
|
if '#include "common.h"' not in src:
|
|
src = '#include "common.h"\n' + src
|
|
open('%s/t.c' % wd, 'w').write(src)
|
|
|
|
|
|
def pipe(cmd, data=None):
|
|
return subprocess.run(cmd, input=data, capture_output=True)
|
|
|
|
|
|
p = pipe([CPP] + CPPFLAGS + ['%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()[-1800:]); 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)
|
|
|
|
# masked compare: my compiled object vs the resolved splat .s (mask driven by my object's relocs)
|
|
mine = masked_diff.insns_from_object('%s/t.o' % wd, a.fn)
|
|
tgt = masked_diff.insns_from_s('%s/%s.s' % (a.asm_subdir, a.fn))
|
|
|
|
if not mine:
|
|
print('FAIL: my object has no function', a.fn, '(compile produced nothing?)'); 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[:40]:
|
|
print(' %3d | %-28s | %s' % (i, me, tg))
|
|
sys.exit(1)
|