docs(phase-29): cookbook §72 — behemoth #3 to 1511/1511; a register pin is a PREFERENCE, not a reservation

func_8017F510 (1,511 ins): EXACT length, frame 0x258 exact, byte-exact prologue AND epilogue,
identical sp-slot set, 99.5% register-masked structural / 93.3% byte-aligned, SYMS-OK, 97 divergent.
Not a match (G3). §71's callee-set lever delivered ~90% of the C and a first compile at 1528/1511.

- CORRECTNESS FINDING (qualifies §17): a local `register s32 x __asm__("$30")` pin produced a
  seductive 1511 ins / 98.9% and was a MISCOMPILE -- gcc-2.7.2 ALSO allocated $s8 to an unrelated
  live value. A pin is a HINT to the allocator, not a reservation. Never ship one without inspecting
  the pinned register's defs. Banked work is safe by construction (the byte-gate rejects a
  miscompile); the exposure is UN-GATED drafts. ACTION: behemoth-2's draft carries FIVE pins
  ($25 $17 $19 $20 $21) and must be re-checked before anyone builds on it.
- THE HONEST FIX was source-level: the +17 drift was live-range stretching from REUSING w/wz for the
  vertex-word reads (spilling amb, 7 lw+nop pairs). Dedicated temps -> 1528->1511, 88%->99.6%, no pin.
- GIV RECORD ORDER (§70 family): part->prim must be read BEFORE part->nprim -- loop.c:combine_givs
  walks bl->giv in REVERSE record order, so the last-recorded giv becomes the combined base.
- RESIDUAL 97 traced to ONE seed: c3 is $a2 in the target, $a3 in the draft; tp takes the other of
  the pair and renames the whole 4-tail block. Fix c3 -> $a2 and ~93 should fall together.
- SPENT, byte-recorded: decl-order permutations, block-scoping, splitting/inlining tp, reusing f0,
  vertex axis orders, and decomp-permuter (4,724 candidates, base 97, ZERO improvement -- a §3 hard
  tail outside the C-randomisation space).
