Files
BFM-decomp/cookbook/C0341.md
T

11 KiB
Raw Blame History

§306a — T4 DISTILL ADDENDA (P31 S62; four byte-proven refinements to existing laws, verified against the book by an independent verifier; each names its parent §)

→ parent: §255 "AND CASE-BODY PLACEMENT"

Addendum (P31 S62 T4, func_800CAF9C): third measured exemplar, and it lands at the smallest tree that has a root — the regime this paragraph's DFS sentence was written from. func_800CAF9C (md_MAIN_015, 105 ins, byte-gate green) dispatches {0x41, 0x53, 0x73}; balance_case_nodes' i == 3 arm roots on the median, so beq $v1,0x53 is the FIRST test (asm/md_MAIN_015/nonmatchings/md_MAIN_015/func_800CAF9C.s @800CAFD4) — yet the root's body sits SECOND: case 0x41's body is .L800CB008, the shared case 0x53: case 0x73: body is .L800CB034. DFS-root-first is false here, and §199-G (L21011) says why it must be: expand_end_case takes before_case = get_last_insn() after every body is already in the stream (stmt.c:4749) and reorder_insns (stmt.c:5054-5056) hoists only the header in front of them — no pass ever permutes bodies. Read ADD-9's "two regimes" as one law plus misread ascending-order exemplars: body order is source-clause order, always; what survives of §255's sentence is its other half, that physical arm order does not name case VALUES. Byte evidence. Draft 1 spelled case 0x53: case 0x73: before case 0x41: — the compare chain (lbu/beq 0x53/slti 0x54/beq 0x41/j) was byte-identical through index ~9, then a total cascade from the first body onward: near, closeness 71, 47 of 104 residual. The ONLY edit was moving the two clauses (0x41 first); draft 2 → MATCH, 105/105, residual: []. Diagnostic tell: a residual that begins exactly at the first case body while the dispatch chain is already byte-clean ⇒ reorder the case CLAUSES in source; do not touch polarity, empty cases, or the tree. ⚠ Bound: this instance alone cannot separate source order from ascending order (0x41 < 0x53) — ADD-9's func_8017F328 swap probe is what discriminates; this card's contribution is killing root-first at 3 nodes.

→ parent: §16Xy

Addendum (P31 S62 T4, func_8017FB38): Fourth byte-instance, and a new cell — the cast is narrower than the load: lh $v0,0x2C($a0) ; beqz ; addu $a0,$v0,$zero ; andi $a0,$a0,0xFF ; jal func_80016450 (0x8017FBC8, ov_SC07_000). §16Xy's table carries (u16)-on-s16 and (u8)-on-s8; the mixed (u8)-on-an-lh cell behaves identically, so read the law as narrow memory load + narrower-or-opposite-signed cast at an SImode use, not as a width match. The crack re-derived §16Xy's ablation row for row without finding it — v0 & 0xFF → andi $a0,$v0,0xff, no copy (closeness 10, residual [36]); u8 t = v0; → neither instruction, residual nop; only the two-instruction form matches. It then reached MATCH by the expensive road: register s32 a0r __asm__("$4") plus __asm__("" : "=r"(a0r) : "0"(a0r)) between the copy and the mask. That launder is §165-04/§165-35's instrument (the "=r"/"0" pair forces the value through an SImode register operand, blocking combine's fold into the lh) — whose "honest scope: ONE A/B pair, body later abandoned" note now has its second byte-proven instance. So the transcript's "mechanism unknown at the RTL-pass level" is answered by §165-03: the stranded SImode extension is a conflict-free orphan pseudo whose preferred class converges to ST_REGS, and alter_reg slots it. Route to the declaration first, not the pin. s16 s = *(s16*)(a0+0x2C); if (s != 0) func_80016450((u8)s, 1); buys the same pair with no register __asm__ — which forfeits the family (§37/§162p3) and, per §164-49, can sell you a schedule. The pin here was never solo-ablated against the barrier (§266), so cite the barrier and treat $4 as an unproven rider. Frame reconciliation — first function needing §16Xy's and §167-10's counters SUMMED. Target .frame is 0x30 with sw $ra,0x28 and args=16 ⇒ vars = 24, with zero $sp references anywhere in the body: three narrow-copy sites × 8 — two §167-10 compare-then-re-read-and-store-back copies (addu $v1,$v0,$zero at 0x8017FB58 and 0x8017FBA0) plus this §16Xy cast-to-call copy. The draft hand-shipped frame_pad[6] for exactly those 24 bytes. Falsifiable prediction: spell all three sites as s16 locals and the 24 bytes mint themselves — the pad then over-shoots to 0x48 and FAILs, the §162i1 footgun §167-10 already names. Index gap: cookbook-index.md L19 routes "andi folded away in your output but present in the target" to §1/I2 + §12 only. When the missing instruction is the copy and not the mask, the route is §16Xy → §165-02 → §167-10.

→ parent: §176-F row 3

Addendum (P31 S62 T4, func_8017FB38): row 3's tell reproduced verbatim on a second, independent function — ov_SC07_000:func_8017FB38, IMM-OFFSET/6, closeness 1, again 44 instructions, again a lone beqz $v0 whose only difference is the local-label immediate (mine 0x10400008 vs target 0x1040000E; target asm asm/ov_SC07_000/nonmatchings/ov_SC07_000_jr_8017BEBC/func_8017FB38.s:30, the beqz $v0,.L8017FBD8 at 8017FB9C).

