5.7 KiB
§8a rodata island in a flat OVERLAY — the tail sandwich, per matched jr-function (Phase 26 — PoC PROVEN)
The EXE's §8 was one central island. The overlays are different: gcc's switch jtbls sit in ONE contiguous
.rodata block at the TAIL of the flat blob — between the .data globals and a tiny .data remnant
(ov_SC01_077: island vram 0x801D7F9C..~0x801D9460, right after .data global D_801D7F94; layout = text →
data-globals → rodata-jtbls → data-tiny). While every jr-function is INCLUDE_ASM the jtbls emit as in-place
.data and the build is byte-fine. The moment you MATCH a jr-function, its C emits the jtbl into .rodata
(which the overlay section_order:[.rodata,.text,.data,.bss] floats to the FRONT @0x80128158) and the raw
copy is still in the data tail → duplicate + wrong address. The proven fix (byte-identical on func_8012ACE0,
a 25-ins single-jtbl jr-function in ov_SC01_077):
- Carve per matched fn. Split the
[…, data, tail]subseg around that function's jtbl(s) into[…, data, tail](globals + pre-carve jtbls, still raw.data) +[<off>, .rodata, <code-subseg-name>](the fn's jtbl → migrates into itsasm/nonmatchings/<subseg>/<fn>.sas.section .rodata; the name MUST match the code subseg the fn lives in, e.g.ov_SC01_077_a) +[<off2>, data, tail2](post-carve jtbls + tail, raw). Other functions' jtbls STAY raw.datauntil they too are matched (per-fn carve, not whole-island). - Place via the parameterized
ld_interleave(Phase-26: added--section .<binary>→ derives the<binary>_TEXT/DATA/RODATA/DATA2/BSSsymbol prefix; default.main= the EXE, byte-identical): it rewrites the overlay's output section to text → data(tail,--front tail.data.o) → rodata → data(tail2+trailing,--tail tail2.data.o --tail trailing.o) → bss. Wired intomake extractvia a per-binary<bin>_JTBL_INTERLEAVEvar inconfig/overlays.mk(holds the--front/--tailbasenames) + anifneq ($(strip $(JTBL_INTERLEAVE)),)branch. GOTCHA: put NO trailing#commenton theJTBL_INTERLEAVE :=line and$(strip)it — a trailing comment leaves whitespace → non-empty → the branch misfires on EVERY binary (ld_interleave then runs with EXE defaults → "front data object not found" on resident). - The C body needs
canon_sig_reconcilebefore it will compile in the real TU (the raw draft hitsconflicting types for <fn>vs the TU's forward decl +conflicting types for <typedef>vs a sibling; reconcile rewrites the def sig to canonical + uniquifies the draft's typedefs + block-scopes externs). Placement is orthogonal — reconcile first, then the carved jtbl lands byte-exact. - Alignment: gcc emits the jtbl
.rdata .align 3(8-byte). If the original jtbl address is 8-aligned (jtbl_801D8078, 0x…078) there is no pad and it lands exact. A 4-aligned original address (jtbl_801D8AFC) would force a 4-byte align pad → HANDLED (Phase 29): the §8eJTBL_PADSpad-spec filter (hit for real byjtbl_801D8144in the func_80131340 bank). - rtu_match is NOT a whole-binary gate for jr-functions — it masks relocs AND excludes the §8 jtbl rodata, so it MATCHes a body whose switch is subtly wrong (e.g. func_80159C84's 2nd jtbl was 5 words vs the real 6 — a false-MATCH). Always confirm jr-function cracks with the whole-binary gate (which now works, via this carve).
- ×134 automation (NEXT): each overlay sibling has the SAME jr-function at a per-overlay address with its own
jtbl in its own tail → the carve config + the
<bin>_JTBL_INTERLEAVEvar must be generated per overlay from the sibling's jtbl address (a tool overfamily_sweep), then reconcile+template the body per sibling. The PoC proves the per-binary mechanism; the fleet rollout is the mechanical generator.
§8a-pad — a trailing .word 0x00000000 under a jtbl dlabel is .align PAD, not an entry (Phase 26 session 6, byte-proven)
⚠ CORRECTED by §8e (Phase 29): the "maspsx drops all
.align" rationale below is FALSE (thatcontinueis in an inventory-only pass; the output path passes.alignverbatim). The TRIM itself remains correct — the pad belongs to the NEXT table's.align 3, absent when that owner isn't compiled in the same object. Multi-table spans now reproduce interior pads via the §8eJTBL_PADSspec filter.
This retroactively explains the §8a func_80159C84 "5 words vs the real 6" false-MATCH.
The raw dlabel jtbl_XXXXXXXX in asm/<ov>/data/*.data.s can span one word MORE than the switch has
cases. That last .word 0x00000000 is the ORIGINAL TU's intra-rdata .align 3 padding — emitted when a
jump table's entries end ≡4 mod 8 and another jtbl of the same TU follows. It cannot be a table entry:
0x00000000 is not a jump target.
- The true entry count is the function's
sltiu <n>range check, not the dlabel span. Byte-confirmed:func_8015AE2C→sltiu $v0, $v1, 0x7= 7 entries, yet its raw dlabel spans 8 words. - maspsx drops all
.align(maspsx.py:435), so a C-emitted jump table can NEVER reproduce the pad. - Therefore
jtbl_carvemust TRIM trailing zero words from the carve range, leaving the pad in the raw post-carvedatapiece. Carving to the next dlabel reserves 8 words while the compiled object supplies only 7 → the.rodatapiece under-fills by 4 bytes → every later symbol shifts +4 (the same image corruption class as §41d: ~271k differing bytes from one missing word). Trimming is always safe. - Existing carves are parsed from the CONFIG (their
end= the next piece's offset), not re-derived from the data asm, so the trim only affects NEW carves — committed banks are unaffected.