Files
BFM-decomp/cookbook/C0489.md
T

3.1 KiB

§440 ★★★ — A .rodata CARVE PIECE BINDS TO A SUBSEG, NOT TO A FUNCTION: EXTEND THE CARVE INSTEAD OF ISOLATING (P31 S74; 6 banked, resident included)

§8b's "non-adjacent tables → ISOLATE the function into its own subseg" is over-strict. A carve piece attaches to a code SUBSEG, and that object's .rodata is the address-ordered concatenation of

  • cc1's tables for the functions in it that are banked, and
  • still-stubbed functions' migrated tables, .included by their INCLUDE_ASM in source order.

So a span may legitimately hold a MIX, and a carve may be EXTENDED across material that belongs to functions nobody has drafted yet. Measured: extending ov_SC06_029_jr_8017C954's carve across an align-pad word and two unrelated still-stubbed tables was byte-identical with nothing banked, and then took two banks — where the tooling had demanded a jr-isolation.

Four corollaries, each byte-proven:

  1. Migrated tables self-align. spimdisasm emits the block's .align 3 iff the table's span-relative offset is 8-aligned (+0x0 got one; +0x3c and +0xb4 did not; +0xc8 got one and reproduced the retail zero word at +0xc4). So a stubbed table needs no spec entry.
  2. JTBL_PADS counts cc1 tables ONLY (the filter keys on .align 3 followed by $L), so a mixed span's spec grows as each sibling banks: 0,0,4,4 → 0,0,4,4,0 → 0,0,4,4,0t1,0.
  3. The zero-word rule (§8e) is INVALID across a migrated boundary — the word before the entry is zero, but that zero is supplied by the preceding migrated block, so the lead entry is 0, not 4.
  4. A covered table at a 4-mod-8 span offset gains 4 bytes when it banks, because cc1 always emits .align 3. That is the covered-tpad case: bankable, but it needs a 0t<n> spec entry.

And the verdict that hid all of this. A table already inside a carve bound to its own subseg needs no carve work at all — in stub state the migrated block fills the piece exactly, and banking swaps it for cc1's identical one. island_probe classified such a function tail on the table's ADDRESS; apply() sent it to build_carve, which resolves spans out of the RAW data asm where a carved table no longer is; harvest_verify booked CARVE-REFUSED. That is a verdict about the route we chose, not about the function (R43). jtbl_carve now has covered / covered-tpad verdicts and treats a fully-covered batch as a no-op.

THE RESIDENT CAN CARVE LIKE AN OVERLAY — its three tables are adjacent and lead its island (0x450e0..0x451ac, one span, subseg resident). The new part is the layout: the resident opens with - [0x0, rodata, hdr], a 1-word .rodata header BEFORE the code, so it is rodata → text → data → rodata(carve) → data. ld_interleave --order cannot express that — every listed piece is emitted after TEXT_START, so hdr.rodata.o(.rodata) lands in the unchecked empties bucket, is parked after the text, and moves every byte. --pre places a leading-rodata piece ahead of the text; resident_JTBL_INTERLEAVE := --pre hdr.rodata.o --order tail.data.o,resident.o,tail2.data.o.