Files
BFM-decomp/cookbook/C0346.md
T

6.1 KiB
Raw Blame History

§310 — A TWO-OPERAND subu's DESTINATION TIES ONLY TO THE OPERAND THAT IS BLOCK-LOCAL: SHARE ONE SIDE ACROSS BLOCKS TO CHOOSE WHICH OPERAND'S REGISTER IT LANDS IN (P31 S64 t5j-t5m; byte-proven func_8017F578)

(COMPLETES §197-C (L20637) — its bound 1 measured a block-local commutative destination, found Fix A1 equally inert on it, and stopped at "state the law by the conflict set"; it never found the dial that DOES move a block-local destination. This is that dial, and it runs on the tie §197-C's bullet 1 declares out of scope for a global destination. INVERTS §48-A3 (L3438) / §76 (L6038) / §162b1 (L11078), which all read the same local-alloc.c:472 predicate in the SPLIT direction — split a shared temp to BUY the tie; here you deliberately share one operand to PUSH the tie onto the other. Distinct from §10 Residual A / Fix A1 (L856), which is source-operand ORDER on a commutative op and cannot apply to a subu at all. Second counter-example this phase — after the S62 T4 addendum at L29871 — to §137's (L9344) "source-level levers are a dead end for REGALLOC-PERM": the edit changes a pseudo's BLOCK COUNT, which §137's R/L arithmetic cannot see.)

THE TELL. match_one reports REGALLOC-PERM with a clean cyclic 2-register swap (REGALLOC-PERM/$v0>$v1>$v0) whose residual is whole lh …; lh …; subu triads, all three instructions of each triad, repeated once per block — here 3 blocks × 3 insns = closeness 9, everything else byte-identical. Read the two encodings, not the mnemonics: mine 00431023 = subu $v0,$v0,$v1 (dest ties rs, the minuend); target 00621023 = subu $v0,$v1,$v0 (dest ties rt, the subtrahend). The loads swap BECAUSE the subu's tie swapped — that is the discriminator against a schedule or polarity residual, where the loads would keep their registers. If only the subu differed you would be looking at something else.

THE MECHANISM. block_alloc's per-insn operand scan calls combine_regs (usedreg = input, setreg = dest) to tie a two-operand insn's destination quantity to one of its inputs. local-alloc.c:472-477 gives a pseudo reg_qty == -2 (tie-eligible) only when reg_basic_block >= 0 && reg_n_deaths == 1; anything else gets -1. combine_regs bails on reg_qty[ureg] == -1 (:1774) and only ties at :1843 — the same three line numbers §197-C already banked, used here for the opposite purpose. So of a subtraction's two operands, only the one confined to that basic block can win the destination. A C variable assigned and read at three different if-nesting depths is multi-block/multi-death ⇒ -1 ⇒ permanently disqualified, and the tie falls to the other side by elimination. subu is non-commutative, so operand order cannot express this and §10's Fix A1 has nothing to move.

THE C SHAPE — asymmetry is the whole ingredient. Name and reuse the operand whose register the destination must not take; leave the other side a fresh inline expression at every use.

/* `va` reused across ALL three tests -> multi-block life (reg_qty == -1, tie-INELIGIBLE);
   the a0-side operand stays a fresh inline expression each time -> block-local (tie-eligible),
   so the destination lands on IT and the triad's two `lh`s swap with it. LOAD-BEARING:
   collapsing either half to match the other returns closeness 9. */
s32 va;
va = *(s16 *)(a2 + 0xA);
if ((u32)(va - *(s16 *)(a0 + 0xA) + 0x30) < 0x51) {
    va = *(s16 *)(a2 + 0x6);
    if ((u32)(va - *(s16 *)(a0 + 0x6) + 0x70) < 0xE1) {
        va = *(s16 *)(a2 + 0xE);
        if ((u32)(va - *(s16 *)(a0 + 0xE) + 0x70) < 0xE1) { goto found; }
    }
}

BYTE EVIDENCE (func_8017F578, 67 ins). Ladder, one axis per round: v1 fully inlined → near, closeness 13 (the three triads plus a li/li swap at [8]/[9]); v3 an explicit t1 = 0x12C local → 11 (drops [8],[9]); v4 a2 += 0x10C moved into the for-increment → near, closeness 9, residual only the three triads at [15,16,18] [23,24,26] [31,32,34], verdict REGALLOC-PERM, map {"$v0":"$v1","$v1":"$v0"}. Then the two-point ablation that is the finding: v5 names both va and vb, each still reused across the three nested ifs → identical closeness 9, identical verdict — naming per se does nothing; v6 names only va and returns the a0-side to an inline per-use expression → match, closeness 0, nins 67, residual [], byte-gate accepted. Three points, one variable moved: both-inline = 9, both-shared = 9, exactly one shared = 0.

BOUNDS. (1) No dump was read. The reg_qty story is taken from the pinned source, not from a -dl/-dg dump of this function — §137/§158 set that bar and §197-C cleared it with real .greg conflict lines. What is byte-proven here is the C dial and its 9→0; the free confirmation nobody ran is §197-C's own test, the ;; N regs to allocate: line, which prints whether each operand is in local-alloc's scope at all. (2) v5 is explained, not measured. Under the mechanism, disqualifying both operands should leave the destination untied and free to take $v0 by ordinary first-fit — which is mine's failing form — so v5's identical 9 is consistent but reconstructed. It is equally consistent with the tie never having been the decider. Treat "share exactly one" as the proven recipe and the elimination story as the leading hypothesis. (3) n = 1 function, one insn shape (three lh/lh/subu triads on a hit-box compare). Untested on addu/and/or (where §10 Fix A1 competes), on operands of different widths, and on any destination that survives its block — a global destination is §197-C's territory, where this tie never fires and the lever is the split accumulate instead. (4) Sharing is not free elsewhere: §162b1 prices the same edit in the other two currencies — a shared multi-block temp costs you combine's known-zero-bits record (surviving andis, §162b1's LAW) and can win or lose a callee-saved pin. Ablate the share per operand, never sweep it. (5) Per §176-B, this is the right class of lever for a small REGALLOC-PERM and the pin is still the wrong one; no register __asm__ variant was tried here, and on this evidence none is owed.