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).