Files
BFM-decomp/cookbook/C0303.md
T

2.5 KiB

§NNN — A LOOP CURSOR'S C TYPE (POINTER vs PLAIN INTEGER) SELECTS sltu vs slt FOR ITS BOUND TEST, INDEPENDENT OF THE VALUES INVOLVED (P31 S60; func_800CAE74, md_MAIN_031, byte-proven; cross-confirmed same wave by func_8017F2A4, ov_SC03_096)

THE LAW. C mandates unsigned comparison semantics for T* relational operators (<, >=, …), so a loop cursor DECLARED as a pointer (u8 *p; ... while (p < end)) always lowers to sltu, while the identical byte-address arithmetic done through a PLAIN INTEGER cursor (s32 p; ... while (p < base+K)) lowers to slt — even though the underlying values, the loop body, and the generated addresses are byte-identical either way. This is a pure declared-TYPE dial with zero effect on the address arithmetic itself (both spellings emit the same addiu/addu for the bump and the same base computation); it only flips the one comparison opcode.

ASM EVIDENCE. func_800CAE74: cursor p/end base+0x1F declared s32, target emits slt $v0,$v1,$a3 (signed) at func_800CAE74.s:D8, bound hoisted at addiu $a3,$a1,0x1F (line B8). Independent same-wave confirmation, func_8017F2A4 (ov_SC03_096): the submitting agent independently reports "sltu forces pointer-typed rec/end (s32 arithmetic emits signed slt)" for a different record-scan loop in a different overlay — same law, opposite direction, unrelated function.

WHEN IT APPLIES. Any bottom-tested or top-tested loop whose residual is a slt/sltu MISMATCH on the bound test with everything else (bump, base, body) already byte-identical. Check the target opcode first: sltu/sltiu ⇒ declare the cursor and its bound as a pointer type; slt/slti ⇒ declare both as a plain signed integer (s32) and do the pointer arithmetic in integer space (*(u8*)p/*(u8*)s for the dereference, p += 1 for the bump). Do not reach for a cast-only fix ((s32)(p < end) does not change the opcode — the DECLARED type of the compared operands is what the signed/unsigned dispatch reads, not a cast at the compare site) — this needs the variable's own declaration changed, not a use-site cast.

(The candidate's other claims — a "parameter reassignment" narrative for arg1, and a two-cursor split — do not survive checking against the actual banked C: arg1 is untouched and lives in $s2 only because it is read across a later call, ordinary call-crossing liveness already covered by §167-21/§76; not promoted. See the "could not verify" section.)