Files
BFM-decomp/cookbook/C0484.md
T

4.0 KiB

§435 ★★★ — AN OVERLAY TU SPLIT IS NEAR-FREE, AND THE GAP TEST IS "IS THIS WORD A CODE ADDRESS", NOT "IS IT ZERO" (P31 S74; four overlays split, all byte-identical, CARVE-BLOCKED class emptied fleet-wide)

What was done. The last four carve-blocked subsegs — ov_SC01_084_jr_8017F690, ov_SC02_005_jr_80181D30, ov_SC02_011_jr_8017AE2C, ov_SC03_105_jr_8017C8D0 — were split at their jtbl-span TU boundaries (§431), each proved BYTE-IDENTICAL with nothing banked, and split_indicator went to 213 OK of 213. 17 open switch functions were unblocked. The make tools-health check is a HARD GATE from this session on, as its own comment promised.

1. OVERLAY SPLITS COST ALMOST NOTHING, AND THE REASON IS STRUCTURAL — the opposite of main. Measured crossings, each with its denominator (R41):

overlay names that crossed typedefs that crossed compiler errors on the first post-split build
ov_SC03_105 0 of 3,074 0 of 21 0
ov_SC01_084 1 of 2,679 0 of 23 1
ov_SC02_011 2 of 3,254 0 of 27 3
ov_SC02_005 5 of ~44 typedefs 5 of 44 16

main's split moved 57 of 1,247. The difference is the Phase-26 §8b carried decl layer: an overlay TU re-emits each region's externs locally (one of these files carries 128 separate #include "common.h" lines), so a cut inherits its declarations automatically and only typedefs — which the layer deliberately does not repeat — can cross. Budget an overlay split at "find the typedefs", not "refactor the declarations".

2. THE MINIMUM SPLIT IS NOT ALWAYS THE ONE THE SPANS NAME. Two of the four needed THREE pieces, and the third cut was invisible to the span list: each already carried a carve run from previously banked functions, and that run could not merge with span 1 because of what sat between them.

ov_SC02_005   existing carve 0x801E2E38..0x801E2E70 | 0000F040 00000000 | span 1 0x801E2E78...
ov_SC02_011   existing carve 0x801E9D60..0x801E9DA0 | FEBEF6AE 000002DC 0 0 | span 1 0x801E9DB0...
ov_SC01_084   existing carve 0x801C60C8..0x801C6104 | 00000000 (ONE zero word) | span 1 0x801C6108...

The first two gaps are real data — 0000F040 is not a code pointer, and 8 bytes is twice the widest .align 3 pad the JTBL_PADS spec can emit; FEBEF6AE 000002DC is most likely a TU's trailing static const. The third is a compiler-emitted .align 3 pad (0x801C6108 ≡ 0 mod 8) and merges cleanly. So the load-bearing test is "is this word a valid code address in this binary's text range", not "is it zero" — a zero-word detector would have merged spans A and B on ov_SC02_011 and produced an unbuildable carve. Run the test on the payload words BEFORE choosing the cut, and let jtbl_carve confirm: it refuses non-contiguous carves loudly and by name.

3. THE CARVE IS A CONSEQUENCE OF BANKING, NOT A PREDICTION OF IT — and the refusal is correct. Carving a span whose owners are still INCLUDE_ASM writes a JTBL_PADS spec with no partner, and the build stops: jtbl_rodata_pads: consumed 3 rodata jump table(s) but 9 pad spec(s) given — table-count drift vs the carve. That is R43 working, reproduced identically on the PRISTINE unsplit config, so it is a property of the pads pipeline and never evidence about a split. A single-table carve has no pad spec and so probes green either way — which is why the probe in this session's split brief used one. Carve when the function banks.

4. THE PROBE PROTOCOL that made all four verdicts trustworthy. Negative-control an UNMODIFIED build in the worktree FIRST (R35/R40 — a red baseline makes every later verdict a claim about the harness); split; clean rm -rf asm/<bin> + re-extract + -j build + make check; then jtbl_carve --func <span-2 owner> + rebuild as an unlock proof, and revert it. Provision a split worktree with verify_worktree.provision() — the canonical list of the three untracked build deps (.venv, cc1 from the committed checksum-verified tarball, extracted/); do not hand-roll it.