diff --git a/.run/giants/func_80176734.fable.c b/.run/giants/func_80176734.fable.c new file mode 100644 index 0000000000..b319145d11 --- /dev/null +++ b/.run/giants/func_80176734.fable.c @@ -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); + } +} diff --git a/.run/giants/func_80176734.fable.md b/.run/giants/func_80176734.fable.md new file mode 100644 index 0000000000..9bf8c23ee9 --- /dev/null +++ b/.run/giants/func_80176734.fable.md @@ -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 ` (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 ` — 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). diff --git a/docs/gcc-2.7.2-map/cse_expr.md b/docs/gcc-2.7.2-map/cse_expr.md index 309ca77e7d..bfadb7eeee 100644 --- a/docs/gcc-2.7.2-map/cse_expr.md +++ b/docs/gcc-2.7.2-map/cse_expr.md @@ -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. diff --git a/phase-ends/CURRENT_PHASE.md b/phase-ends/CURRENT_PHASE.md index 5fae9984be..3228697f54 100644 --- a/phase-ends/CURRENT_PHASE.md +++ b/phase-ends/CURRENT_PHASE.md @@ -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)*