Files
BFM-decomp/cookbook/C0267.md
T

2.7 KiB

§246 — THREE-LIVE-VALUE SCAN LOOPS WANT ADDRESS-FROM-INDEX; AND TWO SYMBOLS CAN SHARE ONE giv (P31 S58b)

1. base + i*stride INSIDE THE LOAD EXPRESSION. func_800CAED8 (md_MAIN_025): when the target's loop keeps three live values ($a0 unit counter, $v1 stepped pointer, $a1 accumulator), neither of the two obvious spellings works —

  • p += 0x34 (pointer form) is fused down to two values by combine_givs;
  • base[i].field (array-index form) strength-reduces to a scaled-offset giv anchored on %hi(sym)+off via $at.

The winning spelling is the address computed from the index inside the load expression:

*(u16 *)((s32)&sym + i * 0x68) == K

This keeps i alive for the exit test while letting induction-variable reduction materialise the FULL base into the stepped giv (lui/addiu, then addiu step 0x68); the scaled-index giv never appears. Three-register scan loops want address-from-index — not pointer arithmetic and not array syntax.

2. TWO DISTINCT GLOBALS AS PARALLEL ARRAYS OF ONE STRIDE-TYPE SHARE A SINGLE ADDRESS giv. func_8017E95C (ov_SC06_025, 23/23): the target walks D_801B12F0 and D_801B12F8 with one byte-offset IV ($v1 += 0x104) feeding both through lui-$at/addu/sh %lo(). Declaring both externs as [4] arrays of a common 0x104-byte record type makes gcc's giv-combine fuse the two address computations onto one induction variable. §2397's IV-COMBINE is stated for fields off ONE base pointer; here the single-IV result is obtained across two different symbols by giving them identical element strides.

3. AND THE OPPOSITE CASE. func_8017DC50 (ov_SC05_008): a loop-carried p += 0x10C compiled to a second address register; q = &base[i*stride] recomputed per iteration reused one register and matched. func_80187754 (ov_SC06_000): two walkers of different scale off one base must both be spelled as indexed expressions (((s16*)a0)[i+2], a0[i+5]) so IV elimination creates the two givs in the target's order. func_8017F13C (ov_SC03_001): switching a pointer-IV to an index-biv form changed the movable ranking so exactly ONE loop-invariant constant hoists — §193-F's "target hoists one constant, leaves its twin inline" tell, reachable by redraft rather than by pinning.

UNSOLVED, RECORDED. func_801AB5D4 (md_SC07_004, 13 oracle calls across two sessions): no spelling suppresses combine_givs' second address-giv for a constant +2 offset off an unconditional walked base when the target keeps it as a displacement. §162n's cant_derive cure needs a conditional alias; there is no if-arm to hide the assignment in. Nine drafts, all near-40. If you meet this shape, go straight to the permuter.