fix(phase-30 S48-0b): JTBL_PADS follows its span through jr isolation

The 0b blocker was not "the pads line is left behind" alone — it fails two
different ways, and the second one is silent:

* bare isolate + `make build`: the stale line arms the pads filter on the
  RESIDUAL object, which emits no jump table ->
  `jtbl_rodata_pads: consumed 0 rodata .align(s) but 4 pad spec(s) given` (S47).
* isolate -> jtbl_carve (the jtbl_family_bank path): `set_pads_vars`
  regenerates the block keyed by the CURRENT subseg names, finds no prior spec
  under the new `_jr_<addr>` name, and DROPS the line. cc1's natural `.align 3`
  then pads the span's non-8-aligned interior tables and the image shifts —
  reported only as `built, bytes differ`.

- jr_isolate_all.repoint_overlays_mk: repoint the `build/src/<ov>/<sub>.o:
  JTBL_PADS` target with the `--order` leaf whenever a carve moves; refuse
  loud if the old object still hosts a .rodata piece (R32).
- jtbl_carve.set_pads_vars: second, disagreeing oracle (R34) — refuse when a
  spec would vanish for a subseg no longer in the carve set (rename/merge
  drift), instead of silently emitting a padless object.

R37 probe: func_801789AC -> ov_SC02_037 went `built, bytes differ` -> BANKED
on the whole-binary byte gate. ov_SC02_037's spec is 0,0,0,0 over tables
+0x0,+0x14,+0x34,+0x4c — load-bearing (span start is 4 mod 8).
This commit is contained in:
Drew T
2026-08-11 15:31:57 -06:00
parent 20a7cc59ee
commit ec749fa584
7 changed files with 13404 additions and 5984 deletions
+2 -2
View File
@@ -4938,8 +4938,8 @@ ov_SC02_037_ELF := $(ov_SC02_037_OUT).elf
ov_SC02_037_MAPFILE := $(ov_SC02_037_OUT).map
ov_SC02_037_LD_SCRIPT := $(ov_SC02_037_OUT).ld
ov_SC02_037_SPLAT_YAML := config/splat.ov_SC02_037.yaml
ov_SC02_037_JTBL_INTERLEAVE := --order tail.data.o,ov_SC02_037.o,tail2.data.o,trailing.o # Phase-26 §8 jtbl-rodata carve
build/src/ov_SC02_037/ov_SC02_037.o: JTBL_PADS := 0,0,0,0 # §8e pads (jtbl_carve.py) tables=+0x0,+0x14,+0x34,+0x4c
ov_SC02_037_JTBL_INTERLEAVE := --order tail.data.o,ov_SC02_037_jr_801789AC.o,tail2.data.o,ov_SC02_037_jr_8017AE2C.o,tail3.data.o,trailing.o # Phase-26 §8 jtbl-rodata carve
build/src/ov_SC02_037/ov_SC02_037_jr_8017AE2C.o: JTBL_PADS := 0,0,0,0 # §8e pads (jtbl_carve.py) tables=+0x0,+0x14,+0x34,+0x4c
ov_SC02_037_CHECK_SHA := config/check.ov_SC02_037.sha
ov_SC02_037_SYMBOLS := config/symbols.ov_SC02_037.txt
ov_SC02_037_SIG := .run/sig.ov_SC02_037.jsonl
+7 -3
View File
@@ -93,10 +93,14 @@ segments:
# shift the code start. Mark it `rodata` (section_order places .rodata first) so it lands at
# 0x80128158 ahead of the code, a 1-word analogue of main's rodata-island (no ld_interleave).
# - [0x0, rodata, head]
- [0x0, c, ov_SC02_037] # code: starts at file 0x0 (overlays open with a prologue)
- [0x0, c, ov_SC02_037]
- [0x50854, c, ov_SC02_037_jr_801789AC]
- [0x52cd4, c, ov_SC02_037_jr_8017AE2C]
- [0x5ba68, data, tail]
- [0xa01d4, .rodata, ov_SC02_037] # Phase-26 §8 jtbl-rodata carve (jtbl_carve.py)
- [0xa0238, data, tail2]
- [0x9ff24, .rodata, ov_SC02_037_jr_801789AC] # Phase-26 §8 jtbl-rodata carve (jtbl_carve.py)
- [0x9ff3c, data, tail2]
- [0xa01d4, .rodata, ov_SC02_037_jr_8017AE2C] # Phase-26 §8 jtbl-rodata carve (jtbl_carve.py)
- [0xa0238, data, tail3]
- [0xA198C, bin, trailing] # final 3 bytes (EOF not 4-aligned; spimdisasm drops a partial word)
- [0xA198F] # EOF marker = the 0.4.dec byte length
# @TRAILING@ (above) is replaced by tools/new_overlay.sh: for a non-4-aligned overlay it becomes
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+37 -5
View File
@@ -535,7 +535,26 @@ def _render_region(header, items, old_sub, new_sub, ambient):
return "\n".join(parts) + "\n"
def repoint_overlays_mk(carve_renames, dry):
def repoint_overlays_mk(carve_renames, dry, ov=None, cfg_lines=None):
"""Repoint both overlays.mk consumers of a renamed carve object: the `--order` leaf AND the
§8e `JTBL_PADS` target var.
THE PADS LINE MUST FOLLOW ITS SPAN (P30 S48, the 0b blocker). A multi-table span's pad spec is
keyed by the OBJECT that emits the tables, so when isolation moves the span's owner into
`<ov>_jr_<addr>` the spec has to move with it. Leaving it behind fails TWO different ways, both
observed on ov_SC02_037's 4-table span (spec `0,0,0,0`, tables +0x0,+0x14,+0x34,+0x4c):
* plain `make build` after a bare isolate -> the stale line arms the pads filter on the
residual object, which now emits NO jump table:
`jtbl_rodata_pads: consumed 0 rodata .align(s) but 4 pad spec(s) given` (S47, hard error);
* the jtbl_family_bank path (isolate -> jtbl_carve) -> `set_pads_vars` regenerates the block
keyed by the CURRENT subseg names, finds no prior spec under the new name, and the line is
SILENTLY DROPPED. cc1's natural `.align 3` then pads the span's non-8-aligned interior
tables (+4 before table 2 here) and the image shifts: `built, bytes differ`.
Byte-neutral: only the target NAME changes; the spec and its `tables=` record are untouched.
Fails loud (R32) if the old object still hosts a `.rodata` piece — then the line is ambiguous
(jtbl_carve's invariant is one contiguous .rodata run per object, so this should be
unreachable; if it ever fires, the carve set is the thing to fix, not this rename)."""
mk = os.path.join(REPO, "config/overlays.mk")
txt = open(mk).read()
changed = []
@@ -543,7 +562,20 @@ def repoint_overlays_mk(carve_renames, dry):
pat = rf'(--order[^#\n]*?){re.escape(old_sub)}\.o'
if re.search(pat, txt):
txt = re.sub(pat, lambda m: m.group(1) + new_sub + ".o", txt, count=1)
changed.append(f"{old_sub}.o -> {new_sub}.o")
changed.append(f"--order {old_sub}.o -> {new_sub}.o")
if ov is None:
continue
pads_pat = rf'^(build/src/{re.escape(ov)}/){re.escape(old_sub)}(\.o: JTBL_PADS := )'
if not re.search(pads_pat, txt, re.M):
continue
if cfg_lines is not None and any(
re.match(rf'^\s*- \[0x[0-9A-Fa-f]+,\s*\.rodata,\s*{re.escape(old_sub)}\]', ln)
for ln in cfg_lines):
sys.exit(f"jr_isolate_all: {old_sub} has a JTBL_PADS line AND still hosts a .rodata "
f"carve after the split — refusing to repoint the spec to {new_sub} (R32). "
f"One object must own at most one contiguous .rodata run.")
txt = re.sub(pads_pat, lambda m: m.group(1) + new_sub + m.group(2), txt, count=1, flags=re.M)
changed.append(f"JTBL_PADS {old_sub}.o -> {new_sub}.o")
if not dry:
open(mk, "w").write(txt)
return changed
@@ -588,10 +620,10 @@ def main():
f"subseg lines from an un-reverted isolation — `git diff config/splat.{a.ov}.yaml` "
f"and clean it first.")
seen_off, seen_nm = off, seen_nm | {nm}
mk_changes = repoint_overlays_mk(carve_renames, dry=True)
mk_changes = repoint_overlays_mk(carve_renames, dry=True, ov=a.ov, cfg_lines=cfg_lines)
print(f" -> {len(new_files)} region .c files; carve repoints: {carve_renames or '(none)'}")
for c in mk_changes:
print(f" overlays.mk --order: {c}")
print(f" overlays.mk: {c}")
if a.dry_run:
print(" [dry-run] no files written.")
@@ -605,7 +637,7 @@ def main():
# shadow nothing since we overwrite region 0 to the same path). Write all region files:
for path, content in new_files.items():
open(path, "w").write(content)
repoint_overlays_mk(carve_renames, dry=False)
repoint_overlays_mk(carve_renames, dry=False, ov=a.ov, cfg_lines=cfg_lines)
print(f" wrote config + {len(new_files)} region files + overlays.mk. Run `make extract "
f"BINARY={a.ov} && make build BINARY={a.ov}` to byte-gate (R22).")
+15
View File
@@ -721,6 +721,21 @@ def set_pads_vars(ov, pads_map):
txt = open(mk).read()
before = current_pads_specs(ov, txt)
after = {sub: sr for sub, sr in pads_map.items() if len(sr[0]) > 1}
# SECOND, DISAGREEING ORACLE (R34; P30 S48). The carry above is keyed by SUBSEG NAME, so a span
# whose owning object was RENAMED (jr_isolate_all moving it into `<ov>_jr_<addr>`) looks like a
# span with no prior spec and its line is dropped — silently, and never byte-neutral: the pads
# exist precisely because cc1's `.align 3` would otherwise pad a non-8-aligned interior table
# (measured on ov_SC02_037: spec 0,0,0,0 lost => `built, bytes differ`). A spec may legitimately
# disappear only when its span is re-derived to a single table; if the SUBSEG ITSELF is gone from
# the carve set, the loss is drift, not derivation. jr_isolate_all now repoints the line with the
# span, so this should be unreachable — it is here because the failure mode is a silent byte diff.
live_subs = set(re.findall(r"- \[0x[0-9A-Fa-f]+,\s*\.rodata,\s*(\w+)\]", open(cfg_path(ov)).read()))
vanished = [s for s in before if s not in after and s not in live_subs]
if vanished:
sys.exit(f"jtbl_carve: JTBL_PADS spec(s) for {vanished} would VANISH — their subseg is no "
f"longer in {ov}'s carve set, so the spec was not re-derived, it was LOST (R32/R34). "
f"If the object was renamed, repoint the line to the new object; if two spans "
f"merged, fold the `tables=` starts into the surviving line first.")
# drop all current lines for this overlay, then insert the regenerated block
txt = re.sub(pads_line_re(ov) + r"\n", "", txt, flags=re.M)
if after: