diff --git a/.gitignore b/.gitignore index 5534501f9..0107abc93 100644 --- a/.gitignore +++ b/.gitignore @@ -60,6 +60,15 @@ !/.run/backlog.jsonl !/.run/fuel_manifest.json +# Phase-27 pin-crash characterization: the minimal repro + gdb harness (the irreplaceable ~KB +# core; pin_crash_sigabrt.md above is already covered by giants/*.md). The 260M of RTL dumps + +# bisection .s/.c beside them are regenerable via abort.gdb -> stay ignored (R33). +!/.run/giants/pincrash/ +/.run/giants/pincrash/* +!/.run/giants/pincrash/minimal_repro.c +!/.run/giants/pincrash/*.gdb +!/.run/giants/pincrash/*.md + # Regenerated build output (deterministic churn) /build/ /expected/ diff --git a/.run/giants/d820_seed.pre.c b/.run/giants/d820_seed.pre.c new file mode 100644 index 000000000..4a4d6f71f --- /dev/null +++ b/.run/giants/d820_seed.pre.c @@ -0,0 +1,169 @@ +#include "common.h" +// func_8014D820 -- "attach/track to parent entity" mover (304 ins, ov_SC01_077_after). +// param_1 = self entity (s32 base), param_2/param_3 = u16[] pos snapshots (prev/cur). +// Delta -> validate tracked parent (e=*(param_1+0x170)) -> ratan2/func_80012A60 heading +// gate (<0x400) -> either the 0x3008 special case (func_800139C8 fan-of-3 + func_8014DCE0 +// collide loop) or the generic func_80135888 loop -> func_80133784 -> write back the pose. +// Returns s32 (1 = applied, 0 = detach: *(param_1+0x170)=0). +// +// §43 return-type-flip pair: the target materializes v0=1 / v0=0 at the two exits, so the +// def MUST be s32. engine_core.h declares it void (via DEFINE_func_8014D790, invoked at +// split line 1572 -> in scope), so cc1 hard-errors "conflicting types" unless flipped. +// Byte-neutral: the sole caller (func_8014D790, engine_core.h:9765) discards the result. +// There is NO split-scope self-decl to //@EDIT (func_8014D820 is INCLUDE_ASM'd; the ov077 +// canon-sig layer lists only its callees). So the ONLY pre-edit is the engine_core.h flip: +// +// EC_EDIT (src/shared/engine_core.h line 9752, in the DEFINE_func_8014D790 body): +// old: extern void func_8014D820(s32 a0, void *a1, void *a2); +// new: extern s32 func_8014D820(s32 a0, void *a1, void *a2); +// +// PINS? NO. Fully block-scoped (typedefs + externs in-body), void*/s32 params (no +// s16-by-value -> no K&R param form, no param //@EDIT) -> ×134-clean via family_sweep +// --edit-remap (contrast the old pinned seed, which cc1-SIGABRTs in sibling TUs). +// +// STATUS: STRUCTURALLY COMPLETE, NOT byte-identical. rtu_match: 304/304 ins, all calls/ +// constants/control-flow/two-pointer loops/unaligned Blk8 copy/apply+detach branches +// byte-correct; LCS 237/304 (close=67), pin-free -> beats the prior pinned seed (LCS 228). +// Residual = gcc-2.7.2 INTRINSIC sched1/regalloc knife-edge (§31 RC-6 / S3): prologue +// deferred param_1->$s4 copy + serialized-vs-parallel dx/dz load temps; sched1 store/load +// ORDER in the e3/e5/e7 setup, the func_800139C8 out0/1/2 combine, and the apply block; +// dead-dx/dz $s0/$s1 reuse for the ratan2#2 operands. 4 structural probes (reused-t temp, +// d2 hoist, alias-removal, out2 store-swap) each cascaded 20+ ins (the RC-6 tell) -> NOT +// hand-steerable. Route: permuter (this pin-free seed is permuter-viable) / Fable5. +// +// OPUS INVESTIGATION (2026-07-11, cheap-Opus giant-cracker, Lever A/B pass): CONFIRMED RC-6. +// This IS the best pin-free seed (match_one 261 positional / masked-LCS 226/304; every probe <=226). +// Uses the EXACT target hard-reg set ($v0,$v1,$a0-$a3,$t0,$s0-$s5,$ra) + all 304 correct opcodes -> +// pure schedule/alloc-ORDER permutation (§31 RC-6 tell: "regs+opcodes exact, placement off, edits explode"). +// * PROLOGUE ROOT CAUSE (new): the deferred param_1->$s4 copy is PINNED early by sched.c:3199 +// ("don't delay getting parameters" -> INSN_REF_COUNT=1 on the leading (set pseudo hardreg) run). +// The target UN-pins it because it SERIALIZES the dx/dz loads (2-at-a-time thru $v0/$v1), opening +// a load-delay slot the copy fills (t12). The serialize-vs-parallel choice is sched1's list-sched +// tie-break (rank_for_schedule sched.c:2385/2414) -> NO clean C-lever. Probes: s32 `self=param_1` +// copy FOLDS (cse) back into the pin (neutral); `u8* self=(u8*)param_1` cast still lands early. +// * LEVER A PARTIALLY VALID but pressure-blocked: merging dz->d1 / dx->dzz is right in spirit +// (v2 d2-hoist DID land d1->$s1, t60 exact), but a long-lived dzz allocno then EVICTS e->$s2 and +// param_1->$s4 to $s3/$s5 (K4/K2 cascade). The target keeps dzz SHORT-lived by computing it in +// ratan2#1's DELAY SLOT (t62) -> not C-expressible w/o disturbing the 6-callee-saved balance. +// Source-reorder merge (v1) cascades 226->199. K8: global.c has no coalescing, so the $s0/$s1 +// reuse must come from sched1 placement, not a C variable. Full evidence: func_8014D820.opus.md. +s32 func_8014D820(s32 param_1, void *param_2, void *param_3) +{ + typedef struct { u16 x, y, z, w; } V8; + typedef struct { u8 b[8]; } Blk8; /* align-1 -> unaligned lwl/lwr/swl/swr block copy */ + typedef struct { + V8 out0; V8 out1; V8 out2; V8 pvec; V8 dvec; + u8 gap[0x10]; + s32 z0; s32 z1; s32 z2; + } Ctx; + extern s16 currentLocationId; + extern u8 D_801202A0[]; + extern u8 D_8018876C[]; + extern u16 D_801152A8, D_801152AC; + extern s32 ratan2(s32 a0, s32 a1); + extern s32 func_80012A60(s32 a0, s32 a1); + extern s32 func_80135A4C(s32 a0, s32 a1, s32 *a2, s32 a3); + extern s32 func_80135888(s32 a0, s32 a1, s32 a2, s32 a3); + extern void func_800139C8(s32 a0, void *a1, void *a2); + extern s32 func_8014DCE0(s32 arg0, s32 arg1, s32 arg2); + extern s32 func_80133784(s32 a0, void *a1, s32 a2); + + Ctx f; + u8 *e; + u8 *p; + u16 *p2 = param_2; + u16 *p3 = param_3; + s32 dx, dz, d1; + s32 a, b; + s32 e3, e5, e7; + s32 ang, r; + + dx = p3[0] - p2[0]; + dz = p3[2] - p2[2]; + if ((s16)(dx | dz) == 0) goto fail; + + f.dvec.x = dx; + f.dvec.y = p3[1] - p2[1]; + f.dvec.z = p3[2] - p2[2]; + + e = *(u8 **)(param_1 + 0x170); + if (*(u16 *)e == 0) goto fail; + if ((*(u16 *)(e + 0x5C) & 0x1000) == 0) goto fail; + if (*(s32 *)(e + 0x58) == 0) goto fail; + if (func_80135A4C(*(s32 *)(e + 0x20), *(s32 *)(e + 0x58), (s32 *)param_2, (s32)param_3) == 0) goto fail; + + d1 = *(u16 *)(e + 0x6) - *(u16 *)(param_1 + 0x6); + a = ratan2((s16)dz, (s16)dx); + b = ratan2((s16)(*(u16 *)(e + 0xE) - *(u16 *)(param_1 + 0xE)), (s16)d1); + a = (s16)func_80012A60((s16)a, (s16)b); + if (a >= 0x400) goto fail; + + f.z2 = 0; + f.z1 = 0; + f.z0 = 0; + + e3 = *(u16 *)(e + 0x6); + e5 = *(u16 *)(e + 0xA); + e7 = *(u16 *)(e + 0xE); + f.dvec.x = e3 + f.dvec.x; + f.pvec.x = e3; + f.dvec.y = e5 + f.dvec.y + 8; + f.pvec.y = e5; + f.dvec.z = e7 + f.dvec.z; + f.pvec.z = e7; + + if (currentLocationId != 0x3008) { + for (p = D_801202A0; p < D_801202A0 + 0x6480; p += 0x10C) { + if (p == e) continue; + if (*(u16 *)p == 0) continue; + if (*(u16 *)(p + 0x5C) == 0) continue; + if (*(s32 *)(p + 0x58) == 0) continue; + func_80135888(*(s32 *)(p + 0x20), *(s32 *)(p + 0x58), (s32)&f.pvec, (s32)&f.dvec); + } + } else { + ang = ratan2((s16)f.pvec.x - (s16)f.dvec.x, (s16)f.pvec.z - (s16)f.dvec.z) & 0xFFF; + func_800139C8(ang, D_8018876C, &f.out0); + func_800139C8((s16)(ang - 0x155), D_8018876C, &f.out1); + func_800139C8(ang + 0x155, D_8018876C, &f.out2); + f.out0.x += f.pvec.x; + f.out0.z += f.pvec.z; + f.out0.y = f.pvec.y; + f.out1.y = f.pvec.y; + f.out2.y = f.pvec.y; + f.out1.x += f.pvec.x; + f.out1.z += f.pvec.z; + f.out2.x += f.pvec.x; + f.out2.z += f.pvec.z; + for (p = D_801202A0; p < D_801202A0 + 0x6480; p += 0x10C) { + if (p == e) continue; + if (*(u16 *)p == 0) continue; + if (*(u16 *)(p + 0x5C) == 0) continue; + if (*(s32 *)(p + 0x58) == 0) continue; + if (func_8014DCE0((s32)p, (s32)&f.pvec, (s32)&f.out0) != 0) { + *(Blk8 *)&f.dvec = *(Blk8 *)&f.pvec; + } + } + } + + r = func_80133784(0, &f.pvec, (s32)&f.dvec); + if (r == 0 || (r == 0x2000 && (D_801152A8 | D_801152AC) == 0)) { + *(u16 *)(e + 0x6) = f.dvec.x - f.z0; + *(u16 *)(e + 0xA) = f.dvec.y; + *(u16 *)(e + 0xE) = f.dvec.z - f.z2; + f.dvec.x = f.dvec.x - f.pvec.x; + f.dvec.z = f.dvec.z - f.pvec.z; + f.dvec.y = f.dvec.y - f.pvec.y; + *(u16 *)(param_1 + 0x6) = p2[0] + f.dvec.x; + *(u16 *)(param_1 + 0xA) = p2[1] + f.dvec.y + 0x10; + *(u16 *)(param_1 + 0xE) = p2[2] + f.dvec.z; + } else { + *(u16 *)(param_1 + 0x6) = p2[0]; + *(u16 *)(param_1 + 0xA) = p2[1] + 0x10; + *(u16 *)(param_1 + 0xE) = p2[2]; + } + return 1; + +fail: + *(s32 *)(param_1 + 0x170) = 0; + return 0; +} diff --git a/.run/giants/func_8014D820.fable.c b/.run/giants/func_8014D820.fable.c new file mode 100644 index 000000000..fd08304cc --- /dev/null +++ b/.run/giants/func_8014D820.fable.c @@ -0,0 +1,181 @@ +// func_8014D820 -- "attach/track to parent entity" mover (304 ins, ov_SC01_077_after). +// param_1 = self entity (s32 base), param_2/param_3 = u16[] pos snapshots (prev/cur). +// Delta -> validate tracked parent (e=*(self+0x170)) -> ratan2/func_80012A60 heading gate +// (<0x400) -> either the 0x3008 special case (func_800139C8 fan-of-3 + func_8014DCE0 collide +// loop) or the generic func_80135888 loop -> func_80133784 -> write back the pose. +// Returns s32 (1 = applied, 0 = detach: *(self+0x170)=0). +// +// EC_EDIT (src/shared/engine_core.h line 9752, DEFINE_func_8014D790 body): void->s32 return +// flip (unchanged from the opus seed; byte-neutral, the sole caller discards the result). +// +// PINS? NO. Fully block-scoped; the only asm is ABSENT (this draft is asm-free). `self`/`t0`/`t1` +// are plain s32 locals; no `register __asm__("$N")`, no generic-constraint asm, zero file-scope +// footprint -> ×134-clean via family_sweep --edit-remap. +// +// ============================================================================================ +// FABLE5 gdb-on-cc1 PASS (2026-07-15) -- match_one 261 -> **110 positional** (mine=305/target=304). +// The opus "RC-6 whole-function permutation, pin-blocked" verdict is REFUTED at the mechanism +// level; the block-0 residual is CRACKED pin-free. Details in func_8014D820.fable.md. +// +// ROOT-CAUSE CORRECTION (gdb oracle, .run/giants/fable_d820/nopin.gdb): +// The opus blamed the sched.c:3199 "don't delay getting parameters" pin (INSN_REF_COUNT=1 on +// the leading param copy). FALSE. Patching cc1 to skip BOTH the pin store AND the head-advance +// (so insn 4 = `reg72=a0` re-enters the schedulable range) leaves the schedule BYTE-IDENTICAL: +// the copy still lands at the block head, the 4 loads still PARALLELIZE. The pin is a red herring. +// The real block-0 divergence: the target SERIALIZES the dx/dz load temps (load-pair -> subu -> +// load-pair -> subu, reusing $v1/$v0), which opens the load-delay slot the param_1->$s4 copy +// fills. A direct-expression draft (`dx = p3[0]-p2[0]`) gives 4 DISTINCT load pseudos -> the +// scheduler batches them (parallel, 4 scratch regs) -> no slot for the copy. +// +// THE CRACK (261 -> 110, PIN-FREE): +// (1) SERIALIZE via reused load-temps -- the `{ s32 t0,t1; t0=p3[0]; t1=p2[0]; dx=t0-t1; +// t0=p3[2]; t1=p2[2]; dz=t0-t1; }` block. K8/no-coalescing: reusing ONE pair of temp +// VARIABLES forces one pseudo each -> the read-before-2nd-write anti-dependence forces the +// serial schedule AND the $v1/$v0 reuse -- an exact block-0 load structure + temp regs. +// (This is §45 Lever A / the func_801670E4 SPLIT<->MERGE duality applied to LOAD TEMPS.) +// (2) ROUTE param_1 through `self` -- `self=param_1;` then all base accesses via `self`. This +// moves param_1's callee-saved copy INTO block 0 (it participates in scheduling) instead of +// the copy-at-head that param_1-direct produces. (param_1-direct + serial = 287; this = 110.) +// +// RESIDUAL 110 = ~5 LOCALIZED knife-edges (NOT a whole-function permutation -- see fable.md): +// (a) the `self` copy sinks (S2 birthing boost) to block-0 slot2 (dz-delay); target has it in +// slot1 (dx-delay). Source position is invariant (sinks to the lowest slot with a live +// successor use; both branch-successors use self). 2-set re-tie to stop the sink +// MATERIALIZES a move (307/266). INTRINSIC -> permuter. +// (b) beqz-delay dvec.x temp reg (a0 vs v1) -- CASCADES from (a): a0 is freed for the temp only +// if the copy took slot1. +// (c) ratan2 d1/dzz callee-saved reuse ($s0/$s1 order) -- RC-3/Lever-A callee tie; the reuse +// form cascaded (129). INTRINSIC -> permuter. +// (d) e3/e5/e7 + out0/1/2 combine + writeback store/load ORDER -- coupled schedule (source +// reorder cascaded 231). +// (e) return-1 const: mine materializes `li v0,1` standalone (the +1 ins); target CROSS-JUMPS +// the `sh 0xE(s4); j; li v0,1` tail (§5a). goto-ret1 ADDS an insn (134). Cross-jump-shaped. +// +// ROUTE: this pin-free 110 seed -> decomp-permuter. Block 0 is now STRUCTURALLY EXACT (serial +// loads + correct temp regs), which the old opus seed was not; the prior ILS walled at masked-38 +// from the worse (parallel-block-0) seed, so a warm restart from here should go lower. +s32 func_8014D820(s32 param_1, void *param_2, void *param_3) +{ + typedef struct { u16 x, y, z, w; } V8; + typedef struct { u8 b[8]; } Blk8; /* align-1 -> unaligned lwl/lwr/swl/swr block copy */ + typedef struct { + V8 out0; V8 out1; V8 out2; V8 pvec; V8 dvec; + u8 gap[0x10]; + s32 z0; s32 z1; s32 z2; + } Ctx; + extern s16 currentLocationId; + extern u8 D_801202A0[]; + extern u8 D_8018876C[]; + extern u16 D_801152A8, D_801152AC; + extern s32 ratan2(s32 a0, s32 a1); + extern s32 func_80012A60(s32 a0, s32 a1); + extern s32 func_80135A4C(s32 a0, s32 a1, s32 *a2, s32 a3); + extern s32 func_80135888(s32 a0, s32 a1, s32 a2, s32 a3); + extern void func_800139C8(s32 a0, void *a1, void *a2); + extern s32 func_8014DCE0(s32 arg0, s32 arg1, s32 arg2); + extern s32 func_80133784(s32 a0, void *a1, s32 a2); + + Ctx f; + u8 *e; + u8 *p; + s32 self; + u16 *p2 = param_2; + u16 *p3 = param_3; + s32 dx, dz, d1; + s32 a, b; + s32 e3, e5, e7; + s32 ang, r; + + { + s32 t0, t1; + self = param_1; + t0 = p3[0]; t1 = p2[0]; dx = t0 - t1; + t0 = p3[2]; t1 = p2[2]; dz = t0 - t1; + } + if ((s16)(dx | dz) == 0) goto fail; + + f.dvec.x = dx; + f.dvec.y = p3[1] - p2[1]; + f.dvec.z = p3[2] - p2[2]; + + e = *(u8 **)(self + 0x170); + if (*(u16 *)e == 0) goto fail; + if ((*(u16 *)(e + 0x5C) & 0x1000) == 0) goto fail; + if (*(s32 *)(e + 0x58) == 0) goto fail; + if (func_80135A4C(*(s32 *)(e + 0x20), *(s32 *)(e + 0x58), (s32 *)param_2, (s32)param_3) == 0) goto fail; + + d1 = *(u16 *)(e + 0x6) - *(u16 *)(param_1 + 0x6); + a = ratan2((s16)dz, (s16)dx); + b = ratan2((s16)(*(u16 *)(e + 0xE) - *(u16 *)(param_1 + 0xE)), (s16)d1); + a = (s16)func_80012A60((s16)a, (s16)b); + if (a >= 0x400) goto fail; + + f.z2 = 0; + f.z1 = 0; + f.z0 = 0; + + e3 = *(u16 *)(e + 0x6); + e5 = *(u16 *)(e + 0xA); + e7 = *(u16 *)(e + 0xE); + f.dvec.x = e3 + f.dvec.x; + f.pvec.x = e3; + f.dvec.y = e5 + f.dvec.y + 8; + f.pvec.y = e5; + f.dvec.z = e7 + f.dvec.z; + f.pvec.z = e7; + + if (currentLocationId != 0x3008) { + for (p = D_801202A0; p < D_801202A0 + 0x6480; p += 0x10C) { + if (p == e) continue; + if (*(u16 *)p == 0) continue; + if (*(u16 *)(p + 0x5C) == 0) continue; + if (*(s32 *)(p + 0x58) == 0) continue; + func_80135888(*(s32 *)(p + 0x20), *(s32 *)(p + 0x58), (s32)&f.pvec, (s32)&f.dvec); + } + } else { + ang = ratan2((s16)f.pvec.x - (s16)f.dvec.x, (s16)f.pvec.z - (s16)f.dvec.z) & 0xFFF; + func_800139C8(ang, D_8018876C, &f.out0); + func_800139C8((s16)(ang - 0x155), D_8018876C, &f.out1); + func_800139C8(ang + 0x155, D_8018876C, &f.out2); + f.out0.x += f.pvec.x; + f.out0.z += f.pvec.z; + f.out0.y = f.pvec.y; + f.out1.y = f.pvec.y; + f.out2.y = f.pvec.y; + f.out1.x += f.pvec.x; + f.out1.z += f.pvec.z; + f.out2.x += f.pvec.x; + f.out2.z += f.pvec.z; + for (p = D_801202A0; p < D_801202A0 + 0x6480; p += 0x10C) { + if (p == e) continue; + if (*(u16 *)p == 0) continue; + if (*(u16 *)(p + 0x5C) == 0) continue; + if (*(s32 *)(p + 0x58) == 0) continue; + if (func_8014DCE0((s32)p, (s32)&f.pvec, (s32)&f.out0) != 0) { + *(Blk8 *)&f.dvec = *(Blk8 *)&f.pvec; + } + } + } + + r = func_80133784(0, &f.pvec, (s32)&f.dvec); + if (r == 0 || (r == 0x2000 && (D_801152A8 | D_801152AC) == 0)) { + *(u16 *)(e + 0x6) = f.dvec.x - f.z0; + *(u16 *)(e + 0xA) = f.dvec.y; + *(u16 *)(e + 0xE) = f.dvec.z - f.z2; + f.dvec.x = f.dvec.x - f.pvec.x; + f.dvec.z = f.dvec.z - f.pvec.z; + f.dvec.y = f.dvec.y - f.pvec.y; + *(u16 *)(self + 0x6) = p2[0] + f.dvec.x; + *(u16 *)(self + 0xA) = p2[1] + f.dvec.y + 0x10; + *(u16 *)(self + 0xE) = p2[2] + f.dvec.z; + } else { + *(u16 *)(self + 0x6) = p2[0]; + *(u16 *)(self + 0xA) = p2[1] + 0x10; + *(u16 *)(self + 0xE) = p2[2]; + } + return 1; + +fail: + *(s32 *)(self + 0x170) = 0; + return 0; +} diff --git a/.run/giants/func_8014D820.fable.md b/.run/giants/func_8014D820.fable.md new file mode 100644 index 000000000..5011dc042 --- /dev/null +++ b/.run/giants/func_8014D820.fable.md @@ -0,0 +1,121 @@ +# func_8014D820 (304 ins, ×134 core, ov_SC01_077_after) — Fable5 gdb-on-cc1 pass + +**Result:** `match_one` **DIFF 110 positional** (mine=305 / target=304), down from the opus seed's +**261**. **PIN-FREE, ×134-safe** (asm-free; `self`/`t0`/`t1` plain s32 locals; zero file-scope +footprint). Draft: `.run/giants/func_8014D820.fable.c`. **NOT byte-identical → hand off to the +decomp-permuter** (this seed is far better than the prior one — block 0 is now structurally exact). + +**Headline: the opus "RC-6 pin-blocked whole-function permutation" verdict is REFUTED.** The +sched.c:3199 pin is a red herring (oracle-disproven), and the block-0 residual — the largest, +which the opus called intrinsic — is **CRACKED pin-free**. What remains is ~5 *localized* knife-edges, +not a whole-function permutation. + +## Verified starting point (not inherited) +- `match_one` on the opus seed = **261 mismatched** (matches the ledger; not stale). +- Full 261-diff is one cascade from block 0: the target **serializes** the dx/dz load temps through + `$v1/$v0` and fills the resulting load-delay slot with the `param_1→$s4` copy; the seed + **parallelizes** 4 distinct load pseudos (`$a1/$v1/$a0/$v0`) with the copy floating to the head. + Allocation is byte-identical in both (dx→$s0, dz→$s1, param_1→$s4) — pure schedule. + +## The gdb oracle (the decisive experiment) — the pin is NOT the blocker +`.run/giants/fable_d820/nopin.gdb` patches cc1 (i386, symbols, no line info) to neutralize the +sched.c:3199 "don't delay getting parameters" pin. **Key mechanic:** the pin works by +`INSN_REF_COUNT(head)=1` (0x817b3c1) AND `head=NEXT_INSN(head)` (advances head *past* the pinned +insn, removing it from the schedulable range `[head,tail]`). Setting ref_count=0 alone is a NO-OP +(head already advanced past it). The working patch jumps from the pin store straight to the loop exit +(0x817b3c1 → 0x817b43c), skipping BOTH the store and the advance → insn 4 (`reg72=a0`) re-enters the +ready list (verified: `insn[ 4]: priority = 1` now appears in the block-0 trace). + +**Outcome: the schedule is byte-identical with or without the pin.** insn 4 has the *lowest LUID*, so +`rank_for_schedule`'s stable-sort tie-break (`sched.c:2428 INSN_LUID`) always sorts it to be placed at +the block head; the 4 loads still parallelize. **⇒ the deferred-param-copy residual is governed by +LUID + the S2 sink, NOT the pin. The opus root cause is wrong.** + +## The block-0 crack (261 → 110, byte-verified ladder), PIN-FREE + +| step | change | mismatch | note | +|---|---|---|---| +| seed | opus baseline | **261** | parallel loads, copy at head | +| v1 | reused load-temps, `param_1` direct | 287 (+2) | **serial loads achieved**, but copy at head + 2 nops | +| v2 | + asm-copy of param_1 mid-block | 286 | asm has unknown latency → not a clean slot filler + extra $s5 | +| **v3/v_before** | **reused temps + route param_1 through `self`** | **110** | **serial loads + correct temp regs ($v1,$v0) + copy IN block 0** | +| v_merge | + Lever-A reuse dz/dx for d1/dzz | 129 | ratan2 callee-order tie cascades | +| v_ld | + §45 Lever-D goto-ret1 | 134 | ADDS an insn here (tail is a cross-jump, not a shared goto) | +| v_ro | + e3/e5/e7 store reorder | 231 | coupled schedule cascades | + +**The two levers that landed 261→110:** +1. **SERIALIZE via reused load-temps.** `{ s32 t0,t1; t0=p3[0]; t1=p2[0]; dx=t0-t1; t0=p3[2]; + t1=p2[2]; dz=t0-t1; }`. K8 (no coalescing): reusing ONE pair of temp *variables* forces one + pseudo each → the **read-before-2nd-write anti-dependence** on the reused pseudo forces the serial + schedule (load-pair→subu→load-pair→subu) AND the `$v1/$v0` reuse — reproducing the target's exact + block-0 load structure and temp registers. A direct-expression draft gives 4 distinct pseudos → the + scheduler batches the loads (parallel, 4 scratch regs) with no delay slot for the copy. +2. **ROUTE param_1 through `self`.** `self=param_1;` then all base accesses via `self`. This puts + param_1's callee-saved copy INSIDE block 0 (it participates in scheduling) rather than the + copy-at-head that `param_1`-direct produces. (Measured: param_1-direct+serial = 287; +self = 110.) + +## Residual 110 — five LOCALIZED knife-edges (per-cluster verdict) + +- **(a) copy in block-0 slot2 (dz-delay) not slot1 (dx-delay) — INTRINSIC (sink) → permuter.** + The `self=param_1` body copy SINKS (S2 birthing boost, `sched.c` `birthing_insn_p`) toward its + far first use, landing in the *lowest* block-0 slot with a live successor use. Both branch + successors use `self` (not-fail: `e=*(self+0x170)`; fail: `*(self+0x170)=0`), so it sinks to the + block end = slot2. Source position is invariant (tested `before`/`mid`: both 110). The 2-set + dead-re-tie to disable the sink (`__asm__("":"=r"(self):"0"(self))`) MATERIALIZES a move (307/266). + The target's copy is *intermediate* (slot1, not head, not slot2) — not source-reachable as a + sink-eligible body copy. +- **(b) beqz-delay dvec.x temp reg (a0 vs v1) — CASCADES from (a).** The target uses `a0` for the + `f.dvec.x = dx` store temp because a0 is freed once param_1's copy took slot1; mine can't free a0 + as early. Falls out with (a). +- **(c) ratan2 d1/dzz $s0/$s1 reuse — INTRINSIC (RC-3 callee tie) → permuter.** The target reuses + dz's reg ($s1) for d1 and dx's reg ($s0) for dzz after the first ratan2 (a merged-accumulator). + Reproducing it via variable reuse (v_merge) cascaded to 129 — the callee-saved allocno birth order + is a density tie, not a coalescing choice. +- **(d) e3/e5/e7 + out0/1/2 combine + writeback store/load ORDER — coupled schedule.** Source + statement reorder (v_ro) cascaded to 231; these are §T2/S3 ties that only settle once the upstream + block-0/ratan2 schedule is exact (permuter territory). +- **(e) return-1 const (the +1 instruction) — §5a cross-jump.** Mine materializes `li v0,1` + standalone; the target CROSS-JUMPS the two apply branches' common tail (`sh ,0xE(s4); j + epilogue; li v0,1` in the `j` delay slot). §45 Lever-D (goto-ret1) ADDS an insn here (134) because + the shared tail is a cross-jump, not a plain shared block. + +## Cookbook-worthy findings (the highest-value output) + +1. **NEW LEVER — reused-load-temp SERIALIZATION (the MERGE direction, for load temps).** When the + target serializes two independent load-fed ops (load-pair → op → load-pair → op, reusing 2 scratch + regs) but a direct-expression draft PARALLELIZES them (N distinct scratch regs, all loads batched), + **reuse ONE set of named temp variables across both computations.** The read-before-second-write + anti-dependence on the reused pseudo (K8, no coalescing) forces the serial schedule AND the + 2-register reuse in one move. This is **§45 Lever A applied to LOAD TEMPS**, and the MERGE pole of + the func_801670E4 SPLIT↔MERGE duality: *a value the target holds in reused scratch across serial + regions wants MERGED temp variables; a value it holds in one scratch across disjoint regions wants a + per-region FRESH local.* Byte-proven: func_8014D820 block 0, 261→110, PIN-FREE. Cheap-Opus can + apply this directly (it's a source rewrite, no asm). + +2. **The sched.c:3199 leading-param-copy pin is a RED HERRING for deferred-param-copy residuals** + (gdb-oracle-disproven). A param-copy's schedule position is governed by its LUID + the S2 sink, not + the pin. To test any "pinned" hypothesis: patch cc1 to skip BOTH the `INSN_REF_COUNT=1` store AND + the `head=NEXT_INSN` advance (setting ref_count=0 alone is insufficient — head is already advanced + past the pinned insn). Do NOT attribute a deferred-param-copy diff to sched.c:3199. + +3. **Route a param through a plain `self=param` local to land its callee-saved copy INSIDE the entry + block** (vs param-direct → copy at head). The copy survives cse because `self` has real later uses. + Useful whenever the target schedules a param→callee-saved copy into an entry-block delay slot. Its + exact slot is sink-governed (lowest slot with a live successor use) — so this gets the copy into the + block but the permuter/Fable5 must place the final slot. + +## Method / reproducibility +- Draft + all variants: `.run/giants/fable_d820/{v1,v2,v3,v_before,v_merge,v_ld,v_ro,v_2set}.c`. +- gdb oracle: `.run/giants/fable_d820/nopin.gdb` (pin-neutralizer; the decisive experiment). +- Dumps: `.run/giants/dump.sh ` (pinned triple + `-dS` sched trace); block-0 ready-list + trace in the `.i.sched` handed over the parallel-vs-serial mechanism directly (read it before + hand-modeling, per §34). +- Gate: `match_one func_8014D820 --c … --asm-subdir asm/ov_SC01_077/nonmatchings/ov_SC01_077_after`. + +## Recommendation for the next tier +Feed `func_8014D820.fable.c` (110, pin-free, ×134-safe, block-0 structurally exact) to the +decomp-permuter as a warm restart. The prior ILS walled at masked-38 from the *worse* (parallel +block-0) seed; the four remaining clusters (copy-slot, ratan2 callee-order, store-order, cross-jump +tail) are exactly the localized schedule/allocation perturbations the permuter explores. A byte match +is plausible from here; if the permuter also walls, the copy-slot (a) and the ratan2 callee-tie (c) +are the two genuinely-intrinsic residuals to characterize. diff --git a/.run/giants/func_801670E4.fable.c b/.run/giants/func_801670E4.fable.c new file mode 100644 index 000000000..76f12582d --- /dev/null +++ b/.run/giants/func_801670E4.fable.c @@ -0,0 +1,196 @@ +/* func_801670E4 (279 ins, ov_SC01_077_jr_8015AE2C) — Fable5 gdb-on-cc1 pass (2026-07-15). + * STATUS: match_one DIFF 23/279 (NOT a MATCH), PIN-FREE, x134-safe. Body == the Opus seed + * (.run/giants/func_801670E4.opus.c); I could not beat 23 pin-free. The value of this pass is + * the RECLASSIFICATION + oracle PROOF of the dominant residual (see .fable.md). + * + * Residual 23 = region A (idx 19-24, 6: block0 fence-lock, a scheduling permutation) + + * region B (idx 112-133, 17, DOMINANT: the pos-subtraction v0/v1 swap). + * + * ==== KEY FABLE5 FINDING (corrects the seed's S3 verdict) ==== + * Region B is NOT an S3 scheduling-priority wall. The FINAL schedule already matches the target + * byte-for-byte; the ENTIRE residual is register IDENTITY ($v0<->$v1 swapped on the t/r1 family). + * PROVEN by gdb oracle: patching reg_renumber at reload entry (0x0815d4d7) to swap + * reg78(t)$v1->$v0, reg82(r1)$v0->$v1, reg132/136/140 (the D_ loads), reg127 (0x10 const) + * makes cc1 emit region B BYTE-EXACT to the target, INCLUDING the col[1].g-store-before-A4-load + * reordering (that reorder is a sched2 CONSEQUENCE of the register identity, not its cause). + * + * ROOT CAUSE: `t` (reused for pos[0]+pos[1], 2 deaths) is a GLOBAL allocno; `r1` (pos[2], 1 death) + * is a LOCAL allocno. local-alloc runs before global-alloc, so the {AC,pos2} local qty grabs $v0 + * first-fit; this makes the global `t` conflict with $v0 -> forced to $v1. The target needs the + * opposite coloring (t=$v0, r1=$v1). It is a self-consistent 2-coloring whose tie the allocator + * resolves the wrong way; no pin-free C construct flips it (7 byte-verified failures, see .md). + * The seed cracked it only with `r1 __asm__("$3")` (x1-only pin, unusable). + * + * NEXT TIER: decomp-permuter (may stumble on a globalizing perturbation), OR accept the x1 pin + * for this ONE location if x134 propagation is later abandoned. Region A is likewise fence-locked. + */ +s32 func_801670E4(s32 param_1, s32 param_2, s32 param_3, s32 param_4) { + typedef struct { s16 x, y, z, pad; } SVEC; + typedef struct { + SVEC center; /* 0x00 -> sp+0x10 */ + SVEC diff; /* 0x08 -> sp+0x18 */ + s32 pos[3]; /* 0x10 -> sp+0x20 */ + s32 _pad1c; /* 0x1C -> sp+0x2C */ + SVEC v[4]; /* 0x20 -> sp+0x30 */ + struct { u8 r, g, b, pad; } col[4]; /* 0x40 -> sp+0x50 */ + u32 code; /* 0x50 -> sp+0x60 */ + u32 _pad54; /* 0x54 -> sp+0x64 */ + } Fr; + extern u8 D_80189A20[]; + extern u8 D_80189A0C[]; + extern u8 D_80189A34[]; + extern s32 D_801269A4; + extern s32 D_801269A8; + extern s32 D_801269AC; + extern u16 D_80126CE0; + extern void func_80017E68(void *a0, void *a1); + extern void func_800D20C0(void *a0, void *a1, s32 a2); + extern void func_800D23D0(void *a0); + extern void RotMatrixYXZ(void *a0, void *a1); + extern s32 func_80017758(void *a0, void *a1); + extern s32 func_80017DC4(void *a0, void *a1); + extern void func_80048EAC(void *a0, void *a1); + extern s32 func_80149350(); + + Fr fr; + u8 m1[0x20]; + u8 m2[0x20]; + u8 *p; + s32 i; + s32 t; + s32 t1; + s32 t2; + s32 t3; + s32 r1; + s32 arg0; + s32 d1; + s32 d2; + s32 c12; + u16 h; + s32 c12b; + + arg0 = *(s32 *)(param_1 + 0x34); + fr.v[1].z = 0; + fr.v[1].y = 0; + fr.v[1].x = 0; + fr.v[3].z = 0; + fr.v[2].z = 0; + fr.v[0].z = 0; + fr.col[1].b = 0x70; + c12 = *(u8 *)(param_1 + 0x12); + fr.center.x = param_2; + fr.center.y = param_3; + __asm__ __volatile__(""); + p = D_80189A20; + i = 0; + fr.col[0].b = 0; + fr.col[0].g = 0; + fr.col[0].r = 0; + fr.col[2].b = 0; + fr.col[2].g = 0; + fr.col[2].r = 0; + fr.col[3].b = 0; + fr.col[3].g = 0; + fr.col[3].r = 0; + fr.code = 0x50000000; + fr.center.z = param_4; + fr.col[1].r = c12; + fr.col[1].g = c12; + ((void (*)(s32, void *, void *))func_80149350)(arg0, &fr.center, &fr.center); + + h = *(u16 *)&fr.center.x; + *(s16 *)(param_1 + 6) = h; + *(s32 *)(param_1 + 0x4C) = (s16)h; + h = *(u16 *)&fr.center.y; + *(s16 *)(param_1 + 0xA) = h; + *(s32 *)(param_1 + 0x50) = (s16)h; + h = *(u16 *)&fr.center.z; + *(s16 *)(param_1 + 0xE) = h; + *(s32 *)(param_1 + 0x54) = (s16)h; + func_800D20C0(&fr.center, &fr.diff, 7); + func_80017E68(&fr.center, m1); + func_800D23D0(&fr.diff); + RotMatrixYXZ(&fr.diff, m1); + +L1: + fr.v[0].x = (s8)*p++; + fr.v[0].y = (s8)*p++; + fr.v[2].x = (s8)*p++; + fr.v[2].y = (s8)*p++; + fr.v[3].x = (s8)*p++; + fr.v[3].y = (s8)*p--; + func_80017758(fr.v, m1); + t1 = i + 1; + __asm__("addu %0,%1,$zero":"=r"(i):"r"(t1)); + if ((s16)t1 < 4) goto L1; + + p = D_80189A0C; + fr.col[1].g = 0xA0; + fr.col[1].b = 0x10; + fr.col[1].r = 0x10; + t = D_801269A4 - fr.center.x; + fr.pos[0] = t; + i = 0; + fr.v[3].y = 0; + fr.v[2].y = 0; + fr.v[0].y = 0; + r1 = D_801269AC - fr.center.z; + fr.pos[2] = r1; + t = D_801269A8 - fr.center.y; + fr.pos[1] = t; +L2: + fr.v[0].x = (s8)*p++; + fr.v[0].z = (s8)*p++; + fr.v[2].x = (s8)*p++; + fr.v[2].z = (s8)*p++; + fr.v[3].x = (s8)*p++; + fr.v[3].z = (s8)*p--; + func_80017758(fr.v, (void *)(param_1 + 0x38)); + t2 = i + 1; + __asm__("addu %0,%1,$zero":"=r"(i):"r"(t2)); + if ((s16)t2 < 4) goto L2; + + if (*(s32 *)(param_1 + 0x30) > 0) { + d1 = (s16)*(u16 *)&fr.diff.x >> 6; + fr.center.x = *(u16 *)&fr.center.x + d1; + fr.center.y = *(u16 *)&fr.center.y + d1; + fr.center.z = *(u16 *)&fr.center.z + d1; + func_80017E68(&fr.center, m1); + p = D_80189A34; + i = 0; + d2 = (*(u32 *)(param_1 + 0x30) >> 1) + D_80126CE0 * 0x20; + fr.center.z = d2; + fr.center.y = d2; + fr.center.x = d2; + func_80017DC4(&fr.center, m2); + func_80048EAC(m2, m1); + fr.v[3].z = 0; + fr.v[2].z = 0; + fr.v[0].z = 0; + c12b = *(u8 *)(param_1 + 0x12); + __asm__("" :: "r"(c12b)); + fr.col[1].b = 0x20; + fr.col[1].r = 0x20; + fr.col[3].b = 0; + fr.col[3].r = 0; + fr.col[2].b = 0; + fr.col[2].r = 0; + fr.col[0].b = 0; + fr.col[0].r = 0; + fr.col[3].g = 0; + fr.col[2].g = 0; + fr.col[0].g = 0; + fr.col[1].g = c12b + 0x60; +L3: + fr.v[0].x = (s8)*p++; + fr.v[0].y = (s8)*p++; + fr.v[2].x = (s8)*p++; + fr.v[2].y = (s8)*p++; + fr.v[3].x = (s8)*p++; + fr.v[3].y = (s8)*p--; + func_80017758(fr.v, m1); + t3 = i + 1; + __asm__("addu %0,%1,$zero":"=r"(i):"r"(t3)); + if ((s16)t3 < 8) goto L3; + } +} diff --git a/.run/giants/func_801670E4.fable.md b/.run/giants/func_801670E4.fable.md new file mode 100644 index 000000000..4f1495b62 --- /dev/null +++ b/.run/giants/func_801670E4.fable.md @@ -0,0 +1,161 @@ +# func_801670E4 (279 ins, ov_SC01_077_jr_8015AE2C) — Fable5 gdb-on-cc1 pass + +**Result:** `match_one` **DIFF 23/279** (NOT a MATCH), **PIN-FREE, ×134-safe** — unchanged from the +Opus seed. I could **not** beat 23 pin-free. The deliverable of this pass is the **oracle-proven +RECLASSIFICATION** of the dominant residual (region B), which corrects the seed's S3 verdict and +retargets the next tier. Per the brief: *a wall verdict that names the exact compiler decision is a +first-class deliverable.* Draft: `.run/giants/func_801670E4.fable.c` (body == the seed). + +Residual 23 = **region A** (idx 19-24, 6: block0 fence-lock) + **region B** (idx 112-133, 17, +DOMINANT: the pos-subtraction `$v0`/`$v1` swap). + +--- + +## Region B (17, DOMINANT) — VERDICT: intrinsic **register 2-coloring tie**, NOT S3 + +### The oracle proof (the headline) + +The seed called region B an **S3** wall ("load/mul-fed chain sinks below stores; C cannot LOWER a +priority; sched1 emits the D_801269A4 load before the col[1].g store"). **This is wrong.** I proved +it with a gdb-on-cc1 reg_renumber patch: + +- Harness: break at `reload` entry (`*0x0815d4d7`), patch `reg_renumber` (a `short*` at + `0x082d4330`, deref then index) to swap the $v0↔$v1 family: + `78→2 82→3 132→2 136→3 140→2 127→3`. (`.run/giants/fable_670E4/swap2.gdb`.) +- Result: cc1 emits region B **BYTE-EXACT to the target** — 17→0 — **including** the + `col[1].g`-store-before-`D_801269A4`-load reordering the seed attributed to sched1 priority. + +**Conclusion:** the FINAL schedule already matches the target. The col-store-vs-load order is a +**sched2 (post-reload) CONSEQUENCE of the register identity**, not a sched1 priority decision. The +entire 17-diff residual is one thing: **register identity — `$v0`/`$v1` swapped on the `t`/`r1` +family.** (`.i.sched` even shows the seed's premise inverted: the col[1].g store *is* emitted before +the A4 load in sched1 RTL; the swap in the final `.s` is reload+sched2 reacting to the reg map.) + +### Root cause (from the disposition dump + find_reg trace) + +`.i.greg` dispositions (v0=2, v1=3): `78 in 3, 82 in 2, 132 in 3, 136 in 2, 140 in 3, 127 in 2` +(all $v0↔$v1 opposite the target). Everything cascades from **one allocno**: + +- **`t`** (`s32 t`, reused for pos[0] AND pos[1] → 2 deaths) = pseudo **reg78**, a **GLOBAL** + allocno (one of the 5 in `;; 5 regs to allocate`). It ties the three D_ loads + (pos[k]=D-center reuses the load reg destructively): reg132/140 (A4/A8) follow reg78. +- **`r1`** (`s32 r1`, pos[2], 1 death) = pseudo **reg82**, a **LOCAL** allocno (merged qty + {AC(136), pos2(82)}). + +`local-alloc` runs **before** `global-alloc`. The local qty {AC,pos2} grabs `$v0` by first-fit; this +records the conflict `78 conflicts: … 2 …` (reg78 ⟂ `$v0`), so when global-alloc's `find_reg` +(traced: allocno 3 = pseudo 78, `hard_reg_preferences=0`, `hard_reg_conflicts` has bit 2) runs, `$v0` +is blocked → `t`→`$v1`. The A4/A8 loads and pos results all cascade to `$v1`; the `0x10` const and +pos[2] cascade to `$v0`. + +The target needs the **opposite, equally-valid coloring** (`t`=$v0, `r1`=$v1). It is a +**self-consistent 2-coloring**: A4=$v0 ⇐ t=$v0 ⇐ r1=$v1 ⇐ (A8 in $v0 overlaps pos2) ⇐ A8=$v0 ⇐ +t=$v0. Both colorings close; the allocator's deterministic tie-break (**local-before-global + +first-fit** on the source-order-invariant sched1 order) picks mine. There is no external anchor in +the block to break the symmetry. + +Why source order can't touch it (the seed's true "S3-like" observation): the sched1 **backward list +scheduler** always emits `pos1subu` before `pos2subu` regardless of C statement order, which makes +the {AC,pos2} qty **live longer** than the A8 qty (`qty_compare`, `local-alloc.c`), so {AC,pos2} +always wins `$v0`. Confirmed: statement reorder pos1↔pos2 is a byte no-op. + +### Pin-free levers tried — all fail (byte-verified) + +| # | lever | result | why | +|---|---|---|---| +| m1 | split `t` → t/tb (both local 1-death) | **24** | breaks the schedule (the reused `t` is load-bearing for sched1 order) | +| m3 | inline all three pos (no temps) | **24** | same schedule shift | +| m4 | reorder pos1 before pos2 (source) | **23** (no-op) | sched1 re-derives the same order (the true invariance) | +| m5 | pos1 & pos2 share `r1` | **24** | wrong reg group | +| m6 | globalize `r1` via block3 reuse | **34** | becomes **call-crossing** → `$a2` (wrong class) | +| m7 | pre-load A8 into a var spanning pos2 | **28** | adds a copy / schedule shift | +| m8 | `r1` in-out re-tie (2nd set → global) | **29** | `#APP` perturbs; r1 chases `$a1` | +| m9 | input-only anchor on `r1` | **29** | `#APP` perturbs | +| m2 | scheduling fence between pos2/pos1 | **169** | bare `asm("")` mid-block = cse flush + maspsx nop-killer | + +The mechanism that WOULD work — **make `r1` a block2-confined 2-death global** (so it competes with +`t` in global-alloc by density, where `t`'s higher ref count wins `$v0` first, leaving `r1`→`$v1`) — +has no pin-free spelling: the only ways to add a 2nd death are (a) a cross-block reuse → call-crossing +→ wrong class (m6), or (b) an in-out/anchor `asm` → `#APP` maspsx perturbation (m8/m9). The seed +cracked it only with `register s32 r1 __asm__("$3")` — the ×1-only pin (§42e), unusable at ×134. + +**VERDICT (region B):** intrinsic **RC-6-class local-alloc qty-tie** (downgraded from the seed's S3). +Route: **decomp-permuter** (its temp-split/merge randomizers may stumble on a globalizing form the +directed C-lever space can't reach), or — if ×134 propagation is ever abandoned for this core — +accept the one `r1 __asm__("$3")` pin for this ×1 location. + +--- + +## Region A (6) — VERDICT: intrinsic **fence-lock scheduling permutation** + +The 6 diffs are a pure permutation of `{addiu a1=¢er, sh center.y, addu a2=a1, lui/addiu p, addu +i=0}`. Target floats the func_80149350 arg-addresses (`a1=&fr.center`, `a2=a1`) **above** `p`/`i=0` +(a1 reuses param_2's freed `$a1` right after `sh $a1,0x10`). The mandatory empty `__volatile__("")` +fence keeps `p` (single-set → S2 birthing-boost) from floating to idx4 (else +250) but simultaneously +**walls the args** (def at the call, high LUID) from floating up past it. Levers tried: + +| # | lever | result | +|---|---|---| +| m10 | drop fence + `p` in-out re-tie (kill boost) | **41** (`#APP` + p position) | +| m11 | `pc=&fr.center` var before the fence | **280 ins / 248** (pc → callee-saved, crosses call) | +| m12 | `pc=&fr.center` between the center stores | **280 / 248** (same) | + +Consistent with the seed. The tension (barrier must block `p`'s upward boost yet not the args' +upward float, but both move up through the same point) has no pin-free C resolution I found. + +--- + +## Cookbook-worthy findings (the highest-value output) + +1. **The reg_renumber-swap oracle: prove a residual is pure register-identity in ONE run.** When a + whole diff block is the same opcodes with two registers transposed, break at `reload` entry + (`*0x0815d4d7`), patch `reg_renumber` (`*(short**)0x082d4330`, index by pseudo) to the target + assignment, and re-emit. If the block goes byte-exact, the residual is **100% allocation, 0% + scheduling** — even if the final *order* differs, because sched2 re-derives order from the reg + map. This **cheaply discriminates S3 (scheduling) from RC-6 (allocation)** and stops you hunting + scheduling levers for an allocation problem (the trap this function was in for a whole seed pass). + Pseudos come from the `.i.greg` dispositions; `reg_renumber` is a `short*` **pointer**, not an + array (deref first — same gotcha as `qty_first_reg`). + +2. **"Store-before-load reordering" can be a CONSEQUENCE of register identity, not a cause.** A + target that stores a dying value (freeing its reg) immediately before a load that reuses that reg + looks like a scheduling decision. It often is not: once the allocator gives the load that reg, + **sched2 (post-reload) produces the reorder for free** to satisfy the WAR/reuse. Verify with the + swap oracle before invoking S3/S4 filler levers. + +3. **The local-vs-global allocation tie is a distinct RC-6 sub-class with a NAMED failure mode.** + When the target's coloring is the *global-first* solution of a self-consistent 2-coloring but + yours is the *local-first* solution: a **reused scalar** (2 deaths → global, e.g. a temp written + in two disjoint regions) loses `$v0` to a **single-use sibling** (1 death → local) because + `local_alloc` runs first. The fix wants the sibling **globalized while staying block-confined** + (so global-alloc density order — `t` outranks `r1` — decides), but pin-free that is unreachable: + cross-block reuse → call-crossing (wrong class); `asm` re-tie/anchor → `#APP` maspsx perturbation. + **Tell:** `.i.greg` shows the reused temp in `;; N regs to allocate` (global) while its sibling is + absent (local), and the sibling's hard reg is the lower one. **Recognize → route to permuter; do + not spend C-lever budget** (this pass byte-verified 7 forms to zero effect). + +4. **Corollary — the reused-temp split is double-edged.** §44-Lever-3 / §45-A say "split a reused + value into per-region locals to get first-fit." Here the reuse is **load-bearing for the + schedule** (splitting `t` shifts sched1 → +1). Check the schedule delta before splitting a reused + temp; when the reuse feeds the sched1 order you want, the split is a regression, and the residual + it leaves is the local-vs-global tie above (finding #3), not something the split can fix. + +--- + +## Method / reproducibility +- Oracle workspace: `.run/giants/fable_670E4/` — `orc.i` (the seed's preprocessed input), + `swap2.gdb` (the byte-exact reg_renumber-swap proof), `probe2.gdb` (reg_renumber deref pattern), + `freg.gdb`/`ffr3.gdb` (find_reg / find_free_reg traces), `m0..m12.c` (the 12 byte-verified + variants above). Dumps `m0.i.{sched,lreg,greg,…}` via `.run/giants/dump.sh`. +- cc1 symbols (i386, `tools/bin/gcc-2.7.2-psx/cc1`): `reload 0x0815d4d7`, `find_reg 0x0814c8ca`, + `find_free_reg 0x08149ad0`, `local_alloc 0x0814575b`, `global_alloc 0x0814a579`; + data `reg_renumber 0x082d4330 (short*)`, `qty_first_reg 0x082c5404 (live copy)`, + `allocno_reg 0x082c5448`, `hard_reg_preferences 0x082c5470`, `hard_reg_conflicts 0x082c546c`. +- Gate: `python3 tools/match_one.py func_801670E4 --c --asm-subdir asm/ov_SC01_077/nonmatchings/ov_SC01_077_jr_8015AE2C`. + +## Next-tier hand-off +- **Bank candidate?** No — draft is 23, not a MATCH. +- **Permuter:** point at region B specifically; its temp-introduction/merge randomizers are the only + automated path to a block-confined `r1` globalization. Seed the permuter with the seed draft. +- **Cheap-Opus:** nothing new to apply here (the 7-lever space is byte-exhausted); use findings #1/#2 + as a general discriminator on OTHER giants that were tagged S3 — some may be mislabeled RC-6. diff --git a/.run/giants/func_8016CBC0.fable.c b/.run/giants/func_8016CBC0.fable.c new file mode 100644 index 000000000..1d782f9e6 --- /dev/null +++ b/.run/giants/func_8016CBC0.fable.c @@ -0,0 +1,178 @@ +/* func_8016CBC0 — trail/afterimage RENDERER over the 0xC4-stride records at D_801D9CA0. + * ×134 per-location family core (28,006 templatable ins). Sibling of func_80166994/func_8016CF04. + * + * ===== STATUS: PIN-FREE, match_one 46/209 (positional, CORRECT length 209). ROOT A CRACKED. ===== + * + * This is the LOWEST-positional pin-free draft (the best next-tier seed by the gate metric). + * Companion draft `func_8016CBC0.workhorse.c` (variant e4) cracks the harder root-B WORKHORSE + * residual (scoped-merge) at the cost of an RC-6 secondary coupling — read `.fable.md` for the + * full ladder and which base the next tier should take. + * + * PIN-FREE: YES. Zero `register __asm__("$N")` pins, zero hard-reg names. The only asm are + * generic-constraint zero-byte optimisation barriers (counter fences `__asm__("":"=r"(t):"0"(t))` + * + one input-only density anchor `__asm__("":: "r"(mat))`) — all ×134-safe (the §42e SIGABRTs + * are from FIXED-reg pins, which this has none of). Block-scoped decls, no file-scope footprint. + * + * WHAT LANDED vs the prior seed (m7, was 151/208): + * • ROOT A — mat/i $s4/$s5 swap: CRACKED with a PURE DENSITY DIAL (not coalescing!). mat(pseudo + * 76) and i(77) both had live_length 163; mat had 7 refs, i had 9. `allocno_compare` + * (global.c:594) pri = floor_log2(n_refs)*n_refs/live_length: mat 2*7=14→858, i 3*9=27→1656 — + * i wins $s4 (the 7→8 ref jump crosses the floor_log2 boundary at 8, DOUBLING the multiplier). + * Fix: `__asm__("":: "r"(mat))` inside the outer-loop `if(active)` body (loop_depth 2 → + * reg_n_refs += 2, flow.c:2067) → mat 7→9 refs → ties i at 27 → mat wins $s4 by the creation- + * order tie-break (76<77, global.c:616). Byte-zero. This ALSO fixed the 208→209 length. + * + * RESIDUAL (root B, the ~46 masked): the vertex-block WORKHORSE. w0 loads to $a0 (mine) vs $v1 + * (target); w3 to $a2/$a3 vs $a0. The target holds the whole w0→(short)w0→sum→av chain in ONE + * workhorse $v1 (a per-coord merged variable). Here w0 is a SEPARATE, MULTI-death (reused across + * 3 coords) → GLOBAL allocno (local-alloc.c:472 gate REG_N_DEATHS==1 fails) → no destructive tie + * (K8: gcc-2.7.2 has NO global coalescing) → gets its own reg $a0. See `.fable.md`: this is + * CRACKABLE (workhorse.c does it via per-coordinate scoped-merge) but the crack trades into an + * RC-6 register-contention on $a1 (pv vs the coord-z accumulator) — the honest wall. + * + * Best pin-free draft (variant e1) follows. + */ +#include "common.h" + +short func_8016CBC0(void) { + typedef struct { u16 v[6]; } VtxR; + typedef struct { u8 active, nv, cnt, col; VtxR verts[16]; } BlkR; + typedef struct { s32 w[8]; } Blk20; + extern BlkR D_801D9CA0[]; + extern Blk20 D_800AE620; + extern s32 func_80017758(void *a0, void *a1); + + u8 buf[0x90]; + u8 col; + int uv; + int cnt; + u8 *vb; + void *mat; + int i; + short ret; + + *(Blk20 *)(buf + 0x70) = D_800AE620; + ret = 0; + i = 0; + mat = buf + 0x70; + *(s32 *)(buf + 0x68) = 0x50000000; + *(s32 *)(buf + 0x30) = 0x50000000; + *(u8 *)(buf + 0x66) = 0; + *(u8 *)(buf + 0x65) = 0; + *(u8 *)(buf + 0x64) = 0; + *(u8 *)(buf + 0x5E) = 0; + *(u8 *)(buf + 0x5D) = 0; + *(u8 *)(buf + 0x5C) = 0; + *(u8 *)(buf + 0x2A) = 0; + *(u8 *)(buf + 0x29) = 0; + *(u8 *)(buf + 0x28) = 0; + *(u8 *)(buf + 0x22) = 0; + *(u8 *)(buf + 0x21) = 0; + *(u8 *)(buf + 0x20) = 0; + do { + BlkR *e = &D_801D9CA0[(short)i]; + if (e->active != 0) { + int tcnt; + vb = (u8 *)e + 4; + uv = e->nv; + cnt = e->cnt; + col = e->col; + ret = ret + 1; + __asm__("" :: "r"(mat)); + tcnt = cnt - 1; + __asm__("" : "=r"(tcnt) : "0"(tcnt)); + cnt = tcnt; + if (tcnt != -1) { + do { + s32 uvm; + u8 *pv; + { + s32 po = (short)uv * 0xc; + s32 w0, w3, av; + u8 c8c; + pv = vb + po; + w0 = *(u16 *)(pv + 0x0); + *(short *)(buf + 0x00) = w0; + w3 = *(u16 *)(pv + 0x6); + *(short *)(buf + 0x40) = w3; + av = (u32)((short)w0 + (short)w3) >> 1; + *(short *)(buf + 0x38) = av; + *(short *)(buf + 0x08) = av; + w0 = *(u16 *)(pv + 0x2); + *(short *)(buf + 0x02) = w0; + w3 = *(u16 *)(pv + 0x8); + c8c = col >> 3; + uvm = uv - 1; + __asm__("" : "=r"(uvm) : "0"(uvm)); + uv = uvm; + *(short *)(buf + 0x42) = w3; + av = (u32)((short)w0 + (short)w3) >> 1; + *(short *)(buf + 0x3A) = av; + *(short *)(buf + 0x0A) = av; + w0 = *(u16 *)(pv + 0x4); + *(short *)(buf + 0x04) = w0; + w3 = *(u16 *)(pv + 0xa); + *(u8 *)(buf + 0x59) = col; + *(u8 *)(buf + 0x25) = col; + *(u8 *)(buf + 0x5A) = c8c; + *(u8 *)(buf + 0x26) = c8c; + *(u8 *)(buf + 0x58) = c8c; + *(u8 *)(buf + 0x24) = c8c; + *(short *)(buf + 0x44) = w3; + av = (u32)((short)w0 + (short)w3) >> 1; + *(short *)(buf + 0x3C) = av; + *(short *)(buf + 0x0C) = av; + } + if ((int)(uvm << 16) < 0) uv = 0xf; + { + s32 po = (short)uv * 0xc; + s32 w0, w3, av; + u8 c84; + pv = vb + po; + col = col - 0xf; + c84 = col >> 3; + w0 = *(u16 *)(pv + 0x0); + *(short *)(buf + 0x10) = w0; + w3 = *(u16 *)(pv + 0x6); + *(short *)(buf + 0x50) = w3; + av = (u32)((short)w0 + (short)w3) >> 1; + *(short *)(buf + 0x48) = av; + *(short *)(buf + 0x18) = av; + w0 = *(u16 *)(pv + 0x2); + *(short *)(buf + 0x12) = w0; + w3 = *(u16 *)(pv + 0x8); + *(short *)(buf + 0x52) = w3; + av = (u32)((short)w0 + (short)w3) >> 1; + *(short *)(buf + 0x4A) = av; + *(short *)(buf + 0x1A) = av; + w0 = *(u16 *)(pv + 0x4); + *(short *)(buf + 0x14) = w0; + w3 = *(u16 *)(pv + 0xa); + *(u8 *)(buf + 0x61) = col; + *(u8 *)(buf + 0x2D) = col; + *(u8 *)(buf + 0x62) = c84; + *(u8 *)(buf + 0x2E) = c84; + *(u8 *)(buf + 0x60) = c84; + *(u8 *)(buf + 0x2C) = c84; + *(short *)(buf + 0x54) = w3; + av = (u32)((short)w0 + (short)w3) >> 1; + *(short *)(buf + 0x4C) = av; + *(short *)(buf + 0x1C) = av; + } + func_80017758(buf + 0x00, mat); + func_80017758(buf + 0x38, mat); + tcnt = cnt - 1; + __asm__("" : "=r"(tcnt) : "0"(tcnt)); + cnt = tcnt; + } while ((short)tcnt != -1); + } + } + { + int ti = i + 1; + __asm__("" : "=r"(ti) : "0"(ti)); + i = ti; + if ((short)ti >= 8) break; + } + } while (1); + return ret; +} diff --git a/.run/giants/func_8016CBC0.fable.md b/.run/giants/func_8016CBC0.fable.md new file mode 100644 index 000000000..4d41f0b2d --- /dev/null +++ b/.run/giants/func_8016CBC0.fable.md @@ -0,0 +1,138 @@ +# func_8016CBC0 (209 ins, ×134 family core) — Fable5 gdb/dump-oracle pass + +**Result:** the "coalescing knife-edge" verdict is **REFUTED as named**. gcc-2.7.2 has NO global +coalescing (map key K8), so nothing here is a coalescing decision. The residual is two SEPARATE, +precisely-located compiler decisions — one **CRACKED**, one **crackable-but-RC-6-coupled**: + +| root | what | mechanism (exact gcc file:line) | verdict | +|------|------|---------------------------------|---------| +| **A** | mat/i `$s4/$s5` swap | `global.c:594 allocno_compare` density gap at a `floor_log2` boundary | **CRACKED** — pure density dial, byte-zero | +| **B** | vertex-block workhorse (w0→`$a0` vs target `$v1`) | `local-alloc.c:472` single-death gate + destructive tie (`combine_regs`) | **workhorse CRACKED** (scoped-merge) but exposes an RC-6 `$a1` contention | + +**Ledger reconciliation (task asked):** BOTH recorded numbers were stale — they were measured +against the now-renamed subdir `ov_SC01_077_after`. Against the CURRENT subdir +`ov_SC01_077_jr_8016AB6C`, the seed `m7`/opus.c scores **match_one positional 151 (length 208), real +aligned-diff 111**. Neither "close=148" (backlog) nor "153 masked" (.opus.md) reproduces; the backlog's +prose ("166/209 aligned, edit-dist ~44") was the honest figure. The `match_one` positional count is +**cascade-inflated whenever the length is wrong** — always cross-check with the aligned differ +(`.run/giants/opus_8016CBC0/adiff.py `). + +## Deliverable drafts (both PIN-FREE, ×134-safe — generic-constraint zero-byte asm only) + +- **`func_8016CBC0.fable.c`** (variant **e1**) — **match_one 46/209, correct length**. Root A cracked; + ONE clean residual (root B, w0-load register). Best next-tier seed by the gate metric. +- **`func_8016CBC0.workhorse.c`** (variant **e4**) — match_one 105/209 (length 210, +1) but **real + aligned-diff 81** (best) and it **CRACKS the root-B workhorse** (w0 chain lands in `$v1`). Its 105 is + cascade-inflated by the +1 length. Recommended base for a permuter finish — see root B below. + +## The byte-verified lever ladder (match_one positional / real aligned-diff, current subdir) + +| variant | change | pos | real | note | +|---------|--------|-----|------|------| +| e0 (=m7 seed) | prior pin-free seed | 151 | 111 | length 208 | +| **e1** | + `__asm__("":: "r"(mat))` density anchor | **46** | 92 | **root A cracked + length→209** | +| e2 | e1 + merge w0/av (vertex-block scope) | 52 | 110 | merge → whole chain to `$a0` (wrong dir) | +| e3 | e1 + per-coord block scope, no merge | 96 | 182 | fragments — scope-without-merge is wrong | +| **e4** | e1 + per-coord **scoped MERGE** | 105 | **81** | **workhorse w0→`$v1` (cracked)**; +1 ins, pv→`$a3` | +| e9 | e4 but coord-z left plain (hybrid) | 96 | 120 | mixing scoped/plain fragments worse | + +## ROOT A — CRACKED. The `$s4/$s5` swap is a density gap, NOT coalescing. + +From the `m7` `.greg`: callee-saved pseudos `72..78` = col,uv,cnt,vb,**mat(76),i(77)**,ret. Seed +disposition `76 in 21 ($s5) 77 in 20 ($s4)` — i wins `$s4`; target wants mat=`$s4`, i=`$s5`. + +`.lreg`: **mat(76) = 7 refs / 163 live; i(77) = 9 refs / 163 live** (identical live_length). Plug into +`allocno_compare` (`tools/reference/gcc-2.7.2/global.c:594`) +`pri = floor_log2(n_refs)*n_refs / live_length * 10000`: +- mat: `floor_log2(7)*7 = 2*7 = 14` → **858** +- i: `floor_log2(9)*9 = 3*9 = 27` → **1656** + +i allocates first (higher density) → takes the lower reg `$s4`. The gap is NOT a tie and NOT +coalescing — it is the **`floor_log2` step**: 7→8 refs crosses `2^3=8`, doubling the multiplier from +×2 to ×3. To flip, mat needs `floor_log2*refs ≥ 27`; mat=8 gives 24 (still loses!), mat=9 gives 27 = a +TIE, which mat then wins by the creation-order tie-break (`global.c:616 return v1-v2`, and 76<77). + +**The dial:** `__asm__("" :: "r"(mat));` placed inside the outer-loop `if (e->active)` body. That block +is at **loop_depth 2** (function=1, outer do-while=2; inner do-while=3), and `flow.c:2067` +`reg_n_refs[regno] += loop_depth` → mat's ref count rises **7→9**. Zero bytes (input-only asm), ×134- +safe. Result: `76 in 20 ($s4) 77 in 21 ($s5)` — exact. Side benefit: it also repaired the 208→209 +length (the swap had been perturbing the tail schedule). + +**This retires the "coalescing knife-edge" name for root A**: it is the same `allocno_compare` density +dial as cookbook §47/§48-A, just gated by the `floor_log2` boundary rather than an int-truncation tie. + +## ROOT B — the workhorse. Crackable, but the crack is RC-6-coupled. + +The target keeps `w0 → (short)w0 → sum → av` all in ONE workhorse `$v1` for every coordinate (a +per-coord in-place accumulator). The seed/e1 keeps `av` in `$v1` (correct) but the raw `w0` load in +`$a0` (wrong). **Why:** `w0` is ONE C variable reused across the 3 unrolled coordinates → `.lreg` +"Register 109 … dies in 3 places" → `REG_N_DEATHS = 3 ≠ 1` fails the local-alloc gate +(`local-alloc.c:472`) → `w0` becomes a **GLOBAL allocno**. gcc-2.7.2 global-alloc has **no coalescing +(K8)**, so `w0` cannot tie into the `$v1` av-chain; it gets its own reg. Confirmed from the conflict +matrix: `109` conflicts with hard regs `2 (v0)` and `3 (v1)` (scratch pressure across its multi-coord +range) → first-fit `$a0`. + +**The crack (workhorse.c / e4):** give each coordinate its own block-scoped, MERGED accumulator — +`{ s32 w0 = *(u16*)(pv+K); s32 w3; store w0; w3=…; store w3; w0 = (u32)((short)w0+(short)w3)>>1; +store w0; store w0; }`. Now each coord's accumulator is **single-death → a LOCAL qty**, and the store- +before-sign-extend order makes `w0` die into its own in-place update → the local-alloc **destructive +tie fires** → the whole chain sits in one workhorse reg = **`$v1`, matching the target** (verified: e4 +loads are `lhu v1,0(a1)`). This is **§48-A3 (block-scoped per-arm temps) + §45-Lever-A (merged +accumulator) COMBINED on loop-unrolled coordinates** — a new composite. + +**Why e4 is not yet a match — the RC-6 coupling:** the per-coord scoped-merge, applied to the LAST +(coord z, highest-pressure — it also carries the 6 colour stores + the `uvm<<16` bgez test), makes +coord-z's accumulator a high-priority local that grabs **`$a1`** (its `.greg` pri = `4*18/18 = 40000`), +**evicting `pv`** (pseudo 101, pri `5*42/63 = 33333`) to `$a3`. So all 12 vertex loads read `(a3)` +instead of the target's `(a1)`, plus one delay-slot fill flips (+1 ins). Every attempt to relieve it +moved 20+ instructions discontinuously (e2/e3/e9 above; a `pv` density anchor needs +9 refs to out- +rank the coord-z accumulator — 3 depth-3 anchors, itself perturbing). This is textbook **RC-6 / §31 +S11 coupling at `global.c` `find_reg` first-fit** — NOT a coalescing decision. It is the genuine wall +for the single-C-lever tier; a **decomp-permuter seeded from workhorse.c** (localized `$a1` contention, +workhorse already correct) is the right next move, OR a gdb patch of `find_reg`'s grant for the pv vs +coord-z-accumulator allocnos to confirm the minimal steer. + +## Cookbook-worthy findings (the highest-value output — "Fable5 DISCOVERS, cheap-Opus APPLIES") + +1. **DENSITY DIAL ACROSS A `floor_log2` BOUNDARY (new, byte-proven, root A).** When two equal- + live_length call-crossing allocnos tie-break wrong, don't reach for pins — compute + `floor_log2(refs)*refs` for each. If the loser is one `floor_log2` step below (e.g. 7 refs vs 9: + `2*7=14` vs `3*9=27`), you cannot win by matching refs alone (8 refs = `3*8=24` STILL loses); you + must **reach the next integer of `floor_log2*refs` and then win the creation-order tie**. The dial + is an **input-only `__asm__("":: "r"(v))` placed at the loop depth that supplies the needed + `reg_n_refs += loop_depth` increment** (`flow.c:2067`): depth-2 gives +2 per anchor, depth-3 gives + +3. Zero bytes, ×134-safe. This generalises §47's ±1 live-length slider to the ref-count axis and + subsumes the "coalescing knife-edge" mislabel whenever the residual is a callee-saved swap. + +2. **SCOPED-MERGE = §48-A3 ⊕ §45-Lever-A, for LOOP-UNROLLED coordinates (new composite).** A workhorse + register the target holds across an in-place `load→store→transform` chain, but which your draft + splits because the driving variable is reused across N unrolled iterations (→ multi-death → GLOBAL + → no tie): **wrap each iteration in its own `{ }` with a fresh merged accumulator** (`s32 acc = + load; store acc; acc = f(acc,…); store acc;`). Single-death per iteration → LOCAL qty → the store- + before-transform order fires the destructive tie → the whole chain collapses into the one workhorse + reg. Verified to move `w0` from `$a0` to the target's `$v1` across all coordinates. **Caveat + (also proven):** applying it to the highest-pressure iteration can make its accumulator out-price a + held pointer for a shared reg (RC-6). Apply, then read `.greg` dispositions for collateral before + claiming the match. + +3. **"MERGE" HAS A DIRECTION, AND IT IS THE OPPOSITE OF NAIVE (root B negative result).** Merging at + the WRONG scope backfires: merging `w0/av` at *vertex-block* scope (multi-death, still GLOBAL) drags + the WHOLE chain onto `w0`'s reg `$a0` (e2, regressed) — the reverse of the goal. The merge only + helps when it is ALSO single-death-scoped. Confirms §44/§45's "each giant is its own class": the + merge and the block-scope are ONE lever, not two. + +4. **`match_one` positional count is untrustworthy at wrong length — gate on the aligned differ.** + e4's real closeness (81 aligned) beats e1's (92) yet e4's positional (105) looks far worse than e1's + (46), purely from a +1 length cascade. The ledger disagreements on this function trace to exactly + this. Rule: read `adiff.py`, not the positional headline, when the length is off by any amount. + +## Method / reproducibility +- Iterate: `bash .run/giants/fable_8016CBC0/chk.sh ` (prints match_one positional + the + aligned real-diff). Variants preserved in `.run/giants/fable_8016CBC0/e{0..10}_*.c`. +- Dumps: `bash .run/giants/dump.sh ` (pinned triple + `-dS -dR -dj -dc -dg -dl -dd`) → + read `.i.lreg` (per-pseudo refs/live_length/deaths) and `.i.greg` (dispositions + conflict matrix). + Root A and root B were both settled from these dumps + `tools/reference/gcc-2.7.2/{global,flow, + local-alloc}.c` — NO gdb run was needed (the dumps carried the decision). The gdb oracle + (`find_reg @ 0x0814c8ca`, `find_free_reg @ 0x08149ad0` in this cc1) remains the fallback to confirm + the pv-vs-coord-z `find_reg` grant for a permuter-free root-B close. +- Gate reminder: a `match_one` result is a CANDIDATE; finish any bank on the whole-binary byte-gate. diff --git a/.run/giants/func_8016CBC0.workhorse.c b/.run/giants/func_8016CBC0.workhorse.c new file mode 100644 index 000000000..112ee8ba7 --- /dev/null +++ b/.run/giants/func_8016CBC0.workhorse.c @@ -0,0 +1,202 @@ +/* func_8016CBC0 — trail/afterimage RENDERER over the 0xC4-stride records at D_801D9CA0 + * (sibling of func_80166994/func_8016CF04; same Blk struct: u8 f0..f3 header + 16x {6 verts}). + * Loops entries 0..7; for each active entry, walks its `count` vertices building two GPU + * primitive packets per step (pktA@buf+0x00, pktB@buf+0x38, shared mat@buf+0x70 <- D_800AE620) + * and calls func_80017758(pkt, mat) twice. Returns (short) count of active entries. + * + * ===== STATUS: PIN-FREE NEAR-MISS 153/209 (masked) — ESCALATE TO FABLE5 (RC-6 qty-birth lock) ===== + * + * PIN-FREE: YES. Zero `register __asm__("$N")` pins, zero hard-reg asm. The only asm are FOUR + * empty-string opaque-identity fences `__asm__("" : "=r"(t) : "0"(t))` (generic constraints, + * NO opcode) — pure optimisation barriers, milder than the flagship's in-out `lh`, fully + * ×134-safe (the §42e cc1-SIGABRTs come from FIXED-reg pins, which this has none of). + * Block-scoped typedefs/externs, no file-scope footprint, no //@EDIT (canon-sig already + * `extern short func_8016CBC0(void)`), so it splices + family_sweeps cleanly IF/when cracked. + * + * WHAT THE FLAGSHIP (func_80133CD4) LEVERS FIXED HERE (measured, match_one masked): + * • Lever B / opaque-copy fence (§36): the 3 counter chains (cnt initial + loopback, uv, i) now + * emit the target's compute-in-caller-temp-then-copy K8-no-coalesce form + * (`addiu vN,sN,-1; addu sN,vN,zero`) instead of the seed's in-place `addiu sN,sN,-1`. + * Root: `t = x-1; fence(t); x = t; test t` — the fence blocks cse/copy-prop from folding t + * into x, so x (a call-crossing GLOBAL allocno, global.c K8) keeps the copy. (The dB seed's + * PINS FORCED in-place counters — a pinned var is a hard reg, no coalescing decision at all.) + * • uv-split cured: the pin-free baseline scattered uv to a reload pseudo (201); the counter + * fence lands it back on a clean early pseudo -> $s1. + * • SINGLE reused inner-scope `pv` pointer (NOT block-local — the OPPOSITE of naive scoping): + * makes pv a GLOBAL allocno that global-alloc gives a1 AFTER local w3 grabs a0 — this fixed + * the whole first-vertex-block spine: (short)uv->v1, po->v0, pv-base->a1 (all == target). + * + * RESIDUAL (the ~56 masked mismatches — ONE interlinked RC-6 qty-birth/priority lock, §31/§27): + * 1. w0-LOAD register: mine `lhu a0,0(a1)` vs target `lhu v1,0(a1)`. At the IDENTICAL register + * state (v0=po dead, v1=(short)uv dead, a1=pv live) gcc first-fits the w0 load to a0 (mine) + * vs v1 (target). Because the load lands in a0, its (short)/sum/av chain then lives in v1 — + * the target keeps the whole chain in v1 (workhorse). Merging w0/av (var reuse, Lever A) + * DOES pull w0->v1 but REGRESSES av in the 3rd unrolled vertex (v1->a0), net −13 (m8 144). + * 2. w3 register: mine a2/a3 vs target a0 (target's 3rd w3 then merges into the dead pv-base + * a1: `lhu a1,0xa(a1)`). Pure local-alloc qty-birth order — w3 should be born early enough + * to win a0 and share it with uvm; mine's w3 qty is born late -> a2/a3. + * 3. mat/i $s4/$s5 SWAP: mine mat=$s5,i=$s4; target mat=$s4,i=$s5. Both are call-crossing + * globals; both have 4 refs. gcc's allocno_compare (global.c:594) ranks i's priority + * (floor_log2(refs)*freq/live_length) strictly above mat's, so i takes $s4 first. Declaration + * order (col,uv,cnt,vb,mat,i,ret -> pseudos 72..78) WOULD give the target by the `v1-v2` + * tie-break IF priorities tied — they don't. NOT sensitive to: i-fence on/off (m6), + * break-vs-while outer loop (m10), pointer split (m5). Needs mat.pri >= i.pri, uncontrollable + * from C without a byte-visible mat ref (the block-copy is sp-relative; mat-relative mismatches). + * 4. dbr: target steals `addiu v0,s5,1` (i+1) into the outer beqz delay slot (mine: `li v0,-1`), + * because the -1 test-constant is materialised AFTER the lbu loads in the target, freeing the + * slot. Downstream of the counter/pressure equilibrium. + * 5. mine=208 vs target=209 (one load-delay nop the target leaves, mine's tighter schedule fills). + * + * LEVERS TRIED (match_one masked-match / 209): dB-seed pinned 155 · pin-free structural 140 · + * +counter fences (m4) 140 · +single-pv spine (m7, THIS) 153 · +w0/av merge (m8) 144 · + * no-uvm-fence (m7a) 146 · clean-while outer (m10) 153 · block-scoped pv (m5)=m4 · siblings + * func_80166994/CF04 are switch-shaped (no transferable vertex-loop idiom). + * => genuine gdb-on-cc1 ORACLE territory (§34): the w0-load/w3 qty-birth order + the mat/i + * allocno_compare priority are the two roots. Patch find_free_reg's qty pick (ffr2.gdb) + + * the allocno priority mid-compile to confirm each, then hunt the pin-free C form + * (flagship method). Confirms the prior agent's Fable5 verdict, now from a pin-free base. + * + * Best pin-free draft (m7, 153/209 masked in match_one) follows verbatim. + */ +#include "common.h" + +short func_8016CBC0(void) { + typedef struct { u16 v[6]; } VtxR; + typedef struct { u8 active, nv, cnt, col; VtxR verts[16]; } BlkR; + typedef struct { s32 w[8]; } Blk20; + extern BlkR D_801D9CA0[]; + extern Blk20 D_800AE620; + extern s32 func_80017758(void *a0, void *a1); + + u8 buf[0x90]; + u8 col; + int uv; + int cnt; + u8 *vb; + void *mat; + int i; + short ret; + + *(Blk20 *)(buf + 0x70) = D_800AE620; + ret = 0; + i = 0; + mat = buf + 0x70; + *(s32 *)(buf + 0x68) = 0x50000000; + *(s32 *)(buf + 0x30) = 0x50000000; + *(u8 *)(buf + 0x66) = 0; + *(u8 *)(buf + 0x65) = 0; + *(u8 *)(buf + 0x64) = 0; + *(u8 *)(buf + 0x5E) = 0; + *(u8 *)(buf + 0x5D) = 0; + *(u8 *)(buf + 0x5C) = 0; + *(u8 *)(buf + 0x2A) = 0; + *(u8 *)(buf + 0x29) = 0; + *(u8 *)(buf + 0x28) = 0; + *(u8 *)(buf + 0x22) = 0; + *(u8 *)(buf + 0x21) = 0; + *(u8 *)(buf + 0x20) = 0; + do { + BlkR *e = &D_801D9CA0[(short)i]; + if (e->active != 0) { + int tcnt; + vb = (u8 *)e + 4; + uv = e->nv; + cnt = e->cnt; + col = e->col; + ret = ret + 1; + __asm__("" :: "r"(mat)); + tcnt = cnt - 1; + __asm__("" : "=r"(tcnt) : "0"(tcnt)); + cnt = tcnt; + if (tcnt != -1) { + do { + s32 uvm; + u8 *pv; + { + s32 po = (short)uv * 0xc; + u8 c8c; + pv = vb + po; + { s32 w0 = *(u16 *)(pv + 0x0); s32 w3; + *(short *)(buf + 0x00) = w0; + w3 = *(u16 *)(pv + 0x6); + *(short *)(buf + 0x40) = w3; + w0 = (u32)((short)w0 + (short)w3) >> 1; + *(short *)(buf + 0x38) = w0; + *(short *)(buf + 0x08) = w0; } + { s32 w0 = *(u16 *)(pv + 0x2); s32 w3; + *(short *)(buf + 0x02) = w0; + w3 = *(u16 *)(pv + 0x8); + c8c = col >> 3; + uvm = uv - 1; + __asm__("" : "=r"(uvm) : "0"(uvm)); + uv = uvm; + *(short *)(buf + 0x42) = w3; + w0 = (u32)((short)w0 + (short)w3) >> 1; + *(short *)(buf + 0x3A) = w0; + *(short *)(buf + 0x0A) = w0; } + { s32 w0 = *(u16 *)(pv + 0x4); s32 w3; + *(short *)(buf + 0x04) = w0; + w3 = *(u16 *)(pv + 0xa); + *(u8 *)(buf + 0x59) = col; + *(u8 *)(buf + 0x25) = col; + *(u8 *)(buf + 0x5A) = c8c; + *(u8 *)(buf + 0x26) = c8c; + *(u8 *)(buf + 0x58) = c8c; + *(u8 *)(buf + 0x24) = c8c; + *(short *)(buf + 0x44) = w3; + w0 = (u32)((short)w0 + (short)w3) >> 1; + *(short *)(buf + 0x3C) = w0; + *(short *)(buf + 0x0C) = w0; } + } + if ((int)(uvm << 16) < 0) uv = 0xf; + { + s32 po = (short)uv * 0xc; + u8 c84; + pv = vb + po; + col = col - 0xf; + c84 = col >> 3; + { s32 w0 = *(u16 *)(pv + 0x0); s32 w3; + *(short *)(buf + 0x10) = w0; + w3 = *(u16 *)(pv + 0x6); + *(short *)(buf + 0x50) = w3; + w0 = (u32)((short)w0 + (short)w3) >> 1; + *(short *)(buf + 0x48) = w0; + *(short *)(buf + 0x18) = w0; } + { s32 w0 = *(u16 *)(pv + 0x2); s32 w3; + *(short *)(buf + 0x12) = w0; + w3 = *(u16 *)(pv + 0x8); + *(short *)(buf + 0x52) = w3; + w0 = (u32)((short)w0 + (short)w3) >> 1; + *(short *)(buf + 0x4A) = w0; + *(short *)(buf + 0x1A) = w0; } + { s32 w0 = *(u16 *)(pv + 0x4); s32 w3; + *(short *)(buf + 0x14) = w0; + w3 = *(u16 *)(pv + 0xa); + *(u8 *)(buf + 0x61) = col; + *(u8 *)(buf + 0x2D) = col; + *(u8 *)(buf + 0x62) = c84; + *(u8 *)(buf + 0x2E) = c84; + *(u8 *)(buf + 0x60) = c84; + *(u8 *)(buf + 0x2C) = c84; + *(short *)(buf + 0x54) = w3; + w0 = (u32)((short)w0 + (short)w3) >> 1; + *(short *)(buf + 0x4C) = w0; + *(short *)(buf + 0x1C) = w0; } + } + func_80017758(buf + 0x00, mat); + func_80017758(buf + 0x38, mat); + tcnt = cnt - 1; + __asm__("" : "=r"(tcnt) : "0"(tcnt)); + cnt = tcnt; + } while ((short)tcnt != -1); + } + } + { + int ti = i + 1; + __asm__("" : "=r"(ti) : "0"(ti)); + i = ti; + if ((short)ti >= 8) break; + } + } while (1); + return ret; +} diff --git a/.run/giants/pin_crash_sigabrt.md b/.run/giants/pin_crash_sigabrt.md new file mode 100644 index 000000000..0a6d351e4 --- /dev/null +++ b/.run/giants/pin_crash_sigabrt.md @@ -0,0 +1,198 @@ +# The cc1-2.7.2 "pin-crash" SIGABRT — characterized (Fable5 discovery, 2026-07-15) + +**Verdict up front: the §42e "CC1-CRASH WALL" is a misdiagnosis. The SIGABRT is real and now +fully characterized (a gcc-2.7.2 sched1 REG_DEAD-note conservation bug), but it was triggered +by the PROPAGATION STAGING dropping file-scope `#define` dependencies — not by "ov077-TU-context +/ fixed-size allocator tables". All four backlogged pin families compile CLEAN in sibling TUs +when properly staged: func_80133AB0 133/133 (era AND today), func_8013D9B0 133/133 (today, +fleet-swept), func_8016DF5C + func_80156044 (era TU, spot-proven). The pin-×1 ceiling is a +staging-tool artifact. P31's pin-propagation route is OPEN.** + +All artifacts referenced below live in `.run/giants/pincrash/`. + +--- + +## 1. The abort site (exact) + +**`gcc-2.7.2/sched.c:2725-2728`, in `create_reg_dead_note()`:** + +```c + /* The number of registers killed after scheduling must be the same as the + number of registers killed before scheduling. ... + We carefully remove REG_DEAD notes from the dead_notes list, so that + there will be none left at the end. If we run out early, then there + is a bug somewhere in flow, combine and/or sched. */ + if (dead_notes == 0) + { +#if 1 + abort (); /* <-- the SIGABRT / make Error 134 */ +``` + +`dead_notes` is a per-basic-block free list: `schedule_block` strips every REG_DEAD note off the +block's insns onto it (sched.c:3581-3582, reset at 3255), reschedules, then re-attaches death +notes from the recount (`attach_deaths_insn → attach_deaths → create_reg_dead_note`). The pool +runs DRY when the post-schedule recount needs more deaths in the block than flow ever noted +there. (The mirror-image surplus abort is sched.c:3967.) + +**gdb backtrace (shipped i386 cc1, `tools/bin/gcc-2.7.2-psx/cc1`):** + +``` +#0 0x08049160 in abort () +#1 0x08179ff1 in create_reg_dead_note () +#2 0x0817a79b in attach_deaths () +#3..#4 in attach_deaths () (recursive walk of the pattern) +#5 0x0817ac12 in attach_deaths_insn () +#6 0x0817cd60 in schedule_block () +#7 0x0817f2f7 in schedule_insns () +#8 0x08073335 in rest_of_compilation () +``` + +**Pass: sched1** (`reload_completed == 0` at the abort — the before-reload scheduler, +`-fschedule-insns`, on at -O2). Repro scripts: `abort.gdb`, `detail.gdb`, `min.gdb`. + +At the abort on the real body (era staging of func_8013D9B0), the reg being killed was +`(reg/v:SI 11 t3)` — the `register u16 *puVar10 __asm__("$11")` pin — at a plain +`(zero_extend (mem:HI (reg 11)))` load. On the 12-line minimal repro it is `(reg/v:SI 4 a0)` +at the outer `if (a != b)` `jump_insn`. + +## 2. The minimal repro (12 lines, `minimal_repro.c`, ddmin-reduced from the real crasher) + +```c +typedef unsigned int u32; +typedef int s32; +extern void g(); +void f(int param_1) +{ + register u32 a __asm__("$3"); /* caller-saved pin */ + register u32 b __asm__("$4"); /* caller-saved pin */ + g(); /* a CALL the pins' values must cross */ + if (a != b) { /* use after the call ... */ + if ((s32)a < (s32)b) a = a + 1; /* ... + branch-dependent partial update */ + } +} +``` + +`cc1 -quiet -O2 -G0 -mips1 -mcpu=3000 -mgas -msoft-float -fgnu-linker minimal_repro.c` → SIGABRT +(exit 134; Python subprocess sees returncode -6). + +**Why (visible in `minimal_repro.c.combine`, the RTL fed to sched1):** flow places the single +REG_DEAD note for each pinned reg at the last use on the FALL-THROUGH path (the inner `slt` +insn, a later basic block). The uses at the outer branch (same block as the call) carry NO +notes. sched1's per-block recount, walking the block that contains the CALL, sees the +call-clobber kill the caller-saved regs and therefore demands a REG_DEAD note for their +use-after-call inside THAT block — whose harvested pool is empty → abort. Flow's death +placement and sched's clobber-aware recount disagree exactly and only for this shape. +(sched.c's own comment names the class: "a bug somewhere in flow, combine and/or sched.") + +## 3. The predicate — which pins are fatal vs safe (probe matrix, all byte-run) + +| probe | shape | exit | +|---|---|---| +| minimal_repro | $3+$4 pins, call, nested-if partial update after | **134** | +| p1 | same, NO call | 0 | +| p2 | ONE pin ($3), call, nested-if | **134** | +| p3 | pins initialized before the call (live-across, not uninit) | **134** | +| p4 | same shape, callee-saved pins ($17/$18) | 0 | +| p5 | one-level `if (a!=b) a=a+1;` after call | 0 | +| p6 | non-arg caller-saved pins ($8/$9) | **134** | +| p7 | single `if ((s32)a<(s32)b) a=a+1;` after call | 0 | +| p8 | use-only after call (`if (a!=b) h();`) | 0 | +| minimal_repro `-fno-schedule-insns` (or -O1) | sched1 off | 0 | + +**FATAL:** `register T x __asm__("$N")` where $N is CALL-CLOBBERED ($2–$15, $24, $25), the +value is used AFTER a CALL_INSN, and the post-call uses have a branch-dependent +use-then-conditionally-set (nested-condition) shape. +**SAFE:** callee-saved pins ($16–$23, $30) in any shape; caller-saved pins whose live range +never crosses a call; use-only or single-level-conditional shapes. +$0 (`zr`) pins: never observed to abort (fixed register; 133/133 clean, §5). + +## 4. Why "compiles in ov077, dies in the sibling" — the REAL mechanism + +`family_remap.extract_unit` carries a function's contiguous preceding `extern`/comment/ +single-line-`typedef` lines — **it does NOT carry `#define` lines** (and, in the Phase-25-era +version, no typedef lines either; and its "definition" regex false-matches a one-line +`extern ...; int wrapper(){...}` construct). ov_SC01_077's TU supplies each body's file-scope +dependencies; the staged sibling body arrives WITHOUT them: + +- **func_8013D9B0 (GTE 20-pin, THE one genuine crasher):** ov077 defines + `#define gte_ldIR0z()/gte_ldrgb/gte_ldIRGB/gte_dpcl/gte_stORGB` as inline `__asm__ __volatile__` + right above the body (ov_SC01_077.c:435-439, present in the era file too). The macro-less + staged sibling body turns those five GTE ops into **implicit-declaration CALLS** — putting the + caller-saved pins ($2,$3,$4,$6,$7,$8,$9,$10,$11,$13,$14) live across calls in the fatal shape + → the sched1 abort → `make` Error 134. Era-exact replication: `era_d9b0.c` → **exit 134** + (the historical crash, reproduced). With the macros carried: **exit 0 in the era TU and in + ALL 133 sibling TUs today** (`d9b0_fleet.py`: clean 133/133; 4 SC07 overlays have no image = + not siblings). In the REAL body no pin crosses a real call (the GTE ops are asm; the one true + call, func_800599B8, is crossed only by callee-saved pins) — which is why ov077 banked it. +- **func_80133AB0 (zero-reg pin, "crashed 3/3"): NEVER crashed.** Era staging dropped its + multi-line `typedef struct Map_80133AB0` (era extract_unit couldn't carry it) → + `Map_80133AB0 undeclared` → **exit 33** (plain error), both era layouts tested (`histA/histB`). + Properly staged (typedef + the sibling's `extern int func_80133AB0(int,s16,s16,int);` → + s16-form decl flip): **exit 0 in 133/133 sibling TUs today** (`sweep.sh`) **and 133/133 at the + era commit commit:0510** (`era_sweep.sh`). The Error-134 attribution: in the era one-big-split + layout its stub shared `ov_SC01_XXX.c` — and therefore the (overlay,split) gate compile — + with func_8013D9B0's stub; d9b0's genuine abort killed the shared TU compile and the 134 was + blamed on every family staged there. +- **func_8016DF5C (GTE-pin):** era staging dropped its single-line + `typedef struct { s32 w[8]; } Blk20_8016DF5C;` → **exit 33** (`era_df5c.c`). With the typedef + (today's extract_unit carries it): **exit 0** against the era sibling TU (`era_df5c_td.c`). + Its pins are $17 (callee-saved) + block-scoped $3 not crossing calls → safe class. +- **func_80156044 (trampoline):** extract_unit false-positive — the def-search regex matches the + ONE-LINE `extern void func_80156044(int,int); int func_80155FF8(...) { __asm__(...); }` + wrapper (the trailing `/* dedup */` comment defeats the era `;`-endswith guard), so the staged + "body" was func_80155FF8's WRAPPER → `redefinition of func_80155FF8` → **exit 33** + (`era_x6044.c`). The REAL body + the engine_core int→void flip: **exit 0** against the era + sibling TU (`era_x6044_flip.i`). Its $2/$3 pins are used before the first call → safe class. + +So: "the pins are ov077-TU-context-specific" is REFUTED. The pins were never the problem in 3 of +4 families, and in the 4th the problem was the staging deleting the asm macros around them. No +fixed-size-table, no register-pressure, no surrounding-function-set effect exists — decls-only +context reproduces every result (ddmin `mask_min.txt`: the masking "TU context" reduced to the +5 basic typedefs + 3 externs, i.e. to nothing). + +## 5. Repro commands + +```sh +# the genuine SIGABRT (minimal): +tools/bin/gcc-2.7.2-psx/cc1 -quiet -O2 -G0 -mips1 -mcpu=3000 -mgas -msoft-float -fgnu-linker \ + .run/giants/pincrash/minimal_repro.c -o /dev/null # exit 134 +# the historical crash, era-exact (staged macro-less GTE body in the commit:0510 sibling TU): +tools/bin/gcc-2.7.2-psx/cc1 .run/giants/pincrash/era_d9b0.i -o /dev/null # exit 134 +# the same body properly staged (macros carried) — clean: +tools/bin/gcc-2.7.2-psx/cc1 .run/giants/pincrash/d9b0_000.tu3.i -o /dev/null # exit 0 +# gdb: gdb -batch -x .run/giants/pincrash/min.gdb tools/bin/gcc-2.7.2-psx/cc1 +``` + +Fleet evidence: `sweep_results.txt` (80133AB0 today, 133×exit=0), `era_sweep_results.txt` +(80133AB0 at commit:0510, 133×exit=0), `d9b0_fleet.py` output (8013D9B0 today, 133×exit=0). + +## 6. Implications for Phase 31 (the pin-×1 endgame) + +1. **The ×1 ceiling is not a compiler wall — it is a fixable staging gap.** To propagate the four + backlogged families, the per-sibling unit must carry: (a) file-scope `#define` blocks the body + uses (extract_unit currently drops ALL `#define` lines — add `"#define"` to its prefix-walk, + or stage macros like the single-line-typedef carry), (b) multi-line typedefs (route through + the engine_types.h lift or flatten to single-line), (c) the per-sibling decl flips + (`//@EDIT`-class: the s16-decl flip for 80133AB0, `(void)`→`()` + the hoisted + `extern s16 *D_...;` for 8013D9B0, the engine_core int→void flip for 80156044), and (d) a fix + for the extract_unit one-line-wrapper false-positive (require the def-line's brace-block, or + reject lines starting `extern`). With (a)-(d), cc1 compiles all four in every sibling TU. + Byte-identity per sibling remains for the whole-binary gate to decide — but the "cc1-crash" + barrier does not exist. +2. **The fatal-pin predicate is checkable at draft time** (§3): flag any caller-saved + `register __asm__` pin whose variable is referenced after a function call. Such drafts are the + only ones that can ever hit this abort — and even they are fine if the "call" was really a + macro/asm that staging must preserve. +3. **Diagnostic signature:** cc1 exit 134 + this backtrace (abort ← create_reg_dead_note ← + attach_deaths) = this bug, ALWAYS. An exit-33 is ordinary decl/typedef plumbing — the R14 + lesson recursed: the era experiment's "SIGABRT, universal, 3/3" folded three distinct exit-33 + plumbing failures into one crash bucket because the shared-TU gate reported one code. +4. `-fno-schedule-insns` (or -O1) suppresses the abort — useless for matching (changes schedule), + but confirms the sched1 locus and is a safe probe for "is this the dead-notes bug?". + +## 7. What was NOT determined + +- The exact flow.c line placing the fall-through REG_DEAD (the mechanism is proven from the + combine dump + the abort state; the flow-side walk was not source-traced line-by-line). +- Whether the four families byte-MATCH their siblings once staged correctly (compile-clean is + proven; matching is the byte-gate's question, out of scope for this characterization). diff --git a/.run/giants/pincrash/abort.gdb b/.run/giants/pincrash/abort.gdb new file mode 100644 index 000000000..e5c0d03b0 --- /dev/null +++ b/.run/giants/pincrash/abort.gdb @@ -0,0 +1,12 @@ +set pagination off +set confirm off +set height 0 +set width 0 +set debuginfod enabled off +break abort +break fancy_abort +break fatal_insn +run -quiet -O2 -G0 -mips1 -mcpu=3000 -mgas -msoft-float -fgnu-linker /home/musashi/bfm-decomp/.run/giants/pincrash/era_d9b0.i -o /home/musashi/bfm-decomp/.run/giants/pincrash/era_d9b0.s +echo \n=== BACKTRACE ===\n +bt 25 +quit diff --git a/.run/giants/pincrash/detail.gdb b/.run/giants/pincrash/detail.gdb new file mode 100644 index 000000000..b225daa51 --- /dev/null +++ b/.run/giants/pincrash/detail.gdb @@ -0,0 +1,25 @@ +set pagination off +set confirm off +set height 0 +set width 0 +set debuginfod enabled off +break abort +run -quiet -O2 -G0 -mips1 -mcpu=3000 -mgas -msoft-float -fgnu-linker /home/musashi/bfm-decomp/.run/giants/pincrash/mini_call.c -o /dev/null +echo \n=== reload_completed (0=sched1, 1=sched2):\n +print (int)reload_completed +echo \n=== BACKTRACE ===\n +bt 8 +echo \n=== create_reg_dead_note args (frame 1): reg rtx ===\n +frame 1 +info frame +echo \n=== the reg being killed (debug_rtx of arg0) ===\n +# cdecl: args at ebp+8 (reg), ebp+12 (insn) +set $cr_ebp = $ebp +frame 0 +# walk to frame 1's saved ebp: at abort entry, esp points at return addr; caller ebp unknown -> use frame cmd +frame 1 +x/2wx $ebp+8 +call (void)debug_rtx(*(void**)($ebp+8)) +echo \n=== the insn being processed ===\n +call (void)debug_rtx(*(void**)($ebp+12)) +quit diff --git a/.run/giants/pincrash/min.gdb b/.run/giants/pincrash/min.gdb new file mode 100644 index 000000000..c659c11c6 --- /dev/null +++ b/.run/giants/pincrash/min.gdb @@ -0,0 +1,12 @@ +set pagination off +set confirm off +set debuginfod enabled off +break abort +run -quiet -O2 -G0 -mips1 -mcpu=3000 -mgas -msoft-float -fgnu-linker /home/musashi/bfm-decomp/.run/giants/pincrash/minimal_repro.c -o /dev/null +bt 8 +print (int)reload_completed +frame 1 +x/2wx $ebp+8 +call (void)debug_rtx(*(void**)($ebp+8)) +call (void)debug_rtx(*(void**)($ebp+12)) +quit diff --git a/.run/giants/pincrash/minimal_repro.c b/.run/giants/pincrash/minimal_repro.c new file mode 100644 index 000000000..5fdda428a --- /dev/null +++ b/.run/giants/pincrash/minimal_repro.c @@ -0,0 +1,12 @@ +typedef unsigned int u32; +typedef int s32; +extern void g(); +void f(int param_1) +{ + register u32 a __asm__("$3"); + register u32 b __asm__("$4"); + g(); + if (a != b) { + if ((s32)a < (s32)b) a = a + 1; + } +}