1.9 KiB
§418 ★★★ — TWO LOOP-STRUCTURE LEVERS: MAKE THE SECOND INDEX A GIV, AND KEEP A TABLE ADDRESS UNFOLDED (P31 S71; byte-proven ov_SC04_016/func_8017DF8C, 184 ins, 32 → 0 in seven compiles)
1. A second biv and a giv of the first are different CODE, not different style.
Writing the stride variable as its own induction variable gives one shape; writing it as
off = j * 0x50 at the top of the body — a giv of biv j — gives another. loop.c chains givs
LIFO (loop.c:4421), so with j = 0 spelled BEFORE idx/tp the move $s0,$zero emits LAST
and the arg-3 giv lands in its natural record slot. That single respelling fixed 14 preheader rows
and 8 latch rows at once. When the preheader ORDER is wrong, look at which variables are bivs and
which are givs before touching anything else.
2. To keep la + addu + lh 0(reg) unfolded, hoist the table pointer OUT of the loop — once.
A loop body that must emit la $t6 / addu $v0,$a1,$t6 / lh 0($v0) (rather than a folded
%hi/%lo(sym+K)) gets it from a function-scope pointer local set once outside both loops:
s16 *tbl = &D_80184D0A;. Two mechanisms combine — there is no LOG_LINK across basic blocks, so
combine cannot fold it; and with every $s register taken, reload rematerialises it from
reg_equiv_constant at the use and deletes the initialisation, which is why the hoist costs
nothing. Spell the sum idx + (s32)tbl to get (plus a1 t6); (s32)tbl + idx costs exactly one
instruction (operand swap).
MEASURED INERT here: §328's volatile cast, for a reg+sym address — it splits the extend into
lhu/sll/sra and still folds.
Relation to §20/§42d-2. Those cover keeping reads on one la via an opaque launder inside a
block; this is the cross-block form, and the lever is placement (outside the loop) rather than a
launder. The reload-rematerialisation is what makes the two look identical in the output.