- b3_align.py / b3_pos.py supersede the b2_* aligners.
This commit is contained in:
Drew T
2026-07-25 02:05:40 -06:00
parent 1de9a1a2e9
commit dfa366c7c6
4 changed files with 803 additions and 0 deletions
+102
View File
@@ -0,0 +1,102 @@
"""Shape-agnostic word-level masked sequence aligner (generalised from .run/giants/b2_*.py).
usage: python b3_align.py <obj.o> <target.s> [nhunks]
prints BOTH the register-MASKED (structural) and the register-KEPT (byte) aligned numbers.
"""
import re, subprocess, difflib, shutil, sys, collections
OBJ = sys.argv[1]
TGT = sys.argv[2]
NH = int(sys.argv[3]) if len(sys.argv) > 3 else 60
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:
b = bytes.fromhex(m.group(1))
v = int.from_bytes(b, 'little')
if '%hi(' in line or '%lo(' in line:
w.append(('R', v))
else:
w.append(v)
return w
def mask(x, keepimm=True):
rel = isinstance(x, tuple)
v = x[1] if rel else x
op = (v >> 26) & 0x3F
if op == 0 or op == 0x1C:
k = (op << 6) | (v & 0x3F) | (((v >> 6) & 0x1F) << 12)
elif op in (2, 3):
k = (op << 26)
elif op in (0x12,):
k = v & 0xFC1F07FF
elif op in (1, 4, 5, 6, 7):
k = (op << 20) | (((v >> 16) & 0x1F) << 8)
else:
k = (op << 20) | ((v & 0xFFFF) if (keepimm and not rel) else 0)
return (k, '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, '')
wm, wt = words_mine(), words_tgt()
print("mine", len(wm), "target", len(wt))
mt = []
for line in out.splitlines():
m = re.match(r'\s*[0-9a-f]+:\s+[0-9a-f]{8}\s+(.*)', line)
if m:
mt.append(m.group(1).strip())
tt = []
for line in open(TGT):
m = re.match(r'\s*/\* \w+ [0-9A-F]{8} [0-9A-F]{8} \*/\s+(.*)', line)
if m:
tt.append(m.group(1).strip())
for name, fn, show in (("REGISTER-MASKED (structural: opcodes+imms+sp-offsets kept)", mask, True),
("FULL (registers KEPT; relocs + branch displacements masked)", full, False)):
a = [fn(x) for x in wm]
b = [fn(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(name)
print(" aligned-identical %d / %d target ins = %.1f%%" % (eq, len(b), 100.0 * eq / len(b)))
print(" divergent hunks: %d total divergent target ins: %d"
% (len(bad), sum(o[4] - o[3] for o in bad)))
if show:
for t, i1, i2, j1, j2 in bad[:NH]:
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 + 6)):
print(" T %4d %s" % (k, tt[k] if k < len(tt) else '??'))
for k in range(i1, min(i2, i1 + 6)):
print(" M %4d %s" % (k, mt[k] if k < len(mt) else '??'))
+41
View File
@@ -0,0 +1,41 @@
"""Full positional diff (same-length assumption) + register-permutation class summary."""
import re, subprocess, shutil, sys, collections
OBJ = sys.argv[1]
TGT = sys.argv[2]
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
mw, mt = [], []
for line in out.splitlines():
m = re.match(r'\s*[0-9a-f]+:\s+([0-9a-f]{8})\s+(.*)', line)
if m:
mw.append(int(m.group(1), 16)); mt.append(m.group(2).strip())
elif 'R_MIPS_' in line and mw and not isinstance(mw[-1], tuple) and (mw[-1] >> 26) not in (2, 3):
mw[-1] = ('R', mw[-1])
tw, tt = [], []
for line in open(TGT):
m = re.match(r'\s*/\* \w+ [0-9A-F]{8} ([0-9A-F]{8}) \*/\s+(.*)', line)
if m:
v = int.from_bytes(bytes.fromhex(m.group(1)), 'little')
tw.append(('R', v) if ('%hi(' in line or '%lo(' in line) else v)
tt.append(m.group(2).strip())
def norm(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, '')
print("mine %d target %d" % (len(mw), len(tw)))
n = min(len(mw), len(tw))
bad = [i for i in range(n) if norm(mw[i]) != norm(tw[i])]
print("positional mismatches: %d" % len(bad))
buckets = collections.Counter(i // 100 * 100 for i in bad)
print("by 100-ins bucket:", dict(sorted(buckets.items())))
for i in bad:
print(" %4d | %-34s | %s" % (i, mt[i], tt[i]))
+603
View File
@@ -0,0 +1,603 @@
#include "common.h"
#include "/home/musashi/bfm-decomp/src/shared/engine_types.h"
/* ===========================================================================
* func_8017F510 -- 1,511 ins, ov_SC03_006 (behemoth #3).
*
* WHAT IT IS: the SINGLE-point-light variant of the mesh renderer family.
* base = func_8017CA80 (952 ins, MATCHED, src/ov_SC03_090/...)
* lit x3 = func_8017D960 (3,338 ins, 98.8% draft, .run/giants/)
* this = func_8017F510 (1,511 ins) -- ONE light box, plus an ambient
* term added to every vertex and an "unlit" flat colour derived
* from that same ambient.
*
* Differences vs the 3-light variant:
* - 3 args (arg0 = actor, arg1 = model = *(arg0+8), arg2 = arg0+0x10).
* - early-out `if (*(s32 *)arg0) return;` BEFORE the 3-call prologue.
* - Part[] lives INLINE at arg1 + 0x14 (not *(arg0+0xC)).
* - ONE light descriptor at D_801F7790 {s32 enable; u16 cx,cy,cz; pad;
* s32 range; ...; s32 ambient@+0x18}; defaults cx=cy=cz=0x6000,
* r=0x100, rlo=0x80, amb=0, col=0 when disabled.
* - per vertex: atten (x,z,y separable falloff) + amb, clamped to 0x80.
* - lit -> POLY_GT3 (0x28, OT 0x9000000, GPU 0x34) /
* POLY_GT4 (0x34, OT 0xC000000, GPU 0x3C)
* rgb = cbase | c | (c<<8) | 0x800000 (blue fixed at 0x80),
* cbase = (tp[0] & 0x2000000) | GPUCODE.
* unlit -> POLY_FT3 (0x20, OT 0x7000000) / POLY_FT4 (0x28, OT 0x9000000)
* rgbc = (tp[0] & 0xFF000000) | col.
*
* Frame (measured from the .s): 0x258 =
* 0x00 args | 0x10 tmpxy[4] | 0x20 box[8] | 0x60 sxy[8] | 0xA0 mtx |
* 0xC0 g{otz,flag,opz,sz0..sz3} | 0xE0..0x22F = 42 EIGHT-BYTE spill slots |
* 0x230..0x257 = 10 saved regs (s0-s7, fp, ra).
*
* MEASURED (2026-07-25, gcc-2.7.2 pinned triple):
* tools/match_one.py mine=1511 ins target=1511 ins **97 mismatched**
* class ADDRESSING [permuter] sig=ADDRESSING/srl!=addu
* .run/drafts-behemoth3/b3_align.py (shape-agnostic aligners):
* REGISTER-MASKED (structural) 1503 / 1511 = **99.5%** (8 divergent ins,
* 8 hunks)
* FULL (registers KEPT) 1410 / 1511 = **93.3%** (101 divergent)
* tools/symcheck.py SYMS-OK, 12 symbols agree (no invented symbols)
* Frame 0x258 EXACT; prologue + epilogue byte-exact; the same 10 saved regs
* at the same offsets; the SET of sp slots used is IDENTICAL to the target.
* NOT A MATCH. Only a byte-identical whole-binary rebuild is a match (G3/P9).
*
* THE THREE LEVERS THAT GOT IT HERE (all byte-measured, each one a step):
* 1. The already-MATCHED 952-ins sibling func_8017CA80 + the 3,338-ins
* func_8017D960 draft supplied ~90% of the C. First compile: 1528 ins,
* 88.0% structural, exact 10-saved-reg set, frame off by one spill slot.
* 2. **Dedicated temps for the vertex-word loads.** Reusing the `w`/`wz`
* temps (which also carry prim->w1/w2 and part->zz) for
* `vw = *(u32*)va; vzw = *(u32*)(va+4);` extended two live ranges enough
* that `amb` lost its callee-saved register and got spilled -- 7 extra
* `lw amb`+`nop` pairs = the whole +17-instruction drift. Separate
* `vw`/`vzw` -> 1511/1511 ins, 99.6% structural, 131 mismatched.
* (A `register s32 amb __asm__("$30")` pin ALSO produced 1511 ins, but it
* MISCOMPILES: gcc-2.7.2 gave $s8 to `hi_z` as well -- the same hard reg
* held two live values. Local register pins are a preference, not a
* reservation; never ship one without checking the pinned reg's defs.)
* 3. `part->prim` read BEFORE `part->nprim` (cookbook §70 family): loop.c's
* combine_givs walks bl->giv in reverse record order, so the LAST-recorded
* giv becomes the combined base. prim-then-nprim -> base = part+0xC (the
* target); nprim-then-prim -> base = part+0x10. Worth ~7 ins + the whole
* $s6/$s7 naming cascade.
* 4. `my = wy >> 16;` BEFORE `mny = wy;` in the box[] build (the matched
* 952 sibling has the opposite order): flipped the $a2/$a3 global-alloc
* decision for the my/mny pair, 131 -> 97 mismatched.
*
* REMAINING DIVERGENCE (all measured, all localised, 97 ins):
* A. 2 ins, tgt[118..120]: the box-build `addu $a2,$v1` (mny) and
* `srl $a3,$v1,16` (my) are TRANSPOSED. Source order sets BOTH the
* emission order (sched.c rank_for_schedule ties break on insn UID) and
* the allocation order (global.c allocno_compare ties break on allocno
* number). `mny;my` gives the right emission + wrong regs; `my;mny`
* (shipped) the right regs + wrong emission. Needs a third form.
* B. 3 ins x 2, tgt[864..870] and tgt[1454]: in the two UNLIT tails the
* target materialises the single-`lui` OT tag (0x7000000 / 0x9000000)
* BEFORE the two-insn 0xFFFFFF constant; mine does the reverse. Pure
* sched.c ordering; the LIT tails (which lack the third 0xFF000000
* constant) already agree.
* C. ~93 ins of pure REGISTER NAMING, and they all trace to ONE seed:
* `c3` (the 4th quad vertex colour). The target gives it $a2 (reusing the
* dead flag `f0`); mine gives it $a3. `tp` is one pseudo across all four
* emit tails and CONFLICTS with c3 in the quad-lit tail, so it takes
* whichever of $a2/$a3 c3 did not -- target $a3, mine $a2 -- and the whole
* 4-tail block (tp / 0xFFFFFF / 0xFF000000 / otp / the rgb OR-chain
* accumulator) renames off that. Fix c3 -> $a2 and ~93 of the 97 should
* fall together.
*
* TRIED AND REJECTED (byte-measured, do not re-buy):
* decl-order permutations of amb / my,mny,mx,mn (no effect - gcc numbers
* pseudos by first USE, not declaration); block-scoping otp/tp/cb/uvw per
* emit branch (1509 ins); dedicated box-build min/max vars (1510 ins);
* splitting `tp` per branch (147); inlining `tp` as `((u32*)prim->w0)[k]`
* (1535 ins - no CSE); reusing `f0` as c3 (176); vertex x/z/y and y/x/z
* orders (99-146); `otp = (u32*)(ot + ...)` operand swap (no change);
* `0x800000 | cb | c | (c<<8)` (1526 ins).
* decomp-permuter (tools/permuter/run_masked.py, klass=ADDRESSING, -j 10):
* 3,663 candidates, base 97, **no improvement** -> cookbook §3 hard tail,
* this residual is not in the C-randomisation search space.
* =========================================================================== */
#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 single light-volume descriptor ---------------------------------- */
extern s32 D_801F7790;
extern u16 D_801F7794, D_801F7796, D_801F7798;
extern s32 D_801F779C;
extern s32 D_801F77A8;
/* ---- the box-containment test for one vertex ----------------------------- */
#define BOXTEST(F, X, Y, Z) \
if (lo_x < (X) && (X) < hi_x && lo_y < (Y) && (Y) < hi_y && lo_z < (Z) && (Z) < hi_z) F = 1
/* ---- the separable per-axis falloff, visited in x, z, y order ------------ */
#define ATTEN(A, X, Y, Z) \
A = 0; \
d = (X) - cx; if (d < 0) d = cx - (X); \
if (d < r) { A = 0x80; if (d >= rlo) A = r - d; } \
d = (Z) - cz; if (d < 0) d = cz - (Z); \
if (r < d) A = 0; \
else if (rlo < d) A = (A * (r - d)) >> 7; \
d = (Y) - cy; if (d < 0) d = cy - (Y); \
if (r < d) A = 0; \
else if (rlo < d) A = (A * (r - d)) >> 7; \
A = A + amb; \
if (A > 0x80) A = 0x80
void func_8017F510(s32 arg0, s32 arg1, s32 arg2)
{
typedef struct { u32 w0, w1, w2; } Prim;
extern s32 func_800491EC(void);
extern void func_800547D8(s32, MATRIX2 *);
extern void func_80052E38(MATRIX2 *);
extern u8 *D_800A5E60;
extern u8 D_800A6610[];
extern u8 D_800AF630[];
DVECTOR2 tmpxy[4];
SVECTOR2 box[8];
SVECTOR2 sxy[8];
MATRIX2 mtx;
struct { long otz, flag, opz, sz0, sz1, sz2, sz3; } g;
s32 lim;
s32 j;
u32 i;
u8 *vd;
u32 ot;
u8 *pkt;
s16 x3, z3, y3;
Prim *prim;
u32 nprim;
u8 *vtx;
s32 nparts;
Part *part;
s16 lo_x, hi_x, lo_y, hi_y, lo_z, hi_z;
s16 cx, cy, cz;
s32 r, rlo;
u32 col;
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;
s32 f0;
s16 x0, y0, z0, x1, y1, z1, x2, y2, z2;
s32 d;
s32 c0, c1, c2, c3;
s32 za, zb;
u32 *otp;
u32 *tp;
u32 cb;
u32 uvw;
s32 amb;
base = D_800AF630;
if (*(s32 *)arg0 != 0) {
return;
}
lim = func_800491EC() + *(s32 *)(arg0 + 0x64);
func_800547D8(arg2, &mtx);
func_80052E38(&mtx);
if (D_801F7790) {
cx = D_801F7794;
cy = D_801F7796;
r = D_801F779C;
rlo = r - 0x80;
amb = D_801F77A8;
cz = D_801F7798;
if (amb < 0x60) {
col = amb | 0x600000 | (amb << 8);
} else {
col = amb | (amb << 8) | (amb << 16);
}
} else {
cz = 0x6000;
cy = 0x6000;
cx = 0x6000;
r = 0x100;
rlo = 0x80;
amb = 0;
col = 0;
}
lo_x = cx - r; hi_x = cx + r;
lo_y = cy - r; hi_y = cy + r;
lo_z = cz - r; hi_z = cz + r;
pkt = D_800A5E60;
part = (Part *)(arg1 + 0x14);
nparts = *(s32 *)(arg1 + 8);
vtx = *(u8 **)(arg1 + 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;
my = wy >> 16;
mny = wy;
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) {
prim = (Prim *)part->prim;
nprim = part->nprim;
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) {
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;
f0 = 0;
vw = *(u32 *)va;
vzw = *(u32 *)(va + 4);
x0 = vw; y0 = vw >> 16; z0 = vzw;
BOXTEST(f0, x0, y0, z0);
vw = *(u32 *)vb;
vzw = *(u32 *)(vb + 4);
x1 = vw; y1 = vw >> 16; z1 = vzw;
BOXTEST(f0, x1, y1, z1);
vw = *(u32 *)vc;
vzw = *(u32 *)(vc + 4);
x2 = vw; y2 = vw >> 16; z2 = vzw;
BOXTEST(f0, x2, y2, z2);
if (f0) {
ATTEN(c0, x0, y0, z0);
ATTEN(c1, x1, y1, z1);
ATTEN(c2, x2, y2, z2);
*(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 = (tp[0] & 0x2000000) | 0x34000000;
((PolyGT3 *)pkt)->rgb0 = cb | c0 | (c0 << 8) | 0x800000;
((PolyGT3 *)pkt)->rgb1 = cb | c1 | (c1 << 8) | 0x800000;
((PolyGT3 *)pkt)->rgb2 = cb | c2 | (c2 << 8) | 0x800000;
((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 *)&((PolyFT3 *)pkt)->x0 = *(u32 *)&tmpxy[0];
*(u32 *)&((PolyFT3 *)pkt)->x1 = *(u32 *)&tmpxy[1];
*(u32 *)&((PolyFT3 *)pkt)->x2 = *(u32 *)&tmpxy[2];
tp = (u32 *)prim->w0;
((PolyFT3 *)pkt)->rgbc = (tp[0] & 0xFF000000) | col;
((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) {
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;
f0 = 0;
vw = *(u32 *)va;
vzw = *(u32 *)(va + 4);
x0 = vw; y0 = vw >> 16; z0 = vzw;
BOXTEST(f0, x0, y0, z0);
vw = *(u32 *)vb;
vzw = *(u32 *)(vb + 4);
x1 = vw; y1 = vw >> 16; z1 = vzw;
BOXTEST(f0, x1, y1, z1);
vw = *(u32 *)vc;
vzw = *(u32 *)(vc + 4);
x2 = vw; y2 = vw >> 16; z2 = vzw;
BOXTEST(f0, x2, y2, z2);
vw = *(u32 *)vd;
vzw = *(u32 *)(vd + 4);
x3 = vw; y3 = vw >> 16; z3 = vzw;
BOXTEST(f0, x3, y3, z3);
if (f0) {
ATTEN(c0, x0, y0, z0);
ATTEN(c1, x1, y1, z1);
ATTEN(c2, x2, y2, z2);
ATTEN(c3, x3, y3, z3);
*(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 = (tp[0] & 0x2000000) | 0x3C000000;
((PolyGT4 *)pkt)->rgb0 = cb | c0 | (c0 << 8) | 0x800000;
((PolyGT4 *)pkt)->rgb1 = cb | c1 | (c1 << 8) | 0x800000;
((PolyGT4 *)pkt)->rgb2 = cb | c2 | (c2 << 8) | 0x800000;
((PolyGT4 *)pkt)->rgb3 = cb | c3 | (c3 << 8) | 0x800000;
((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 *)&((PolyFT4 *)pkt)->x0 = *(u32 *)&tmpxy[0];
*(u32 *)&((PolyFT4 *)pkt)->x1 = *(u32 *)&tmpxy[1];
*(u32 *)&((PolyFT4 *)pkt)->x2 = *(u32 *)&tmpxy[2];
tp = (u32 *)prim->w0;
((PolyFT4 *)pkt)->rgbc = (tp[0] & 0xFF000000) | col;
((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;
}
+57
View File
@@ -5701,3 +5701,60 @@ changed — one crack templates ×3.**
switch**, from grepping `sltiu` jump-table bounds. Wrong — the switch is compiled as a **comparison
tree** (23 `slti`). A jump-table grep is not a switch detector. The agent caught it; a less careful
one would have inherited my false premise.
## §72 — A `register __asm__` pin is a PREFERENCE, not a reservation (Phase 29 SESSION-18, `func_8017F510`)
Behemoth #3, and the best behemoth result so far: **1,511 of 1,511 instructions** (exact length),
frame `0x258` exact, **byte-exact prologue AND epilogue**, identical sp-slot set, **99.5%
register-masked structural / 93.3% byte-aligned**, SYMS-OK, 97 divergent. Not a match (G3) — but ~93
of the 97 trace to a single register decision (below). §71's callee-set lever delivered: diffing the
two references gave ~90% of the C and a first compile of 1528/1511.
### ⚠️ THE CORRECTNESS FINDING — this qualifies §17
Pinning a local with `register s32 amb __asm__("$30")` produced a seductive **1,511 ins / 98.9%** —
and was a **MISCOMPILE**. gcc-2.7.2 *also* allocated `$s8` to an unrelated live value (`hi_z`): one
hard register holding two live values at once.
> **A local `register … __asm__("$N")` declaration is a hint to the allocator, NOT a reservation.**
> gcc-2.7.2 will still hand that hard register to another pseudo. **Never ship a pin without
> inspecting the pinned register's defs in the output.**
§17 (pins + a scheduling barrier) remains valid — it is how `func_8012B8E4` and others were cracked —
but it now carries this obligation. **Anything already BANKED is safe by construction** (the
whole-binary byte-gate would have rejected a miscompile); the exposure is **un-gated drafts**.
**ACTION: `.run/giants/s18_func_8017D960_b2.c` (behemoth #2, 3,334/3,338) carries FIVE pins
(`$25 $17 $19 $20 $21`) and must be re-checked before anyone builds on it.**
### The honest fix was source-level and cheap
The +17-instruction drift was **live-range stretching**: reusing `w`/`wz` (which already carry
`prim->w1/w2` and `part->zz`) for the vertex-word reads stretched two live ranges enough to spill
`amb`, costing 7 `lw`+`nop` pairs. **Dedicated temps for the vertex loads** (`vw`/`vzw`) →
1528→1511 ins, 88%→99.6% structural, **no pin**.
> Generalisable: when a draft is long by a handful of `lw`+`nop` pairs, look for a REUSED local whose
> live range now spans a region it did not before. A fresh temp is cheaper than a pin and cannot
> miscompile.
### Giv record order (the §70 family)
**`part->prim` must be read BEFORE `part->nprim`.** `loop.c:combine_givs` walks `bl->giv` in reverse
record order, so the *last-recorded* giv becomes the combined base: prim-then-nprim yields base
`part+0xC` (the target); the other order yields `part+0x10`.
### What is left, and what is byte-recorded as SPENT
Residual 97 = 2 ins (box-build emission/allocation transposition — source order sets both, via
`sched.c` UID ties and `global.c` allocno ties; needs a third form) + 3×2 ins (unlit-tail OT-tag
scheduling) + **~93 ins of pure register naming off ONE seed**: `c3` (4th quad vertex colour) is
`$a2` in the target and `$a3` in the draft; `tp` is one pseudo across all four emit tails and takes
the other of the pair, renaming the whole block. **Fix `c3` → `$a2` and ~93 should fall together.**
**Do not re-buy:** decl-order permutations (gcc numbers pseudos by first *use*, not declaration) ·
block-scoping the emit temps (1509 ins) · splitting or inlining `tp` · reusing `f0` as `c3` · vertex
x/z/y orderings · **decomp-permuter, 4,724 candidates at `-j 10`, base 97, ZERO improvement** — a §3
hard tail outside the C-randomisation search space. The remaining move is to reason the `c3`
allocation out of `global.c`, not to spend more compute.
**Tooling:** `.run/giants/b3_align.py` (shape-agnostic structural+byte aligner) and `b3_pos.py`
(positional diff for equal-length drafts) supersede the `b2_*` set.