mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-10-05 08:57:59 -04:00
docs(phase-27 T1 close): func_80176734 crack + distill — the CSE address-fold antidote
The last Fable5 pass of the sprint (Drew capped further waves at 86% context). func_80176734 (371 ins, fresh un-drafted core): NO bank (mine=370 vs 371, 5 permuter-shaped clusters — entry-schedule tie, caller-saved shuffles, a combine-merge missing insn, qty ties), pin-free, honestly handed off (P9; match_one confirms the DIFF). Draft -> decomp-permuter warm-start (P29). Idiom harvest (cookbook cse_expr §H): - THE CSE ADDRESS-FOLD ANTIDOTE (zero asm): find_best_addr's cost-ungated qty-const fold + from_plus re-association eat reg-based global accesses on every cse walk; a balanced if/else DIAMOND makes the merge label barrier-preceded -> fresh cse table -> both folds die with no #APP. Replaced two asm dials. - update_equiv_regs doubles live_length for single-set REG_EQUIV pseudos (local-alloc.c:1064) — a 2nd set forfeits the doubling, ~4x the allocno priority; explains a "my dial broke the $s-order" class. - record_jump_equiv fall-through delete (cse.c:7511) — a recognition tell for genuine dead source logic. T1 sprint COMPLETE: 4 cracks + the SIGABRT characterization, 0 direct banks, but 3 wall reclassifications + 2 cracked roots + the pin-crash wall dissolved + ~9 new pin-free levers. Fable5 DISCOVERS, cheap-Opus APPLIES — the ROI is idioms, not banks (docs/calibration.md).
This commit is contained in:
@@ -0,0 +1,307 @@
|
||||
/* func_80176734 (371 ins, ov_SC01_077_jr_801734BC, reach-134 core) — Fable5 pin-free draft
|
||||
*
|
||||
* Closeness: mine=370 vs target=371 ins; match_one positional 111 (cascade-inflated by the
|
||||
* 1 missing insn); REAL aligned diff ~81 lines, of which the true instruction deltas are
|
||||
* ~30 positions in 5 LOCAL clusters (see func_80176734.fable.md):
|
||||
* entry-schedule order, region-1/2/3 caller-saved shuffles (e/q/base a0-a1-v0-v1),
|
||||
* region-8 [sltu][copy] combine-merge (the 1 missing insn) + h/const v0-v1 swap.
|
||||
* ALL callee-saved assignments byte-exact (flag=s0 st1=s1 st2=s2 g=s3 ext=s4 changed=s5 arg0=s6),
|
||||
* frame size exact (0x40), whole LBF0/adjust/tail regions byte-exact.
|
||||
*
|
||||
* PIN-FREE / x134-safe: ONE generic-constraint identity asm on tB (cse.c:7511 fall-through
|
||||
* jump-equiv would otherwise delete the target's provably-dead `beqz v1` branch); everything
|
||||
* else is pure C. No register __asm__("$N") pins anywhere.
|
||||
*
|
||||
* What it does: per-track BGM/SFX state tick. g=&D_8011F7A8 (sound globals), st1/st2 = two
|
||||
* SndSt state blocks inside it (+0x48/+0xE0), ext=&D_80078E78 (the engine-side SndSt).
|
||||
* Region 1: master volume fade write to trk[arg0] (+4/+0x40). Region 2/3: unk48 state machine
|
||||
* (fade-step table D_8018A2D8, program change via func_800183E0). Region 4: unk2E pan/tempo
|
||||
* mirror + unk49 table. Region 5: unk1E 0x8000 flag toggle (two one-shot commands). Region 6:
|
||||
* D_800B9A13 mode-change detect (changed). Switch: ext->unk48 in {3,4,5,6} -> fade checks
|
||||
* (func_801619D0/A00/A30/A60) -> flag = 0xFF/0xBA. Then volume ramp toward w (D_80126CE0
|
||||
* override or ext->unk47) with -3/-8 decay steps, and the func_801775E0(trk+0x64, ...) tail.
|
||||
*/
|
||||
#include "common.h"
|
||||
|
||||
typedef struct Trk {
|
||||
u8 pad00[4];
|
||||
u8 unk4; /* 0x04 */
|
||||
u8 pad05[8];
|
||||
u8 unkD; /* 0x0D */
|
||||
u8 pad0E[0x12];
|
||||
u16 unk20; /* 0x20 */
|
||||
u8 pad22[0x10];
|
||||
s16 unk32; /* 0x32 */
|
||||
u8 pad34[0xC];
|
||||
u8 unk40; /* 0x40 */
|
||||
u8 pad41[8];
|
||||
u8 unk49; /* 0x49 */
|
||||
u8 pad4A[0x1A];
|
||||
u8 unk64; /* 0x64 */
|
||||
} Trk;
|
||||
|
||||
typedef struct SndSt {
|
||||
u8 pad00[0x1E];
|
||||
s16 unk1E; /* 0x1E */
|
||||
u8 pad20[0xE];
|
||||
s16 unk2E; /* 0x2E */
|
||||
u8 pad30[0x17];
|
||||
u8 unk47; /* 0x47 */
|
||||
u8 unk48; /* 0x48 */
|
||||
u8 pad49[2];
|
||||
u8 unk4B; /* 0x4B */
|
||||
u8 pad4C[0x4C]; /* size 0x98 */
|
||||
} SndSt;
|
||||
|
||||
typedef struct SndGlob {
|
||||
u8 pad00[7];
|
||||
u8 unk7; /* 0x07 */
|
||||
u8 unk8; /* 0x08 */
|
||||
u8 pad09[9];
|
||||
u16 unk12; /* 0x12 */
|
||||
u8 pad14[0x14];
|
||||
Trk *trk[8]; /* 0x28 */
|
||||
SndSt st1; /* 0x48 */
|
||||
SndSt st2; /* 0xE0 */
|
||||
} SndGlob;
|
||||
|
||||
extern SndGlob D_8011F7A8;
|
||||
extern SndSt D_80078E78;
|
||||
|
||||
extern s16 D_801152BA;
|
||||
extern u8 D_8011F7B0;
|
||||
extern u8 D_80115214;
|
||||
extern u8 D_800B9A13;
|
||||
extern u16 D_8018A238;
|
||||
extern u8 D_8018A2D8[];
|
||||
extern u16 D_8018A22A[];
|
||||
extern void *D_8018A2E4[];
|
||||
extern u8 D_8018A2CC[];
|
||||
extern s16 D_80126D20;
|
||||
extern s16 D_80126CE0;
|
||||
extern s32 D_80126B58;
|
||||
extern u8 D_800D45D4[];
|
||||
extern u8 D_800D43D4[];
|
||||
extern u8 D_800D4414[];
|
||||
|
||||
extern void func_800183E0(void *);
|
||||
extern s32 func_801619D0(void *);
|
||||
extern s32 func_80161A00(void *);
|
||||
extern s32 func_80161A30(void *);
|
||||
extern s32 func_80161A60(void *);
|
||||
extern void func_801775E0(u8 *, s16);
|
||||
|
||||
void func_80176734(arg0)
|
||||
s16 arg0;
|
||||
{
|
||||
SndGlob *g = &D_8011F7A8;
|
||||
SndSt *st1 = &g->st1;
|
||||
SndSt *st2 = &g->st2;
|
||||
SndSt *ext = &D_80078E78;
|
||||
s32 flag; /* s0 */
|
||||
s32 changed; /* s5 */
|
||||
s32 tA; /* v0 scratch, reused */
|
||||
s32 tB; /* v1 scratch, reused */
|
||||
s32 w; /* a1 */
|
||||
u8 h;
|
||||
s16 self;
|
||||
s8 pad[4];
|
||||
|
||||
{
|
||||
Trk *e = g->trk[arg0];
|
||||
Trk *q = (Trk *)((u8 *)e + 0x3C);
|
||||
self = arg0;
|
||||
if (D_801152BA != 0) {
|
||||
u8 b = D_8011F7B0;
|
||||
u8 v;
|
||||
if (b < 0x80U) {
|
||||
v = b - 0x80;
|
||||
} else {
|
||||
v = ~b - 0x80;
|
||||
}
|
||||
q->unk4 = v;
|
||||
e->unk4 = v;
|
||||
g->unk8 = g->unk8 + D_80115214;
|
||||
} else {
|
||||
e->unk40 = 0x80;
|
||||
e->unk4 = 0x80;
|
||||
}
|
||||
}
|
||||
|
||||
if (st2->unk48 != 0) {
|
||||
g->trk[self]->unkD = D_8018A2D8[st2->unk48];
|
||||
if (st2->unk48 >= 4) {
|
||||
u8 c = st1->unk48;
|
||||
Trk *e = g->trk[self];
|
||||
if (c & 0x80) {
|
||||
e->unk20 = D_8018A238;
|
||||
func_800183E0(D_800D45D4);
|
||||
} else if (c != 0) {
|
||||
e->unk20 = D_8018A22A[c];
|
||||
func_800183E0(D_8018A2E4[st1->unk48]);
|
||||
}
|
||||
{
|
||||
u8 d = st2->unk48;
|
||||
if (d == 5) {
|
||||
if (st1->unk48 == 0) {
|
||||
st2->unk48 = 0;
|
||||
} else {
|
||||
st2->unk48 = d + 1;
|
||||
}
|
||||
} else if (d == 0xA) {
|
||||
st2->unk48 = 0;
|
||||
} else {
|
||||
st2->unk48 = d + 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
st2->unk48++;
|
||||
}
|
||||
} else {
|
||||
if (st1->unk48 != ext->unk48) {
|
||||
if (st1->unk48 == 0 && ext->unk48 != 0) {
|
||||
st2->unk48 = 5;
|
||||
} else {
|
||||
st2->unk48 = 0;
|
||||
}
|
||||
st1->unk48 = ext->unk48;
|
||||
{
|
||||
Trk *e = g->trk[self];
|
||||
u8 t = st2->unk48;
|
||||
st2->unk48 = t + 1;
|
||||
e->unkD = D_8018A2D8[t];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (st1->unk2E == ext->unk2E) {
|
||||
if (st2->unk2E != 0) {
|
||||
st2->unk2E = 0;
|
||||
goto upd49;
|
||||
}
|
||||
} else {
|
||||
st1->unk2E = ext->unk2E;
|
||||
st2->unk2E = 1;
|
||||
upd49:
|
||||
tB = (u16)st1->unk2E;
|
||||
tB = tB << 16;
|
||||
{
|
||||
Trk *e = g->trk[self];
|
||||
if (tB != 0) {
|
||||
e->unk49 = D_8018A2CC[tB >> 20];
|
||||
} else {
|
||||
e->unk49 = 0xA0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
s32 f1 = st1->unk1E & 0x8000;
|
||||
if (f1 != (ext->unk1E & 0x8000)) {
|
||||
if (f1 != 0) {
|
||||
st1->unk1E = 0;
|
||||
func_800183E0(D_800D43D4);
|
||||
} else {
|
||||
st1->unk1E = -0x8000;
|
||||
func_800183E0(D_800D4414);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
u8 m = D_800B9A13;
|
||||
if (m != 3) {
|
||||
tA = (g->unk7 != m);
|
||||
changed = tA;
|
||||
if (tA != 0) {
|
||||
g->unk7 = m;
|
||||
}
|
||||
} else {
|
||||
changed = 0;
|
||||
}
|
||||
}
|
||||
|
||||
flag = 0;
|
||||
switch (ext->unk48) {
|
||||
case 3:
|
||||
if (func_801619D0(&D_80126B58) != 0) flag = 0xFF;
|
||||
break;
|
||||
case 4:
|
||||
if (func_80161A00(&D_80126B58) != 0) flag = 0xFF;
|
||||
break;
|
||||
case 5:
|
||||
if (func_80161A30(&D_80126B58) != 0) flag = 0xFF;
|
||||
break;
|
||||
case 6:
|
||||
if (func_80161A60(&D_80126B58) != 0) flag = 0xBA;
|
||||
break;
|
||||
}
|
||||
|
||||
tA = flag;
|
||||
if (tA != 0) {
|
||||
st2->unk47 = 1;
|
||||
st1->unk4B = ext->unk48 | 0xF0;
|
||||
st1->unk47 = (D_80126D20 << 7) / tA;
|
||||
} else {
|
||||
if (st1->unk4B >= 0xF0) {
|
||||
st1->unk4B = 0;
|
||||
}
|
||||
w = *(u16 *)&D_80126CE0;
|
||||
if (D_80126CE0 != 0) {
|
||||
u8 b = w;
|
||||
st1->unk4B = b;
|
||||
tA = (st1->unk47 != b);
|
||||
flag = tA;
|
||||
} else {
|
||||
w = ext->unk47;
|
||||
flag = 0;
|
||||
if (st1->unk47 != w || st1->unk47 == 0x80) {
|
||||
flag = 1;
|
||||
}
|
||||
if (ext->unk47 != 0 && st1->unk4B != 0) {
|
||||
st1->unk4B = 0;
|
||||
st1->unk47 = ext->unk47;
|
||||
}
|
||||
}
|
||||
|
||||
tB = changed;
|
||||
if (flag != 0) goto adjust;
|
||||
if (tB != 0) goto adjust;
|
||||
if (st2->unk47 == 0) goto posttail;
|
||||
__asm__("" : "=r"(tB) : "0"(tB));
|
||||
if (tB == 0) goto clear;
|
||||
adjust:
|
||||
h = st1->unk47;
|
||||
if ((s32)h < (s16)w) {
|
||||
st1->unk47 = w;
|
||||
} else {
|
||||
if ((s16)w != 0) {
|
||||
tA = h - 3;
|
||||
st1->unk47 = tA;
|
||||
} else {
|
||||
tA = h - 8;
|
||||
st1->unk47 = tA;
|
||||
}
|
||||
if (st1->unk47 == 0 || st1->unk47 >= 0x81) {
|
||||
st1->unk47 = 0;
|
||||
st1->unk4B = 0;
|
||||
} else if ((s32)st1->unk47 < (s16)w) {
|
||||
st1->unk47 = w;
|
||||
}
|
||||
}
|
||||
st2->unk47 = 1;
|
||||
goto posttail;
|
||||
clear:
|
||||
st2->unk47 = 0;
|
||||
posttail:;
|
||||
}
|
||||
|
||||
{
|
||||
s32 fl = (g->unk7 != 0) << 8;
|
||||
s32 k = fl + 5;
|
||||
__asm__("" :: "r"(fl));
|
||||
g->trk[self]->unk32 = g->unk12 + k;
|
||||
fl += 9;
|
||||
func_801775E0(&g->trk[self]->unk64, g->unk12 + fl);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
# func_80176734 (371 ins, ×134 core, ov_SC01_077_jr_801734BC) — Fable5 pass (FRESH, un-drafted)
|
||||
|
||||
**Result:** from NOTHING (no seed) to **mine=370 vs target=371, match_one positional 111
|
||||
(cascade-inflated by the single missing insn), real aligned diff ~81 lines ≈ ~30 true instruction
|
||||
positions in 5 localized clusters.** PIN-FREE, ×134-safe (one generic-constraint identity asm; the
|
||||
rest pure C). **NOT a match → NOT a bank candidate**; it is a very strong permuter/next-tier seed:
|
||||
all 7 callee-saved assignments byte-exact, frame exact, all region structure exact, the LBF0/adjust/
|
||||
tail thirds byte-exact. Draft: `.run/giants/func_80176734.fable.c`. Ladder artifacts:
|
||||
`.run/giants/fable_76734/` (v1..v22 + dumps + micro/ + chk.sh + v19.adiff).
|
||||
|
||||
This pass was almost entirely **compiler-source archaeology** (cse.c / local-alloc.c / flow.c /
|
||||
combine.c read at the exact decision), and it produced FOUR new byte-proven mechanisms that
|
||||
generalize (see "cookbook-worthy findings") — including the missing half of the §46-L2 story and a
|
||||
brand-new allocno-priority mechanism (`update_equiv_regs` live-length doubling) that explains a
|
||||
whole class of "why does the pointer get the wrong $s-reg when I dial it" failures.
|
||||
|
||||
## The byte-verified ladder (match_one positional / real aligned)
|
||||
|
||||
| v | change | ins | pos | real |
|
||||
|---|--------|-----|-----|------|
|
||||
| v1 | first hand draft from asm-semantics + m2c scaffold | 363 | 336 | 188 |
|
||||
| v2 | q hoist; RC-7 g-dial; region-3/4/6 temp architecture; per-arm adjust stores; switch | 367 | 335 | 174 |
|
||||
| v3 | + identity-asm on e (kills the q `from_plus` fold) | 368 | 233 | 174 |
|
||||
| v5 | drop ALL dials (rotation experiment); st1-laundered unk8 | 371 | 319 | 140 |
|
||||
| **v8** | **balanced if/else for v — kills BOTH region-1 folds with ZERO asm; g single-set** | 369 | 193 | 126 |
|
||||
| v10 | in-place `fl += 9`; two-statement `tB = load; tB <<= 16` | 368 | 122 | 120 |
|
||||
| v12c | + unused `s8 pad[4]` local → frame 0x38→0x40 (exact) | 368 | 118 | 112 |
|
||||
| v17 | goto-shaped LBF0 CFG (store out-of-line after adjust) + tB identity-asm | 370 | 118 | 99 |
|
||||
| v18a | + input-only anchor on `fl` (tail local-alloc order flip) → whole tail exact | 370 | 118 | 85 |
|
||||
| **v19** | **+ `self = arg0` K&R body-copy placed after e/q (d820 lever) → entry chain into v0** | **370** | **111** | **81** |
|
||||
|
||||
Regressions kept for the record: v4 (u8 flags → andi extends: nonzero_bits punts on multi-set),
|
||||
v6/v7 (any 2-set dial on g → priority explosion, see finding 2), v20/v21 (statement reorder of the
|
||||
head inits breaks the callee-saved balance), v22 (anchor blocks the region-8 merge but the #APP
|
||||
blocks the delay-slot steal: net +1 nop).
|
||||
|
||||
## Residual (~30 positions, 5 clusters) — per-cluster verdict
|
||||
|
||||
1. **Entry-block schedule (~10 lines).** Target: `[sw s3;lui;addiu s3][sll/sra/addu → v0][sw s6;
|
||||
addu s6,a0][st1;st2;ext inits][lw a1]`. Mine: st1/st2 inits before the extension chain. Both
|
||||
orders are sched1 boost/LUID ties among single-set birthing insns; moving the statement order
|
||||
(v20/v21) flips it but wrecks the callee-saved priority balance (birth positions feed
|
||||
live_lengths feed `allocno_compare`). **Coupled knife-edge → permuter**, or needs the original's
|
||||
exact decl layout.
|
||||
2. **Region-1/2/3 caller-saved shuffles (e/q/base in a0↔a1, v1↔a1; ~12 lines).** Pure
|
||||
`local-alloc.c qty_compare` priority margins (§50-A) — e.g. region-2's base vs c-temp priorities
|
||||
differ by <5%. Each is steerable in isolation (scope/order nudges) but they share the same
|
||||
entry-block insn stream; every nudge tried moved a sibling. **Permuter-shaped.**
|
||||
3. **Region-8 `[sltu tA][addu s0,tA]` combine-merge (THE missing insn, 3 lines).** Target keeps the
|
||||
pair; combine merges mine (tA dead at the copy → 2-insn merge into `sltu s0`). An input-only
|
||||
anchor blocks it (v22 ✓) but the `#APP` then blocks reorg's delay-slot steal of the copy (+1
|
||||
nop, net 0). Needs tA live-after by a REAL later read that doesn't perturb — not found in 6
|
||||
variants. **INTRINSIC to this C-lever tier** (combine.c try_combine, dead-i2dest 2-insn merge);
|
||||
permuter may stumble on a protecting form.
|
||||
4. **Region-8 h/const v0↔v1 swap (4 lines).** `qty_compare` tie between the h-load qty (3 refs) and
|
||||
the li-0x80 qty (2 refs/2 insns = 1.0 density): mine's h-range is 1-2 insns shorter than
|
||||
target's, flipping the tie. Same class as 2.
|
||||
5. **`beqz v0/beqz v1` polarity + j-position ripples** from 3 (cascade, not independent).
|
||||
|
||||
## Cookbook-worthy findings (the headline output)
|
||||
|
||||
### 1. THE CSE ADDRESS-FOLD PAIR and its pure-C structural antidote (NEW — the biggest)
|
||||
gcc-2.7.2 cse silently rewrites addresses two ways, **cost-ungated** (both inside
|
||||
`find_best_addr`'s initial `validate_change(insn, loc, fold_rtx(addr, insn), 0)`, cse.c:2664):
|
||||
- **qty-const fold**: `(plus g 8)` where g's qty holds a CONSTANT (the `&D_xxx` init) →
|
||||
`(const (plus SYM 8))` → the 1-insn `lbu 8(s3)` becomes a 2-insn `lui/lbu %lo` symbolic access
|
||||
(via `equiv_constant` on the operand).
|
||||
- **`from_plus` re-association** (cse.c fold_rtx `from_plus`): `(plus q 4)` where q's class
|
||||
contains `(plus e 60)` → `(plus e 64)` — deletes the `addiu q,e,0x3C` pointer materialization.
|
||||
|
||||
Both folds happen on EVERY cse walk that carries the defining table entries to the use. The walk
|
||||
structure (cse_end_of_basic_block): ebbs END at stream labels, but **follow_jumps extends through a
|
||||
branch whose target label is barrier-preceded + single-use, and skip_blocks jumps AROUND a
|
||||
label-free block** (invalidating its SETs); the path is then re-walked with each branch flipped to
|
||||
NOT_TAKEN, and **the LAST walk to touch an insn decides its final form** — so arm placement alone
|
||||
can never protect an address, and a cse1-deleted self-redef resurrects the fold in cse2.
|
||||
|
||||
**The pure-C antidote (byte-proven, v8, zero asm):** give the value-computation a **balanced
|
||||
if/else** (`if (b < 0x80U) v = b-0x80; else v = ~b-0x80;` instead of the assign-then-conditionally-
|
||||
reassign form). The then-arm's `jmp` + barrier makes the merge label **barrier-preceded**, so BOTH
|
||||
walks end AT the label and the store/`g->unk8` block becomes a **fresh-table cse block — no fold
|
||||
possible**. Final bytes are IDENTICAL to the fall-through form (reorg re-derives the delay-slot
|
||||
shape). **Generalizes: to protect a derived pointer or keep a global's field access reg-based, put
|
||||
a balanced diamond (or any barrier-producing construct) between the pointer defs and the uses.**
|
||||
|
||||
### 2. `update_equiv_regs` DOUBLES live_length for single-set REG_EQUIV pseudos (NEW mechanism —
|
||||
**local-alloc.c:1058-1064: `reg_live_length[regno] *= 2;`**) — and this is load-bearing for
|
||||
`global.c` allocno priorities: a `T *g = &SYM;` pointer gets pri = `flog2(refs)·refs/(2·live)`,
|
||||
i.e. HALF the density it would have as a 2-set pseudo. **Consequence: any RC-7-style 2nd-set dial
|
||||
(`__asm__("":"=r"(g):"0"(g))`) forfeits the doubling AND adds 2 refs — the allocno's priority
|
||||
roughly QUADRUPLES** (byte-measured: 15 refs/542-as-doubled → 830 vs 17 refs/273 → 2509), which
|
||||
re-orders the whole callee-saved bank ({g,st1,st2} rotated s3→s1). The v5→v8 fix was to need no
|
||||
dial at all (finding 1). **Rule: before dialing a single-set address pseudo, check whether the
|
||||
target's $s-assignment depends on its (doubled) priority; if yes, the dial is unaffordable — find a
|
||||
structural fix.** Also explains §47/§48 anomalies where "the same refs" gave inconsistent
|
||||
priorities: the doubling applies only to REG_EQUIV-noted (constant/valid-mem-init single-set)
|
||||
pseudos.
|
||||
|
||||
### 3. The reused-temp qty-head promotion — WHICH register a test reads, and copy survival
|
||||
(`make_regs_eqv`, cse.c:826). At a copy `(set tB (reg changed))`, the DEST becomes the class head
|
||||
(so later canon_reg rewrites read IT, and the copy survives) **iff tB is mentioned beyond the
|
||||
current cse block AND `regno_last_uid[tB] > regno_last_uid[changed]`** — i.e., steered by REUSING
|
||||
scratch temps so their last mention is later. This function's flag architecture only compiles right
|
||||
with two function-wide scratches (`tA`→v0: region-6 ne, switch-result copy, div, adjust arms;
|
||||
`tB`→v1: region-4 shift-temp, LBF0 changed-copy) — the m2c "temp soup" is REAL signal about the
|
||||
original's variable reuse, not decompiler noise. Corollary byte-proven here: `tA = flag;` after the
|
||||
switch survives (head-promoted, first-mention earlier + last-mention later) and reorg then
|
||||
distributes it into every predecessor's delay slot + retargets the jumps past it — reproducing the
|
||||
target's "per-path copy" pattern from ONE source statement.
|
||||
|
||||
### 4. `record_jump_equiv(insn, 0)` on FALL-THROUGH (cse.c:7511) deletes provably-dead branches —
|
||||
the identity-asm resurrection. EVERY conditional jump records its fall-through implication
|
||||
(`tB ≡ 0`), so a second same-value test in the same cse block ALWAYS folds away — even across a
|
||||
`do{}while(0)` (cse1 stops at LOOP_END notes but cse2 runs `after_loop` and folds anyway). The
|
||||
target binary contains a **provably-dead `beqz v1`** (Ghidra elides it too); no pure-C spelling
|
||||
can keep it. The pin-free fix: `__asm__("" : "=r"(tB) : "0"(tB));` immediately before the dead
|
||||
test — the 2nd set makes the recorded ≡0 unusable, zero bytes, sweep-safe. (A fresh tB2 pseudo via
|
||||
`"0"`-tie does NOT work: reload materializes the tie as a real `addu` when the input's reg differs.)
|
||||
**Recognition tell: a conditional branch in the target that dominators prove untakeable = the
|
||||
original had dead source logic; you must re-opaque the flag to keep it.**
|
||||
|
||||
### 5. Small confirmed levers (each byte-verified here)
|
||||
- **K&R s16 param + `self = arg0` body-copy (d820 lever confirmed):** placing the copy AFTER other
|
||||
head statements moves its LUID so the sign-extend chain schedules first — kills the in-place
|
||||
`sll a0,a0,16` tie and lands the chain in v0 with `addu s6,a0` later (v19, −4 real).
|
||||
- **In-place update `fl += 9`** (vs a fresh `k2 = fl + 9`): the fresh temp gets combine-merged into
|
||||
its consumer and re-associated (`(unk12+9)+fl`); the in-place form survives as `addiu v1,v1,9`
|
||||
via the destructive local-alloc tie.
|
||||
- **Two-statement narrow-load shift `tB = (u16)X; tB = tB << 16;`** keeps ONE pseudo (in-place
|
||||
`sll v1,v1,16`) where the one-expression form makes a load-temp + shift-temp pair.
|
||||
- **Input-only anchor `__asm__("" :: "r"(fl))` as a LOCAL-alloc order dial** (§48-A extension to
|
||||
`qty_compare`): +1 ref flipped the tail's fl-vs-base first-fit order and made the whole tail
|
||||
byte-exact, including un-blocking a reorg delay-slot steal in a DIFFERENT block (the stolen insn's
|
||||
dest must be dead on the taken path — its identity depended on the tail allocation).
|
||||
- **Frame +8 = an unused small local array** (`s8 pad[4]`, any ≤8-byte array; `s32 pad;` scalar
|
||||
gets NO slot, `s32 pad[2]` gets +16). The original evidently had a dead local buffer. Unknowable
|
||||
identity; byte-effect only on the frame immediates.
|
||||
- **Per-arm stores** `if (c) st1->x = h-3; else st1->x = h-8;` (not compute-then-store): keeps the
|
||||
store-load pair un-forwarded (fresh cse block at the merge) so the target's reload `lbu` survives;
|
||||
cross_jump merges the two `sb`s after regalloc (§44-L4 family).
|
||||
- **The dead-redef invalidation gambit FAILS** (m2): a same-value redef inside a skipped arm is
|
||||
cse1-deleted as a self-copy, and cse2 then re-folds everything — invalidation constructs must
|
||||
SURVIVE cse (asm) or be structural (finding 1).
|
||||
|
||||
## Method / reproducibility
|
||||
- Gate: `bash .run/giants/fable_76734/chk.sh <c>` (match_one + the aligned differ; positional is
|
||||
cascade-inflated at any length mismatch — gate on the aligned diff, §45/§48 discipline).
|
||||
- Dumps: `bash .run/giants/fable_76734/dump.sh <c> <prefix>` — includes `-ds -dt -df -dJ` (cse1,
|
||||
cse2, flow, jump2) beyond the standard set; the cse1 dump + `tools/reference/gcc-2.7.2/cse.c`
|
||||
settled every fold question without gdb (the dumps carried the decision; no gdb-on-cc1 run was
|
||||
needed this pass).
|
||||
- Micro-tests for the fold mechanics: `.run/giants/fable_76734/micro/m1..m5.c` (m5 = the balanced
|
||||
if/else proof).
|
||||
|
||||
## Next-tier recommendation
|
||||
Feed `.run/giants/func_80176734.fable.c` to the decomp-permuter (or one more Fable5/Opus pass)
|
||||
targeting: (a) the entry-block statement/schedule permutation, (b) the region-1/2/3 caller-saved
|
||||
shuffles, (c) a protecting form for the region-8 [sltu][copy] pair. All five clusters are
|
||||
independent, localized, and register/schedule-shaped — exactly the permuter's search space. A byte
|
||||
match is plausible from here. If banked later: the tB identity-asm is generic-constraint (§42e-safe
|
||||
for the ×134 sweep), and the TU integration needs the usual §8b decl reconcile (Trk/SndSt/SndGlob
|
||||
types are file-local here; externs match the TU's canonical sigs — note func_800183E0 is
|
||||
`void(s32)` in the TU vs `void(void*)` here, and func_801775E0 is `void(s32,s32)` in the TU vs
|
||||
`void(u8*,s16)` here — reconcile at bank time; both are byte-neutral at the call sites).
|
||||
@@ -307,3 +307,11 @@ internal offsets AND changes `/s` (stmt.c:3646 gives the array home `/s`) and IV
|
||||
3. §30a extension: `/s` full setter list (SAVE_EXPR arm, aggregate-deref arm, store-side
|
||||
4292, temp-slot reset) + the store-side CSE flush table (§4b).
|
||||
4. §5-layout: BLKmode 8-align/8-round + temp-slot recycling as the frame-fragility mechanism.
|
||||
|
||||
## §H Phase-27 — the CSE address-fold antidote + the fall-through delete (func_80176734, Fable5, 2026-07-15)
|
||||
|
||||
Fresh-core pass (`.run/giants/func_80176734.fable.md`, full pass dumps `.run/giants/fable_76734/`) — no bank (5 permuter-shaped clusters), but two byte-proven CSE mechanisms with pure-C antidotes worth reusing:
|
||||
|
||||
1. **The cse address-fold pair — killed by a balanced if/else diamond (zero asm).** `find_best_addr`'s cost-ungated qty-const fold + `from_plus` re-association eat reg-based global accesses and derived pointers on *every* cse walk (so a target that recomputes `&g + k` per use, instead of folding, looks unreachable). The pure-C antidote: wrap the merge in a **balanced `if/else` diamond** so its label is **barrier-preceded** → cse starts a FRESH table there → both folds die with no `#APP`. This replaced two asm dials on this function — prefer it to an inline-asm fence whenever the divergence is a cse fold across a join.
|
||||
2. **`update_equiv_regs` doubles live_length for single-set REG_EQUIV pseudos** (`local-alloc.c:1064`) — a **2nd set** of an address pointer forfeits the doubling and ~quadruples its allocno priority, rotating the callee-saved bank. Explains a whole "my zero-byte dial broke the $s-order" class: the dial added a second set. (Companion to regalloc §H; recorded there too.)
|
||||
3. **`record_jump_equiv` fall-through recording** (`cse.c:7511`) deletes a target's provably-dead branch; only an identity-asm 2nd-set re-opaques the value. A recognition **tell**: if the original keeps a branch cse would prove dead, the source had a genuine (non-constant-foldable) second writer.
|
||||
|
||||
@@ -35,7 +35,7 @@ Phase 26 closed on an honest pivot — the mechanical/templating harvest is byte
|
||||
|
||||
## Task checklist (effort per R7 · one commit per completed task after this file is updated, Drew pushes — R6/R20)
|
||||
|
||||
- [~] ▶ **Task 1 — Fable5 discovery sprint** `[orchestration xHigh · agents model:fable · distillation Max]` — **wave 1 + SIGABRT DONE + DISTILLED; `func_80176734` fresh-core agent still running.** **Wave 1** (3 recon-done seeds): none banked, but all three produced oracle-proven **reclassifications refuting §44-Lever-5's wall names** + new pin-free levers — `func_8016CBC0` root-A CRACKED byte-zero (density gap, "coalescing knife-edge" refuted — gcc has no coalescing), `func_8014D820` block-0 CRACKED pin-free 261→110 (reused-load-temp serialization), `func_801670E4` residual proven **RC-6 not S3** (the reg_renumber-swap oracle). **Wave 2 SIGABRT (major):** the §42e pin-crash wall is **REFUTED** — it's the T5 macro-drop, not a compiler limit (`sched.c:2725`; per-pin predicate; pinned families stage 133/133 clean → **P31's pin route is OPEN**). **DISTILLED (R16/R30):** cookbook §42e-CORRECTION + §44-Lever-5 reclassification; regalloc-map **§H** (the reg_renumber-swap oracle + RC-14 reused-load-temp / RC-15 density-dial + the local-vs-global tie sub-class); `docs/decision-log.md` R31 (the 3 strategic findings). Recon preserved (R20, 112K). *(Task 3 was pulled ahead of it — see the Log.)* Wave 1 (parallel-isolated): the 3 recon-done pin-free seeds `func_8014D820` (304), `func_8016CBC0` (209), `func_801670E4` (279, close=23); seeds at `.run/giants/*.opus.{c,md}`. **Distill idioms into cookbook §31/§52 + `docs/gcc-2.7.2-map/` IN-SESSION (R30)** — the value is the idiom, not the bank (§52: a *failed* Fable5 pass still fed 670 cheap-Opus instances). Wave 2, informed by wave 1: `0x80176734` (371) + **the pin-crash cc1 SIGABRT characterization** (gates P31's pin-×1 endgame; harness works at `.run/fable_80178004/{runorc.sh,oracle2.gdb}`; cause is currently **hypothesis-only** — no abort site, assert identity, backtrace, or minimal repro exists). `func_80178004`'s `qty_n_refs` = wave-2 filler only (decision-log prices grinding it low-EV). **Gate: idioms distilled, not functions banked.** Verify: whole-binary byte-gate per crack; `family_sweep` propagate; R22 clean-fleet.
|
||||
- [x] ▶ **Task 1 — Fable5 discovery sprint** `[orchestration xHigh · agents model:fable · distillation Max]` — **COMPLETE: 4 cracks + the SIGABRT, 0 direct banks, rich idiom harvest (the doctrine confirmed).** `func_80176734` (fresh core, 371 ins) landed last — no bank (mine=370 vs 371, 5 permuter-shaped clusters, honesty-gated) but **5 new byte-proven mechanisms**, distilled: the **CSE address-fold antidote** (a balanced if/else diamond forces a fresh cse table — pure C, no asm) + `update_equiv_regs` live_length-doubling + `record_jump_equiv` fall-through (cookbook cse_expr §H). Its draft → decomp-permuter warm-start (P29). **No more Fable5 waves this session (Drew, context cap).** **Wave 1** (3 recon-done seeds): none banked, but all three produced oracle-proven **reclassifications refuting §44-Lever-5's wall names** + new pin-free levers — `func_8016CBC0` root-A CRACKED byte-zero (density gap, "coalescing knife-edge" refuted — gcc has no coalescing), `func_8014D820` block-0 CRACKED pin-free 261→110 (reused-load-temp serialization), `func_801670E4` residual proven **RC-6 not S3** (the reg_renumber-swap oracle). **Wave 2 SIGABRT (major):** the §42e pin-crash wall is **REFUTED** — it's the T5 macro-drop, not a compiler limit (`sched.c:2725`; per-pin predicate; pinned families stage 133/133 clean → **P31's pin route is OPEN**). **DISTILLED (R16/R30):** cookbook §42e-CORRECTION + §44-Lever-5 reclassification; regalloc-map **§H** (the reg_renumber-swap oracle + RC-14 reused-load-temp / RC-15 density-dial + the local-vs-global tie sub-class); `docs/decision-log.md` R31 (the 3 strategic findings). Recon preserved (R20, 112K). *(Task 3 was pulled ahead of it — see the Log.)* Wave 1 (parallel-isolated): the 3 recon-done pin-free seeds `func_8014D820` (304), `func_8016CBC0` (209), `func_801670E4` (279, close=23); seeds at `.run/giants/*.opus.{c,md}`. **Distill idioms into cookbook §31/§52 + `docs/gcc-2.7.2-map/` IN-SESSION (R30)** — the value is the idiom, not the bank (§52: a *failed* Fable5 pass still fed 670 cheap-Opus instances). Wave 2, informed by wave 1: `0x80176734` (371) + **the pin-crash cc1 SIGABRT characterization** (gates P31's pin-×1 endgame; harness works at `.run/fable_80178004/{runorc.sh,oracle2.gdb}`; cause is currently **hypothesis-only** — no abort site, assert identity, backtrace, or minimal repro exists). `func_80178004`'s `qty_n_refs` = wave-2 filler only (decision-log prices grinding it low-EV). **Gate: idioms distilled, not functions banked.** Verify: whole-binary byte-gate per crack; `family_sweep` propagate; R22 clean-fleet.
|
||||
- [x] **Task 2 — Makefile fail-closed (the enabling fix)** `[xHigh]` — **DONE.** `.SHELLFLAGS := -ec` (global fail-closed) with ONE documented opt-out: `check-env` (`set +e` — its contract is accumulate-every-failure). Fixed the `check-all:610` `grep -c` landmine (`|| true` — grep -c exits 1 on 0 matches, which `-e` would treat as fatal → check-all would fail when nothing failed). Strengthened `check-all`/`extract-all` from `fail == 0` → **`pass == N`** (coverage assertion, R32 — the old form was a vacuous pass on an empty pipeline). Gave the two audit oracles a dependent: **new `make tools-health`** = `audit-corpus` + `audit-cdecl` + `report`, fail-closed (NOT a `report`/`build` prereq — audit-cdecl is ~minutes). SETUP §6.3 documents it (R21). **VERIFIED:** (1) known-answer — a broken `lint_symbol_refs` makes `make report` exit non-zero, and a **negative control** proves it: the *identical* break exits **0** under old `.SHELLFLAGS=-c`, **2** under `-ec`; (2) the `grep -c` landmine and the vacuous-pass both reproduced + fixed in isolation; (3) `check-env` still exits 0 (opt-out works); (4) **`make check-all` → 136/136 byte-identical**, and a forced `main` re-extract+rebuild exercised the full splat→cpp→cc1→maspsx→as→ld→objcopy→check pipeline under `-e` → `143dbb89…`; (5) `audit-corpus` (7s) + `audit-cdecl` (green) + `tools-health` dry-run all wired. Recipe scan found the Makefile was already `-e`-aware (`set -o pipefail`, explicit `|| true`, guarded `@` lines) — line 610 was the only real hazard. *(completes with this commit)*
|
||||
- [x] **Task 3 — Curated `.run/` preservation** `[xHigh]` — **DONE** (pulled ahead of Task 1 — it de-risks the sprint's inputs). `.gitignore` `/.run/` → contents-exclude form (`/.run/*` + `!` exceptions, the `/tools/bin/*.sha256` precedent). **Refined at execution against the bytes:** the naive "commit the dirs" would have been **12.3 MB of regenerable gcc RTL scratch**; the genuinely irreplaceable set is **~2.2 MB / 31 files** — the 6 Phase-25 `*.opus.{c,md}` seed recons (49K), the `func_80178004` gdb-on-cc1 **harness + `ORACLE_PROOF.md` + the v00–v07 draft ladder + the sched/combine `.lst` evidence** (~110K), and the two frontier ledgers (`backlog.jsonl` 1.9M, `fuel_manifest.json` 67K). `dumps_v00..v07/` + `d_pf*.i.*` stay ignored — **regenerable via `runorc.sh` + the `.gdb` scripts** (R33: commit what a rerun cannot reproduce). **VERIFIED:** `git add --dry-run .run/` stages exactly the 30 intended files, 0 bulk; negative control — `.run/ghidra-mcp.log`, `dumps_v00`, `d_pf.i.sched`, `d_pf.s` all still `IGNORED`; no `db.*.gbf` staged (R23). *(completes with this commit)*
|
||||
- [x] **Task 4 — The cdecl strip primitive + surface cc1 stderr** `[xHigh]` — **DONE.** Found the defect is **six** copied scalar-name regexes, not two (`harvest_verify._TD`, `masked_diff.SCALAR_TYPEDEF_RE`, `canon_sig_reconcile`'s own, `eval_lora`, `format_finetune`, + the 2 masked_diff consumers). Added **one primitive to `cdecl`**: `typedef_names(tu_path)` + `strip_provided_typedefs(draft, provided)` — built on `tu_statements` (robust) **not** `tu_scope` (which coverage-asserts → would crash the byte-gate on any unrelated unparseable file-scope statement; a deliberate refinement of the plan). Split multi-typedef lines via `split_statements` (depth-aware); covers scalar AND struct typedefs; keeps draft-local types. **`harvest_verify`:** per-TU strip-set (unblocks the 39 struct-typedef drafts) + **cc1 stderr surfaced** — `build()` stashes it, a single-draft failure is classified **DIFF / PLUMBING:… / CC1-FAIL / SKIP** (`.run/harvest_failed.classified.txt`), so a `redefinition` is no longer recorded as a byte miss. **`masked_diff.strip_scalar_typedefs()`** (common.h set derived once, R33) wired into `match_one` + `p16_permute`. Unblocks B4's `func_8015C32C` (`redefinition of 's16'`). **VERIFIED:** (1) headline known-answer — `func_8015C030` → **`MATCH (23 ins)` UNEDITED** (was CC1-FAIL; multi-line split alone fixes it); (2) unit — 7/7 scalars stripped, a local struct KEPT, a TU-provided `Blk16` stripped; (3) classifier unit — DIFF/PLUMBING/CC1-FAIL/SKIP all correct; (4) all 5 tools import + parse; (5) **R22 clean-fleet 136/136** + main clean-rebuild `143dbb89` (a mid-test `c4546248` "mismatch" was a stale-incremental artifact from concurrent compiles — resolved by a clean rebuild, the R22 lesson; my edits touch only `tools/`, `src/` stayed git-clean). A strip bug can only fail-to-bank, never falsely bank (the audit invariant). SETUP §6.3 + cdecl inventory updated (R21). *(completes with this commit)*
|
||||
|
||||
Reference in New Issue
Block a user