Files
BFM-decomp/cookbook/C0331.md
T

2.1 KiB

§297 — ONE GIV SERVING MIXED-WIDTH LOADS AND A STORE NEEDS STRUCT-MEMBER SPELLING; CAST/INDEX SPELLINGS RE-SPLIT THE PSEUDO (P31 S61; wave m0a, func_800347C8, main, byte-proven 31/31)

The target shape (asm/nonmatchings/800/func_800347C8.s): an 0x54-stride record walk with TWO givs — $a1 = base+0 (lhu 0x0($a1), the state read) and $v1 = base+0x16 with NEGATIVE displacements: lhu -0x12($v1) (id @0x04), lhu -0x10($v1) (group @0x06), sb $t0,0x0($v1) (active @0x16). The $v1 giv carries two u16 LOADS and one u8 STORE.

What re-split it, measured: a raw u8 *q walker made gcc spawn a THIRD giv rebased at +6 while the store kept its own; casting the store back through the pointer re-split it again even after the loads had unified. Only the MEMBER spelling held all three accesses on the single pseudo — banked src/800.c:19086:

typedef struct {
    u16 state;      /* 0x00 */
    u16 unk02;
    u16 id;         /* 0x04 */
    u16 group;      /* 0x06 */
    ...
    u8  active;     /* 0x16 */
    u8  pad17[0x3D];
} Sl;               /* 0x54 */

    e = (Sl *)D_800A46E8;
    for (i = 0; i < 8; i++, e++) {
        if (e->state == 5 && e->id == (s16)arg0 &&
            ((arg0 >> 16) == 0 || (arg0 >> 16) == e->group)) {
            e->active = 1;
        }
    }

The law. Mixed-width accesses (loads AND a store) that must ride ONE strength-reduced induction register must be spelled as MEMBER SELECTS off one record pointer. Pointer-arithmetic and cast spellings build distinct address trees per width, and combine_givs re-splits the pseudo.

Two companion dials on the same card, both banked: (1) BOTH latch increments belong in the for-header (i++, e++ as the comma expression) — written as the last body statement, the pointer bump emitted on the wrong side of the counter bump (a 2-insn SCHEDULE-REORDER the residual class files as permuter fuel; the header spelling fixes it for free). (2) The packed parameter's halves: (s16)arg0 promoted ONCE inline in the condition; arg0 >> 16 used RAW in two basic blocks by naming the parameter itself — no local, since there is no cross-bb CSE to defeat.