Files
BFM-decomp/cookbook/C0245.md
T

2.9 KiB

§224 — CROSS-JUMP: WRITE THE DUPLICATE, AND READ A SHARED DELAY SLOT AS THE MERGE SIGNATURE (P31 S58)

Bounded by §193-C (cross_jump merges the scheduled common SUFFIX only — there is no head merge) and extends §1893's "exploit cross-jumping" with the recognition tell and two new bounds.

THE RECOGNITION TELL (new). An argument setup sitting in a branch's delay slot and executed on BOTH paths is the signature of two cross-jumped call sites — not a scheduling pin. func_8018C3C0 (ov_SC02_011, 25 ins): the residual after the twin-shaped single-call drafts was ADDRESSING/lui!=addu, and the target's addu $a0,$s0,$zero in the beqz delay slot was the clue. The twin's __asm__ barrier idiom was the wrong tool here (it forced the move early, idx 7); splitting into if/else with two literal calls (0x40000/0xC4000 vs 0x9000/0x24000) reproduced the merged layout naturally.

THE PRESCRIPTION, three confirmations.

card binary what factoring cost what duplicating bought
func_80185CA4 ov_SC02_017 every draft that factored the |= 0x20 RMW out (single temp, early return, sequential ifs) lost 3 instructions plain nested if/else{if/else} with one RMW statement per leaf; the two arms tail-merge onto one shared sw via j — MATCH 27
func_8018B684 ov_SC03_006 the ≥0.9 twin's barrier gave 24 ins / wrong fills duplicate the whole call per arm with literal constants; cross_jump merges only the common jal + ori $a2 tail, landing move a0,s0 in the beqz slot
func_801866C8 / func_801832B4 ov_SC02_017 / ov_SC03_007 — write the jal func_8012A828 literally in both arms; cc1 merges them into one site reached by j-from-if + fall-through-from-else

THE NEW BOUND (worth adding to §1893's bullet). An instruction occupying a MERGED jal's delay slot pins a pre-call statement in that arm. func_801832B4: the else-arm's sh $zero,0x34($s0) sits in the shared jal's slot, so it must be emitted before the call in the else arm's source. The first draft copied the neighbour's visual order (call first, store after) and got near 6 — gcc scheduled the store late and shifted the whole tail by 4 slots. The banked twin ov_SC02_017:func_80187044 had it right.

AND THE FOURTH FACE — a shared tail can be ONE boolean expression, not two ifs. func_801883FC (ov_SC06_029, 29 ins): the layout looks like an if/else-if chain but is a single short-circuit expression whose arms share one tail-merged call body. Two tells: (a) the st==7 arm falls THROUGH into the st==0xA || st==0x42 tests (no jump skips them), and (b) every arm's delay slot reloads li v0,0xA — gcc's bool-to-int lowering recomputing the accumulator in each slot. Drafting it as two separate ifs keeps base live across the call (gcc allocates $s1, extra save/restore, 36 ins vs 29).