Files
BFM-decomp/.run/giants/d960_hist.py
T
Drew T f9a2de3edd feat(phase-29): BEHEMOTH #2 func_8017D960 CRACKED (1806 -> 0) + its 5-member family = 16,690 ins
- CRACKED pin-free at xHigh (Opus 5 agent), then ALL FOUR family siblings banked via §40 remap,
  each MATCHING FIRST TRY: ov_SC03_090 (the crack) · ov_SC03_089 · ov_SC03_104 ·
  func_8017E778 @ ov_SC03_091 · func_8017CD9C @ ov_SC03_102 (the last two cross-address).
  Verified independently before believing the report (R14): match_one MATCH (3338 ins), then
  harvest_verify BYTE-IDENTICAL on all five binaries, then R22 clean-fleet 140/140.
- METRICS: distinct-code 3,816,534 -> 3,833,224 (+16,690) = 67.7% -> 68.0%, the first
  percentage-point movement in that metric all session. instr-weighted 80.3% -> 80.5%.
  Session distinct-code total +19,712 ins, ALL from the three behemoths; propagation gave +0.
- MY BRIEF WAS WRONG IN AN INSTRUCTIVE WAY -> §78. I said a negative length drift means "missing
  instructions". The 4 absent instructions were 4 emit tails × 1 nop -- delay slots the target
  could NOT FILL because the register it wanted was still live. otp at function scope has 4
  deaths -> fails local-alloc.c:472 -> global allocno in $a2 -> via global.c:668-671 pushes tp
  off $a1 -> the 0xFFFFFF mask is free early -> maspsx hoists it into the slot. Declaring otp
  PER EMIT ARM fixed the whole drift in one edit (3334->3338, 1806->333).
  SECOND TIME IN ONE SESSION a "structural"-looking residual was an allocno-class choice (the
  first: F510's "scheduling" transposition, §76). A nop present in the target but absent from the
  draft is usually a register-liveness fact, not missing code.
- TWO MORE REUSABLE FINDINGS (§78): gcc-2.7.2 fold NEVER leaves a literal first in an `|` chain
  (7 parenthesisations, all reassociate) -- so `or acc, var, K` first in the target means K was a
  VARIABLE in the source, an asm->source read that retires a whole sweep family. And "make it a
  variable" has TWO separable effects (fold-opacity vs a new allocno): a fresh short-lived local
  fixes structure and wrecks allocation (690 mismatched, damage ~300 ins away); reuse a busy one.
- ECONOMICS: 9 levers, each necessary by drop-one ablation, and 5 of the 9 were read straight off
  the MATCHED relatives func_8017F510 (cracked earlier today) and func_8017CA80. Crack the
  smaller family member first -- it is a lever library for the larger one.
- NEW TOOL .run/giants/s19_remap_family.py: family_remap + the §77 preamble carry in one step
  (reproduces the exemplar's FULL file-scope preamble with the tool's own substitution map
  applied). Took the 4 siblings from "4 rounds of CC1 FAIL each" to MATCH first try, ×4.
2026-07-25 15:48:54 -06:00

36 lines
1.6 KiB
Python

#!/usr/bin/env python3
"""Bucketed histogram of FULL(register-kept) divergent target indices.
usage: d960_hist.py <obj.o> [bucket]"""
import sys, re, subprocess, shutil, difflib
sys.path.insert(0, '/home/musashi/bfm-decomp/.run/giants')
OBJ = sys.argv[1]; BK = int(sys.argv[2]) if len(sys.argv) > 2 else 100
TGT = '/home/musashi/bfm-decomp/asm/ov_SC03_090/nonmatchings/ov_SC03_090_jr_8017CA80/func_8017D960.s'
OD = [c for c in ["mips-linux-gnu-objdump","mipsel-linux-gnu-objdump"] if shutil.which(c)][0]
out = subprocess.run([OD,"-drz",OBJ],capture_output=True,text=True).stdout
wm=[]
for line in out.splitlines():
m=re.match(r'\s*[0-9a-f]+:\s+([0-9a-f]{8})\s',line)
if m: wm.append(int(m.group(1),16))
elif 'R_MIPS_' in line and wm and not isinstance(wm[-1],tuple) and (wm[-1]>>26) not in (2,3): wm[-1]=('R',wm[-1])
wt=[]
for line in open(TGT):
m=re.match(r'\s*/\* \w+ [0-9A-F]{8} ([0-9A-F]{8}) \*/\s+(\S+)\s*(.*)',line)
if m:
v=int.from_bytes(bytes.fromhex(m.group(1)),'little')
wt.append(('R',v) if ('%hi(' in line or '%lo(' in line) else v)
def full(x):
rel=isinstance(x,tuple); v=x[1] if rel else x; op=(v>>26)&0x3F
if rel: return (v&0xFFFF0000,'R')
if op in (2,3): return (op<<26,'')
if op in (1,4,5,6,7): return (v&0xFFFF0000,'')
return (v,'')
a=[full(x) for x in wm]; b=[full(x) for x in wt]
sm=difflib.SequenceMatcher(None,a,b,autojunk=False)
bad=[]
for t,i1,i2,j1,j2 in sm.get_opcodes():
if t!='equal': bad.extend(range(j1,j2))
from collections import Counter
c=Counter(i//BK for i in bad)
print('total divergent %d' % len(bad))
for k in sorted(c): print(' %5d-%5d : %4d %s' % (k*BK,(k+1)*BK-1,c[k],'#'*min(60,c[k])))