diff --git a/.run/giants/bf14_ali.py b/.run/giants/bf14_ali.py new file mode 100644 index 000000000..4630c2ac9 --- /dev/null +++ b/.run/giants/bf14_ali.py @@ -0,0 +1,68 @@ +"""Anchor-segmented shape alignment (robust against repetitive-block confusion). +Anchors = the 56 `mult` sites (+ start/end). Aligns segment-by-segment. +usage: bf14_ali.py [--worst N]""" +import re,subprocess,difflib,sys +OBJ=sys.argv[1] +TGT='/home/musashi/bfm-decomp/asm/ov_SC03_116/nonmatchings/ov_SC03_116_jr_8017AE2C/func_8017BF14.s' +def load_tgt(): + W=[];T=[] + for l in open(TGT): + m=re.match(r'\s*/\* \w+ [0-9A-F]{8} ([0-9A-F]{8}) \*/\s+(.*?)\s*$',l) + if m: + v=int.from_bytes(bytes.fromhex(m.group(1)),'little') + W.append(('R',v) if ('%hi(' in l or '%lo(' in l) else v); T.append(re.sub(r'\s+',' ',m.group(2))) + return W,T +def load_mine(): + out=subprocess.run(['mipsel-linux-gnu-objdump','-drz',OBJ],capture_output=True,text=True).stdout + W=[];T=[];inside=False + for l in out.splitlines(): + if re.match(r'^[0-9a-f]+ :',l): inside=True;continue + if inside: + if re.match(r'^[0-9a-f]+ <',l) and W: break + m=re.match(r'\s*[0-9a-f]+:\s+([0-9a-f]{8})\s+(.*)',l) + if m: W.append(int(m.group(1),16)); T.append(re.sub(r'\s+',' ',m.group(2).strip())) + elif 'R_MIPS_' in l and W and not isinstance(W[-1],tuple) and (W[-1]>>26) not in (2,3): + W[-1]=('R',W[-1]) + return W,T +MEM=set(range(0x20,0x30))|{0x32,0x3A} +def shape(x): + rel=isinstance(x,tuple); v=x[1] if rel else x + op=(v>>26)&0x3F + if op==0 or op==0x1C: return ((op<<12)|(v&0x3F)|(((v>>6)&0x1F)<<7),'R' if rel else '') + if op in (2,3): return (op<<26,'') + if op==0x12: return (v&0xFC1F07FF,'') + if op in (1,4,5,6,7): return ((op<<20)|(((v>>16)&0x1F)<<8),'') + imm=0 if rel else (v&0xFFFF) + if op in MEM and ((v>>21)&0x1F)==29: imm=0 + return ((op<<20)|imm,'R' if rel else '') +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,'') + +def anchors(W): + return [i for i,v in enumerate(W) if not isinstance(v,tuple) and (v&0xFC00003F)==0x18] # SPECIAL mult +TW,TT=load_tgt(); MW,MT=load_mine() +ta=anchors(TW); ma=anchors(MW) +if len(ta)!=len(ma): + print('ANCHOR-MISMATCH tgt=%d mine=%d (falling back to global)'%(len(ta),len(ma))) + ta=[];ma=[] +tb=[0]+ta+[len(TW)]; mb=[0]+ma+[len(MW)] +tot=0; ftot=0; segs=[] +for k in range(len(tb)-1): + a=[shape(x) for x in MW[mb[k]:mb[k+1]]]; b=[shape(x) for x in TW[tb[k]:tb[k+1]]] + sm=difflib.SequenceMatcher(None,a,b,autojunk=False) + eq=sum(i2-i1 for t,i1,i2,j1,j2 in sm.get_opcodes() if t=='equal') + fa=[full(x) for x in MW[mb[k]:mb[k+1]]]; fb=[full(x) for x in TW[tb[k]:tb[k+1]]] + fsm=difflib.SequenceMatcher(None,fa,fb,autojunk=False) + feq=sum(i2-i1 for t,i1,i2,j1,j2 in fsm.get_opcodes() if t=='equal') + tot+=eq; ftot+=feq; segs.append((tb[k+1]-tb[k]-eq, k, tb[k],tb[k+1], mb[k],mb[k+1])) +print('ANCHOR-ALIGNED shape %d / %d = %.2f%% BYTE %d = %.2f%% (mine=%d target=%d)' + %(tot,len(TW),100.0*tot/len(TW),ftot,100.0*ftot/len(TW),len(MW),len(TW))) +if '--worst' in sys.argv: + n=int(sys.argv[sys.argv.index('--worst')+1]) + for bad,k,t0,t1,m0,m1 in sorted(segs,reverse=True)[:n]: + print(' seg %3d bad=%4d tgt[%d:%d](%d) mine[%d:%d](%d)'%(k,bad,t0,t1,t1-t0,m0,m1,m1-m0)) diff --git a/.run/giants/bf14_hist.py b/.run/giants/bf14_hist.py new file mode 100644 index 000000000..923d47c3c --- /dev/null +++ b/.run/giants/bf14_hist.py @@ -0,0 +1,25 @@ +"""opcode-histogram delta mine-vs-target (alignment free). usage: bf14_hist.py [--sum]""" +import re,collections,subprocess,sys +OBJ=sys.argv[1] +TGT='/home/musashi/bfm-decomp/asm/ov_SC03_116/nonmatchings/ov_SC03_116_jr_8017AE2C/func_8017BF14.s' +t=collections.Counter() +for l in open(TGT): + m=re.match(r'\s*/\* \w+ \w+ \w+ \*/\s+(\S+)',l) + if m: t['GTE' if m.group(1) in ('rtps','rtpt','nclip') else m.group(1)]+=1 +out=subprocess.run(['mipsel-linux-gnu-objdump','-drz',OBJ],capture_output=True,text=True).stdout +alias={'move':'addu','li':'addiu','b':'j','negu':'subu','c2':'GTE'} +m=collections.Counter(); inside=False +for l in out.splitlines(): + if re.match(r'^[0-9a-f]+ :',l): inside=True; continue + if inside: + if re.match(r'^[0-9a-f]+ <',l): break + mm=re.match(r'\s*[0-9a-f]+:\s+[0-9a-f]{8}\s+(\S+)',l) + if mm: m[alias.get(mm.group(1),mm.group(1))]+=1 +d={k:m[k]-t[k] for k in set(t)|set(m) if m[k]!=t[k]} +tot=sum(m.values())-sum(t.values()) +if '--sum' in sys.argv: + print('len%+d L1=%d %s'%(tot,sum(abs(v) for v in d.values()), + ' '.join('%s%+d'%(k,v) for k,v in sorted(d.items(),key=lambda kv:-abs(kv[1]))))) +else: + for k,v in sorted(d.items(),key=lambda kv:-abs(kv[1])): print('%-8s %+d'%(k,v)) + print('TOTAL %+d'%tot) diff --git a/.run/giants/bf14_land.py b/.run/giants/bf14_land.py new file mode 100644 index 000000000..58845ed98 --- /dev/null +++ b/.run/giants/bf14_land.py @@ -0,0 +1,30 @@ +"""Landmark alignment: index of each occurrence of rare opcodes in mine vs target.""" +import re,subprocess,sys,collections +OBJ=sys.argv[1] +TGT='/home/musashi/bfm-decomp/asm/ov_SC03_116/nonmatchings/ov_SC03_116_jr_8017AE2C/func_8017BF14.s' +def tgt(): + o=[] + for l in open(TGT): + m=re.match(r'\s*/\* \w+ \w+ \w+ \*/\s+(\S+)',l) + if m: o.append(m.group(1)) + return o +def mine(): + out=subprocess.run(['mipsel-linux-gnu-objdump','-drz',OBJ],capture_output=True,text=True).stdout + o=[];inside=False + alias={'move':'addu','li':'addiu','b':'j','negu':'subu'} + for l in out.splitlines(): + if re.match(r'^[0-9a-f]+ :',l): inside=True;continue + if inside: + if re.match(r'^[0-9a-f]+ <',l): break + m=re.match(r'\s*[0-9a-f]+:\s+[0-9a-f]{8}\s+(\S+)',l) + if m: o.append(alias.get(m.group(1),m.group(1))) + return o +T,M=tgt(),mine() +OPS=sys.argv[2].split(',') if len(sys.argv)>2 else ['mult'] +ti=[i for i,x in enumerate(T) if x in OPS] +mi=[i for i,x in enumerate(M) if x in OPS] +print('target %d occurrences, mine %d'%(len(ti),len(mi))) +for k in range(max(len(ti),len(mi))): + a=ti[k] if k lever [lever ...] +Every transformation asserts it applied, so a "neutral" reading can never be a +silent no-op edit (d960_mk.py discipline).""" +import sys, re + +def _one(src, old, new, n=1): + c = src.count(old) + assert c == n, 'expected %d occurrences, found %d of %r' % (n, c, old[:80]) + return src.replace(old, new) + +RDECL = (' s16 r0;\n s16 r1;\n s16 r2;\n s16 r3;\n' + ' s16 r0lo;\n s16 r1lo;\n s16 r2lo;\n s16 r3lo;\n') + +LEVERS = {} +def lever(fn): + LEVERS[fn.__name__] = fn + return fn + +def rdecl_order(src, order): + """order: e.g. '0,0lo,1,1lo,2,2lo,3,3lo'""" + new = ''.join(' s16 r%s;\n' % t for t in order.split(',')) + return _one(src, RDECL, new) + +# ---- assignment order inside each box's if-block --------------------------- +BOXASN = { + 0: (' r0lo = D_80197C34 - 0x200;\n r0 = D_80197C34;\n', + ' r0 = D_80197C34;\n r0lo = D_80197C34 - 0x200;\n'), + 1: (' r1 = D_80197C50;\n r1lo = D_80197C50 - 0x200;\n', + ' r1lo = D_80197C50 - 0x200;\n r1 = D_80197C50;\n'), + 2: (' r2 = D_80197C6C;\n r2lo = D_80197C6C - 0x200;\n', + ' r2lo = D_80197C6C - 0x200;\n r2 = D_80197C6C;\n'), + 3: (' r3lo = D_80197C6C - 0x200;\n r3 = D_80197C88;\n', + ' r3 = D_80197C88;\n r3lo = D_80197C6C - 0x200;\n'), +} +def swapbox(src, k): + a, b = BOXASN[k] + return _one(src, a, b) + +for k in range(4): + def mk(k=k): + def f(src): return swapbox(src, k) + return f + g = mk(); g.__name__ = 'swapasn%d' % k; lever(g) + +# ---- the r-init block ------------------------------------------------------- +RINIT = (' r2lo = 0;\n r1lo = 0;\n r0lo = 0;\n' + ' r2 = 0;\n r1 = 0;\n r0 = 0;\n') +def rinit(src, spec): + new = ''.join(' r%s = 0;\n' % t for t in spec.split(',')) if spec else '' + return _one(src, RINIT, new) + +# ---- misc ------------------------------------------------------------------- +@lever +def basefn(src): + src = _one(src, ' base = D_800AF630;\n', '') + return _one(src, '(*(u16 *)(base + 0xA3D2))', '(*(u16 *)(D_800AF630 + 0xA3D2))') + +@lever +def baselate(src): + src = _one(src, ' base = D_800AF630;\n', '') + return _one(src, ' pkt = D_800A5E60;\n', ' base = D_800AF630;\n pkt = D_800A5E60;\n') + +@lever +def fzero_fwd(src): + return _one(src, 'f3 = 0; f2 = 0; f1 = 0; f0 = 0;', + 'f0 = 0; f1 = 0; f2 = 0; f3 = 0;', 2) + +@lever +def mnydial1(src): + o = ' s32 za, zb;\n' + return _one(src, o, o + ' __asm__ __volatile__ ("" :: "r" (mny));\n') + +@lever +def primorder(src): + return _one(src, + ' nprim = part->nprim;\n prim = (Prim *)part->prim;', + ' prim = (Prim *)part->prim;\n nprim = part->nprim;') + +@lever +def r3own(src): + """box3 uses its OWN range global for r3lo (not the copy-paste of box2's).""" + return _one(src, ' r3lo = D_80197C6C - 0x200;\n', ' r3lo = D_80197C88 - 0x200;\n') + + +@lever +def cb101010(src): + """unlit rgbc is (tp[0] & 0xFF000000) | 0x101010, not plain black.""" + return _one(src, 'cb = tp[0] & 0xFF000000;', + 'cb = (tp[0] & 0xFF000000) | 0x101010;', 2) + +@lever +def k101010(src): + """same, but through a dedicated function-scope constant holder.""" + src = _one(src, ' u32 cb;\n', ' u32 cb;\n u32 kk;\n') + src = _one(src, ' base = D_800AF630;\n', ' base = D_800AF630;\n kk = 0x101010;\n') + return _one(src, 'cb = tp[0] & 0xFF000000;', 'cb = (tp[0] & 0xFF000000) | kk;', 2) + + +# ---- declaration-scope levers (cookbook 76) -------------------------------- +CULL = ['\n' + ' '*44 + 's32 za, zb;\n', + '\n' + ' '*48 + 's32 za, zb;\n'] +LIT = [' u32 *otp;\n', + ' u32 *otp;\n'] +UNLIT= [' u32 *otp;\n', + ' u32 *otp;\n'] + +def _movedecl(src, fndecl, anchors): + """delete the function-scope decl line and re-insert after each anchor.""" + src = _one(src, ' %s\n' % fndecl, '') + for a in anchors: + ind = ' ' * (len(a.rstrip('\n').split('\n')[-1]) - len(a.rstrip('\n').split('\n')[-1].lstrip())) + src = _one(src, a, a + ind + fndecl + '\n') + return src + +def scope(src, fndecl, where): + if where == 'cull': return _movedecl(src, fndecl, CULL) + if where == 'lit': + # 4 occurrences of `u32 *otp;` (2 lit + 2 unlit arms); target only the LIT ones + out = src.replace(' %s\n' % fndecl, '', 1) + assert out != src + for a in (' if (f0 | f1 | f2 | f3) {\n u32 *otp;\n', + ' if (f0 | f1 | f2 | f3) {\n u32 *otp;\n'): + ind = ' ' * (len(a.split('\n')[1]) - len(a.split('\n')[1].lstrip())) + out = _one(out, a, a + ind + fndecl + '\n') + return out + raise KeyError(where) + +def _mkscope(name, decl, where): + def f(src): return scope(src, decl, where) + f.__name__ = name; lever(f); return f + +_mkscope('xyz_cull', 's16 x0, y0, z0, x1, y1, z1, x2, y2, z2;', 'cull') +_mkscope('a_cull', 's32 a0v, a1v, a2v, a3v;', 'cull') +_mkscope('c_cull', 's32 c0, c1, c2, c3;', 'cull') +_mkscope('d_cull', 's32 d;', 'cull') +_mkscope('f_cull', 's32 f0, f1, f2, f3;', 'cull') +_mkscope('tp_cull', 'u32 *tp;', 'cull') +_mkscope('a_lit', 's32 a0v, a1v, a2v, a3v;', 'lit') +_mkscope('c_lit', 's32 c0, c1, c2, c3;', 'lit') +_mkscope('d_lit', 's32 d;', 'lit') +_mkscope('xyz_lit', 's16 x0, y0, z0, x1, y1, z1, x2, y2, z2;', 'lit') + +@lever +def novw(src): + """drop the dedicated vertex-word temps; reuse w / wz.""" + src = _one(src, ' u32 vw, vzw;\n', '') + src = src.replace('vw = *(u32 *)', 'w = *(u32 *)').replace('vzw = *(u32 *)', 'wz = *(u32 *)') + src = re.sub(r'= vw;( |$)', r'= w;\1', src) + src = src.replace('vw >> 16', 'w >> 16').replace('= vzw;', '= wz;') + assert 'vw' not in src and 'vzw' not in src + return src + +def dial(src, name, n=2): + """RC-15 zero-byte ref dial: first statement of each cull block.""" + for a in CULL: + last = a.rstrip('\n').split('\n')[-1] + ind = ' ' * (len(last) - len(last.lstrip())) + src = _one(src, a, a + ind + '__asm__ __volatile__ ("" :: "r" (%s));\n' % name) + return src + +@lever +def dial_mny(src): return dial(src, 'mny') +@lever +def dial_my(src): return dial(src, 'my') +@lever +def dial_r1lo(src): return dial(src, 'r1lo') +@lever +def dial_r0lo(src): return dial(src, 'r0lo') +@lever +def dial_r1(src): return dial(src, 'r1') + + +# ---- generic zero-byte ref dial on any variable, at a chosen anchor -------- +PRIMHEAD = (' for (i = 0; i < nprim; i++, prim++) {\n' + ' w = prim->w1;\n') +LITARM = ['\n' + ' '*44 + 'if (f0 | f1 | f2 | f3) {\n' + ' '*44 + 'u32 *otp;\n', + '\n' + ' '*48 + 'if (f0 | f1 | f2 | f3) {\n' + ' '*48 + 'u32 *otp;\n'] + +def dialat(src, name, where): + stmt = '__asm__ __volatile__ ("" :: "r" (%s));\n' + if where == 'cull': + for a in CULL: + last = a.rstrip('\n').split('\n')[-1] + ind = ' ' * (len(last) - len(last.lstrip())) + src = _one(src, a, a + ind + stmt % name) + elif where == 'prim': + src = _one(src, PRIMHEAD, PRIMHEAD + ' '*24 + stmt % name) + elif where == 'lit': + for a in LITARM: + last = a.rstrip('\n').split('\n')[-1] + ind = ' ' * (len(last) - len(last.lstrip())) + src = _one(src, a, a + ind + stmt % name) + else: raise KeyError(where) + return src + +# ---- move the whole r-declaration block ------------------------------------ +def rpos(src, where): + src2 = _one(src, RDECL, '') + if where == 'top': + return _one(src2, ' s32 j;\n', RDECL + ' s32 j;\n') + if where == 'bot': + return _one(src2, ' u32 rgbw;\n', ' u32 rgbw;\n' + RDECL) + if where == 'prelo': + return _one(src2, ' s16 lo0x, hi0x,', RDECL + ' s16 lo0x, hi0x,') + if where == 'precxy': + return _one(src2, ' s16 cx0, cy0, cz0,', RDECL + ' s16 cx0, cy0, cz0,') + raise KeyError(where) + +# ---- per-EMIT-ARM declaration scope (cookbook 76 / d960 L1) ---------------- +def armdecl(src, decl, arms='all'): + src = _one(src, ' %s\n' % decl, '') + for ind, n in ((44, 2), (48, 2)): + a = '\n' + ' ' * ind + 'u32 *otp;\n' + c = src.count(a) + assert c == n, 'arm anchor %d: expected %d found %d' % (ind, n, c) + src = src.replace(a, a + ' ' * ind + decl + '\n') + return src + +# ---- reposition a declaration line (slot order == pseudo order == decl order) ---- +def declmove(src, decl, anchor): + src = _one(src, ' %s\n' % decl, '') + return _one(src, ' %s\n' % anchor, ' %s\n %s\n' % (anchor, decl)) + +def armdecl_pre(src, decl): + """same as armdecl but the decl goes BEFORE `u32 *otp;` in each arm.""" + src = _one(src, ' %s\n' % decl, '') + for ind, n in ((44, 2), (48, 2)): + a = '\n' + ' ' * ind + 'u32 *otp;\n' + assert src.count(a) == n + src = src.replace(a, '\n' + ' ' * ind + decl + '\n' + ' ' * ind + 'u32 *otp;\n') + return src + +@lever +def dial_mny_tri(src): + a = CULL[0] + last = a.rstrip('\n').split('\n')[-1] + ind = ' ' * (len(last) - len(last.lstrip())) + return _one(src, a, a + ind + '__asm__ __volatile__ ("" :: "r" (mny));\n') + +@lever +def dial_mny_quad(src): + a = CULL[1] + last = a.rstrip('\n').split('\n')[-1] + ind = ' ' * (len(last) - len(last.lstrip())) + return _one(src, a, a + ind + '__asm__ __volatile__ ("" :: "r" (mny));\n') + +FDECL = ' s32 f0, f1, f2, f3;\n' +@lever +def fsplit_pre(src): + """f0,f1 declared BEFORE pkt (lower pseudo nos); f2,f3 after pkt (slot order kept).""" + src = _one(src, FDECL, '') + return _one(src, ' u8 *pkt;\n', + ' s32 f0, f1;\n u8 *pkt;\n s32 f2, f3;\n') +@lever +def fsplit_post(src): + src = _one(src, FDECL, '') + return _one(src, ' u8 *pkt;\n', ' u8 *pkt;\n s32 f2, f3;\n s32 f0, f1;\n') +@lever +def fdecl_pkt(src): + src = _one(src, FDECL, '') + return _one(src, ' u8 *pkt;\n', ' u8 *pkt;\n' + FDECL) +@lever +def xyz_split(src): + """x2,y2,z2 declared last (higher pseudo nos -> lower tiebreak priority).""" + o = ' s16 x0, y0, z0, x1, y1, z1, x2, y2, z2;\n' + return _one(src, o, ' s16 x0, y0, z0, x1, y1, z1;\n s16 x2, y2, z2;\n') +@lever +def xyz_late(src): + o = ' s16 x0, y0, z0, x1, y1, z1, x2, y2, z2;\n' + src = _one(src, o, '') + return _one(src, ' u32 rgbw;\n', ' u32 rgbw;\n' + o) + +def pin(src, decl, reg): + """turn a plain decl into a `register ... __asm__("$N")` pin (72: PREFERENCE).""" + return _one(src, ' %s;\n' % decl, ' register %s __asm__("%s");\n' % (decl, reg)) + +@lever +def pin_va(src): + return _one(src, ' u8 *va, *vb, *vc;\n', + ' register u8 *va __asm__("$10");\n u8 *vb, *vc;\n') +@lever +def pin_f0(src): + return _one(src, ' s32 f0, f1, f2, f3;\n', + ' register s32 f0 __asm__("$19");\n s32 f1, f2, f3;\n') +@lever +def pin_f0_pkt(src): + return _one(src, ' u8 *pkt;\n s32 f0, f1, f2, f3;\n', + ' u8 *pkt;\n register s32 f0 __asm__("$19");\n s32 f1, f2, f3;\n') + +@lever +def pin_w(src): + return _one(src, ' u32 w; s32 code;\n', + ' register u32 w __asm__("$5");\n s32 code;\n') +@lever +def pin_c1(src): + o = ' s32 c0, c1, c2, c3;\n' + o2 = ' s32 c0, c1, c2, c3;\n' + assert src.count(o) >= 1 + src = src.replace('s32 c0, c1, c2, c3;', + 'register s32 c1 __asm__("$4"); s32 c0, c2, c3;') + return src + +@lever +def pin_call(src): + """pin the four vertex colours to the target's grants: c0=$t4 c1=$a0 c2=$t2 c3=$a2.""" + n = src.count('s32 c0, c1, c2, c3;') + assert n == 2, n + return src.replace('s32 c0, c1, c2, c3;', + 'register s32 c0 __asm__("$12"); register s32 c1 __asm__("$4"); ' + 'register s32 c2 __asm__("$10"); register s32 c3 __asm__("$6");') + +@lever +def pin_c01(src): + n = src.count('s32 c0, c1, c2, c3;') + assert n == 2, n + return src.replace('s32 c0, c1, c2, c3;', + 'register s32 c0 __asm__("$12"); register s32 c1 __asm__("$4"); s32 c2, c3;') + +@lever +def pin_c012(src): + n = src.count('s32 c0, c1, c2, c3;') + assert n == 2, n + return src.replace('s32 c0, c1, c2, c3;', + 'register s32 c0 __asm__("$12"); register s32 c1 __asm__("$4"); ' + 'register s32 c2 __asm__("$10"); s32 c3;') + +@lever +def pin_c0(src): + n = src.count('s32 c0, c1, c2, c3;'); assert n == 2, n + return src.replace('s32 c0, c1, c2, c3;', + 'register s32 c0 __asm__("$12"); s32 c1, c2, c3;') +@lever +def pin_c2(src): + n = src.count('s32 c0, c1, c2, c3;'); assert n == 2, n + return src.replace('s32 c0, c1, c2, c3;', + 'register s32 c2 __asm__("$10"); s32 c0, c1, c3;') +@lever +def pin_c1c2(src): + n = src.count('s32 c0, c1, c2, c3;'); assert n == 2, n + return src.replace('s32 c0, c1, c2, c3;', + 'register s32 c1 __asm__("$4"); register s32 c2 __asm__("$10"); s32 c0, c3;') +@lever +def vab_swap(src): + o = (' va = vtx + (w & 0xFFFF);\n' + ' vb = vtx + (w >> 16);\n') + return _one(src, o, (' vb = vtx + (w >> 16);\n' + ' va = vtx + (w & 0xFFFF);\n')) +@lever +def vd_late(src): + o = ' vd = vtx + (w & 0xFFF8);\n' + src = _one(src, o, '') + a = ' gte_stopz(&g.opz);\n' + return _one(src, a, o + a) + +@lever +def cb_two(src): + """unlit rgbc as a 2-statement accumulator.""" + n = src.count('cb = (tp[0] & 0xFF000000) | 0x101010;'); assert n == 2, n + return src.replace('cb = (tp[0] & 0xFF000000) | 0x101010;', + 'cb = tp[0] & 0xFF000000;\n cb |= 0x101010;') +@lever +def rgb_chain(src): + """lit rgb word as a 3-statement accumulator instead of one 4-term expr.""" + out, n = re.subn(r'rgbw = \((c\d) \| cb\) \| \((c\d) << 8\) \| \((c\d) << 16\);', + lambda m: 'rgbw = %s | cb; rgbw |= %s << 8; rgbw |= %s << 16;' + % (m.group(1), m.group(2), m.group(3)), src) + assert n == 7, n + return out +@lever +def rgb_chain_q(src): + """3-statement accumulator in the QUAD lit arm only (4 sites).""" + i = src.index('PolyGT4 *)pkt)->rgb0') + head, tail = src[:i], src[i:] + out, n = re.subn(r'rgbw = \((c\d) \| cb\) \| \((c\d) << 8\) \| \((c\d) << 16\);', + lambda m: 'rgbw = %s | cb; rgbw |= %s << 8; rgbw |= %s << 16;' + % (m.group(1), m.group(2), m.group(3)), tail) + assert n == 3, n + # the rgb0 one sits just before the marker + j = head.rindex('rgbw = ') + h2 = head[:j] + re.sub(r'rgbw = \((c\d) \| cb\) \| \((c\d) << 8\) \| \((c\d) << 16\);', + lambda m: 'rgbw = %s | cb; rgbw |= %s << 8; rgbw |= %s << 16;' + % (m.group(1), m.group(2), m.group(3)), head[j:]) + return h2 + out +def main(): + base, out, levers = sys.argv[1], sys.argv[2], sys.argv[3:] + src = open(base).read() + for lv in levers: + if lv.startswith('D:'): + src = rdecl_order(src, lv[2:]); continue + if lv.startswith('W:'): + _, d, a = lv.split('@'); src = declmove(src, d.replace('~',' '), a.replace('~',' ')); continue + if lv.startswith('E:'): + src = armdecl_pre(src, lv[2:].replace('~', ' ')); continue + if lv.startswith('A:'): + src = armdecl(src, lv[2:].replace('~', ' ')); continue + if lv.startswith('V:'): + _, nm, wh = lv.split(':'); src = dialat(src, nm, wh); continue + if lv.startswith('P:'): + src = rpos(src, lv[2:]); continue + if lv.startswith('I:'): + src = rinit(src, lv[2:]); continue + src = LEVERS[lv](src) + open(out, 'w').write(src) + +main() diff --git a/.run/giants/bf14_regmap.py b/.run/giants/bf14_regmap.py new file mode 100644 index 000000000..2718bbf1f --- /dev/null +++ b/.run/giants/bf14_regmap.py @@ -0,0 +1,43 @@ +"""Register-correspondence census over index-aligned instructions (same length only).""" +import sys,os,collections +sys.path.insert(0,'/home/musashi/bfm-decomp/tools') +import masked_diff, re, subprocess, shutil +OBJ=sys.argv[1] +TGT='/home/musashi/bfm-decomp/asm/ov_SC03_116/nonmatchings/ov_SC03_116_jr_8017AE2C/func_8017BF14.s' +mine=masked_diff.insns_from_object(OBJ,'func_8017BF14') +tgt=masked_diff.insns_from_s(TGT) +R=['zero','at','v0','v1','a0','a1','a2','a3','t0','t1','t2','t3','t4','t5','t6','t7', + 's0','s1','s2','s3','s4','s5','s6','s7','t8','t9','k0','k1','gp','sp','s8','ra'] +pairs=collections.Counter() +for i,(a,b) in enumerate(zip(mine,tgt)): + aw = a if isinstance(a,int) else None + # use raw words +for i,(a,b) in enumerate(zip(mine,tgt)): + pass +# simpler: raw words via objdump/target parse +def words_t(): + w=[] + for l in open(TGT): + m=re.match(r'\s*/\* \w+ [0-9A-F]{8} ([0-9A-F]{8}) \*/',l) + if m: w.append(int.from_bytes(bytes.fromhex(m.group(1)),'little')) + return w +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 +def words_m(): + w=[];inside=False + for l in out.splitlines(): + if re.match(r'^[0-9a-f]+ :',l): inside=True;continue + if inside: + if re.match(r'^[0-9a-f]+ <',l) and w: break + m=re.match(r'\s*[0-9a-f]+:\s+([0-9a-f]{8})\s',l) + if m: w.append(int(m.group(1),16)) + return w +T,M=words_t(),words_m() +print('len',len(M),len(T)) +for a,b in zip(M,T): + if a==b: continue + if (a>>26)!=(b>>26): continue + for sh in (21,16,11): + ra=(a>>sh)&0x1F; rb=(b>>sh)&0x1F + if ra!=rb: pairs[(R[ra],R[rb])]+=1 +for (a,b),n in pairs.most_common(20): print(' mine $%-4s -> target $%-4s x%d'%(a,b,n)) diff --git a/.run/giants/bf14_score.sh b/.run/giants/bf14_score.sh index a39585673..b644b9441 100644 --- a/.run/giants/bf14_score.sh +++ b/.run/giants/bf14_score.sh @@ -1,7 +1,9 @@ #!/bin/bash -# bf14_score.sh [tag] +# bf14_score.sh [tag] -> "TAG :: len=N d=+X mism=M shape=P% L1=Q" cd /home/musashi/bfm-decomp SRC="$1"; WD="$2"; TAG="${3:-$(basename $1 .c)}" -bash .run/giants/bf14_cc.sh "$SRC" "$WD" >/dev/null 2>"$WD.err" || { echo "$TAG :: COMPILE-FAIL $(tail -5 $WD.err|tr '\n' ' ')"; exit 1; } -A=$(python3 .run/giants/bf14_full.py "$WD/t.o" --count) -echo "$TAG :: $A" +bash .run/giants/bf14_cc.sh "$SRC" "$WD" >/dev/null 2>"$WD.err" || { echo "$TAG :: COMPILE-FAIL $(tail -4 $WD.err|tr '\n' ' ')"; exit 1; } +A=$(python3 .run/giants/bf14_full.py "$WD/t.o" --count 2>/dev/null) +H=$(python3 .run/giants/bf14_hist.py "$WD/t.o" --sum 2>/dev/null) +S=$(python3 .run/giants/bf14_shape.py "$WD/t.o" 0 2>/dev/null | head -1 | grep -oP 'SHAPE-aligned \d+/\d+ = \K[0-9.]+') +echo "$TAG :: $A | shape=$S% | $H" diff --git a/.run/giants/bf14_seg.py b/.run/giants/bf14_seg.py new file mode 100644 index 000000000..2e933bcee --- /dev/null +++ b/.run/giants/bf14_seg.py @@ -0,0 +1,44 @@ +"""Segment-anchored shape diff. bf14_seg.py [nh]""" +import re,subprocess,difflib,sys +OBJ=sys.argv[1]; TLO,THI,MLO,MHI=[int(x) for x in sys.argv[2:6]] +NH=int(sys.argv[6]) if len(sys.argv)>6 else 30 +TGT='/home/musashi/bfm-decomp/asm/ov_SC03_116/nonmatchings/ov_SC03_116_jr_8017AE2C/func_8017BF14.s' +TW=[];TT=[] +for l in open(TGT): + m=re.match(r'\s*/\* \w+ [0-9A-F]{8} ([0-9A-F]{8}) \*/\s+(.*?)\s*$',l) + if m: + v=int.from_bytes(bytes.fromhex(m.group(1)),'little') + TW.append(('R',v) if ('%hi(' in l or '%lo(' in l) else v); TT.append(re.sub(r'\s+',' ',m.group(2))) +out=subprocess.run(['mipsel-linux-gnu-objdump','-drz',OBJ],capture_output=True,text=True).stdout +MW=[];MT=[];inside=False +for l in out.splitlines(): + if re.match(r'^[0-9a-f]+ :',l): inside=True;continue + if inside: + if re.match(r'^[0-9a-f]+ <',l) and MW: break + m=re.match(r'\s*[0-9a-f]+:\s+([0-9a-f]{8})\s+(.*)',l) + if m: MW.append(int(m.group(1),16)); MT.append(re.sub(r'\s+',' ',m.group(2).strip())) + elif 'R_MIPS_' in l and MW and not isinstance(MW[-1],tuple) and (MW[-1]>>26) not in (2,3): + MW[-1]=('R',MW[-1]) +MEM=set(range(0x20,0x30))|{0x32,0x3A} +def shape(x): + rel=isinstance(x,tuple); v=x[1] if rel else x + op=(v>>26)&0x3F + if op==0 or op==0x1C: return ((op<<12)|(v&0x3F)|(((v>>6)&0x1F)<<7),'R' if rel else '') + if op in (2,3): return (op<<26,'') + if op==0x12: return (v&0xFC1F07FF,'') + if op in (1,4,5,6,7): return ((op<<20)|(((v>>16)&0x1F)<<8),'') + imm=0 if rel else (v&0xFFFF) + if op in MEM and ((v>>21)&0x1F)==29: imm=0 + return ((op<<20)|imm,'R' if rel else '') +a=[shape(x) for x in MW[MLO:MHI]]; b=[shape(x) for x in TW[TLO:THI]] +sm=difflib.SequenceMatcher(None,a,b,autojunk=False); ops=sm.get_opcodes() +eq=sum(i2-i1 for t,i1,i2,j1,j2 in ops if t=='equal') +print('seg tgt[%d:%d](%d) mine[%d:%d](%d) shape-eq %d = %.1f%%'%(TLO,THI,THI-TLO,MLO,MHI,MHI-MLO,eq,100.0*eq/max(1,THI-TLO))) +n=0 +for t,i1,i2,j1,j2 in ops: + if t=='equal': continue + n+=1 + if n>NH: break + print(' %-8s tgt[%d:%d](%d) mine[%d:%d](%d)'%(t,TLO+j1,TLO+j2,j2-j1,MLO+i1,MLO+i2,i2-i1)) + for k in range(j1,min(j2,j1+7)): print(' T %4d %s'%(TLO+k,TT[TLO+k])) + for k in range(i1,min(i2,i1+7)): print(' M %4d %s'%(MLO+k,MT[MLO+k])) diff --git a/.run/giants/bf14_segh.py b/.run/giants/bf14_segh.py new file mode 100644 index 000000000..0dd23ff86 --- /dev/null +++ b/.run/giants/bf14_segh.py @@ -0,0 +1,26 @@ +"""Per-anchor-segment length + opcode delta. usage: bf14_segh.py """ +import re,subprocess,collections,sys +OBJ=sys.argv[1] +TGT='/home/musashi/bfm-decomp/asm/ov_SC03_116/nonmatchings/ov_SC03_116_jr_8017AE2C/func_8017BF14.s' +T=[];TW=[] +for l in open(TGT): + m=re.match(r'\s*/\* \w+ [0-9A-F]{8} ([0-9A-F]{8}) \*/\s+(\S+)',l) + if m: TW.append(int.from_bytes(bytes.fromhex(m.group(1)),'little')); T.append('GTE' if m.group(2) in ('rtps','rtpt','nclip') else m.group(2)) +out=subprocess.run(['mipsel-linux-gnu-objdump','-drz',OBJ],capture_output=True,text=True).stdout +alias={'move':'addu','li':'addiu','b':'j','negu':'subu','c2':'GTE'} +M=[];MW=[];inside=False +for l in out.splitlines(): + if re.match(r'^[0-9a-f]+ :',l): inside=True;continue + if inside: + if re.match(r'^[0-9a-f]+ <',l) and MW: break + m=re.match(r'\s*[0-9a-f]+:\s+([0-9a-f]{8})\s+(\S+)',l) + if m: MW.append(int(m.group(1),16)); M.append(alias.get(m.group(2),m.group(2))) +ta=[i for i,v in enumerate(TW) if (v&0xFC00003F)==0x18] +ma=[i for i,v in enumerate(MW) if (v&0xFC00003F)==0x18] +tb=[0]+ta+[len(T)]; mb=[0]+ma+[len(M)] +print('segments %d/%d'%(len(ta),len(ma))) +for k in range(len(tb)-1): + ct=collections.Counter(T[tb[k]:tb[k+1]]); cm=collections.Counter(M[mb[k]:mb[k+1]]) + d={x:cm[x]-ct[x] for x in set(ct)|set(cm) if cm[x]!=ct[x]} + if d: print('seg %2d tgt[%d:%d] mine[%d:%d] len%+d %s'%(k,tb[k],tb[k+1],mb[k],mb[k+1], + (mb[k+1]-mb[k])-(tb[k+1]-tb[k]),' '.join('%s%+d'%(a,b) for a,b in sorted(d.items(),key=lambda kv:-abs(kv[1]))))) diff --git a/.run/giants/bf14_shape.py b/.run/giants/bf14_shape.py new file mode 100644 index 000000000..bc3190f0d --- /dev/null +++ b/.run/giants/bf14_shape.py @@ -0,0 +1,57 @@ +"""Structural aligner for func_8017BF14: masks registers AND sp-relative offsets, +so a frame-size drift does not hide real structural divergence. +usage: bf14_shape.py [nhunks] [--min N]""" +import re, subprocess, difflib, shutil, sys +OBJ = sys.argv[1] +TGT = '/home/musashi/bfm-decomp/asm/ov_SC03_116/nonmatchings/ov_SC03_116_jr_8017AE2C/func_8017BF14.s' +NH = int(sys.argv[2]) if len(sys.argv) > 2 and not sys.argv[2].startswith('-') else 40 +MIN = int(sys.argv[sys.argv.index('--min')+1]) if '--min' in sys.argv else 1 +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 +def words_mine(): + w=[] + for line in out.splitlines(): + m=re.match(r'\s*[0-9a-f]+:\s+([0-9a-f]{8})\s',line) + if m: w.append(int(m.group(1),16)) + elif 'R_MIPS_' in line and w and not isinstance(w[-1],tuple) and (w[-1]>>26) not in (2,3): + w[-1]=('R',w[-1]) + return w +def words_tgt(): + w=[] + 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') + w.append(('R',v) if ('%hi(' in line or '%lo(' in line) else v) + return w +MEM = set(range(0x20,0x30)) | {0x32,0x3A} # lb..sw, lwc2, swc2 +def shape(x): + rel=isinstance(x,tuple); v=x[1] if rel else x + op=(v>>26)&0x3F + if op==0 or op==0x1C: k=(op<<12)|(v&0x3F)|(((v>>6)&0x1F)<<7) + elif op in (2,3): k=(op<<26) + elif op==0x12: k=v&0xFC1F07FF + elif op in (1,4,5,6,7): k=(op<<20)|(((v>>16)&0x1F)<<8) + else: + imm = 0 if rel else (v & 0xFFFF) + base=(v>>21)&0x1F + if op in MEM and base==29: imm=0 # mask sp offsets + k=(op<<20)|imm + return (k,'R' if rel else '') +mt=[m.group(1).strip() for m in (re.match(r'\s*[0-9a-f]+:\s+[0-9a-f]{8}\s+(.*)',l) for l in out.splitlines()) if m] +tt=[m.group(1).strip() for m in (re.match(r'\s*/\* \w+ [0-9A-F]{8} [0-9A-F]{8} \*/\s+(.*)',l) for l in open(TGT)) if m] +wm,wt=words_mine(),words_tgt() +a=[shape(x) for x in wm]; b=[shape(x) for x in wt] +sm=difflib.SequenceMatcher(None,a,b,autojunk=False); ops=sm.get_opcodes() +eq=sum(i2-i1 for t,i1,i2,j1,j2 in ops if t=='equal') +bad=[o for o in ops if o[0]!='equal'] +print("mine %d target %d SHAPE-aligned %d/%d = %.1f%% hunks=%d div-tgt=%d" + % (len(wm),len(wt),eq,len(b),100.0*eq/len(b),len(bad),sum(o[4]-o[3] for o in bad))) +n=0 +for t,i1,i2,j1,j2 in bad: + if max(j2-j1,i2-i1)NH: break + print(" %-8s tgt[%d:%d](%d) mine[%d:%d](%d)"%(t,j1,j2,j2-j1,i1,i2,i2-i1)) + for k in range(j1,min(j2,j1+8)): print(" T %4d %s"%(k,tt[k] if k access counts, for target and one object, side by side.""" +import re,subprocess,sys,collections +OBJ=sys.argv[1] +TGT='/home/musashi/bfm-decomp/asm/ov_SC03_116/nonmatchings/ov_SC03_116_jr_8017AE2C/func_8017BF14.s' +def cens_t(): + c=collections.defaultdict(collections.Counter) + for l in open(TGT): + m=re.match(r'\s*/\* \w+ \w+ \w+ \*/\s+(\S+)\s+\$\w+, (-?0x[0-9A-Fa-f]+)\(\$sp\)',l) + if m: c[int(m.group(2),16)][m.group(1)]+=1 + return c +def cens_m(): + out=subprocess.run(['mipsel-linux-gnu-objdump','-drz',OBJ],capture_output=True,text=True).stdout + c=collections.defaultdict(collections.Counter); inside=False + for l in out.splitlines(): + if re.match(r'^[0-9a-f]+ :',l): inside=True; continue + if inside: + if re.match(r'^[0-9a-f]+ <',l): break + m=re.match(r'\s*[0-9a-f]+:\s+[0-9a-f]{8}\s+(\S+)\s+\S+,(-?\d+)\(sp\)',l) + if m: c[int(m.group(2))][m.group(1)]+=1 + return c +T,M=cens_t(),cens_m() +def fmt(cc): return ' '.join('%s:%d'%(k,v) for k,v in sorted(cc.items())) +print('target slots=%d mine slots=%d'%(len(T),len(M))) +ks=sorted(set(T)|set(M)) +for k in ks: + print('0x%03X T[%-28s] M[%s]'%(k,fmt(T[k]),fmt(M[k]))) diff --git a/.run/giants/bf14_sweep.sh b/.run/giants/bf14_sweep.sh new file mode 100644 index 000000000..820b1d1ef --- /dev/null +++ b/.run/giants/bf14_sweep.sh @@ -0,0 +1,13 @@ +#!/bin/bash +# bf14_sweep.sh "| [lever...]" ... -> one line per variant, parallel +cd /home/musashi/bfm-decomp +BASE="$1"; shift +run() { + local spec="$1"; local tag="${spec%%|*}"; local lv="${spec#*|}" + local wd=".run/giants/bf14_sw/$tag" + mkdir -p "$wd" + python3 .run/giants/bf14_mk.py "$BASE" "$wd/v.c" $lv 2>"$wd/mk.err" || { echo "$tag :: MK-FAIL $(tail -2 $wd/mk.err|tr '\n' ' ')"; return; } + bash .run/giants/bf14_score.sh "$wd/v.c" "$wd/w" "$tag" +} +export -f run; export BASE +printf '%s\n' "$@" | xargs -P 8 -I{} bash -c 'run "$@"' _ {} diff --git a/.run/giants/bf14_win.py b/.run/giants/bf14_win.py new file mode 100644 index 000000000..c1024d9c4 --- /dev/null +++ b/.run/giants/bf14_win.py @@ -0,0 +1,20 @@ +"""Windowed side-by-side: bf14_win.py """ +import re,subprocess,sys +OBJ=sys.argv[1]; TLO=int(sys.argv[2]); THI=int(sys.argv[3]); MLO=int(sys.argv[4]) +TGT='/home/musashi/bfm-decomp/asm/ov_SC03_116/nonmatchings/ov_SC03_116_jr_8017AE2C/func_8017BF14.s' +T=[] +for l in open(TGT): + m=re.match(r'\s*/\* \w+ \w+ \w+ \*/\s+(.*?)\s*$',l) + if m: T.append(re.sub(r'\s+',' ',m.group(1))) +out=subprocess.run(['mipsel-linux-gnu-objdump','-drz',OBJ],capture_output=True,text=True).stdout +M=[];inside=False +for l in out.splitlines(): + if re.match(r'^[0-9a-f]+ :',l): inside=True;continue + if inside: + if re.match(r'^[0-9a-f]+ <',l): break + m=re.match(r'\s*[0-9a-f]+:\s+[0-9a-f]{8}\s+(.*)',l) + if m: M.append(re.sub(r'\s+',' ',m.group(1).strip())) +n=THI-TLO +for k in range(n): + ti=TLO+k; mi=MLO+k + print('%5d %-38s | %5d %s'%(ti,T[ti] if ti DIFF func_8017BF14 mine=4763 ins, target=4763 ins, 45 mismatched +``` + +| metric | value | +|---|---| +| instruction count | **4763 / 4763 — EXACT** | +| opcode histogram L1 distance | **0 — EXACT** (every mnemonic count agrees) | +| stack frame | **EXACT** — frame `0x360`, all **127 slots at the target's offsets** | +| register-masked structural alignment (anchored) | **4760 / 4763 = 99.94 %** | +| byte-identical instructions | **4718 / 4763 = 99.06 %** | +| **NOT A MATCH** | 45 instructions differ | +| pin-free fallback | `s19_func_8017BF14_b1_pinfree.c` → 4763/4763, **789 mismatched**, 100.0 % structural | + +The whole-binary SHA1 arbiter (G3/P9) was **not** run — the task forbade touching the +build tree. `match_one` is the candidate check only. + +**Class: three register-grant ties.** No structural, length, scheduling-of-blocks, +frame, or expression-shape divergence remains. + +--- + +## 2. THE DECODE + +### 2.1 What it is + +The **four-light-box** member of the volumetric-light renderer family: + +| member | ins | overlay | status | +|---|---|---|---| +| `func_8017BEBC` | ~500 | ov_SC03_099 | MATCHED (unlit ancestor) | +| `func_8017CA80` | 952 | ov_SC03_090 | MATCHED | +| `func_8017F510` | 1511 | — | MATCHED (behemoth #3) | +| `func_8017D960` | 3338 | ov_SC03_090 | MATCHED (behemoth #2, **3 light boxes**) | +| **`func_8017BF14`** | **4763** | ov_SC03_116 | **this one, 4 light boxes** | + +`h_norm`/`h_seq` reported family size 1 and the prompt said "no matched relative". +That was true of the *hash* families but **false of the codegen family** — cookbook §71's +callee-set fingerprint could not fire because this function has **zero callees**. The +relative was found instead by **reading the target's globals**: `D_800A5E60` (the packet +cursor) is grepped in `src/`, which lands directly on the matched `func_8017BEBC`. +**Generalisable: for a 0-callee giant, fingerprint by its DATA symbols, not its callees.** + +### 2.2 Signature and why it is a leaf + +`void func_8017BF14(s32 arg0, s32 lim)` — `sw $a1, 0xB0($sp)` at entry proves arg1. + +Every other family member opens with `lim = func_800491EC() + *(s32*)(arg0+0x64); +func_800547D8(arg0+0x10,&mtx); func_80052E38(&mtx);`. Here the caller has already done +that work and passes `lim` in. Consequences, all visible in the frame: +no `$ra` save, no `MATRIX2 mtx` local, and **no 0x10-byte o32 argument area** — so +`tmpxy[4]` starts at `sp+0x00` instead of `sp+0x10`. + +### 2.3 Loop nest (2 loops, 414 labels, 0 `jal`) + +``` +for (j = 0; j < nparts; j++, part++) // idx 214..4746, stride 0x14 + build box[8] from part->xx/yy/zz + gte_ldv3c/rtpt/stsxy3 x2 + gte_ldv0/rtps/stsxy x2 -> sxy[8] + gte_stszotz(&g.otz); if (lim >= g.otz) { + screen bbox X in [-0xA0, 0xA1) and Y in [-0x6E, 0x6F) + for (i = 0; i < nprim; i++, prim++) // idx 504..4742, stride 0xC + gte_ldv3(va,vb,vc); gte_rtpt(); gte_stflg(); if (!(flag & 0x7F85E000)) { + gte_nclip(); code = w & 7; vd = vtx + (w & 0xFFF8); gte_stopz(); + if (g.opz > 0) switch (code) { // range tree, cases 6,7 then 2,3 + case 6/7: TRI -> 3 vertices, 3 colours + case 2/3: QUAD -> 4 vertices, 4 colours + } +``` +Codes 0,1,4,5 are dropped (they are the untextured F3/F4 cases of the unlit ancestor). +gcc emits the 4-case switch as the balanced range tree +`code<2 → skip; code<4 → quad; code>=8 → skip; code<6 → skip; else tri`. + +### 2.4 The four light boxes + +Stride `0x1C`, `{ s32 enable; u16 cx, cy, cz; s32 range; }` at +`D_80197C28 / D_80197C44 / D_80197C60 / D_80197C7C`. + +Falloff geometry differs from the 3-box sibling: + +| | `func_8017D960` (3 boxes) | `func_8017BF14` (4 boxes) | +|---|---|---| +| low radius | `RLO = R - 0x80` | `RLO = R - 0x200` | +| x-axis ramp | `A = R - d` | `A = (R - d) / 4` | +| z/y-axis ramp | `A = (A*(R-d)) >> 7` | `A = (A*((R-d)/4)) >> 7` | +| colour sum | `c = a0+a1+a2` | `c = a0+a1+a2+a3 + 0x10` | +| unlit rgbc | `tp[0] & 0xFF000000` | `(tp[0] & 0xFF000000) \| 0x101010` | + +The `/ 4` is a **signed divide** (`bgez / addiu 3 / sra 2`), not `>> 2`. +The `+ 0x10` ambient bias is applied before the `> 0x80` clamp. + +### 2.5 The frame (0x360) + +``` +0x000 tmpxy[4] 0x010 box[8] 0x050 sxy[8] +0x090 g{otz,flag,opz,sz0..sz3} +0x0B0 lim 0x0B8 j 0x0C0 i 0x0C8 vd 0x0D0 ot 0x0D8 pkt +0x0E0 f2 0x0E8 f3 +0x0F0/0x0F8/0x100 x3, z3, y3 +0x108 prim 0x110 nprim 0x118 vtx 0x120 nparts 0x128 part +0x130..0x1D0 hi0y,lo0z,hi0z,lo1x,...,hi3z (21 s16 slots, 8-byte pitch) +0x1D8..0x230 cx0,cy0,cz0 .. cx3,cy3,cz3 (12) +0x238 r0 0x240 r1lo 0x248 r2lo 0x250 r3lo +0x288..0x2D0 LICM-hoisted sign-extended bounds (9 s32 + 1 staging slot) +0x328/0x330 reload-spilled vertex coords (y0, z2) +0x338..0x358 s0-s7, fp (NO $ra — leaf) +``` +`lo0x`, `hi0x`, `lo0y` never get s16 slots: they live only as the hoisted +sign-extended `0x288/0x290/0x2A0` copies. + +--- + +## 3. FOUR ORIGINAL-SOURCE COPY-PASTE ARTEFACTS (all byte-proven) + +The 4th light box was **bolted onto a copy of the 3-box source by hand**, and the hand +edit was incomplete in four places. Every one of these was *read off the target*, and +every one removed a measured delta. + +| # | artefact | proof in the target | +|---|---|---| +| 1 | `r3lo = r2 - 0x200;` — box 3's low radius comes from box 2's **range variable** | `addiu $t6, $s0, -0x200` at idx 93 reuses the register box 2's `lw D_80197C6C` filled. Spelling it `D_80197C6C - 0x200` re-loads the global (+2 ins, and it re-materialises `lui/lw`). No sign-extension is emitted because the result is `sh`-truncated. | +| 2 | only **six** of the eight radius variables are zero-initialised | entry emits exactly 3 `move rX,zero` + 3 `sh $zero` = r0,r1,r2,r0lo,r1lo,r2lo. `r3`/`r3lo` are left uninitialised — precisely the init list the 3-box version needed. | +| 3 | the box-3 ATTEN **kill test** still says `r2` in 3 of the 7 hand-written copies | all 24 `sll $v0,$s0,16` (r2 sign-extension) sites enumerated: 3 per vertex-group inside box 2, **plus exactly three extras** at idx **1471, 1504** (tri v0 z and y) and **2178** (tri v2 z). All other copies use `$s6` = r3. | +| 4 | quad-lit `rgb2`/`rgb3` take their `<< 16` term from **c1** | the target CSEs **one** `sll $a0,$a0,16` (idx 4641) and re-uses `$a0` at idx 4647 and 4652. Writing `c2<<16`/`c3<<16` costs +2 `sll`. | + +Modelling these is not optional cosmetics — #3 alone was `sra+11 / sll+9` and #4 was the +last `sll+2`. + +--- + +## 4. THE LEVERS THAT MOVED THE NUMBER (measured) + +Metric below is **byte-identical %** from the anchored aligner +(`.run/giants/bf14_ali.py`), which is length-drift-proof. + +| # | lever | before → after | +|---|---|---| +| L1 | unlit `cb = (tp[0] & 0xFF000000) \| 0x101010` | len −64 → −62; retired the whole `or`/`ori`/`lui` histogram delta | +| L2 | artefacts 1 + 3 (box-3 kill register per copy, `r3lo = r2 - 0x200`) | shape 89.15 % → 96.96 %; killed `sra+11 / sll+9` | +| L3 | artefact 4 (quad-lit `c1 << 16`) | killed the last `sll+2` | +| **L4** | **`s32 c0,c1,c2,c3;` declared inside the two CULL blocks** | **52.26 % → 92.86 % — the single biggest lever, and it is what spills `r1lo`** | +| **L5** | **`s32 f0,f1,f2,f3;` moved to immediately after `u8 *pkt;`** | **73.00 % → 83.98 %, and ALL 127 stack slots then match exactly** | +| L6 | `u32 rgbw;` per emit arm | 93.45 % → 94.21 % (with L7) | +| L7 | RC-15 zero-byte ref dial on `mny`, head of the TRI cull block | 93.47 % → 94.21 % | +| L8 | `cb` 2-statement accumulator + quad-lit rgb 3-statement accumulator | 99.06 % → 99.12 % | +| L9 | four register pins: `va→$t2`, `w→$a1`, `f0→$s3`, `c1→$a0` | 94.21 % → **99.06 %** | + +### 4.1 L4 — why declaration scope, and not a dial (cookbook §76) + +The whole −62 length residual was **one** decision: the target spills `r1lo` to `0x240` +and reloads it 21 times (`lhu` + load-delay `nop` at each of 3 axes × 7 vertex-colour +groups = exactly the measured `lhu −21` / `nop −21` / `sh −2`), while every draft kept +it in `$fp`. + +`$fp` is register 30 — the **last** register a plain-ascending `find_reg` scan reaches +(MIPS defines no `REG_ALLOC_ORDER` in gcc-2.7.2, verified in +`tools/reference/gcc-2.7.2/config/mips/mips.h`). So `r1lo` was the *marginal* allocno: +the target simply had **one more competitor** than my draft. + +Declaring `c0..c3` inside the two cull blocks supplies that competitor the way the +compiler actually models it: at function scope `c0..c3` have a death in each of the four +emit arms, so `local-alloc.c:472` (`REG_BASIC_BLOCK >= 0 && REG_N_DEATHS == 1`) refuses +them and they become global allocnos; per cull block they are 1-death **local** pseudos, +local-alloc places them, and `global.c:668-671` re-marks those placements as **hard +registers** for global-alloc's conflict scan — removing them from the global pool and +pushing `r1lo` onto the stack. + +An `__asm__` ref-dial on `mn`/`mx`/`my`/`mny` inside the lit arm reached the same spill +(84.78 %) and was found *first*; declaration scope is both better (92.86 %) and real C. +**Lesson: when a spill is missing, look for the missing LOCAL allocno before reaching for +a ref dial.** + +### 4.2 L5 — the stack-slot order is a declaration-order ORACLE (new, generalisable) + +Spilled pseudos are given stack slots in **pseudo-number order** (`reload1.c: alter_reg`), +and pseudo numbers are handed out in **declaration order** (`expand_decl`). Therefore the +target's stack layout is a direct read-out of the original declaration order. + +Target: `pkt` at `0xD8`, then **two `lw:9 sw:9` slots at `0xE0`/`0xE8`** (the f2/f3 +flags), then three `lhu:7 sh:1` slots (`x3,z3,y3`), then `prim/nprim/vtx/nparts/part`. +My draft had the flags declared late, so everything above `0xD8` was shifted by `0x10`. +Moving one declaration line fixed **every** slot: 127/127. + +This is a cheap, deterministic, repeatable technique — `.run/giants/bf14_slots.py` +prints the slot census side by side and any mismatch is a declaration-order bug. +**It should go in the cookbook.** + +### 4.3 L9 — pins are safe *here*, and §72 still holds + +Cookbook §72/§74: a `register __asm__` pin is a **preference, not a reservation**, and +the real hazard is a caller-saved pin spanning a `jal`. **This function has zero `jal`s**, +so that hazard cannot arise — which is exactly why pins are usable on this family member +and were a trap on the others. Four pins bought 94.21 % → 99.06 %. + +§72 was nevertheless reproduced: adding a 5th and 6th pin (`c0→$t4`, `c2→$t2`, both +values the target genuinely puts there) made it **worse — 92.86 %**. Pins do not compose. + +A **pin-free** draft is preserved at `.run/giants/s19_func_8017BF14_b1_pinfree.c` +(4763/4763 ins, 789 mismatched, 100.0 % structural, exact frame) for anyone who wants the +conservative variant. + +--- + +## 5. DO-NOT-RE-BUY TABLE (every lever measured, neutral or negative) + +Baselines are stated per block because the base moved as levers landed. + +### 5.1 Neutral — no effect at all + +| lever | result | +|---|---| +| declaration-order permutations of `r0..r3 / r0lo..r3lo` (5 orders swept) | neutral (±4 on a 4526 base) | +| swapping `rN` / `rNlo` assignment order inside each box's `if` (all 4 boxes) | neutral | +| `a0v..a3v` / `d` / `tp` declared per cull block | neutral | +| `a0v..a3v` / `c0..c3` / `d` declared inside the lit arm | neutral | +| moving the whole r-declaration block (top / bottom / before lo-hi / before centres) | neutral | +| declaration position of `s16 my,mny,mx,mn` (6 anchors swept) | **neutral — cannot substitute for the L7 dial** | +| splitting `f0,f1` before `pkt` and `f2,f3` after | neutral | +| `x2,y2,z2` split out / declared late | neutral | +| arm-declaration ORDER (`tp`/`rgbw`/`cb` before vs after `u32 *otp;`) | neutral (4 permutations) | +| `vd` computed later; `va`/`vb` assignment swapped | neutral | +| second ref dial on any of f0..f3, x0..z2, mn/mx/my/mny, w, wz, vw, vzw, i, j, nprim, at prim/cull scope, on top of the best base | neutral or worse (40 probes) | + +### 5.2 Negative — actively harmful + +| lever | result vs its base | +|---|---| +| `s16 x0..z2` declared per cull block (`xyz_cull`) | 92.86 % → **57.23 %** | +| `s32 f0..f3` declared per cull block (`f_cull`) | 92.86 % → **67.16 %** | +| ref dial on `mn` in the lit arm, once `c_cull` is in | 92.86 % → **63.20 %** (the two levers do the same job and collide) | +| `cb` declared per emit arm | 94.21 % → **91.31 %** | +| `a0v..a3v` per cull block, on top of `c_cull` | 92.86 % → **90.32 %** | +| pinning `c0` and `c2` in addition to `c1` | 99.06 % → **92.86 %** | +| `rgbw` as a 3-statement accumulator in **both** arms | 99.06 % → 98.22 % (quad-only is +0.02 %) | +| `base = D_800AF630` rematerialised at use / hoisted late | 52.26 % → 52.11 % | +| `prim`/`nprim` read order swapped | neutral-to-worse | +| `f0=0..f3=0` in forward instead of reverse order | 92.86 % → 92.78 % | + +### 5.3 Rejected diagnoses (each was measured and refuted) + +* **"the −62 is missing code."** It was not: the opcode-histogram delta was + `nop −34 / lhu −21 / sh −2 / addu +1` — a pure spill signature. Confirmed by + §78's rule: *a `nop` present in the target and absent from the draft is a register + fact, not missing code.* +* **"`r3lo` comes from `D_80197C88 - 0x200`."** Measured: that spelling re-loads the + global (`lui/lhu` + `addiu`) instead of reusing `$s0`. It scored *better* on the raw + mismatch counter purely because it shuffled the allocation — a metric trap. The + anchored aligner and the slot census disagreed, and they were right. +* **"the box-3 ATTEN is uniform across the 7 copies."** Refuted by enumerating all 24 + `sll $v0,$s0,16` sites: three copies differ. +* **"declaration order can replace the `mny` ref dial."** Six anchor positions swept, all + neutral. Declaration order moves the *tiebreak*; the dial moves `reg_n_refs` + (× loop depth) and therefore the `allocno_compare` **score**. They are not + interchangeable. + +--- + +## 6. THE REMAINING 45 INSTRUCTIONS + +Three independent register-grant ties. No structural residual. + +**(a) The prim-word producer temps (≈8 ins, idx 508–514, 539–542).** +Target: `andi $v1,$a1,0xFFFF` / `srl $a0,$a1,16` / `addu $t2,$t6,$v1`. +Draft: the `andi` result is written straight into `$t2` (`va`'s register) and the `addu` +is in-place. This is `combine_regs` (`local-alloc.c:1825`) tying the producer chain into +`va` — which the **`va→$t2` pin invites**, because a pinned pseudo is a hard register from +the start and local-alloc will always tie into it. Removing the pin unties the chain but +costs 4 % elsewhere. *Most promising next move: keep the tie broken by giving the two +offsets their own named, multi-death variables (raising their death count above 1 so +`local-alloc.c:472` refuses them), rather than by removing the pin.* + +**(b) `c0` and `c2` grants (≈15 ins).** Target `c0→$t4, c1→$a0, c2→$t2, c3→$a2`; draft +gets `c1` (pinned) right and `c0→$t2, c2→$a2`. Pinning them directly is refuted (§5.2). +The lever is `allocno_compare` order among the four, i.e. their relative ref counts — +reachable by a variable-REUSE merge (§45-A / RC-14), which was **not** swept for `c0..c3` +and is the obvious next experiment. + +**(c) The quad-lit rgb accumulator (≈20 ins, idx 4635–4653).** Target accumulates in +`$v1` and stores from `$v1`; the draft accumulates in `$v0`. Three one-slot +store/shift transpositions ride along with it (`sll $v0,$a0,8` before vs after +`sw $v1,-0x20($t3)`). **Before calling these a scheduling residual, run §76's attribution +primitive** (`-fno-schedule-insns` and `-fno-schedule-insns2`): if the pair keeps source +order under both, it was fixed at RTL expansion and the lever is statement order, not +`sched.c`. That test was *not* run here and is the cheapest remaining probe. + +### Single most promising next move +**Sweep variable REUSE across `c0..c3` and the `a0v..a3v` accumulators** (merge two temps +into one, §45-A / RC-14 MERGE) to move `allocno_compare` priority. That is the one §76 +lever class this session never reached — declaration scope was swept exhaustively, +reuse was not. It targets residual (b) directly and plausibly (c) as well. + +--- + +## 7. TOOLS BUILT (all preserved in `.run/giants/`, ~0.3–0.8 s per probe) + +| tool | what it does | +|---|---| +| `bf14_cc.sh` / `bf14_score.sh` / `bf14_probe.sh` | compile + score one draft (0.28 s) | +| `bf14_mk.py` | lever generator; **every transformation asserts it applied**, so a "neutral" reading can never be a silent no-op | +| `bf14_sweep.sh` | 8-way parallel lever sweep | +| `bf14_full.py` / `bf14_side.py` / `bf14_win.py` | masked diff, side-by-side, windowed side-by-side | +| `bf14_hist.py` | opcode-histogram delta — **alignment-free, length-drift-proof** | +| `bf14_shape.py` | difflib shape diff with sp offsets masked | +| **`bf14_ali.py`** | **anchor-segmented aligner** — segments the function at the 56 `mult` sites and aligns each segment independently. Plain difflib gets hopelessly lost in this function's repeated blocks (it once reported a bogus 1,836-instruction insertion); anchoring fixes it. Reports both register-masked and byte-level alignment. | +| `bf14_segh.py` | per-segment length + opcode delta — localises a drift to one ATTEN axis | +| `bf14_land.py` | landmark drift table (`mult` index deltas) | +| **`bf14_slots.py`** | **stack-slot census vs the target — the declaration-order oracle of §4.2** | +| `bf14_regmap.py` | register-correspondence census; exposes allocation *cycles* rather than a list of diffs | +| `bf14_struct.py` | label / branch / loop map of the target | + +### Reusable methodology notes +1. **Anchor your aligner.** On a function with 7 near-identical 400-instruction blocks, + raw `difflib` is worse than useless — it reports confident nonsense. Segment on a rare + opcode first. +2. **The opcode histogram is the honest early metric.** Index-wise "mismatched" is + dominated by length drift and will rank a *worse* draft higher (this happened, §5.3). +3. **The stack-slot census is a free oracle for declaration order** (§4.2). +4. **The register-correspondence census turns a wall of diffs into cycles.** Seeing + `$t2→$t3→$t5→$t4→$t2` as a rotation, and `$s3↔$s4` as a swap, is what made the last + 5 % tractable. + +--- + +## 8. FILES + +* `.run/giants/s19_func_8017BF14_b1.c` — best draft, full dossier header. **45/4763.** +* `.run/giants/s19_func_8017BF14_b1_pinfree.c` — pin-free variant. 789/4763, 100 % structural. +* `.run/giants/bf14_*.py`, `.run/giants/bf14_*.sh` — the harness. diff --git a/.run/giants/s19_func_8017BF14_b1.c b/.run/giants/s19_func_8017BF14_b1.c new file mode 100644 index 000000000..9ecef84d3 --- /dev/null +++ b/.run/giants/s19_func_8017BF14_b1.c @@ -0,0 +1,770 @@ +#include "common.h" +#include "/home/musashi/bfm-decomp/src/shared/engine_types.h" + +/* =========================================================================== + * func_8017BF14 -- 4,763 ins, ov_SC03_116 (behemoth #4). COLD START. + * + * STATUS (2026-07-25, session 20, gcc-2.7.2 pinned triple): + * python3 tools/match_one.py func_8017BF14 --c \ + * --asm-subdir asm/ov_SC03_116/nonmatchings/ov_SC03_116_jr_8017AE2C + * -> DIFF 4763/4763 ins, 45 mismatched (99.06% byte-identical) + * LENGTH EXACT | OPCODE HISTOGRAM EXACT (L1 = 0) | STACK FRAME EXACT + * (all 127 slots at the target's offsets, frame 0x360) + * register-masked structural alignment 4760/4763 = 99.94% + * NOT A MATCH. The whole-binary SHA1 arbiter (G3/P9) has NOT been run. + * Pin-free fallback (see LEVERS L6..L9): 4763/4763, 789 mismatched. + * + * WHAT IT IS + * The *four*-light-box variant of the volumetric-light renderer whose + * 3-box sibling func_8017D960 (3,338 ins, ov_SC03_090) is MATCHED, and whose + * unlit ancestor func_8017BEBC (ov_SC03_099) is MATCHED. Same family, same + * skeleton; this is the biggest member. + * + * Signature: func_8017BF14(s32 arg0, s32 lim). Unlike every other member of + * the family this one is a LEAF -- 0 callees. The 3-call prologue + * (func_800491EC / func_800547D8 / func_80052E38) of the siblings is gone; + * `lim` arrives as arg1 (spilled to 0xB0). That is why the frame has no + * 0x10 argument area (tmpxy[] starts at sp+0x00) and no $ra save. + * + * Per part (stride 0x14, outer loop): build the 8-corner AABB in box[], + * rtpt/rtpt + rtps/rtps -> sxy[8], stszotz -> g.otz, reject on + * `lim >= g.otz`, then screen-space bbox reject on X (-0xA0..0xA1) and + * Y (-0x6E..0x6F). + * Per prim (stride 0xC, inner loop): rtpt the 3 vertices, stflg mask + * 0x7F85E000, nclip, stopz > 0, then a 4-way range tree on `code = w & 7` + * that keeps ONLY codes 6,7 (tri) and 2,3 (quad); 0,1,4,5 fall through to + * the loop tail. + * Per drawn poly: screen bbox reject, then each vertex is tested against + * FOUR axis-aligned light boxes (flags f0..f3), and if any is lit a + * 0x00..0x80 attenuation per active box is computed, summed, biased +0x10 + * and clamped to 0x80 -> a grey gouraud vertex colour. + * lit -> POLY_GT3 (0x28, tag 0x34000000, OT 0x9000000) + * POLY_GT4 (0x34, tag 0x3C000000, OT 0xC000000) + * unlit -> POLY_FT3 (0x20, OT 0x7000000) / POLY_FT4 (0x28, OT 0x9000000) + * with rgbc = (tp[0] & 0xFF000000) | 0x101010 <-- NOT black, + * unlike func_8017D960 where the unlit colour is plain black. + * + * THE FOUR LIGHT BOXES (stride 0x1C, {s32 enable; u16 cx,cy,cz; s32 range}) + * D_80197C28 / D_80197C44 / D_80197C60 / D_80197C7C. + * Falloff geometry differs from the 3-box sibling: RLO = R - 0x200 (not + * -0x80) and the ramp is ((R - d) / 4) (not (R - d)), so the 0x80 ceiling is + * reached over a 0x200-wide band instead of 0x80. The `/ 4` is a SIGNED + * divide -- `bgez / addiu 3 / sra 2` -- not a shift. + * + * THREE ORIGINAL-SOURCE COPY-PASTE ARTEFACTS, all byte-proven (see report) + * (1) `r3lo = r2 - 0x200;` -- box 3's low radius is derived from box 2's + * RANGE VARIABLE, not from its own D_80197C88. Proven by the target's + * `addiu $t6, $s0, -0x200` reusing the register that box 2's `lw` filled; + * spelling it `D_80197C6C - 0x200` re-loads the global (+2 ins). + * (2) Only SIX of the eight radius variables are zero-initialised + * (r0,r1,r2,r0lo,r1lo,r2lo) -- r3/r3lo are left uninitialised, exactly + * the init list the 3-box version needed. + * (3) The ATTEN body is written out LONGHAND 7 times (3 tri vertices + + * 4 quad vertices). When box 3 was bolted on, the `R` of the z- and + * y-axis KILL tests was left as r2 in three of those copies: + * tri v0: z and y use r2 tri v2: z uses r2 all others use r3. + * Proven by the 24 `sll $v0,$s0,16` sites: 3 per group in box 2 plus + * exactly three extra at idx 1471, 1504 (tri v0) and 2178 (tri v2). + * (4) In the QUAD lit arm only, rgb2 and rgb3 take their `<< 16` term from + * c1, not from c2/c3. Proven by the target CSE-ing ONE + * `sll $a0, $a0, 16` and re-using $a0 for all three stores. + * These are not guesses: each was read off the target and each removed a + * measured instruction-count or opcode-histogram delta. + * + * FRAME (0x360, leaf -- no $ra, no argument area) + * 0x000 tmpxy[4] | 0x010 box[8] | 0x050 sxy[8] | + * 0x090 g{otz,flag,opz,sz0..sz3} | 0x0B0 lim | 0x0B8 j | 0x0C0 i | + * 0x0C8 vd | 0x0D0 ot | 0x0D8 pkt | 0x0E0 f2 | 0x0E8 f3 | + * 0x0F0/0x0F8/0x100 x3,z3,y3 | 0x108 prim | 0x110 nprim | 0x118 vtx | + * 0x120 nparts | 0x128 part | 0x130..0x1D0 lo/hi bounds (21 s16 slots) | + * 0x1D8..0x230 cx0..cz3 (12) | 0x238 r0 | 0x240 r1lo | 0x248 r2lo | + * 0x250 r3lo | 0x288..0x2D0 the LICM-hoisted sign-extended bounds | + * 0x328/0x330 spilled vertex coords | 0x338..0x358 s0-s7,fp. + * *** THE SLOT ORDER IS THE DECLARATION-ORDER ORACLE (see L5). *** + * + * --------------------------------------------------------------------------- + * THE LEVERS, each with its MEASURED effect (byte-identical %, anchored) + * + * L1 `cb = (tp[0] & 0xFF000000) | 0x101010;` in both UNLIT arms. + * Was plain black (copied from the 3-box sibling). -64 -> -62 length, + * and it retired the whole or/ori/lui histogram delta. + * L2 box-3 ATTEN kill-register per copy (artefact 3 above) + `r3lo = r2 - + * 0x200` (artefact 1). 89.15% -> 96.96% shape; killed sra+11 / sll+9. + * L3 quad-lit rgb2/rgb3 use `c1 << 16` (artefact 4). Killed the last sll+2. + * L4 **`s32 c0, c1, c2, c3;` DECLARED INSIDE THE TWO CULL BLOCKS**, not at + * function scope. THE SINGLE BIGGEST LEVER: 52.26% -> 92.86% byte, and it + * is what finally spills r1lo (21 reloads + 21 nops, the entire -62 + * residual length). Cookbook Sec.76 exactly: at function scope c0..c3 have + * a death in each of the four emit arms, so local-alloc.c:472 + * (REG_BASIC_BLOCK >= 0 && REG_N_DEATHS == 1) refuses them; per-cull-block + * they are 1-death local pseudos, local-alloc places them, and via + * global.c:668-671 those placements remove registers from the GLOBAL pool + * -- which is what pushes r1lo out of $fp and onto the stack. + * An `__asm__` ref-dial on mn/mx/my/mny reached the same spill (84.78%) + * but is strictly worse AND artificial. Declaration scope wins. + * L5 **`s32 f0, f1, f2, f3;` MOVED TO IMMEDIATELY AFTER `u8 *pkt;`.** + * 73.00% -> 83.98% byte. Reason (and the reusable trick): spilled pseudos + * get stack slots in PSEUDO-NUMBER order, and pseudo numbers are assigned + * in DECLARATION order -- so the target's stack-slot layout is a direct + * read-out of its declaration order. The target has f2 at 0xE0 and f3 at + * 0xE8, i.e. between `pkt` (0xD8) and `x3,z3,y3` (0xF0..0x100). After this + * one move ALL 127 stack slots agree with the target exactly. + * L6 `u32 rgbw;` per emit arm (+0.6%). + * L7 RC-15 zero-byte ref dial on `mny`, first statement of the TRI cull block + * -- identical to func_8017D960's L4, same cause: `mny` is defined first + * but outlives `my`, so allocno_compare (global.c:594) ranks `my` above it + * and `my` takes the lower $a2. The dial flips it to the target's + * mny->$a2 / my->$a3. 93.47% -> 94.21%. + * L8 `cb` as a 2-statement accumulator; quad-lit rgb word as a 3-statement + * accumulator (+0.1% together). + * L9 FOUR REGISTER PINS: va->$t2, w->$a1, f0->$s3, c1->$a0. + * 94.21% -> 99.06%. Sec.72/Sec.74 note: this function has NO `jal`, so the + * caller-saved-pin-spanning-a-call hazard cannot arise here -- that is why + * pins are usable on this member of the family and were a trap on the + * others. Pins remain PREFERENCES: adding a 5th and 6th (c0->$t4, + * c2->$t2) made it WORSE (92.86%), which is Sec.72 reproduced. + * The PIN-FREE draft is kept at .run/giants/s19_func_8017BF14_b1_pinfree.c + * (4763/4763, 789 mismatched, 100.0% structural) for anyone who wants the + * safe variant. + * + * REMAINING RESIDUAL (45 instructions, 0.9%): three register-grant ties -- + * the `w & 0xFFFF` / `w >> 16` producer temps that local-alloc combine_regs + * ties into the pinned `va`; the c0/c2 grants ($t4/$t2 vs $t2/$a2); and the + * lit rgb accumulator $v1 vs $v0 in the quad arm with its 3 one-slot + * store/shift transpositions. See .run/giants/s19_bf14_report.md. + * =========================================================================== */ + +#define gte_ldv0(r0) __asm__ volatile ( \ + "lwc2 $0, 0( %0 );" \ + "lwc2 $1, 4( %0 )" \ + : \ + : "r"( r0 ) ) + +#define gte_ldv3(r0, r1, r2) __asm__ volatile ( \ + "lwc2 $0, 0( %0 );" \ + "lwc2 $1, 4( %0 );" \ + "lwc2 $2, 0( %1 );" \ + "lwc2 $3, 4( %1 );" \ + "lwc2 $4, 0( %2 );" \ + "lwc2 $5, 4( %2 )" \ + : \ + : "r"( r0 ), "r"( r1 ), "r"( r2 ) ) + +#define gte_ldv3c(r0) __asm__ volatile ( \ + "lwc2 $0, 0( %0 );" \ + "lwc2 $1, 4( %0 );" \ + "lwc2 $2, 8( %0 );" \ + "lwc2 $3, 12( %0 );" \ + "lwc2 $4, 16( %0 );" \ + "lwc2 $5, 20( %0 )" \ + : \ + : "r"( r0 ) ) + +#define gte_rtps() __asm__ volatile ("nop;nop;rtps") +#define gte_rtpt() __asm__ volatile ("nop;nop;rtpt") +#define gte_nclip() __asm__ volatile ("nop;nop;nclip") + +#define gte_stsxy(r0) __asm__ volatile ( \ + "swc2 $14, 0( %0 )" \ + : \ + : "r"( r0 ) \ + : "memory" ) + +#define gte_stsxy3(r0, r1, r2) __asm__ volatile ( \ + "swc2 $12, 0( %0 );" \ + "swc2 $13, 0( %1 );" \ + "swc2 $14, 0( %2 )" \ + : \ + : "r"( r0 ), "r"( r1 ), "r"( r2 ) \ + : "memory" ) + +#define gte_stsxy3c(r0) __asm__ volatile ( \ + "swc2 $12, 0( %0 );" \ + "swc2 $13, 4( %0 );" \ + "swc2 $14, 8( %0 )" \ + : \ + : "r"( r0 ) \ + : "memory" ) + +#define gte_stsz3(r0, r1, r2) __asm__ volatile ( \ + "swc2 $17, 0( %0 );" \ + "swc2 $18, 0( %1 );" \ + "swc2 $19, 0( %2 )" \ + : \ + : "r"( r0 ), "r"( r1 ), "r"( r2 ) \ + : "memory" ) + +#define gte_stsz4(r0, r1, r2, r3) __asm__ volatile ( \ + "swc2 $16, 0( %0 );" \ + "swc2 $17, 0( %1 );" \ + "swc2 $18, 0( %2 );" \ + "swc2 $19, 0( %3 )" \ + : \ + : "r"( r0 ), "r"( r1 ), "r"( r2 ), "r"( r3 ) \ + : "memory" ) + +#define gte_stszotz(r0) __asm__ volatile ( \ + "mfc2 $12, $19;" \ + "nop;" \ + "sra $12, $12, 2;" \ + "sw $12, 0( %0 )" \ + : \ + : "r"( r0 ) \ + : "$12", "memory" ) + +#define gte_stflg(r0) __asm__ volatile ( \ + "cfc2 $12, $31;" \ + "nop;" \ + "sw $12, 0( %0 )" \ + : \ + : "r"( r0 ) \ + : "$12", "memory" ) + +#define gte_stopz(r0) __asm__ volatile ( \ + "swc2 $24, 0( %0 )" \ + : \ + : "r"( r0 ) \ + : "memory" ) + +/* ---- the two gouraud-textured packet layouts this function emits ---------- */ +typedef struct { + u32 tag; + u32 rgb0; s16 x0, y0; u32 uv0; + u32 rgb1; s16 x1, y1; u32 uv1; + u32 rgb2; s16 x2, y2; u16 uv2, p2; +} PolyGT3; /* 0x28 */ + +typedef struct { + u32 tag; + u32 rgb0; s16 x0, y0; u32 uv0; + u32 rgb1; s16 x1, y1; u32 uv1; + u32 rgb2; s16 x2, y2; u16 uv2, p2; + u32 rgb3; s16 x3, y3; u16 uv3, p3; +} PolyGT4; /* 0x34 */ + + +/* ---- the four light-volume descriptors (stride 0x1C) --------------------- */ +extern s32 D_80197C28; +extern u16 D_80197C2C, D_80197C2E, D_80197C30; +extern s32 D_80197C34; +extern s32 D_80197C44; +extern u16 D_80197C48, D_80197C4A, D_80197C4C; +extern s32 D_80197C50; +extern s32 D_80197C60; +extern u16 D_80197C64, D_80197C66, D_80197C68; +extern s32 D_80197C6C; +extern s32 D_80197C7C; +extern u16 D_80197C80, D_80197C82, D_80197C84; +extern u16 D_80197C88; + +/* ---- the box-containment test for one vertex against one light box ------- */ +#define BOXTEST(F, X, Y, Z, LX, HX, LY, HY, LZ, HZ) \ + if ((LX) < (X) && (X) < (HX) && (LY) < (Y) && (Y) < (HY) && (LZ) < (Z) && (Z) < (HZ)) F = 1 + +/* ---- the separable per-axis falloff, visited in x, z, y order ------------ */ +#define ATTEN(A, F, X, Y, Z, CX, CY, CZ, R, RLO) \ + A = 0; \ + if (F) { \ + d = (X) - (CX); if (d < 0) d = (CX) - (X); \ + if (d < (R)) { A = 0x80; if (d >= (RLO)) A = ((R) - d) / 4; } \ + d = (Z) - (CZ); if (d < 0) d = (CZ) - (Z); \ + if ((R) < d) A = 0; \ + else if ((RLO) < d) A = (A * (((R) - d) / 4)) >> 7; \ + d = (Y) - (CY); if (d < 0) d = (CY) - (Y); \ + if ((R) < d) A = 0; \ + else if ((RLO) < d) A = (A * (((R) - d) / 4)) >> 7; \ + } + +#define ATTEN3(A, F, X, Y, Z, CX, CY, CZ, R, RLO, RZ, RY) \ + A = 0; \ + if (F) { \ + d = (X) - (CX); if (d < 0) d = (CX) - (X); \ + if (d < (R)) { A = 0x80; if (d >= (RLO)) A = ((R) - d) / 4; } \ + d = (Z) - (CZ); if (d < 0) d = (CZ) - (Z); \ + if ((RZ) < d) A = 0; \ + else if ((RLO) < d) A = (A * (((R) - d) / 4)) >> 7; \ + d = (Y) - (CY); if (d < 0) d = (CY) - (Y); \ + if ((RY) < d) A = 0; \ + else if ((RLO) < d) A = (A * (((R) - d) / 4)) >> 7; \ + } + +#define CLAMP80(C, A0, A1, A2, A3) C = (A0) + (A1) + (A2) + (A3) + 0x10; if ((C) > 0x80) C = 0x80 + + +void func_8017BF14(s32 arg0, s32 lim) +{ + typedef struct { u32 w0, w1, w2; } Prim; + + extern u8 *D_800A5E60; + extern u8 D_800A6610[]; + extern u8 D_800AF630[]; + + DVECTOR2 tmpxy[4]; + SVECTOR2 box[8]; + SVECTOR2 sxy[8]; + struct { long otz, flag, opz, sz0, sz1, sz2, sz3; } g; + + s32 j; + u32 i; + u8 *vd; + u32 ot; + u8 *pkt; + register s32 f0 __asm__("$19"); + s32 f1, f2, f3; + s16 x3, z3, y3; + Prim *prim; + u32 nprim; + u8 *vtx; + s32 nparts; + Part *part; + s16 lo0x, hi0x, lo0y, hi0y, lo0z, hi0z; + s16 lo1x, hi1x, lo1y, hi1y, lo1z, hi1z; + s16 lo2x, hi2x, lo2y, hi2y, lo2z, hi2z; + s16 lo3x, hi3x, lo3y, hi3y, lo3z, hi3z; + s16 cx0, cy0, cz0, cx1, cy1, cz1, cx2, cy2, cz2, cx3, cy3, cz3; + s16 r0; + s16 r1; + s16 r2; + s16 r3; + s16 r0lo; + s16 r1lo; + s16 r2lo; + s16 r3lo; + register u8 *va __asm__("$10"); + u8 *vb, *vc; + register u32 w __asm__("$5"); + s32 code; + u32 vw, vzw; + u32 wx, wy, wz; + s32 xa32, xb32, t32; + s32 xmn1, xmx1, xmn2, xmx2; + s32 mnc, mxc; + s16 my, mny, mx, mn; + u8 *base; + s16 x0, y0, z0, x1, y1, z1, x2, y2, z2; + s32 a0v, a1v, a2v, a3v; + s32 d; + u32 *tp; + u32 uvw; + u32 cb; + + base = D_800AF630; + + r2lo = 0; + r1lo = 0; + r0lo = 0; + r2 = 0; + r1 = 0; + r0 = 0; + + if (D_80197C28) { + cx0 = D_80197C2C; + cy0 = D_80197C2E; + r0lo = D_80197C34 - 0x200; + r0 = D_80197C34; + cz0 = D_80197C30; + } else { + cz0 = 0x6000; + cy0 = 0x6000; + cx0 = 0x6000; + } + if (D_80197C44) { + cx1 = D_80197C48; + cy1 = D_80197C4A; + r1 = D_80197C50; + r1lo = D_80197C50 - 0x200; + cz1 = D_80197C4C; + } else { + cz1 = 0x6000; + cy1 = 0x6000; + cx1 = 0x6000; + } + if (D_80197C60) { + cx2 = D_80197C64; + cy2 = D_80197C66; + r2 = D_80197C6C; + r2lo = D_80197C6C - 0x200; + cz2 = D_80197C68; + } else { + cz2 = 0x6000; + cy2 = 0x6000; + cx2 = 0x6000; + } + if (D_80197C7C) { + cx3 = D_80197C80; + cy3 = D_80197C82; + r3lo = r2 - 0x200; + r3 = D_80197C88; + cz3 = D_80197C84; + } else { + cz3 = 0x6000; + cy3 = 0x6000; + cx3 = 0x6000; + } + + lo0x = cx0 - r0; hi0x = cx0 + r0; + lo0y = cy0 - r0; hi0y = cy0 + r0; + lo0z = cz0 - r0; hi0z = cz0 + r0; + lo1x = cx1 - r1; hi1x = cx1 + r1; + lo1y = cy1 - r1; hi1y = cy1 + r1; + lo1z = cz1 - r1; hi1z = cz1 + r1; + lo2x = cx2 - r2; hi2x = cx2 + r2; + lo2y = cy2 - r2; hi2y = cy2 + r2; + lo2z = cz2 - r2; hi2z = cz2 + r2; + lo3x = cx3 - r3; hi3x = cx3 + r3; + lo3y = cy3 - r3; hi3y = cy3 + r3; + lo3z = cz3 - r3; hi3z = cz3 + r3; + + pkt = D_800A5E60; + part = *(Part **)(arg0 + 0xC); + nparts = *(s32 *)(*(s32 *)(arg0 + 8) + 8); + vtx = *(u8 **)(*(s32 *)(arg0 + 8) + 0x10); + ot = (u32)&D_800A6610[(*(u16 *)(base + 0xA3D2)) << 14]; + + for (j = 0; j < nparts; j++, part++) { + wx = part->xx; + mn = wx; + mx = wx >> 16; + wy = part->yy; + mny = wy; + my = wy >> 16; + wz = part->zz; + box[0].vx = mn; box[0].vy = mny; + box[1].vx = mx; box[1].vy = mny; + box[2].vx = mn; box[2].vy = mny; + box[3].vx = mx; box[3].vy = mny; + box[4].vx = mn; box[4].vy = my; + box[5].vx = mx; box[5].vy = my; + box[6].vx = mn; box[6].vy = my; + box[7].vx = mx; box[7].vy = my; + wy = wz >> 16; + box[0].vz = wz; + box[1].vz = wz; + box[4].vz = wz; + box[5].vz = wz; + box[2].vz = wy; + box[3].vz = wy; + box[6].vz = wy; + box[7].vz = wy; + + gte_ldv3c(&box[0]); + gte_rtpt(); + gte_stsxy3(&sxy[0], &sxy[1], &sxy[2]); + gte_ldv0(&box[3]); + gte_rtps(); + gte_stsxy(&sxy[3]); + gte_ldv3c(&box[4]); + gte_rtpt(); + gte_stsxy3(&sxy[4], &sxy[5], &sxy[6]); + gte_ldv0(&box[7]); + gte_rtps(); + gte_stsxy(&sxy[7]); + gte_stszotz(&g.otz); + + if (lim >= g.otz) { + xa32 = sxy[0].vx; + xb32 = sxy[1].vx; + if (xb32 < xa32) { xmx1 = xa32; xmn1 = xb32; } else { xmn1 = xa32; xmx1 = xb32; } + t32 = sxy[2].vx; + if (xmx1 < t32) xmx1 = t32; else if (t32 < xmn1) xmn1 = t32; + t32 = sxy[3].vx; + if (xmx1 < t32) xmx1 = t32; else if (t32 < xmn1) xmn1 = t32; + xa32 = sxy[4].vx; + xb32 = sxy[5].vx; + if (xb32 < xa32) { xmx2 = xa32; xmn2 = xb32; } else { xmn2 = xa32; xmx2 = xb32; } + t32 = sxy[6].vx; + if (xmx2 < t32) xmx2 = t32; else if (t32 < xmn2) xmn2 = t32; + t32 = sxy[7].vx; + if (xmx2 < t32) xmx2 = t32; else if (t32 < xmn2) xmn2 = t32; + mnc = xmn1; + if (xmn2 < xmn1) mnc = xmn2; + mxc = xmx1; + if (mxc < xmx2) mxc = xmx2; + if ((s16)mxc >= -0xA0 && (s16)mnc < 0xA1) { + xa32 = sxy[0].vy; + xb32 = sxy[1].vy; + if (xb32 < xa32) { xmx1 = xa32; xmn1 = xb32; } else { xmn1 = xa32; xmx1 = xb32; } + t32 = sxy[2].vy; + if (xmx1 < t32) xmx1 = t32; else if (t32 < xmn1) xmn1 = t32; + t32 = sxy[3].vy; + if (xmx1 < t32) xmx1 = t32; else if (t32 < xmn1) xmn1 = t32; + xa32 = sxy[4].vy; + xb32 = sxy[5].vy; + if (xb32 < xa32) { xmx2 = xa32; xmn2 = xb32; } else { xmn2 = xa32; xmx2 = xb32; } + t32 = sxy[6].vy; + if (xmx2 < t32) xmx2 = t32; else if (t32 < xmn2) xmn2 = t32; + t32 = sxy[7].vy; + if (xmx2 < t32) xmx2 = t32; else if (t32 < xmn2) xmn2 = t32; + mnc = xmn1; + if (xmn2 < xmn1) mnc = xmn2; + mxc = xmx1; + if (mxc < xmx2) mxc = xmx2; + if ((s16)mxc >= -0x6E && (s16)mnc < 0x6F) { + nprim = part->nprim; + prim = (Prim *)part->prim; + for (i = 0; i < nprim; i++, prim++) { + w = prim->w1; + va = vtx + (w & 0xFFFF); + vb = vtx + (w >> 16); + w = prim->w2; + vc = vtx + (w & 0xFFFF); + w = w >> 16; + gte_ldv3(va, vb, vc); + gte_rtpt(); + gte_stflg(&g.flag); + if (!(g.flag & 0x7F85E000)) { + gte_nclip(); + code = w & 7; + vd = vtx + (w & 0xFFF8); + gte_stopz(&g.opz); + if (g.opz > 0) { + switch (code) { + case 6: + case 7: + /* ---------------- TRI (FT3 / GT3) ---------------- */ + gte_stsxy3c(&tmpxy[0]); + gte_stsz3(&g.sz0, &g.sz1, &g.sz2); + if (tmpxy[0].vx > tmpxy[1].vx) { mx = tmpxy[0].vx; mn = tmpxy[1].vx; } + else { mn = tmpxy[0].vx; mx = tmpxy[1].vx; } + if (tmpxy[2].vx > mx) mx = tmpxy[2].vx; + else if (tmpxy[2].vx < mn) mn = tmpxy[2].vx; + if (mx >= -0xA0 && mn < 0xA1) { + if (tmpxy[0].vy > tmpxy[1].vy) { my = tmpxy[0].vy; mny = tmpxy[1].vy; } + else { mny = tmpxy[0].vy; my = tmpxy[1].vy; } + if (tmpxy[2].vy > my) my = tmpxy[2].vy; + else if (tmpxy[2].vy < mny) mny = tmpxy[2].vy; + if (my >= -0x6E && mny < 0x6F) { + s32 za, zb; + register s32 c1 __asm__("$4"); s32 c0, c2, c3; + __asm__ __volatile__ ("" :: "r" (mny)); + if (g.sz0 > g.sz1) { za = g.sz0; if (za < g.sz2) za = g.sz2; } + else { za = g.sz1; if (za < g.sz2) za = g.sz2; } + g.opz = za; + + f3 = 0; f2 = 0; f1 = 0; f0 = 0; + + vw = *(u32 *)va; + vzw = *(u32 *)(va + 4); + x0 = vw; y0 = vw >> 16; z0 = vzw; + BOXTEST(f0, x0, y0, z0, lo0x, hi0x, lo0y, hi0y, lo0z, hi0z); + BOXTEST(f1, x0, y0, z0, lo1x, hi1x, lo1y, hi1y, lo1z, hi1z); + BOXTEST(f2, x0, y0, z0, lo2x, hi2x, lo2y, hi2y, lo2z, hi2z); + BOXTEST(f3, x0, y0, z0, lo3x, hi3x, lo3y, hi3y, lo3z, hi3z); + vw = *(u32 *)vb; + vzw = *(u32 *)(vb + 4); + x1 = vw; y1 = vw >> 16; z1 = vzw; + BOXTEST(f0, x1, y1, z1, lo0x, hi0x, lo0y, hi0y, lo0z, hi0z); + BOXTEST(f1, x1, y1, z1, lo1x, hi1x, lo1y, hi1y, lo1z, hi1z); + BOXTEST(f2, x1, y1, z1, lo2x, hi2x, lo2y, hi2y, lo2z, hi2z); + BOXTEST(f3, x1, y1, z1, lo3x, hi3x, lo3y, hi3y, lo3z, hi3z); + vw = *(u32 *)vc; + vzw = *(u32 *)(vc + 4); + x2 = vw; y2 = vw >> 16; z2 = vzw; + BOXTEST(f0, x2, y2, z2, lo0x, hi0x, lo0y, hi0y, lo0z, hi0z); + BOXTEST(f1, x2, y2, z2, lo1x, hi1x, lo1y, hi1y, lo1z, hi1z); + BOXTEST(f2, x2, y2, z2, lo2x, hi2x, lo2y, hi2y, lo2z, hi2z); + BOXTEST(f3, x2, y2, z2, lo3x, hi3x, lo3y, hi3y, lo3z, hi3z); + + if (f0 | f1 | f2 | f3) { + u32 *otp; + u32 rgbw; + ATTEN(a0v, f0, x0, y0, z0, cx0, cy0, cz0, r0, r0lo); + ATTEN(a1v, f1, x0, y0, z0, cx1, cy1, cz1, r1, r1lo); + ATTEN(a2v, f2, x0, y0, z0, cx2, cy2, cz2, r2, r2lo); + ATTEN3(a3v, f3, x0, y0, z0, cx3, cy3, cz3, r3, r3lo, r2, r2); + CLAMP80(c0, a0v, a1v, a2v, a3v); + ATTEN(a0v, f0, x1, y1, z1, cx0, cy0, cz0, r0, r0lo); + ATTEN(a1v, f1, x1, y1, z1, cx1, cy1, cz1, r1, r1lo); + ATTEN(a2v, f2, x1, y1, z1, cx2, cy2, cz2, r2, r2lo); + ATTEN3(a3v, f3, x1, y1, z1, cx3, cy3, cz3, r3, r3lo, r3, r3); + CLAMP80(c1, a0v, a1v, a2v, a3v); + ATTEN(a0v, f0, x2, y2, z2, cx0, cy0, cz0, r0, r0lo); + ATTEN(a1v, f1, x2, y2, z2, cx1, cy1, cz1, r1, r1lo); + ATTEN(a2v, f2, x2, y2, z2, cx2, cy2, cz2, r2, r2lo); + ATTEN3(a3v, f3, x2, y2, z2, cx3, cy3, cz3, r3, r3lo, r2, r3); + CLAMP80(c2, a0v, a1v, a2v, a3v); + + *(u32 *)&((PolyGT3 *)pkt)->x0 = *(u32 *)&tmpxy[0]; + *(u32 *)&((PolyGT3 *)pkt)->x1 = *(u32 *)&tmpxy[1]; + *(u32 *)&((PolyGT3 *)pkt)->x2 = *(u32 *)&tmpxy[2]; + tp = (u32 *)prim->w0; + cb = 0x34000000; + rgbw = (c0 | cb) | (c0 << 8) | (c0 << 16); + ((PolyGT3 *)pkt)->rgb0 = rgbw; + rgbw = (c1 | cb) | (c1 << 8) | (c1 << 16); + ((PolyGT3 *)pkt)->rgb1 = rgbw; + rgbw = (c2 | cb) | (c2 << 8) | (c2 << 16); + ((PolyGT3 *)pkt)->rgb2 = rgbw; + ((PolyGT3 *)pkt)->uv0 = tp[1]; + ((PolyGT3 *)pkt)->uv1 = tp[2]; + ((PolyGT3 *)pkt)->uv2 = tp[3]; + otp = (u32 *)(((g.opz >> 2) << 2) + ot); + *(u32 *)pkt = (*otp & 0xFFFFFF) | 0x9000000; + *otp = (*otp & 0xFF000000) | ((u32)pkt & 0xFFFFFF); + pkt += 0x28; + } else { + u32 *otp; + u32 rgbw; + *(u32 *)&((PolyFT3 *)pkt)->x0 = *(u32 *)&tmpxy[0]; + *(u32 *)&((PolyFT3 *)pkt)->x1 = *(u32 *)&tmpxy[1]; + *(u32 *)&((PolyFT3 *)pkt)->x2 = *(u32 *)&tmpxy[2]; + tp = (u32 *)prim->w0; + cb = tp[0] & 0xFF000000; + cb |= 0x101010; + ((PolyFT3 *)pkt)->rgbc = cb; + ((PolyFT3 *)pkt)->uvc0 = tp[1]; + ((PolyFT3 *)pkt)->uvp1 = tp[2]; + ((PolyFT3 *)pkt)->uv2 = tp[3]; + otp = (u32 *)(((g.opz >> 2) << 2) + ot); + *(u32 *)pkt = (*otp & 0xFFFFFF) | 0x7000000; + *otp = (*otp & 0xFF000000) | ((u32)pkt & 0xFFFFFF); + pkt += 0x20; + } + } + } + break; + case 2: + case 3: + /* ---------------- QUAD (FT4 / GT4) ---------------- */ + gte_stsxy3c(&tmpxy[0]); + gte_ldv0(vd); + gte_rtps(); + if (tmpxy[0].vx > tmpxy[1].vx) { mx = tmpxy[0].vx; mn = tmpxy[1].vx; } + else { mn = tmpxy[0].vx; mx = tmpxy[1].vx; } + if (tmpxy[2].vx > mx) mx = tmpxy[2].vx; + else if (tmpxy[2].vx < mn) mn = tmpxy[2].vx; + if (tmpxy[0].vy > tmpxy[1].vy) { my = tmpxy[0].vy; mny = tmpxy[1].vy; } + else { mny = tmpxy[0].vy; my = tmpxy[1].vy; } + if (tmpxy[2].vy > my) my = tmpxy[2].vy; + else if (tmpxy[2].vy < mny) mny = tmpxy[2].vy; + gte_stflg(&g.flag); + if (!(g.flag & 0x7F85E000)) { + gte_stsz4(&g.sz0, &g.sz1, &g.sz2, &g.sz3); + gte_stsxy((long *)&((PolyFT4 *)pkt)->x3); + if (((PolyFT4 *)pkt)->x3 < mn) mn = ((PolyFT4 *)pkt)->x3; + else if (mx < ((PolyFT4 *)pkt)->x3) mx = ((PolyFT4 *)pkt)->x3; + if (mx >= -0xA0 && mn < 0xA1) { + if (((PolyFT4 *)pkt)->y3 < mny) mny = ((PolyFT4 *)pkt)->y3; + else if (my < ((PolyFT4 *)pkt)->y3) my = ((PolyFT4 *)pkt)->y3; + if (my >= -0x6E && mny < 0x6F) { + s32 za, zb; + register s32 c1 __asm__("$4"); s32 c0, c2, c3; + zb = g.sz2; + if (zb < g.sz3) zb = g.sz3; + za = g.sz0; + if (za < g.sz1) za = g.sz1; + if (za < zb) za = zb; + g.opz = za; + + f3 = 0; f2 = 0; f1 = 0; f0 = 0; + + vw = *(u32 *)va; + vzw = *(u32 *)(va + 4); + x0 = vw; y0 = vw >> 16; z0 = vzw; + BOXTEST(f0, x0, y0, z0, lo0x, hi0x, lo0y, hi0y, lo0z, hi0z); + BOXTEST(f1, x0, y0, z0, lo1x, hi1x, lo1y, hi1y, lo1z, hi1z); + BOXTEST(f2, x0, y0, z0, lo2x, hi2x, lo2y, hi2y, lo2z, hi2z); + BOXTEST(f3, x0, y0, z0, lo3x, hi3x, lo3y, hi3y, lo3z, hi3z); + vw = *(u32 *)vb; + vzw = *(u32 *)(vb + 4); + x1 = vw; y1 = vw >> 16; z1 = vzw; + BOXTEST(f0, x1, y1, z1, lo0x, hi0x, lo0y, hi0y, lo0z, hi0z); + BOXTEST(f1, x1, y1, z1, lo1x, hi1x, lo1y, hi1y, lo1z, hi1z); + BOXTEST(f2, x1, y1, z1, lo2x, hi2x, lo2y, hi2y, lo2z, hi2z); + BOXTEST(f3, x1, y1, z1, lo3x, hi3x, lo3y, hi3y, lo3z, hi3z); + vw = *(u32 *)vc; + vzw = *(u32 *)(vc + 4); + x2 = vw; y2 = vw >> 16; z2 = vzw; + BOXTEST(f0, x2, y2, z2, lo0x, hi0x, lo0y, hi0y, lo0z, hi0z); + BOXTEST(f1, x2, y2, z2, lo1x, hi1x, lo1y, hi1y, lo1z, hi1z); + BOXTEST(f2, x2, y2, z2, lo2x, hi2x, lo2y, hi2y, lo2z, hi2z); + BOXTEST(f3, x2, y2, z2, lo3x, hi3x, lo3y, hi3y, lo3z, hi3z); + vw = *(u32 *)vd; + vzw = *(u32 *)(vd + 4); + x3 = vw; y3 = vw >> 16; z3 = vzw; + BOXTEST(f0, x3, y3, z3, lo0x, hi0x, lo0y, hi0y, lo0z, hi0z); + BOXTEST(f1, x3, y3, z3, lo1x, hi1x, lo1y, hi1y, lo1z, hi1z); + BOXTEST(f2, x3, y3, z3, lo2x, hi2x, lo2y, hi2y, lo2z, hi2z); + BOXTEST(f3, x3, y3, z3, lo3x, hi3x, lo3y, hi3y, lo3z, hi3z); + + if (f0 | f1 | f2 | f3) { + u32 *otp; + u32 rgbw; + ATTEN(a0v, f0, x0, y0, z0, cx0, cy0, cz0, r0, r0lo); + ATTEN(a1v, f1, x0, y0, z0, cx1, cy1, cz1, r1, r1lo); + ATTEN(a2v, f2, x0, y0, z0, cx2, cy2, cz2, r2, r2lo); + ATTEN3(a3v, f3, x0, y0, z0, cx3, cy3, cz3, r3, r3lo, r3, r3); + CLAMP80(c0, a0v, a1v, a2v, a3v); + ATTEN(a0v, f0, x1, y1, z1, cx0, cy0, cz0, r0, r0lo); + ATTEN(a1v, f1, x1, y1, z1, cx1, cy1, cz1, r1, r1lo); + ATTEN(a2v, f2, x1, y1, z1, cx2, cy2, cz2, r2, r2lo); + ATTEN3(a3v, f3, x1, y1, z1, cx3, cy3, cz3, r3, r3lo, r3, r3); + CLAMP80(c1, a0v, a1v, a2v, a3v); + ATTEN(a0v, f0, x2, y2, z2, cx0, cy0, cz0, r0, r0lo); + ATTEN(a1v, f1, x2, y2, z2, cx1, cy1, cz1, r1, r1lo); + ATTEN(a2v, f2, x2, y2, z2, cx2, cy2, cz2, r2, r2lo); + ATTEN3(a3v, f3, x2, y2, z2, cx3, cy3, cz3, r3, r3lo, r3, r3); + CLAMP80(c2, a0v, a1v, a2v, a3v); + ATTEN(a0v, f0, x3, y3, z3, cx0, cy0, cz0, r0, r0lo); + ATTEN(a1v, f1, x3, y3, z3, cx1, cy1, cz1, r1, r1lo); + ATTEN(a2v, f2, x3, y3, z3, cx2, cy2, cz2, r2, r2lo); + ATTEN3(a3v, f3, x3, y3, z3, cx3, cy3, cz3, r3, r3lo, r3, r3); + CLAMP80(c3, a0v, a1v, a2v, a3v); + + *(u32 *)&((PolyGT4 *)pkt)->x0 = *(u32 *)&tmpxy[0]; + *(u32 *)&((PolyGT4 *)pkt)->x1 = *(u32 *)&tmpxy[1]; + *(u32 *)&((PolyGT4 *)pkt)->x2 = *(u32 *)&tmpxy[2]; + gte_stsxy((long *)&((PolyGT4 *)pkt)->x3); + tp = (u32 *)prim->w0; + cb = 0x3C000000; + rgbw = c0 | cb; rgbw |= c0 << 8; rgbw |= c0 << 16; + ((PolyGT4 *)pkt)->rgb0 = rgbw; + rgbw = c1 | cb; rgbw |= c1 << 8; rgbw |= c1 << 16; + ((PolyGT4 *)pkt)->rgb1 = rgbw; + rgbw = c2 | cb; rgbw |= c2 << 8; rgbw |= c1 << 16; + ((PolyGT4 *)pkt)->rgb2 = rgbw; + rgbw = c3 | cb; rgbw |= c3 << 8; rgbw |= c1 << 16; + ((PolyGT4 *)pkt)->rgb3 = rgbw; + ((PolyGT4 *)pkt)->uv0 = tp[1]; + ((PolyGT4 *)pkt)->uv1 = tp[2]; + uvw = tp[3]; + ((PolyGT4 *)pkt)->uv2 = uvw; + ((PolyGT4 *)pkt)->uv3 = uvw >> 16; + otp = (u32 *)(((g.opz >> 2) << 2) + ot); + *(u32 *)pkt = (*otp & 0xFFFFFF) | 0xC000000; + *otp = (*otp & 0xFF000000) | ((u32)pkt & 0xFFFFFF); + pkt += 0x34; + } else { + u32 *otp; + u32 rgbw; + *(u32 *)&((PolyFT4 *)pkt)->x0 = *(u32 *)&tmpxy[0]; + *(u32 *)&((PolyFT4 *)pkt)->x1 = *(u32 *)&tmpxy[1]; + *(u32 *)&((PolyFT4 *)pkt)->x2 = *(u32 *)&tmpxy[2]; + tp = (u32 *)prim->w0; + cb = tp[0] & 0xFF000000; + cb |= 0x101010; + ((PolyFT4 *)pkt)->rgbc = cb; + ((PolyFT4 *)pkt)->uvc0 = tp[1]; + ((PolyFT4 *)pkt)->uvp1 = tp[2]; + uvw = tp[3]; + ((PolyFT4 *)pkt)->uv2 = uvw; + ((PolyFT4 *)pkt)->uv3 = uvw >> 16; + otp = (u32 *)(((g.opz >> 2) << 2) + ot); + *(u32 *)pkt = (*otp & 0xFFFFFF) | 0x9000000; + *otp = (*otp & 0xFF000000) | ((u32)pkt & 0xFFFFFF); + pkt += 0x28; + } + } + } + } + break; + } + } + } + } + } + } + } + } + D_800A5E60 = pkt; +} diff --git a/.run/giants/s19_func_8017BF14_b1_pinfree.c b/.run/giants/s19_func_8017BF14_b1_pinfree.c new file mode 100644 index 000000000..93086e3d8 --- /dev/null +++ b/.run/giants/s19_func_8017BF14_b1_pinfree.c @@ -0,0 +1,767 @@ +#include "common.h" +#include "/home/musashi/bfm-decomp/src/shared/engine_types.h" + +/* =========================================================================== + * func_8017BF14 -- 4,763 ins, ov_SC03_116 (behemoth #4). COLD START. + * + * STATUS (2026-07-25, session 20, gcc-2.7.2 pinned triple): + * python3 tools/match_one.py func_8017BF14 --c \ + * --asm-subdir asm/ov_SC03_116/nonmatchings/ov_SC03_116_jr_8017AE2C + * -> DIFF 4763/4763 ins, 789 mismatched [PIN-FREE VARIANT] + * LENGTH EXACT | OPCODE HISTOGRAM EXACT (L1 = 0) | STACK FRAME EXACT + * (all 127 slots at the target's offsets, frame 0x360) + * register-masked structural alignment 4760/4763 = 99.94% + * NOT A MATCH. The whole-binary SHA1 arbiter (G3/P9) has NOT been run. + * Pin-free fallback (see LEVERS L6..L9): 4763/4763, 789 mismatched. + * + * WHAT IT IS + * The *four*-light-box variant of the volumetric-light renderer whose + * 3-box sibling func_8017D960 (3,338 ins, ov_SC03_090) is MATCHED, and whose + * unlit ancestor func_8017BEBC (ov_SC03_099) is MATCHED. Same family, same + * skeleton; this is the biggest member. + * + * Signature: func_8017BF14(s32 arg0, s32 lim). Unlike every other member of + * the family this one is a LEAF -- 0 callees. The 3-call prologue + * (func_800491EC / func_800547D8 / func_80052E38) of the siblings is gone; + * `lim` arrives as arg1 (spilled to 0xB0). That is why the frame has no + * 0x10 argument area (tmpxy[] starts at sp+0x00) and no $ra save. + * + * Per part (stride 0x14, outer loop): build the 8-corner AABB in box[], + * rtpt/rtpt + rtps/rtps -> sxy[8], stszotz -> g.otz, reject on + * `lim >= g.otz`, then screen-space bbox reject on X (-0xA0..0xA1) and + * Y (-0x6E..0x6F). + * Per prim (stride 0xC, inner loop): rtpt the 3 vertices, stflg mask + * 0x7F85E000, nclip, stopz > 0, then a 4-way range tree on `code = w & 7` + * that keeps ONLY codes 6,7 (tri) and 2,3 (quad); 0,1,4,5 fall through to + * the loop tail. + * Per drawn poly: screen bbox reject, then each vertex is tested against + * FOUR axis-aligned light boxes (flags f0..f3), and if any is lit a + * 0x00..0x80 attenuation per active box is computed, summed, biased +0x10 + * and clamped to 0x80 -> a grey gouraud vertex colour. + * lit -> POLY_GT3 (0x28, tag 0x34000000, OT 0x9000000) + * POLY_GT4 (0x34, tag 0x3C000000, OT 0xC000000) + * unlit -> POLY_FT3 (0x20, OT 0x7000000) / POLY_FT4 (0x28, OT 0x9000000) + * with rgbc = (tp[0] & 0xFF000000) | 0x101010 <-- NOT black, + * unlike func_8017D960 where the unlit colour is plain black. + * + * THE FOUR LIGHT BOXES (stride 0x1C, {s32 enable; u16 cx,cy,cz; s32 range}) + * D_80197C28 / D_80197C44 / D_80197C60 / D_80197C7C. + * Falloff geometry differs from the 3-box sibling: RLO = R - 0x200 (not + * -0x80) and the ramp is ((R - d) / 4) (not (R - d)), so the 0x80 ceiling is + * reached over a 0x200-wide band instead of 0x80. The `/ 4` is a SIGNED + * divide -- `bgez / addiu 3 / sra 2` -- not a shift. + * + * THREE ORIGINAL-SOURCE COPY-PASTE ARTEFACTS, all byte-proven (see report) + * (1) `r3lo = r2 - 0x200;` -- box 3's low radius is derived from box 2's + * RANGE VARIABLE, not from its own D_80197C88. Proven by the target's + * `addiu $t6, $s0, -0x200` reusing the register that box 2's `lw` filled; + * spelling it `D_80197C6C - 0x200` re-loads the global (+2 ins). + * (2) Only SIX of the eight radius variables are zero-initialised + * (r0,r1,r2,r0lo,r1lo,r2lo) -- r3/r3lo are left uninitialised, exactly + * the init list the 3-box version needed. + * (3) The ATTEN body is written out LONGHAND 7 times (3 tri vertices + + * 4 quad vertices). When box 3 was bolted on, the `R` of the z- and + * y-axis KILL tests was left as r2 in three of those copies: + * tri v0: z and y use r2 tri v2: z uses r2 all others use r3. + * Proven by the 24 `sll $v0,$s0,16` sites: 3 per group in box 2 plus + * exactly three extra at idx 1471, 1504 (tri v0) and 2178 (tri v2). + * (4) In the QUAD lit arm only, rgb2 and rgb3 take their `<< 16` term from + * c1, not from c2/c3. Proven by the target CSE-ing ONE + * `sll $a0, $a0, 16` and re-using $a0 for all three stores. + * These are not guesses: each was read off the target and each removed a + * measured instruction-count or opcode-histogram delta. + * + * FRAME (0x360, leaf -- no $ra, no argument area) + * 0x000 tmpxy[4] | 0x010 box[8] | 0x050 sxy[8] | + * 0x090 g{otz,flag,opz,sz0..sz3} | 0x0B0 lim | 0x0B8 j | 0x0C0 i | + * 0x0C8 vd | 0x0D0 ot | 0x0D8 pkt | 0x0E0 f2 | 0x0E8 f3 | + * 0x0F0/0x0F8/0x100 x3,z3,y3 | 0x108 prim | 0x110 nprim | 0x118 vtx | + * 0x120 nparts | 0x128 part | 0x130..0x1D0 lo/hi bounds (21 s16 slots) | + * 0x1D8..0x230 cx0..cz3 (12) | 0x238 r0 | 0x240 r1lo | 0x248 r2lo | + * 0x250 r3lo | 0x288..0x2D0 the LICM-hoisted sign-extended bounds | + * 0x328/0x330 spilled vertex coords | 0x338..0x358 s0-s7,fp. + * *** THE SLOT ORDER IS THE DECLARATION-ORDER ORACLE (see L5). *** + * + * --------------------------------------------------------------------------- + * THE LEVERS, each with its MEASURED effect (byte-identical %, anchored) + * + * L1 `cb = (tp[0] & 0xFF000000) | 0x101010;` in both UNLIT arms. + * Was plain black (copied from the 3-box sibling). -64 -> -62 length, + * and it retired the whole or/ori/lui histogram delta. + * L2 box-3 ATTEN kill-register per copy (artefact 3 above) + `r3lo = r2 - + * 0x200` (artefact 1). 89.15% -> 96.96% shape; killed sra+11 / sll+9. + * L3 quad-lit rgb2/rgb3 use `c1 << 16` (artefact 4). Killed the last sll+2. + * L4 **`s32 c0, c1, c2, c3;` DECLARED INSIDE THE TWO CULL BLOCKS**, not at + * function scope. THE SINGLE BIGGEST LEVER: 52.26% -> 92.86% byte, and it + * is what finally spills r1lo (21 reloads + 21 nops, the entire -62 + * residual length). Cookbook Sec.76 exactly: at function scope c0..c3 have + * a death in each of the four emit arms, so local-alloc.c:472 + * (REG_BASIC_BLOCK >= 0 && REG_N_DEATHS == 1) refuses them; per-cull-block + * they are 1-death local pseudos, local-alloc places them, and via + * global.c:668-671 those placements remove registers from the GLOBAL pool + * -- which is what pushes r1lo out of $fp and onto the stack. + * An `__asm__` ref-dial on mn/mx/my/mny reached the same spill (84.78%) + * but is strictly worse AND artificial. Declaration scope wins. + * L5 **`s32 f0, f1, f2, f3;` MOVED TO IMMEDIATELY AFTER `u8 *pkt;`.** + * 73.00% -> 83.98% byte. Reason (and the reusable trick): spilled pseudos + * get stack slots in PSEUDO-NUMBER order, and pseudo numbers are assigned + * in DECLARATION order -- so the target's stack-slot layout is a direct + * read-out of its declaration order. The target has f2 at 0xE0 and f3 at + * 0xE8, i.e. between `pkt` (0xD8) and `x3,z3,y3` (0xF0..0x100). After this + * one move ALL 127 stack slots agree with the target exactly. + * L6 `u32 rgbw;` per emit arm (+0.6%). + * L7 RC-15 zero-byte ref dial on `mny`, first statement of the TRI cull block + * -- identical to func_8017D960's L4, same cause: `mny` is defined first + * but outlives `my`, so allocno_compare (global.c:594) ranks `my` above it + * and `my` takes the lower $a2. The dial flips it to the target's + * mny->$a2 / my->$a3. 93.47% -> 94.21%. + * L8 `cb` as a 2-statement accumulator; quad-lit rgb word as a 3-statement + * accumulator (+0.1% together). + * L9 FOUR REGISTER PINS: va->$t2, w->$a1, f0->$s3, c1->$a0. + * 94.21% -> 99.06%. Sec.72/Sec.74 note: this function has NO `jal`, so the + * caller-saved-pin-spanning-a-call hazard cannot arise here -- that is why + * pins are usable on this member of the family and were a trap on the + * others. Pins remain PREFERENCES: adding a 5th and 6th (c0->$t4, + * c2->$t2) made it WORSE (92.86%), which is Sec.72 reproduced. + * The PIN-FREE draft is kept at .run/giants/s19_func_8017BF14_b1_pinfree.c + * (4763/4763, 789 mismatched, 100.0% structural) for anyone who wants the + * safe variant. + * + * REMAINING RESIDUAL (45 instructions, 0.9%): three register-grant ties -- + * the `w & 0xFFFF` / `w >> 16` producer temps that local-alloc combine_regs + * ties into the pinned `va`; the c0/c2 grants ($t4/$t2 vs $t2/$a2); and the + * lit rgb accumulator $v1 vs $v0 in the quad arm with its 3 one-slot + * store/shift transpositions. See .run/giants/s19_bf14_report.md. + * =========================================================================== */ + +#define gte_ldv0(r0) __asm__ volatile ( \ + "lwc2 $0, 0( %0 );" \ + "lwc2 $1, 4( %0 )" \ + : \ + : "r"( r0 ) ) + +#define gte_ldv3(r0, r1, r2) __asm__ volatile ( \ + "lwc2 $0, 0( %0 );" \ + "lwc2 $1, 4( %0 );" \ + "lwc2 $2, 0( %1 );" \ + "lwc2 $3, 4( %1 );" \ + "lwc2 $4, 0( %2 );" \ + "lwc2 $5, 4( %2 )" \ + : \ + : "r"( r0 ), "r"( r1 ), "r"( r2 ) ) + +#define gte_ldv3c(r0) __asm__ volatile ( \ + "lwc2 $0, 0( %0 );" \ + "lwc2 $1, 4( %0 );" \ + "lwc2 $2, 8( %0 );" \ + "lwc2 $3, 12( %0 );" \ + "lwc2 $4, 16( %0 );" \ + "lwc2 $5, 20( %0 )" \ + : \ + : "r"( r0 ) ) + +#define gte_rtps() __asm__ volatile ("nop;nop;rtps") +#define gte_rtpt() __asm__ volatile ("nop;nop;rtpt") +#define gte_nclip() __asm__ volatile ("nop;nop;nclip") + +#define gte_stsxy(r0) __asm__ volatile ( \ + "swc2 $14, 0( %0 )" \ + : \ + : "r"( r0 ) \ + : "memory" ) + +#define gte_stsxy3(r0, r1, r2) __asm__ volatile ( \ + "swc2 $12, 0( %0 );" \ + "swc2 $13, 0( %1 );" \ + "swc2 $14, 0( %2 )" \ + : \ + : "r"( r0 ), "r"( r1 ), "r"( r2 ) \ + : "memory" ) + +#define gte_stsxy3c(r0) __asm__ volatile ( \ + "swc2 $12, 0( %0 );" \ + "swc2 $13, 4( %0 );" \ + "swc2 $14, 8( %0 )" \ + : \ + : "r"( r0 ) \ + : "memory" ) + +#define gte_stsz3(r0, r1, r2) __asm__ volatile ( \ + "swc2 $17, 0( %0 );" \ + "swc2 $18, 0( %1 );" \ + "swc2 $19, 0( %2 )" \ + : \ + : "r"( r0 ), "r"( r1 ), "r"( r2 ) \ + : "memory" ) + +#define gte_stsz4(r0, r1, r2, r3) __asm__ volatile ( \ + "swc2 $16, 0( %0 );" \ + "swc2 $17, 0( %1 );" \ + "swc2 $18, 0( %2 );" \ + "swc2 $19, 0( %3 )" \ + : \ + : "r"( r0 ), "r"( r1 ), "r"( r2 ), "r"( r3 ) \ + : "memory" ) + +#define gte_stszotz(r0) __asm__ volatile ( \ + "mfc2 $12, $19;" \ + "nop;" \ + "sra $12, $12, 2;" \ + "sw $12, 0( %0 )" \ + : \ + : "r"( r0 ) \ + : "$12", "memory" ) + +#define gte_stflg(r0) __asm__ volatile ( \ + "cfc2 $12, $31;" \ + "nop;" \ + "sw $12, 0( %0 )" \ + : \ + : "r"( r0 ) \ + : "$12", "memory" ) + +#define gte_stopz(r0) __asm__ volatile ( \ + "swc2 $24, 0( %0 )" \ + : \ + : "r"( r0 ) \ + : "memory" ) + +/* ---- the two gouraud-textured packet layouts this function emits ---------- */ +typedef struct { + u32 tag; + u32 rgb0; s16 x0, y0; u32 uv0; + u32 rgb1; s16 x1, y1; u32 uv1; + u32 rgb2; s16 x2, y2; u16 uv2, p2; +} PolyGT3; /* 0x28 */ + +typedef struct { + u32 tag; + u32 rgb0; s16 x0, y0; u32 uv0; + u32 rgb1; s16 x1, y1; u32 uv1; + u32 rgb2; s16 x2, y2; u16 uv2, p2; + u32 rgb3; s16 x3, y3; u16 uv3, p3; +} PolyGT4; /* 0x34 */ + + +/* ---- the four light-volume descriptors (stride 0x1C) --------------------- */ +extern s32 D_80197C28; +extern u16 D_80197C2C, D_80197C2E, D_80197C30; +extern s32 D_80197C34; +extern s32 D_80197C44; +extern u16 D_80197C48, D_80197C4A, D_80197C4C; +extern s32 D_80197C50; +extern s32 D_80197C60; +extern u16 D_80197C64, D_80197C66, D_80197C68; +extern s32 D_80197C6C; +extern s32 D_80197C7C; +extern u16 D_80197C80, D_80197C82, D_80197C84; +extern u16 D_80197C88; + +/* ---- the box-containment test for one vertex against one light box ------- */ +#define BOXTEST(F, X, Y, Z, LX, HX, LY, HY, LZ, HZ) \ + if ((LX) < (X) && (X) < (HX) && (LY) < (Y) && (Y) < (HY) && (LZ) < (Z) && (Z) < (HZ)) F = 1 + +/* ---- the separable per-axis falloff, visited in x, z, y order ------------ */ +#define ATTEN(A, F, X, Y, Z, CX, CY, CZ, R, RLO) \ + A = 0; \ + if (F) { \ + d = (X) - (CX); if (d < 0) d = (CX) - (X); \ + if (d < (R)) { A = 0x80; if (d >= (RLO)) A = ((R) - d) / 4; } \ + d = (Z) - (CZ); if (d < 0) d = (CZ) - (Z); \ + if ((R) < d) A = 0; \ + else if ((RLO) < d) A = (A * (((R) - d) / 4)) >> 7; \ + d = (Y) - (CY); if (d < 0) d = (CY) - (Y); \ + if ((R) < d) A = 0; \ + else if ((RLO) < d) A = (A * (((R) - d) / 4)) >> 7; \ + } + +#define ATTEN3(A, F, X, Y, Z, CX, CY, CZ, R, RLO, RZ, RY) \ + A = 0; \ + if (F) { \ + d = (X) - (CX); if (d < 0) d = (CX) - (X); \ + if (d < (R)) { A = 0x80; if (d >= (RLO)) A = ((R) - d) / 4; } \ + d = (Z) - (CZ); if (d < 0) d = (CZ) - (Z); \ + if ((RZ) < d) A = 0; \ + else if ((RLO) < d) A = (A * (((R) - d) / 4)) >> 7; \ + d = (Y) - (CY); if (d < 0) d = (CY) - (Y); \ + if ((RY) < d) A = 0; \ + else if ((RLO) < d) A = (A * (((R) - d) / 4)) >> 7; \ + } + +#define CLAMP80(C, A0, A1, A2, A3) C = (A0) + (A1) + (A2) + (A3) + 0x10; if ((C) > 0x80) C = 0x80 + + +void func_8017BF14(s32 arg0, s32 lim) +{ + typedef struct { u32 w0, w1, w2; } Prim; + + extern u8 *D_800A5E60; + extern u8 D_800A6610[]; + extern u8 D_800AF630[]; + + DVECTOR2 tmpxy[4]; + SVECTOR2 box[8]; + SVECTOR2 sxy[8]; + struct { long otz, flag, opz, sz0, sz1, sz2, sz3; } g; + + s32 j; + u32 i; + u8 *vd; + u32 ot; + u8 *pkt; + s32 f0, f1, f2, f3; + s16 x3, z3, y3; + Prim *prim; + u32 nprim; + u8 *vtx; + s32 nparts; + Part *part; + s16 lo0x, hi0x, lo0y, hi0y, lo0z, hi0z; + s16 lo1x, hi1x, lo1y, hi1y, lo1z, hi1z; + s16 lo2x, hi2x, lo2y, hi2y, lo2z, hi2z; + s16 lo3x, hi3x, lo3y, hi3y, lo3z, hi3z; + s16 cx0, cy0, cz0, cx1, cy1, cz1, cx2, cy2, cz2, cx3, cy3, cz3; + s16 r0; + s16 r1; + s16 r2; + s16 r3; + s16 r0lo; + s16 r1lo; + s16 r2lo; + s16 r3lo; + u8 *va, *vb, *vc; + u32 w; s32 code; + u32 vw, vzw; + u32 wx, wy, wz; + s32 xa32, xb32, t32; + s32 xmn1, xmx1, xmn2, xmx2; + s32 mnc, mxc; + s16 my, mny, mx, mn; + u8 *base; + s16 x0, y0, z0, x1, y1, z1, x2, y2, z2; + s32 a0v, a1v, a2v, a3v; + s32 d; + u32 *tp; + u32 uvw; + u32 cb; + + base = D_800AF630; + + r2lo = 0; + r1lo = 0; + r0lo = 0; + r2 = 0; + r1 = 0; + r0 = 0; + + if (D_80197C28) { + cx0 = D_80197C2C; + cy0 = D_80197C2E; + r0lo = D_80197C34 - 0x200; + r0 = D_80197C34; + cz0 = D_80197C30; + } else { + cz0 = 0x6000; + cy0 = 0x6000; + cx0 = 0x6000; + } + if (D_80197C44) { + cx1 = D_80197C48; + cy1 = D_80197C4A; + r1 = D_80197C50; + r1lo = D_80197C50 - 0x200; + cz1 = D_80197C4C; + } else { + cz1 = 0x6000; + cy1 = 0x6000; + cx1 = 0x6000; + } + if (D_80197C60) { + cx2 = D_80197C64; + cy2 = D_80197C66; + r2 = D_80197C6C; + r2lo = D_80197C6C - 0x200; + cz2 = D_80197C68; + } else { + cz2 = 0x6000; + cy2 = 0x6000; + cx2 = 0x6000; + } + if (D_80197C7C) { + cx3 = D_80197C80; + cy3 = D_80197C82; + r3lo = r2 - 0x200; + r3 = D_80197C88; + cz3 = D_80197C84; + } else { + cz3 = 0x6000; + cy3 = 0x6000; + cx3 = 0x6000; + } + + lo0x = cx0 - r0; hi0x = cx0 + r0; + lo0y = cy0 - r0; hi0y = cy0 + r0; + lo0z = cz0 - r0; hi0z = cz0 + r0; + lo1x = cx1 - r1; hi1x = cx1 + r1; + lo1y = cy1 - r1; hi1y = cy1 + r1; + lo1z = cz1 - r1; hi1z = cz1 + r1; + lo2x = cx2 - r2; hi2x = cx2 + r2; + lo2y = cy2 - r2; hi2y = cy2 + r2; + lo2z = cz2 - r2; hi2z = cz2 + r2; + lo3x = cx3 - r3; hi3x = cx3 + r3; + lo3y = cy3 - r3; hi3y = cy3 + r3; + lo3z = cz3 - r3; hi3z = cz3 + r3; + + pkt = D_800A5E60; + part = *(Part **)(arg0 + 0xC); + nparts = *(s32 *)(*(s32 *)(arg0 + 8) + 8); + vtx = *(u8 **)(*(s32 *)(arg0 + 8) + 0x10); + ot = (u32)&D_800A6610[(*(u16 *)(base + 0xA3D2)) << 14]; + + for (j = 0; j < nparts; j++, part++) { + wx = part->xx; + mn = wx; + mx = wx >> 16; + wy = part->yy; + mny = wy; + my = wy >> 16; + wz = part->zz; + box[0].vx = mn; box[0].vy = mny; + box[1].vx = mx; box[1].vy = mny; + box[2].vx = mn; box[2].vy = mny; + box[3].vx = mx; box[3].vy = mny; + box[4].vx = mn; box[4].vy = my; + box[5].vx = mx; box[5].vy = my; + box[6].vx = mn; box[6].vy = my; + box[7].vx = mx; box[7].vy = my; + wy = wz >> 16; + box[0].vz = wz; + box[1].vz = wz; + box[4].vz = wz; + box[5].vz = wz; + box[2].vz = wy; + box[3].vz = wy; + box[6].vz = wy; + box[7].vz = wy; + + gte_ldv3c(&box[0]); + gte_rtpt(); + gte_stsxy3(&sxy[0], &sxy[1], &sxy[2]); + gte_ldv0(&box[3]); + gte_rtps(); + gte_stsxy(&sxy[3]); + gte_ldv3c(&box[4]); + gte_rtpt(); + gte_stsxy3(&sxy[4], &sxy[5], &sxy[6]); + gte_ldv0(&box[7]); + gte_rtps(); + gte_stsxy(&sxy[7]); + gte_stszotz(&g.otz); + + if (lim >= g.otz) { + xa32 = sxy[0].vx; + xb32 = sxy[1].vx; + if (xb32 < xa32) { xmx1 = xa32; xmn1 = xb32; } else { xmn1 = xa32; xmx1 = xb32; } + t32 = sxy[2].vx; + if (xmx1 < t32) xmx1 = t32; else if (t32 < xmn1) xmn1 = t32; + t32 = sxy[3].vx; + if (xmx1 < t32) xmx1 = t32; else if (t32 < xmn1) xmn1 = t32; + xa32 = sxy[4].vx; + xb32 = sxy[5].vx; + if (xb32 < xa32) { xmx2 = xa32; xmn2 = xb32; } else { xmn2 = xa32; xmx2 = xb32; } + t32 = sxy[6].vx; + if (xmx2 < t32) xmx2 = t32; else if (t32 < xmn2) xmn2 = t32; + t32 = sxy[7].vx; + if (xmx2 < t32) xmx2 = t32; else if (t32 < xmn2) xmn2 = t32; + mnc = xmn1; + if (xmn2 < xmn1) mnc = xmn2; + mxc = xmx1; + if (mxc < xmx2) mxc = xmx2; + if ((s16)mxc >= -0xA0 && (s16)mnc < 0xA1) { + xa32 = sxy[0].vy; + xb32 = sxy[1].vy; + if (xb32 < xa32) { xmx1 = xa32; xmn1 = xb32; } else { xmn1 = xa32; xmx1 = xb32; } + t32 = sxy[2].vy; + if (xmx1 < t32) xmx1 = t32; else if (t32 < xmn1) xmn1 = t32; + t32 = sxy[3].vy; + if (xmx1 < t32) xmx1 = t32; else if (t32 < xmn1) xmn1 = t32; + xa32 = sxy[4].vy; + xb32 = sxy[5].vy; + if (xb32 < xa32) { xmx2 = xa32; xmn2 = xb32; } else { xmn2 = xa32; xmx2 = xb32; } + t32 = sxy[6].vy; + if (xmx2 < t32) xmx2 = t32; else if (t32 < xmn2) xmn2 = t32; + t32 = sxy[7].vy; + if (xmx2 < t32) xmx2 = t32; else if (t32 < xmn2) xmn2 = t32; + mnc = xmn1; + if (xmn2 < xmn1) mnc = xmn2; + mxc = xmx1; + if (mxc < xmx2) mxc = xmx2; + if ((s16)mxc >= -0x6E && (s16)mnc < 0x6F) { + nprim = part->nprim; + prim = (Prim *)part->prim; + for (i = 0; i < nprim; i++, prim++) { + w = prim->w1; + va = vtx + (w & 0xFFFF); + vb = vtx + (w >> 16); + w = prim->w2; + vc = vtx + (w & 0xFFFF); + w = w >> 16; + gte_ldv3(va, vb, vc); + gte_rtpt(); + gte_stflg(&g.flag); + if (!(g.flag & 0x7F85E000)) { + gte_nclip(); + code = w & 7; + vd = vtx + (w & 0xFFF8); + gte_stopz(&g.opz); + if (g.opz > 0) { + switch (code) { + case 6: + case 7: + /* ---------------- TRI (FT3 / GT3) ---------------- */ + gte_stsxy3c(&tmpxy[0]); + gte_stsz3(&g.sz0, &g.sz1, &g.sz2); + if (tmpxy[0].vx > tmpxy[1].vx) { mx = tmpxy[0].vx; mn = tmpxy[1].vx; } + else { mn = tmpxy[0].vx; mx = tmpxy[1].vx; } + if (tmpxy[2].vx > mx) mx = tmpxy[2].vx; + else if (tmpxy[2].vx < mn) mn = tmpxy[2].vx; + if (mx >= -0xA0 && mn < 0xA1) { + if (tmpxy[0].vy > tmpxy[1].vy) { my = tmpxy[0].vy; mny = tmpxy[1].vy; } + else { mny = tmpxy[0].vy; my = tmpxy[1].vy; } + if (tmpxy[2].vy > my) my = tmpxy[2].vy; + else if (tmpxy[2].vy < mny) mny = tmpxy[2].vy; + if (my >= -0x6E && mny < 0x6F) { + s32 za, zb; + s32 c0, c1, c2, c3; + __asm__ __volatile__ ("" :: "r" (mny)); + if (g.sz0 > g.sz1) { za = g.sz0; if (za < g.sz2) za = g.sz2; } + else { za = g.sz1; if (za < g.sz2) za = g.sz2; } + g.opz = za; + + f3 = 0; f2 = 0; f1 = 0; f0 = 0; + + vw = *(u32 *)va; + vzw = *(u32 *)(va + 4); + x0 = vw; y0 = vw >> 16; z0 = vzw; + BOXTEST(f0, x0, y0, z0, lo0x, hi0x, lo0y, hi0y, lo0z, hi0z); + BOXTEST(f1, x0, y0, z0, lo1x, hi1x, lo1y, hi1y, lo1z, hi1z); + BOXTEST(f2, x0, y0, z0, lo2x, hi2x, lo2y, hi2y, lo2z, hi2z); + BOXTEST(f3, x0, y0, z0, lo3x, hi3x, lo3y, hi3y, lo3z, hi3z); + vw = *(u32 *)vb; + vzw = *(u32 *)(vb + 4); + x1 = vw; y1 = vw >> 16; z1 = vzw; + BOXTEST(f0, x1, y1, z1, lo0x, hi0x, lo0y, hi0y, lo0z, hi0z); + BOXTEST(f1, x1, y1, z1, lo1x, hi1x, lo1y, hi1y, lo1z, hi1z); + BOXTEST(f2, x1, y1, z1, lo2x, hi2x, lo2y, hi2y, lo2z, hi2z); + BOXTEST(f3, x1, y1, z1, lo3x, hi3x, lo3y, hi3y, lo3z, hi3z); + vw = *(u32 *)vc; + vzw = *(u32 *)(vc + 4); + x2 = vw; y2 = vw >> 16; z2 = vzw; + BOXTEST(f0, x2, y2, z2, lo0x, hi0x, lo0y, hi0y, lo0z, hi0z); + BOXTEST(f1, x2, y2, z2, lo1x, hi1x, lo1y, hi1y, lo1z, hi1z); + BOXTEST(f2, x2, y2, z2, lo2x, hi2x, lo2y, hi2y, lo2z, hi2z); + BOXTEST(f3, x2, y2, z2, lo3x, hi3x, lo3y, hi3y, lo3z, hi3z); + + if (f0 | f1 | f2 | f3) { + u32 *otp; + u32 rgbw; + ATTEN(a0v, f0, x0, y0, z0, cx0, cy0, cz0, r0, r0lo); + ATTEN(a1v, f1, x0, y0, z0, cx1, cy1, cz1, r1, r1lo); + ATTEN(a2v, f2, x0, y0, z0, cx2, cy2, cz2, r2, r2lo); + ATTEN3(a3v, f3, x0, y0, z0, cx3, cy3, cz3, r3, r3lo, r2, r2); + CLAMP80(c0, a0v, a1v, a2v, a3v); + ATTEN(a0v, f0, x1, y1, z1, cx0, cy0, cz0, r0, r0lo); + ATTEN(a1v, f1, x1, y1, z1, cx1, cy1, cz1, r1, r1lo); + ATTEN(a2v, f2, x1, y1, z1, cx2, cy2, cz2, r2, r2lo); + ATTEN3(a3v, f3, x1, y1, z1, cx3, cy3, cz3, r3, r3lo, r3, r3); + CLAMP80(c1, a0v, a1v, a2v, a3v); + ATTEN(a0v, f0, x2, y2, z2, cx0, cy0, cz0, r0, r0lo); + ATTEN(a1v, f1, x2, y2, z2, cx1, cy1, cz1, r1, r1lo); + ATTEN(a2v, f2, x2, y2, z2, cx2, cy2, cz2, r2, r2lo); + ATTEN3(a3v, f3, x2, y2, z2, cx3, cy3, cz3, r3, r3lo, r2, r3); + CLAMP80(c2, a0v, a1v, a2v, a3v); + + *(u32 *)&((PolyGT3 *)pkt)->x0 = *(u32 *)&tmpxy[0]; + *(u32 *)&((PolyGT3 *)pkt)->x1 = *(u32 *)&tmpxy[1]; + *(u32 *)&((PolyGT3 *)pkt)->x2 = *(u32 *)&tmpxy[2]; + tp = (u32 *)prim->w0; + cb = 0x34000000; + rgbw = (c0 | cb) | (c0 << 8) | (c0 << 16); + ((PolyGT3 *)pkt)->rgb0 = rgbw; + rgbw = (c1 | cb) | (c1 << 8) | (c1 << 16); + ((PolyGT3 *)pkt)->rgb1 = rgbw; + rgbw = (c2 | cb) | (c2 << 8) | (c2 << 16); + ((PolyGT3 *)pkt)->rgb2 = rgbw; + ((PolyGT3 *)pkt)->uv0 = tp[1]; + ((PolyGT3 *)pkt)->uv1 = tp[2]; + ((PolyGT3 *)pkt)->uv2 = tp[3]; + otp = (u32 *)(((g.opz >> 2) << 2) + ot); + *(u32 *)pkt = (*otp & 0xFFFFFF) | 0x9000000; + *otp = (*otp & 0xFF000000) | ((u32)pkt & 0xFFFFFF); + pkt += 0x28; + } else { + u32 *otp; + u32 rgbw; + *(u32 *)&((PolyFT3 *)pkt)->x0 = *(u32 *)&tmpxy[0]; + *(u32 *)&((PolyFT3 *)pkt)->x1 = *(u32 *)&tmpxy[1]; + *(u32 *)&((PolyFT3 *)pkt)->x2 = *(u32 *)&tmpxy[2]; + tp = (u32 *)prim->w0; + cb = tp[0] & 0xFF000000; + cb |= 0x101010; + ((PolyFT3 *)pkt)->rgbc = cb; + ((PolyFT3 *)pkt)->uvc0 = tp[1]; + ((PolyFT3 *)pkt)->uvp1 = tp[2]; + ((PolyFT3 *)pkt)->uv2 = tp[3]; + otp = (u32 *)(((g.opz >> 2) << 2) + ot); + *(u32 *)pkt = (*otp & 0xFFFFFF) | 0x7000000; + *otp = (*otp & 0xFF000000) | ((u32)pkt & 0xFFFFFF); + pkt += 0x20; + } + } + } + break; + case 2: + case 3: + /* ---------------- QUAD (FT4 / GT4) ---------------- */ + gte_stsxy3c(&tmpxy[0]); + gte_ldv0(vd); + gte_rtps(); + if (tmpxy[0].vx > tmpxy[1].vx) { mx = tmpxy[0].vx; mn = tmpxy[1].vx; } + else { mn = tmpxy[0].vx; mx = tmpxy[1].vx; } + if (tmpxy[2].vx > mx) mx = tmpxy[2].vx; + else if (tmpxy[2].vx < mn) mn = tmpxy[2].vx; + if (tmpxy[0].vy > tmpxy[1].vy) { my = tmpxy[0].vy; mny = tmpxy[1].vy; } + else { mny = tmpxy[0].vy; my = tmpxy[1].vy; } + if (tmpxy[2].vy > my) my = tmpxy[2].vy; + else if (tmpxy[2].vy < mny) mny = tmpxy[2].vy; + gte_stflg(&g.flag); + if (!(g.flag & 0x7F85E000)) { + gte_stsz4(&g.sz0, &g.sz1, &g.sz2, &g.sz3); + gte_stsxy((long *)&((PolyFT4 *)pkt)->x3); + if (((PolyFT4 *)pkt)->x3 < mn) mn = ((PolyFT4 *)pkt)->x3; + else if (mx < ((PolyFT4 *)pkt)->x3) mx = ((PolyFT4 *)pkt)->x3; + if (mx >= -0xA0 && mn < 0xA1) { + if (((PolyFT4 *)pkt)->y3 < mny) mny = ((PolyFT4 *)pkt)->y3; + else if (my < ((PolyFT4 *)pkt)->y3) my = ((PolyFT4 *)pkt)->y3; + if (my >= -0x6E && mny < 0x6F) { + s32 za, zb; + s32 c0, c1, c2, c3; + zb = g.sz2; + if (zb < g.sz3) zb = g.sz3; + za = g.sz0; + if (za < g.sz1) za = g.sz1; + if (za < zb) za = zb; + g.opz = za; + + f3 = 0; f2 = 0; f1 = 0; f0 = 0; + + vw = *(u32 *)va; + vzw = *(u32 *)(va + 4); + x0 = vw; y0 = vw >> 16; z0 = vzw; + BOXTEST(f0, x0, y0, z0, lo0x, hi0x, lo0y, hi0y, lo0z, hi0z); + BOXTEST(f1, x0, y0, z0, lo1x, hi1x, lo1y, hi1y, lo1z, hi1z); + BOXTEST(f2, x0, y0, z0, lo2x, hi2x, lo2y, hi2y, lo2z, hi2z); + BOXTEST(f3, x0, y0, z0, lo3x, hi3x, lo3y, hi3y, lo3z, hi3z); + vw = *(u32 *)vb; + vzw = *(u32 *)(vb + 4); + x1 = vw; y1 = vw >> 16; z1 = vzw; + BOXTEST(f0, x1, y1, z1, lo0x, hi0x, lo0y, hi0y, lo0z, hi0z); + BOXTEST(f1, x1, y1, z1, lo1x, hi1x, lo1y, hi1y, lo1z, hi1z); + BOXTEST(f2, x1, y1, z1, lo2x, hi2x, lo2y, hi2y, lo2z, hi2z); + BOXTEST(f3, x1, y1, z1, lo3x, hi3x, lo3y, hi3y, lo3z, hi3z); + vw = *(u32 *)vc; + vzw = *(u32 *)(vc + 4); + x2 = vw; y2 = vw >> 16; z2 = vzw; + BOXTEST(f0, x2, y2, z2, lo0x, hi0x, lo0y, hi0y, lo0z, hi0z); + BOXTEST(f1, x2, y2, z2, lo1x, hi1x, lo1y, hi1y, lo1z, hi1z); + BOXTEST(f2, x2, y2, z2, lo2x, hi2x, lo2y, hi2y, lo2z, hi2z); + BOXTEST(f3, x2, y2, z2, lo3x, hi3x, lo3y, hi3y, lo3z, hi3z); + vw = *(u32 *)vd; + vzw = *(u32 *)(vd + 4); + x3 = vw; y3 = vw >> 16; z3 = vzw; + BOXTEST(f0, x3, y3, z3, lo0x, hi0x, lo0y, hi0y, lo0z, hi0z); + BOXTEST(f1, x3, y3, z3, lo1x, hi1x, lo1y, hi1y, lo1z, hi1z); + BOXTEST(f2, x3, y3, z3, lo2x, hi2x, lo2y, hi2y, lo2z, hi2z); + BOXTEST(f3, x3, y3, z3, lo3x, hi3x, lo3y, hi3y, lo3z, hi3z); + + if (f0 | f1 | f2 | f3) { + u32 *otp; + u32 rgbw; + ATTEN(a0v, f0, x0, y0, z0, cx0, cy0, cz0, r0, r0lo); + ATTEN(a1v, f1, x0, y0, z0, cx1, cy1, cz1, r1, r1lo); + ATTEN(a2v, f2, x0, y0, z0, cx2, cy2, cz2, r2, r2lo); + ATTEN3(a3v, f3, x0, y0, z0, cx3, cy3, cz3, r3, r3lo, r3, r3); + CLAMP80(c0, a0v, a1v, a2v, a3v); + ATTEN(a0v, f0, x1, y1, z1, cx0, cy0, cz0, r0, r0lo); + ATTEN(a1v, f1, x1, y1, z1, cx1, cy1, cz1, r1, r1lo); + ATTEN(a2v, f2, x1, y1, z1, cx2, cy2, cz2, r2, r2lo); + ATTEN3(a3v, f3, x1, y1, z1, cx3, cy3, cz3, r3, r3lo, r3, r3); + CLAMP80(c1, a0v, a1v, a2v, a3v); + ATTEN(a0v, f0, x2, y2, z2, cx0, cy0, cz0, r0, r0lo); + ATTEN(a1v, f1, x2, y2, z2, cx1, cy1, cz1, r1, r1lo); + ATTEN(a2v, f2, x2, y2, z2, cx2, cy2, cz2, r2, r2lo); + ATTEN3(a3v, f3, x2, y2, z2, cx3, cy3, cz3, r3, r3lo, r3, r3); + CLAMP80(c2, a0v, a1v, a2v, a3v); + ATTEN(a0v, f0, x3, y3, z3, cx0, cy0, cz0, r0, r0lo); + ATTEN(a1v, f1, x3, y3, z3, cx1, cy1, cz1, r1, r1lo); + ATTEN(a2v, f2, x3, y3, z3, cx2, cy2, cz2, r2, r2lo); + ATTEN3(a3v, f3, x3, y3, z3, cx3, cy3, cz3, r3, r3lo, r3, r3); + CLAMP80(c3, a0v, a1v, a2v, a3v); + + *(u32 *)&((PolyGT4 *)pkt)->x0 = *(u32 *)&tmpxy[0]; + *(u32 *)&((PolyGT4 *)pkt)->x1 = *(u32 *)&tmpxy[1]; + *(u32 *)&((PolyGT4 *)pkt)->x2 = *(u32 *)&tmpxy[2]; + gte_stsxy((long *)&((PolyGT4 *)pkt)->x3); + tp = (u32 *)prim->w0; + cb = 0x3C000000; + rgbw = c0 | cb; rgbw |= c0 << 8; rgbw |= c0 << 16; + ((PolyGT4 *)pkt)->rgb0 = rgbw; + rgbw = c1 | cb; rgbw |= c1 << 8; rgbw |= c1 << 16; + ((PolyGT4 *)pkt)->rgb1 = rgbw; + rgbw = c2 | cb; rgbw |= c2 << 8; rgbw |= c1 << 16; + ((PolyGT4 *)pkt)->rgb2 = rgbw; + rgbw = c3 | cb; rgbw |= c3 << 8; rgbw |= c1 << 16; + ((PolyGT4 *)pkt)->rgb3 = rgbw; + ((PolyGT4 *)pkt)->uv0 = tp[1]; + ((PolyGT4 *)pkt)->uv1 = tp[2]; + uvw = tp[3]; + ((PolyGT4 *)pkt)->uv2 = uvw; + ((PolyGT4 *)pkt)->uv3 = uvw >> 16; + otp = (u32 *)(((g.opz >> 2) << 2) + ot); + *(u32 *)pkt = (*otp & 0xFFFFFF) | 0xC000000; + *otp = (*otp & 0xFF000000) | ((u32)pkt & 0xFFFFFF); + pkt += 0x34; + } else { + u32 *otp; + u32 rgbw; + *(u32 *)&((PolyFT4 *)pkt)->x0 = *(u32 *)&tmpxy[0]; + *(u32 *)&((PolyFT4 *)pkt)->x1 = *(u32 *)&tmpxy[1]; + *(u32 *)&((PolyFT4 *)pkt)->x2 = *(u32 *)&tmpxy[2]; + tp = (u32 *)prim->w0; + cb = tp[0] & 0xFF000000; + cb |= 0x101010; + ((PolyFT4 *)pkt)->rgbc = cb; + ((PolyFT4 *)pkt)->uvc0 = tp[1]; + ((PolyFT4 *)pkt)->uvp1 = tp[2]; + uvw = tp[3]; + ((PolyFT4 *)pkt)->uv2 = uvw; + ((PolyFT4 *)pkt)->uv3 = uvw >> 16; + otp = (u32 *)(((g.opz >> 2) << 2) + ot); + *(u32 *)pkt = (*otp & 0xFFFFFF) | 0x9000000; + *otp = (*otp & 0xFF000000) | ((u32)pkt & 0xFFFFFF); + pkt += 0x28; + } + } + } + } + break; + } + } + } + } + } + } + } + } + D_800A5E60 = pkt; +} diff --git a/docs/matching-cookbook.md b/docs/matching-cookbook.md index 6c1f3ec37..a31efefc7 100644 --- a/docs/matching-cookbook.md +++ b/docs/matching-cookbook.md @@ -6144,3 +6144,53 @@ shared `rgbw` result temp · `s32 za, zb;` per case. **Five of the nine were read straight off the MATCHED relatives** (`func_8017F510` 1,511 and `func_8017CA80` 952, same renderer family) — worth more than every expression sweep combined. **Crack the smaller family member first; it is a lever library for the larger one.** + +## §79 — For a 0-callee giant, fingerprint by DATA symbols (§71 cannot fire); and the STACK-SLOT ORDER is a declaration-order oracle (Phase 29 SESSION-19, `func_8017BF14` 4,763 ins, cold start → 45/4763) + +A cold-start attempt on the project's second-largest function reached **4763/4763 ins, 45 mismatched +(99.06% byte-identical, 99.94% structural, exact frame, exact opcode histogram)** — not a match, but it +produced two levers and refuted the premise it was given. + +### §71 has a blind spot, and this is it +The target was briefed as "**no matched relative — a genuine cold start**": `h_norm`/`h_seq` family +size 1, and §71's callee-set fingerprint returned jaccard 0.00 against every matched giant. **That +premise was wrong.** §71 fingerprints by **callee set** — and this function makes **zero `jal` calls**, +so the fingerprint is empty and cannot fire *by construction*. Grepping the target's **data** symbol +`D_800A5E60` landed immediately on the matched `func_8017BEBC`: it is the **4-light-box** member of the +same volumetric-light renderer family whose 3-box sibling (`func_8017D960`, 3,338 ins) was matched +hours earlier. + +**Rule: when §71 returns an empty or zero-overlap callee set, fall back to DATA-symbol fingerprinting** +(`lui %hi(D_xxxxxxxx)` operands in the target `.s`). A leaf giant has no callees to fingerprint by, but +it still touches the same globals as its family. **An empty fingerprint is a "cannot answer", not a +"no relative" — do not let it become a cold-start brief.** + +### NEW LEVER — the frame layout reads back the original declaration order +gcc-2.7.2 assigns stack slots to spilled pseudos in **pseudo-number order**, and pseudo numbers are +issued in order of first use ≈ **declaration order**. Therefore **the target's frame layout is a direct +readout of its source's declaration order.** Compare your draft's slot assignments against the +target's and reorder declarations until they agree — moving a single line (`f0..f3` after `pkt`) took +73% → 84% structural and brought **all 127 slots** into exact correspondence. Automatable; the +session's implementation is `.run/giants/bf14_slots.py`. + +This is the counterpart to §78's "read the asm back to source shape": there, an `|`-chain's first term +tells you a literal was a variable; here, the frame map tells you the declaration order. + +### §76 confirmed at scale, and a pin nuance +- The **entire −62 length residual was ONE allocno-class decision**: declaring `s32 c0..c3` *inside the + cull blocks* (1 death ⇒ local allocno ⇒ `global.c:668-671` removes those hard regs from the global + pool) spilled `r1lo` and moved the draft 52% → 93%. An `__asm__` ref-dial reached the same spill and + scored **worse** — **declaration scope beat the ref dial**, again. +- **Pins are safe on a 0-`jal` function** — §74's caller-saved-across-a-call hazard cannot arise, so + the usual suspicion is unwarranted here; 4 pins took 94% → 99%. **But §72 still held: pins 5 and 6 + made it worse.** Pins remain a preference, and past a small number they fight the allocator. + +### The residual, and the honest read +45 mismatches, **three register-grant ties, zero structural divergence**. The one §76 lever class the +session never reached is **variable REUSE across `c0..c3` / `a0v..a3v`** — that is the named next move. +Artifacts: `.run/giants/s19_func_8017BF14_b1.c` (45/4763), a **pin-free fallback at 789/4763 that is +100% structural**, and `s19_bf14_report.md` (~40-row do-not-re-buy table + 4 refuted diagnoses). + +**Cold-start economics, measured:** a 4,763-instruction leaf giant with a *findable* matched relative +reached 99.06% in one pass but did not close. Budget a second pass for anything this size; the first +pass buys the decode, the frame, and the length — the last ~1% is register grants. diff --git a/phase-ends/CURRENT_PHASE.md b/phase-ends/CURRENT_PHASE.md index 78784e66a..5ba3454c4 100644 --- a/phase-ends/CURRENT_PHASE.md +++ b/phase-ends/CURRENT_PHASE.md @@ -3838,19 +3838,53 @@ conditional) · main-EXE/B9 + GLM/B6 + resident's 14 walls (P30) · behemoths B7 `src/*.c` (no config), so `make check-all` (140/140, run) is sound here; **the full clean R22 must still be run once the agent finishes.** +- **⚖️ 2026-07-25 (SESSION-19) — THE COLD-START EXPERIMENT: `func_8017BF14` (4,763 ins) reached + 4763/4763, **45 mismatched** (99.06% byte, 99.94% structural) — NOT a match, and the honest answer + to Drew's effort question. It also REFUTED THE PREMISE I GAVE IT (§79).** + Verified independently: `match_one` → `mine=4763 target=4763, 45 mismatched, OPCODE-MIXED`. Agent + respected its sandbox (only `.run/giants/`). **Nothing banked — 45 ≠ 0, and the byte-gate is the + sole arbiter (G3/P9).** + **MY BRIEF'S "NO MATCHED RELATIVE — A GENUINE COLD START" WAS WRONG, BY CONSTRUCTION.** I picked this + target partly BECAUSE §71's callee-set fingerprint returned jaccard 0.00 against every matched giant. + But **this function makes ZERO `jal` calls**, so its callee fingerprint is EMPTY and §71 *cannot + fire* — 0.00 meant "cannot answer", not "no relative". Grepping the target's **data** symbol + `D_800A5E60` found the matched `func_8017BEBC` immediately: `func_8017BF14` is the **4-light-box** + member of the very renderer family whose 3-box sibling `func_8017D960` we matched hours earlier. + **⇒ §79: when §71 returns an empty/zero-overlap callee set, fall back to DATA-symbol fingerprinting. + An empty fingerprint must never be allowed to become a cold-start brief.** + **NEW LEVER — THE FRAME LAYOUT IS A DECLARATION-ORDER ORACLE (§79).** gcc-2.7.2 assigns stack slots + to spilled pseudos in pseudo-number order, and pseudo numbers follow first use ≈ declaration order — + so **the target's frame map reads back its source's declaration order**. Moving ONE line (`f0..f3` + after `pkt`) took 73% → 84% structural and brought **all 127 slots** into exact correspondence. + Counterpart to §78's asm→source reads. Automatable (`.run/giants/bf14_slots.py`). + **§76 CONFIRMED AT SCALE:** the entire **−62 length residual was ONE allocno-class decision** + (`s32 c0..c3` declared inside the cull blocks ⇒ 1-death local allocnos ⇒ `global.c:668-671` removes + those regs from the global pool ⇒ `r1lo` spills): 52% → 93%. An `__asm__` ref-dial reached the same + spill and scored WORSE — **declaration scope beat the ref dial, again.** + **PIN NUANCE:** pins are SAFE on a 0-`jal` function (§74's caller-saved-across-a-call hazard cannot + arise); 4 pins took 94% → 99%. **But §72 held — pins 5 and 6 made it worse.** + **THE EFFORT ANSWER, HONESTLY:** xHigh from a genuine cold start on a 4,763-ins giant bought the + decode, the exact length, the exact frame and 99.06% — but **did not close**. The residual is **three + register-grant ties, zero structural divergence**. Budget a SECOND pass for anything this size: the + first buys structure, the last ~1% is register grants. Named next move: **variable REUSE across + `c0..c3`/`a0v..a3v`** — the one §76 lever class this pass never reached. + Artifacts: `s19_func_8017BF14_b1.c` (45/4763) + a **pin-free fallback at 789/4763 that is 100% + structural** + `s19_bf14_report.md` (~40-row do-not-re-buy table, 4 refuted diagnoses). + > **🛑 SESSION-19 CLOSING CHECKPOINT (2026-07-25, Opus 5 @ High) — REFRESHED mid-session; supersedes > both the SESSION-18 block and the earlier SESSION-19 block (which was written before the > ENGINE_SHB / class-B / dedup_extend-bug work and went stale). Fresh session safe here.** -> **No background job is running.** Three behemoths banked this session: `func_8017F510` +> **No background job is running; the deferred full R22 has been run (140/140).** Three behemoths banked this session: `func_8017F510` > (1,511, §76 crack), `func_8017F5B4` (1,511, §40 remap), and **`func_8017D960` + its entire > 5-member family (5 × 3,338 = 16,690 ins, §78 crack + 4 first-try remaps)**. > **Tree clean** (only R23 `db.*.gbf` churn — never staged). -> **STATE:** HEAD `commit:1012`, **22 commits this session**. **R22 clean-fleet 140/140, run 8×** — the -> last after the behemoth-#2 family bank. +> **STATE:** HEAD (see git log), **26 commits this session**. **R22 clean-fleet 140/140, run 9×** — the +> last one FULL (`make clean` + extract-all + check-all) AFTER the BF14 agent finished, which +> discharges the deferral noted in the pool entry. `make tools-health` → **OK**. > **0 NON_MATCHING** (G4). dedup **1886 validated / 0 failed**, C1 coverage 239,604/239,604. **Drew pushes** (R6/R20). -> **FLEET: 80.5% instr** — 10,575,671 / 13,141,652 · **distinct-code 3,833,224 = 68.0% -> (+19,712 ins this session — ALL of it from the three behemoths; every propagation win -> contributed +0)** · fn-count **89.18%** (session opened 80.0 / 67.7 / 89.02). +> **FLEET: 80.5% instr** — 10,580,590 / 13,141,652 · **distinct-code 3,838,143 = 68.1% +> (+24,631 ins this session: 19,712 from the three behemoths + 4,919 from the h_norm-remap pool; +> every PROPAGATION win contributed +0)** · fn-count **89.18%** (session opened 80.0/67.7/89.02). > > ## BANKED THIS SESSION > | fn | ins | reach | lever | @@ -3892,7 +3926,12 @@ conditional) · main-EXE/B9 + GLM/B6 + resident's 14 walls (P30) · behemoths B7 > ## ⚠️ OPEN ACTIONS, ranked > 1. **[DONE] `func_80174CB0` ×135.** Residual = the **3 class-A overlays** (`func_80012ABC`, census > 73 `s32` vs 7 `s16`) — worth 3 overlays only; normalize the 7 `s16` decls if trivially cheap. -> 2. **BEHEMOTHS — now the PROVEN distinct-code lever** (+3,022 ins today vs +0 from all +> 2. **`func_8017BF14` SECOND PASS — 45/4763 away** (99.06%), three register-grant ties, zero +> structural divergence. Named next move: **variable REUSE across `c0..c3`/`a0v..a3v`** (the one +> §76 lever class the cold pass never reached). Draft `.run/giants/s19_func_8017BF14_b1.c`; +> pin-free 100%-structural fallback at 789/4763. **Its matched relative is `func_8017BEBC`** +> (found by DATA-symbol fingerprint, §79 — NOT by §71, which cannot fire on a 0-callee fn). +> 3. **BEHEMOTHS — the PROVEN distinct-code lever** (+3,022 ins today vs +0 from all > propagation). **8 untouched:** `func_8017BF14` 4,763 · `func_8017E778` 3,338 · > `func_8017D2DC` 1,586 · `func_8017DC1C` 1,518 · `func_8017C954` 1,194 · `func_8017C730` > 1,061 (family of 2) · (+`func_80183814` 5,122, mapped only). **`func_8017E778`/`func_8017CD9C`