6.0 KiB
§260 — THE §154-A LEADING-ISLAND SPLIT: ONE CONFIG LINE, AND THE ISLAND PEELS FROM THE END (P31 S59, byte-proven)
An md_* module binds .rodata at offset 0 to the same subseg as its code, so the object's rodata
order is the C file's include chain: the INCLUDE_RODATA blobs, then every INCLUDE_ASM'd
function's still-migrated jump table, in source order. That island reproduces byte-exactly while
the functions are stubs, and the moment one is matched its table leaves the chain and cc1 re-emits
it at the end of the object's .rodata — the +8-and-everything-shifts failure P30 S48 measured.
THE WHOLE FIX IS ONE INSERTED CONFIG LINE PLUS AN ISOLATION. On md_SC03_076 /
func_801F218C:
- [0x0, .rodata, md_SC03_076] # leave the island piece ALONE
- [0x268, .rodata, md_SC03_076_jr_801F218C] # the split: the function's own table
- [0x27c, c, md_SC03_076]
- [0x2d24, c, md_SC03_076_jr_801F218C] # jr_isolate_all.py --only
Do not create a _pre piece (a dotted .rodata with no sibling .c points splat's ld at a
never-built implied C file and unbinds the island from its module). Do not touch
ld_interleave — the generated script is already rodata-first in yaml order. Use
jr_isolate_all.py --only, not jr_isolate.py (its backend sys.exits on a top-level extern
block).
THE CONTROL THAT SEPARATES "IT WORKED" FROM "IT DID NOTHING." A green SHA proves nothing on its own here, because a split that silently failed to happen is also green. Pair it with the object-level size:
| whole-binary sha1 | md_SC03_076.o .rodata |
md_SC03_076_jr_801F218C.o .rodata |
|
|---|---|---|---|
| before | 9a165e36… |
0x27c (the whole island) | — |
| after | 9a165e36… |
0x268 | 0x14 (the 5-entry table) |
THE ISLAND IS A STACK. Census of md_SC03_076 (file offsets): D_801EF468 0x000, D_801EF540
0x0D8, then func_801EFBB4 0x144 · func_801F0210 0x1B4 · func_801F0734 0x1EC ·
func_801F0A9C 0x214 · func_801F0F28 0x23C · func_801F218C 0x268 → 0x27C, the island end. Only
the end-adjacent table carves cheaply; each isolation makes the next one end-adjacent, so a
module peels from the end, one function at a time. Picking a middle table first is what makes the
job look like cascading isolation.
TWO TOOL BLINDNESSES THIS EXPOSED, both md_*-only and both now fixed. (1)
jtbl_carve.parse_config took the FIRST data/.rodata piece in the file rather than the trailing
run after the last c — identical on 171 configs, and on the 42 md_* it pointed at the island, so
apply()'s splice DELETED the c line and wrote the yaml to disk before erroring for unrelated
reasons. (2) jr_isolate_all.jr_inventory asserts every .rodata piece resolves to exactly one
banked owner (R32) — true where jtbl_carve created every piece, false for the island, which has no
owner; it aborted with UNOWNED 0x801ef468 and md_* could not be isolated at all. The structural
discriminator, verified over all 213 configs: a .rodata piece at offset 0 whose subseg is the
binary's own alias exists in exactly the 42 md_* and in none of the others.
§260-A — STAGE 2 IS PROVEN, AND THE WHOLE jtbl PIPELINE IS AUTOMATED AT THE GATE (P31 S59, same day)
Three byte-proofs, one per structural class, each carve→draft→bank through
tools/jtbl_lane.py → gate_stage.run_gate → harvest_verify._jtbl_prep_one with the live
campaign running (per-binary flock + exclusive shared lock + the campaign's draw lock held across
gate+commit — gates never overlap):
- tail (ov_SC03_014/func_8017DCC0, 45 ins, commit
b801b499e): the §8b same-subseg ADJACENT MERGE + §8e single-table-predecessor pad recovery, untouched machinery — jr_8017AE2C.o .rodata 0x14→0x28 TIGHT,JTBL_PADS := 0,0 tables=+0x0,+0x14self-derived, shad84b01a2…green. - covered (md_SC03_076/func_801F218C, 83 ins, commit
bad793c73): §260 STAGE 2 — the matched C emits the island table itself; sha9a165e36…IDENTICAL to stage 1, jr .rodata 0x14 now COMPILER-EMITTED, md .rodata 0x268. The first md_ jr function ever banked.*jtbl_carvenow reports this state as a no-op success instead of the historical refusal. - island-end (md_SC03_135/func_801E5358, 83 ins, commit
f74ad7ac8): the FULL split done BY THE GATE on a virgin module — isolate → insert- [0x268, .rodata, <ov>_jr_<ADDR>]→ re-extract → covered no-op → bank; shab901fda5…, md.o 0x27c→0x268 + jr.o 0x14. ~1.1 s wall (an md module is 34 KB; R40-checked against artifact mtimes before believing it).
The joins that made it automatic (each one line to find): jtbl_carve.island_probe — the
READ-ONLY structural classifier (tail / covered / island-end / island-blocked / island-pads /
main-manual) that build_wave_atlas (--levers jtbl-carve only) and jtbl_lane --census route
on; jtbl_carve --island-split — the one-line §260 insert, refusing non-end-adjacent tables;
harvest_verify._ISLAND_WALLS — the prep branch that runs isolate (body spliced, §61b) →
split → extract → re-carve on the §154-A refusal. Census over all 245 jtbl members (R32):
tail 173 / island-end 7 / covered 1 = 181 members, 26,445 ins reachable unattended;
main-manual 47 (parked), island-blocked 10 (stack-ordered, convert as peels land), island-pads 6
(needs §8e pads wired for config/modules.mk — jtbl_carve's mk writers are overlays.mk-only),
no-jtbl 1 (atlas mislabel). ORDER LAW for the island branch: isolate FIRST, insert SECOND —
jr_inventory's 1:1 owner assertion runs inside the isolation, and the fresh line has no owner
until the body is real C. Drafter nuance that cost the only iteration: a callee reached via an
eagerly-filled branch delay slot (a0=s0 serving the ELSE path) reads like an argument —
func_801EF6E4 takes NONE (extern void f(void)), and the one-mismatch signature is a redundant
move a0,s0 in the jal's own slot. Full design + failure semantics:
docs/sunset/tool-designs/jtbl-automation-s59.md.