The dropped conditional edge has a SECOND C-level cause, and its fix is the mirror of row 3's. Row 3's cause was a trailing statement written after a guard's closing brace (fix: move it in). This one is a missing early return;: the t == 0 path was left to fall through into a later, logically-redundant if (field2C != 0) { func_80016450(field2C & 0xFF, 1); } instead of terminating, so that shared guard — not the epilogue — became what the beqz reaches. Spelling the bail-out explicitly, } else { t = field2C; if (t == 0) { return; } t -= 0x10; … }, gave MATCH 44/44 with every other byte unchanged.

Read the SIGN of the immediate; it names the defect. match_one emits residuals as [i, mine, target] (tools/match_one.py:222), so the two displacements are directly comparable. Mine NEARER than the target's ⇒ my C falls into a downstream block the target's C skips — add the return;. Mine FARTHER ⇒ I terminate a path the target lets fall through — drop the terminator, or move the trailing statement inside the guard (row 3). And the delta is a ruler, not noise: it is the instruction distance between the two candidate landing points (here 6, +0x88 inside the shared guard → +0xA0 the epilogue), so you can count straight to the block in question instead of searching the .s.

⚠ Do NOT bank the strong form "gcc-2.7.2 never merges or elides a later guard whose earlier predecessor would also fail it." The bytes refute it here: the fall-through draft did not land on the shared guard's label, it landed one instruction inside it (+0x88, past the lh $v0,0x2C($a0) reload jump1 knew was redundant given $v0 == 0) — which is exactly why the delta is 6 and not the guard block's full 7. The law is about which block the edge enters, not about guard elision. (Complements §225-2 / §225-8, which put early-return-vs-if-block on the prologue/epilogue and j-vs-branch axes with large residuals; this axis is the count-neutral one. §176-G's bound still applies: the same edge error is silent when the arm exit is a j.)

→ parent: §211

Addendum (P31 S62 T4, func_8017F608): §211's half (b) — "hoisting it above the guard lengthens the pseudo's live range across the guard block, flipping the local-alloc contest" — fires with no loop, no induction pseudo, and no instruction-count or delay-slot change at all, so §211's own BOUNDARY ("it only bites when the guard/branch actually has a fillable slot and the two induction pseudos actually contend") is too narrow: the contest can be a single constant against local-alloc's generic-temp default. func_8017F608 (ov_SC02_028, jr_8017D898, 99 ins) sat at closeness 2, REGALLOC-PERM/$v0>$a0, on exactly one pair — mine li $v0,0x600 / sh $v0,0x10($sp) vs target addiu $a0,$zero,0x600 / sh $a0,0x10($sp) — with §211's half (a) already satisfied (both builds put the addiu in the guard's own delay slot; only the colour differed). The fix: hoist the clamp constant into a named local above the guard and make the guard test the local, not the literal — s16 clampval = 0x600; if (sp10.v[0] < clampval) { sp10.v[0] = clampval; } → match, 99/99, residual []; the stuck form declared the local inside the taken arm and tested the raw literal (closeness 2). THE TELL IS IN THE TARGET, NOT IN YOUR DIFF. Read the neighbouring guard's delay slot: asm/ov_SC02_028/nonmatchings/ov_SC02_028_jr_8017D898/func_8017F608.s holds 8017F680 addu $a0,$v1,$zero (outer slti …,0x700 slot) and 8017F68C addiu $a0,$zero,0x600 (inner slti …,0x600 slot) — the constant re-defines the same register an earlier guard's slot already loaded, which is one named local with two defs spanning both guards, never a store-time temp. A fresh temp takes $v0 by local-alloc's copy-suggestion default (§186c, as corrected by §194-F). This is §208's pseudo-set principle run in the merge direction (§208 splits one expression into two locals to buy two registers; this merges a compare operand and a store source into one local to buy one register), and it is a second counter-example to §137's "source-level levers are a dead end for REGALLOC-PERM": the edit changes the pseudo SET, which §137's R/L arithmetic cannot see. BOUNDS (read these before quoting the recipe). (1) The A/B moved two things at once — declaration position and the compare's operand — so per §266 neither half is independently citable; probe decl-above-with-literal-test before treating "test the local, not the literal" as the load-bearing clause. (2) The rematerialisation story is the drafter's hypothesis, not read out of a -dl/-dg dump; what is byte-proven is the source edit → MATCH. (3) The drafter's side claim that the same hoist "also flipped several BRANCH-POLARITY residuals earlier in the same draft" is unquantified and unverified — treat as a lead, not a law. (4) Tried and failed, worth as much: register s32 clampval __asm__("$4") on the same body REGRESSED 2 → 62 with +1 ins (99 → 100) — another row for §257/§268's pin ledger, and a straight confirmation of §176-B: on a 2-instruction REGALLOC-PERM, reach for the naming/scope lever first and the pin never.

Harness note banked with these (P31 S62 T4). 22 of 60 probe agents reported that the pack's warm-start body was a DIFFERENT function: drafts are stored by function name, and the same name at the same address in another overlay is usually unrelated code. api_agent.prior_draft now accepts a candidate only when the symbols it references overlap the target .s's relocations (law 1c); the haiku arm re-run on its misses with the filtered packs banked 2 more. Read "warm start" as "a body whose symbols are this .s's symbols", never as "a body with this name".