3.3 KiB
§446 ★★★ — "RELOC-ONLY REMAP GATED DIFF" IS A CARVE VERDICT, NOT A CODEGEN VERDICT, UNTIL YOU DIFF THE .text (P31 S75; 4 siblings + 1 latent, ~1,300 ins)
Four 279-ins siblings of a banked exemplar (ov_SC01_004/005/006/008 ← ov_SC01_009:func_8017EB08)
sat DIFF across five sessions and were carried as a codegen wall. Their .text was byte-identical
the whole time. Word-level classification against the retail images — run twice, independently, by a
Fable agent and then by me from scratch, with identical results:
nins=279 EQ 213 · RELOC-HI16 23 · RELOC-LO16 24 · INTERNAL-J 19 · CODEGEN 0
213 identical + 23 lui hi16 + 24 %lo + 19 internal j (26-bit target differs, function-relative
offset identical) = 279. Zero register-allocation, instruction-selection or scheduling
differences, on all four. RELOC-ONLY was true of the C, not merely of the asm.
THE DEFECT — AND WHY THE GUARD STOOD DOWN EXACTLY WHEN IT WAS NEEDED. jtbl_carve reserved ONE
WORD TOO MANY for each table. spimdisasm runs the island's LAST jtbl_ dlabel one word into the
following NON-ZERO data (string bytes 0x696F760A / 0x000013FF / 0x62647020), so the zero-word
trim cannot see it. The over-span clamp that would have caught it was guarded by
len(sltiu_bounds) == 1 — and sltiu is also how gcc emits an unsigned range check
((u32)(x - lo) < n, I1). These four carry five distinct sltiu immediates
{0x4, 0xA, 0x28, 0x32, 0xF0}, so the guard silently disabled itself on precisely the functions
that needed it. A 0x28 table got 0x2C reserved: the image comes out 4 bytes short, ~850 %lo
immediates shift, and the whole-binary gate reports DIFF about a function whose own bytes are perfect.
Fix: a PER-TABLE bound. gcc-2.7.2's dispatch is a fixed idiom — sltiu $v0,$idx,N ; beqz ; sll ; lui $at,%hi(jtbl_X) ; addu ; lw $v0,%lo(jtbl_X)($at) — so the sltiu nearest ABOVE that table's own
%hi(jtbl_X) is unambiguous whatever else the function tests.
SECOND DEFECT, STACKED BEHIND THE FIRST. A carve span whose JTBL_PADS line carries no
tables= comment (written by jtbl_pads_fix, or predating persistence) fell through both merge
branches, lost its existing table's start, and refused with "table starts do not fit the span" —
which harvest_verify then "repaired" with a needless jr_isolate_all, which walked straight back
into defect 1. The invariant the validator already asserts settles it: every carve span begins with
a table, so its start is always a known start, whatever the line says.
THE DIAGNOSTICS THAT FIND THIS IN MINUTES (all cheaper than reading the C):
- the built image is a few bytes SHORTER than retail;
- the first differing word is a
%loimmediate FAR BEFORE the function under test; git diff config/after the gate's carve shows thetailpiece moved by more than4 x entries.
THE LAW. When a standalone-MATCH jtbl draft gates DIFF, diff the carve extent against
4 x sltiu before touching the body. And note what the negative control bought: run over every
other open table-bearing stub fleet-wide, the fixed bound changed exactly one more table —
ov_SC06_022:func_80185B80, a fifth victim nobody had drafted against. A guard that disables itself
on a common idiom does not fail once; it fails quietly across the whole corpus.