Files
BFM-decomp/cookbook/C0099.md
T

4.2 KiB

§88 — cross_jump will not merge a common suffix containing a CALL; and §78 is scoped to ORDERED comparisons only (Phase 29 SESSION-20, the behemoth close-out)

Three behemoths closed the same night (func_80183814 5,122 · func_8017D2DC 1,586 · func_8017DC1C 1,518), taking the fleet to zero unmatched functions >1000 ins. Their durable output:

§88a — repeated CALL-shaped blocks are left UNMERGED; call-free tails are merged for you

Measured on func_8017D2DC: ~34 byte-identical 6-instruction jal-bearing blocks were left unmerged, and case 66's two byte-identical 12-instruction blocks likewise — while every call-free tail (.L8017EB5C/EB64/EB70, .L8017E9A0/E9AC/E9B4, .L8017E06C, .L8017DAF0, .L8017E804) was merged. ⇒ Write repeated call-shaped cases LONGHAND; never hand-factor the call-free tails — the compiler does that itself, and pre-factoring them puts you off-target.

§88b — the slti literal-position law (extends §78 to comparisons)

MIPS slti can only carry its constant on the RIGHT, so mips.c rewrites LE/GT vs CONST_INT into LT(x, K+1). Consequence, decidable from bytes alone: if the target materialises the limit into a REGISTER on the LEFT of slt (li K; slt K,x; beqz), K was NOT a literal in the source. All eight literal spellings were swept (> K, >= K+1, K < x, !(x<=K), ++x > K, early-return…) and every one folds to slti x,K+1. Only a block-local variable holding the limit (s32 lim = K; if (lim < X)) reproduces it — and per §76 that variable's block scope matters: if its live range spans a call it takes a callee-saved register and the li hoists.

⚠️ §88c — THE MIRROR-IMAGE FALSE POSITIVE (this one costs 25 wasted edits)

addiu $vX,$zero,1; bne looks exactly like §88b's "the constant was materialised ⇒ it was a variable" signature. It is not. MIPS has no beqi, so equality constants are ALWAYS materialised. §78/§88b are scoped to ORDERED comparisons only — the ones mips.c rewrites into slti. Believing the signature on an ==/!= sends you rewriting perfectly correct literals.

§88d — BANKING ORDER: run the §81 carve chain BEFORE banking, never after

Banking func_8017DC1C first broke the build. Its draft establishes the canon for 39 previously-undeclared externs; jr_isolate_all's re-partition (overlay_src_split) then dropped all 39 across the new split boundary — D_801C1EB0 undeclared, present in NEITHER file. That is the §77 preamble-drop class in a third tool. The fix is ordering, not patching: carve chain first on a clean tree (each step byte-gated), then bank. Re-sequenced, both banked first try.

§88e — a wrong diagnosis, refuted properly (the model for how to treat an inherited lever)

Round 1 handed round 2 "close the +5 length delta to move max_reg", citing cse.c:8340. Round 2 refuted it three ways: a 15-line reproducer reproduced the case-0/3 CSE exactly (so it cannot be max_reg-gated); max_qty only gates extension ACROSS blocks and both call groups share one block; and the target leaves $s7/$fp unused, killing the pressure story. The real discriminator came from a second direction — case C01 has the identical two groups over the identical symbols with zero residual, because a break puts a CODE_LABEL between them. The +5 was a SYMPTOM. Both biggest levers were pure DECLARATION SCOPE (§45/§76), not pins. ⇒ An inherited "named lever" is a hypothesis with a citation, not a fact. Reproduce it small before spending a round on it.

§88f — the missing rung: a RELOCATION gate between match_one and the binary

match_one masks jal/HI16/LO16 and never links (§81/§84/§87). The behemoth run produced .run/giants/s21_g21_reloc_verify.py, which resolves every relocation — including the implicit MIPS-REL addend objdump -r does not print (§84's trap, in tool form) — against the target: 359/359 jal callees, 60/60 internal j destinations, 406/406 %hi/%lo addresses incl. all 6 derived-offset sites. Promote this to tools/ — it closes three of the four blindness classes before a gate cycle is ever spent.