Files
BFM-decomp/cookbook/C0227.md
T

3.8 KiB

§206 — THE JTBL-CARVE DRAFTING IDIOM: the bounds check is the entry count, and an EMPTY case owns a slot (P31 S56)

Scope. 286 open jtbl-carve members, 46,068 instructions, 191 families — of which 245 members (36,685 ins) are COLD (no sim≥0.99 twin) and therefore need this idiom rather than a remap. Worked exemplar: func_801F218C (md_SC03_076, 83 ins, jtbl_801EF6D0), MATCH in 5 oracle calls, reloc_identity AGREE on 13 relocations.

1. THE RECOGNITION TELL — the bounds check IS the entry count. Find the sltiu $vN,$vM,K feeding the beqz that guards the jr. K is the number of cases:

lui  $at, %hi(jtbl_801EF6D0) ; lw $v0, %lo(jtbl_801EF6D0)($at) ; jr $v0
sltiu $v0,$v1,0x5            <- FIVE entries

CONFIRM against the dlabel…enddlabel span, but when they disagree trust the sltiu: a trailing extra word is §8e alignment pad, not a case (§8a's law, byte-confirmed on func_8015AE2C at 7-vs-8). Here the span is 5 and 0x801EF6D0 is 8-aligned, so no pad was possible.

2. AN EMPTY CASE OWNS A TABLE SLOT, AND THE SLOT POINTS AT THE EPILOGUE. (new — not in §8a/§8e) Read the table entries as labels first, and only then assign case numbers:

[case0=.L801F21C8, case1=.L801F22C4 (= the EPILOGUE -> EMPTY case), case2=.L801F2238, ...]

An entry aimed at the epilogue is case N: break; — it must be written literally. Omitting it is not cosmetic:

variant result
case 1: break; present (correct) MATCH 83/83
the empty case omitted 90 ins, 66 mismatched

Note the direction: dropping it makes the function seven instructions LONGER, because gcc re-derives a denser table and different bounds handling — so a length-drift of +N on a jtbl function is a tell for a missing empty case, not for a missing body.

3. WHEN THE OUT-OF-RANGE TARGET EQUALS A TABLE ENTRY, THERE IS NO default CLAUSE. (new) One observation settles two source decisions: the beqz (out-of-range) target here is .L801F22C4, which is also table entry 1. So the source has no default clause AND entry 1 is the empty case.

4. A 3-CASE DISPATCH ON A CALLEE RETURN IS A NESTED switch, NEVER AN IF-CHAIN. (new, ablated) gcc-2.7.2 compiles switch(t){case 1;case 2;case 3;} to a binary tree — beq t==2 first, then slti at the middle value, li/beq leaves, explicit j to the join for the fall-through leaf. The if-chain form does not reproduce it:

inner form result
nested switch (t) MATCH 83/83
if (t==1) … else if (t==2) … else if (t==3) 77 ins, 51 mismatched

5. TAIL-MERGE ACROSS CASES IS SOURCE-ORDER, NOT A LEVER TO FIGHT. Two sites share jal func_80178CBC; sh $zero,0x34: one arrives by an explicit j, the other FALLS THROUGH. Write the same two statements in each case and the asymmetry falls out of source order for free.

6. KEEP EXACTLY ONE POINTER LIVE. Type the parameter as the struct pointer itself; a param + derived copy pair forced $s0+$s1 saves and a 32-byte frame against the target's 24.

BANKING (this is drafting only — the carve is a separate, deterministic step). The draft matches against the .s, but banking needs the §8a carve first: move jtbl_801EF6D0 (5 words, 8-aligned ⇒ no pad) into the binary's rodata subseg per §8/§8b's ld_interleave sandwich, then replace the INCLUDE_ASM line. tools/jtbl_family_bank.py does carve → extract → remap → gate per sibling.

PROVENANCE. Cracked and distilled by stealth/ox-alpha (free tier) under the P31 S56 bake-off, at $0.00 and 5 oracle calls. Both negative results in §4 and §2 were independently re-run and byte-confirmed before this entry was written (51 and 66 mismatches respectively) — the model asserted them, the gate proved them.