Files
BFM-decomp/cookbook/C0348.md
T

4.7 KiB

§312 — A BARE RELATIONAL IN AN if GIVES THE slt NO TARGET: ASSIGN THE COMPARISON INTO THE ALREADY-ALLOCATED VARIABLE TO CHOOSE ITS DESTINATION REGISTER, AT ZERO INSTRUCTION COST (P31 S64 t5o-t5r; byte-proven func_80180DCC)

(NEW axis, not a restatement. §164-38/§164-39 and §164-39's t5j-t5m addendum own the compare's INPUT colouring — which operand got which register — and their levers are the in-out fence on operand 0 and the relational transposition; both are the wrong door when the inputs are already correct. §197-C/§164-64 own "write it IN PLACE so the destination is an existing pseudo" for */+/|/&; this extends that lever to the relational operator, where §197-C's companion note — "a pinned pseudo can never be the load's destination" — had left the impression that a pin cannot own a computed dest at all. §164-65 also names a value you were going to consume inline, but its payload is allocno REF COUNT, not SET_DEST. §195-E is the other naming law and is about whether a 0/1 materialises at a join; here naming is length-neutral, which is exactly what makes this lever free.)

THE TELL. The target's slt writes back into one of its own two source registers — an in-place compare, slt $v0,$v0,$v1 — and is immediately consumed by a bnez/beqz on that same register. Your draft emits the identical pair with every operand register already correct and only the destination one register over: match_one reports REGALLOC-PERM with a single-register cycle ({"$v1":"$v0"}, kinds:{"reg":2}), residual exactly 2 instructions — the slt and its branch — and zero length drift. That is not structure and it is not input colouring; nothing but the compare's SET_DEST is wrong.

THE MECHANISM — INFERRED, NOT TRACED (cite as a place to look, never as proven pass behaviour; §164z / §137's bar). A truth value used only inside an if reaches do_jump/expand_expr with no fixed target, so the slt's destination is synthesised from whatever pseudo is cheapest at that point, tied to neither compared operand — which is why it can land one register away even when both operands are correctly pinned. Writing the comparison as an assignment hands expand_expr an explicit target pseudo and store_expr honours it, forcing the slt's SET_DEST to that variable's hard register. No -dS/-dg/-dl/-dg dump was taken; local-alloc.c combine_regs' tie-to-a-dying-source-operand (§10 Residual A) is the other place worth dumping first if this recurs.

THE C SHAPE.

/* wrong — dest register unpredictable */
if (v0 < v1) { goto TRUE1; }

/* right — forces the slt's dest into v0's pinned hard reg */
v0 = (v0 < v1);
if (v0 != 0) { goto TRUE1; }

/* v0 is `register s32 v0 __asm__("$2")`; v1 a sibling pinned s16-cast temp,
   `register s32 v1 __asm__("$3")` */

The two spellings cost the same instruction count — a bare relational has no join and no constant arms, so §195-E's "naming materialises a 0/1" does not fire and the naming is byte-free apart from the register it buys.

BYTE EVIDENCE. func_80180DCC, 66 ins, all quotes from match_one --json on the pinned triple. v0 (backlog draft, register s16 s1 pin + call-site (s16) casts) → closeness 9, STRENGTH/sll!=sra. Unpinning s1 → closeness 6, same sig (block 2 clean, block 1's sll/sra/slt/bnez still wrong). v6 (pin register s32 v1 __asm__("$3"), hoist v1 = (s16)s1; v0 = (s16)v0; as statements ahead of a still-bare if (v0 < v1)) → 6 → 2: {"status":"near","closeness":2,"residual":[[34,"0043182a slt v1,v0,v1","0043102a slt $v0, $v0, $v1"],[35,"14600013 bnez v1,…","14400013 bnez $v0, .L80180EA8"]],"verdict":{"klass":"REGALLOC-PERM","sig":"REGALLOC-PERM/$v1>$v0","detail":{"map":{"$v1":"$v0"}}}}. v7, the only change being if (v0 < v1) {…} → v0 = (v0 < v1); if (v0 != 0) {…} → 2 → 0: {"status":"match","closeness":0,"residual":[],"verdict":{"klass":"MATCH"}}.

BOUND. (1) n = 1. (2) Only the v6 → v7 delta is solo-proven (§266): the pins and the cast hoist arrived in compound edits and are co-requisites of the shape, not separately ablated — the pin is what makes "the variable's hard register" a nameable thing here, and an unpinned local would only move the dest to whatever that local got. (3) Routing: on a REGALLOC-PERM whose single wrong register is the compare's DEST, try this first — one statement, zero bytes; go to §164-39's fence or its addendum's transposition only when the wrong register is one of the compare's INPUTS. (4) Says nothing about compare-against-constant shape (slti/sltiu immediate-vs-register is §194-L's 2-D lookup) or about signedness (§280).