feat(phase-29 T2 Arm A): swing verdict = BANKED FACT (9/9 -O0 members on ov_SC07_010); fleet -O0 rollout deferred at the splat wall

- tools/rollout_o0_cluster.py (new) + Makefile O0_CLUSTER_OBJS -O0 wildcard: the -O0-cluster
  carve (0x13410..0x14834), adapting rollout_whale_o0.py to a 3-way <ov>/<ov>_o0/<ov>_o2b split
- ov_SC07_010: carve byte-neutral -> family_sweep --hseq banked 9/9 -O0 exemplar-family members
  whole-binary (R22 clean-fleet 140/140). The Task-1 masked-MATCH swing verdict is now a BANKED
  FACT: -O0 cluster members DO bank at -O0 (§52b). Phase-20 'func_8013B7AC overlay-local' refuted.
- THE WALL (byte-proven, TOOLING not compiler): the same carve on 006/007/011 byte-shifts the whole
  image (+0x20 %lo data-symbol shift, 34% diff) from a CLEAN build; boundaries verified as real
  fn-starts. Root cause = splat re-disassembly of a 3-way-split subseg that still holds INCLUDE_ASM
  stubs (the whale's stub-free _o0b shape avoids it). The Phase-20 '-O0 split infra' wall, root-caused.
- DEFERRED (ROI): full -O0 fleet rollout (~1,233 / ~0.6pp) — 3/4 sampled walled + 134 jr-embedded +
  bigger levers (Task 3 core-cracks, Task 6 tiny-IMM ~5,566). decision-log R31 + cookbook §18-P29.
- 140/140 byte-identical; dedup 1840/0; 0 NON_MATCHING (G4); main 143dbb89. Task 2 substantively done.
This commit is contained in:
Drew T
2026-07-16 17:05:52 -06:00
parent 908dded511
commit f6f89781ff
10 changed files with 12918 additions and 12630 deletions
+8
View File
@@ -564,6 +564,14 @@ build/src/ov_SC01_077/ov_SC01_077_o0.o: CC1FLAGS := -quiet -O0 -G0 -mips1 -mcpu=
WHALE_O0B_OBJS := $(patsubst src/%.c,build/src/%.o,$(wildcard src/ov_*/ov_*_o0b.c))
$(WHALE_O0B_OBJS): CC1FLAGS := -quiet -O0 -G0 -mips1 -mcpu=3000 -mgas -msoft-float -fgnu-linker
# Phase-29 T2 Arm A: the -O0 cluster (0x8013B568..0x8013C98C) carved per single-file overlay into
# <ov>_o0.o (tools/rollout_o0_cluster.py) — same per-file -O0 mechanism so its h_seq family members
# bank whole-binary (the Task-1 swing verdict: they masked-MATCH only at -O0). One wildcard rule
# -O0-compiles every overlay's _o0.o; ov_SC01_077_o0.o already has its explicit rule above (filtered
# out to avoid a duplicate target-specific assignment). `*_o0.c` never matches the whale's `*_o0b.c`.
O0_CLUSTER_OBJS := $(patsubst src/%.c,build/src/%.o,$(filter-out src/ov_SC01_077/ov_SC01_077_o0.c,$(wildcard src/ov_*/ov_*_o0.c)))
$(O0_CLUSTER_OBJS): CC1FLAGS := -quiet -O0 -G0 -mips1 -mcpu=3000 -mgas -msoft-float -fgnu-linker
# link (the .ld pulls in the .o by path) + objcopy to the raw PS-X EXE image.
$(OUT): $(OBJS) $(ASSET_OBJS) $(LD_SCRIPT)
@set -e
+3 -1
View File
@@ -93,7 +93,9 @@ 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_SC07_010] # code: starts at file 0x0 (overlays open with a prologue)
- [0x0, c, ov_SC07_010] # -O2 before -O0 cluster (file 0x0..0x13410)
- [0x13410, c, ov_SC07_010_o0] # -O0 cluster 0x8013B568..0x8013C98C (0x13410..0x14834)
- [0x14834, c, ov_SC07_010_o2b] # -O2 after -O0 cluster (0x14834..data tail)
- [0x59E1C, data, tail] # data tail: pointer tables + packed data (split iterated)
- [0x813EC, bin, trailing] # final 3 bytes (EOF not 4-aligned; spimdisasm drops a partial word)
- [0x813EF] # EOF marker = the 0.4.dec byte length
+40
View File
@@ -1279,3 +1279,43 @@ exemplar's own bank, and refuse to gate at the wrong build step rather than book
builds the -O0 arm of that; the carve arm (§53/`jtbl_family_bank`) already exists — they should converge
into one build-step-faithful sweep. That would have made both the Phase-26 "≈0%" and the Phase-28 "~3%"
impossible to manufacture.
### 2026-07-16 — P29 Task 2 Arm A: the swing verdict is now a BANKED FACT, but the fleet-scale -O0 carve hits a splat-integration wall (deferred, not a compiler wall)
**Context + belief.** Task 1 proved the swing pool is -O0-flag tooling (masked-MATCH at -O0), a *candidate*
(§52b). Arm A was to build the -O0-cluster split rollout (`tools/rollout_o0_cluster.py`, adapting the whale
`rollout_whale_o0.py`) and **whole-binary-gate one overlay to convert the candidate to a fact**, then roll
the ~478k-ins -O0 pool out fleet-wide.
**What was proven (byte-gated).** The tool carves the -O0 cluster (16 fns, vram 0x8013B568..0x8013C98C,
file 0x13410..0x14834) into a per-overlay `<ov>_o0.c` compiled -O0 (new Makefile `O0_CLUSTER_OBJS`
wildcard). On **`ov_SC07_010` the carve is byte-neutral and `family_sweep --hseq` banked 9/9 of the -O0
exemplar families' members whole-binary** (`func_8013B568/B7AC/B7F4/BC7C/BCDC/BD34/C360/C938/C964`), R22
clean-fleet 140/140. **So the swing verdict is confirmed as a FACT — these -O0 cluster members DO bank when
compiled at -O0, not just masked-MATCH.** (Phase 20's "`func_8013B7AC` is overlay-local" blanket claim is
also refuted at the member level — it banks in 010.)
**The wall (byte-proven, and it is TOOLING not the compiler).** Carving the SAME cluster in the other 3
sampled tail overlays (006/007/011) **byte-shifts the whole image** — a +0x20 data-symbol-address shift
(`lw v0,%lo(D_..3b6c)` → `..3b8c`), 34% of bytes differ — from a genuinely-clean `make clean &&
extract-all`. The boundary offsets are verified real fn-starts in every overlay's sig (identical 14-fn
layout), so this is **not** a wrong-boundary bug: it is a splat *re-disassembly* sensitivity — 3-way
splitting a code subseg makes splat resolve some `%lo` data references to a different auto-symbol. The
whale carve avoids it (single fn, a shared-header `_o0b` body, no INCLUDE_ASM in the split); the multi-stub
cluster carve triggers it on most overlays. This is the same "-O0 cluster split infra" that **Phase 20
built + reverted** — now characterized precisely (splat data-symbol resolution, not the compiler).
**The pivot (ROI-gated, honest).** The full -O0 fleet rollout (~1,233 members / ~0.6pp) is **deferred**: (1)
the splat-data-shift wall blocks 3/4 sampled overlays and debugging splat's `%lo` resolution is deep
splat-internals work; (2) the 134 whale-swept overlays have the cluster embedded INSIDE the
`jr_801380E0` carve (a carve-within-a-carve, even harder); (3) the remaining P29 levers are bigger and
cleaner — Task 6's tiny-IMM mega-pools (`0x80131eec` 2887×15 + `0x80130d0c` 2679×15 ≈ 5,566 members via
`imm_map`) and Task 3's core-cracks. The swing verdict's *strategic* claim (the ~478k-ins pool is real
matchable work, ceiling ≫ 3%) stands, banked-confirmed; only its *mechanical fleet harvest* is blocked on
the splat-carve integration, logged for a future session. The tool + the byte-neutral 010 carve are kept.
**Better path, in hindsight.** The whale's `_o0b` shape (a thin split whose body is a shared header
`#include`, no INCLUDE_ASM in the -O0 object) is splat-safe; the cluster rollout should likely mirror it —
route each overlay's -O0 members through a shared-per-member header rather than leaving INCLUDE_ASM stubs
in the split that splat re-disassembles. Testing that hypothesis is the cheap first move if/when the -O0
pool is revisited; it may dissolve the +0x20 shift the same way the whale never hit it.
+14
View File
@@ -1414,6 +1414,20 @@ worked-example templates). Measured on ov_SC01_077 tractable reach-134 residuals
exclusive — a real §10 residual.)
## §18 Per-file `-O0` split inside an overlay/blob (Phase 19 T1)
> **§18-P29 — the multi-stub cluster carve is splat-integration-fragile (defer, or use the whale's shape).**
> P29 Arm A generalized the whale rollout (§38) to the 0x13410..0x14834 `-O0` cluster via
> `tools/rollout_o0_cluster.py` (3-way `<ov>` / `<ov>_o0` / `<ov>_o2b` carve + `O0_CLUSTER_OBJS` Makefile
> wildcard). It **banked 9/9 -O0 members on ov_SC07_010** (whole-binary, R22 140/140 — the Task-1 swing
> verdict is a BANKED FACT, not just masked-MATCH), **but the SAME carve byte-shifts 006/007/011** (+0x20
> `%lo` data-symbol shift, 34% of bytes, from a CLEAN build; boundaries verified as real fn-starts). Root
> cause is **splat re-disassembly**: 3-way splitting a code subseg that still contains `INCLUDE_ASM` stubs
> makes spimdisasm resolve some `%lo` refs to a different auto-symbol — NOT the compiler, NOT a boundary bug.
> The **whale carve (§38) never hits this** because its `_o0b` split is a thin `#include "../shared/<fn>.h"`
> wrapper with **no INCLUDE_ASM to re-disassemble**. Lesson: a per-overlay `-O0` cluster rollout should route
> each member through a shared-per-member header (whale shape), not leave stubs in the split. Until then the
> fleet `-O0` harvest (~1,233 members / ~0.6pp) is DEFERRED; `docs/decision-log.md` (2026-07-16) has the full
> byte-evidence. This is the "-O0 cluster split infra" Phase 20 "built + reverted", now root-caused.
A cluster of functions compiled `-O0` inside an otherwise-`-O2` binary needs its own `-O0`-compiled `.c` (the
`src/boot.c` precedent — gcc-2.7.2 has no per-function optimize pragma, opt is per-file via a target-specific
`build/src/<path>.o: CC1FLAGS := -O0 …`). Detect `-O0` by the prologue `21F0A003` (`addu $fp,$sp,$zero`) + param
+6 -6
View File
@@ -4,15 +4,15 @@
# cross-binary collapsible-byte leverage: docs/duplicates.cross.md.
# THREE progress metrics (all matter — see the labels):
FLEET fn-count byte-ident: 300440 / 353722 = 84.94% (REAL+LINKED+empties; FUNCTION-count, ×134-inflated — one crack counts per overlay)
FLEET instr-weighted : 9211298 / 13081451 = 70.4% (shipped .text across resident+138 overlays; the decomp.dev-DISPLAY number)
FLEET distinct-code(uniq): 2916464 / 5574674 = 52.3% (56441/87459 unique fns; the DISTINCT-RE number)
FLEET fn-count byte-ident: 300449 / 353722 = 84.94% (REAL+LINKED+empties; FUNCTION-count, ×134-inflated — one crack counts per overlay)
FLEET instr-weighted : 9211474 / 13081451 = 70.4% (shipped .text across resident+138 overlays; the decomp.dev-DISPLAY number)
FLEET distinct-code(uniq): 2916595 / 5574674 = 52.3% (56449/87459 unique fns; the DISTINCT-RE number)
MAIN game-code weighted : 436 / 60201 = 0.7% (Phase-27 T10; SEPARATE — LINKED-excluding Ghidra sig dated 2026-06-14, PROVISIONAL until a fresh/complete main sig; NOT folded into the fleet number)
FLEET REAL substantive : 298585 (of which dedup-shared 233341 via 1840 groups / 233385 instances)
FLEET REAL substantive : 298594 (of which dedup-shared 233341 via 1840 groups / 233385 instances)
FLEET LINKED PsyQ objs : 959
FLEET NON_MATCHING : 7 (0 in any default build — G4)
FLEET INCLUDE_ASM stubs : 53275
FLEET INCLUDE_ASM stubs : 53266
FLEET matchable : 353722
| binary | REAL | shared | LINKED | byte-ident | matchable | byte-ident % |
@@ -155,5 +155,5 @@ FLEET matchable : 353722
| ov_SC07_007 | 1938 | 1544 | 0 | 2022 | 2614 | 77.4% |
| ov_SC07_008 | 2131 | 1693 | 0 | 2131 | 2386 | 89.3% |
| ov_SC07_009 | 2141 | 1693 | 0 | 2143 | 2430 | 88.2% |
| ov_SC07_010 | 1927 | 1544 | 0 | 2010 | 2526 | 79.6% |
| ov_SC07_010 | 1936 | 1544 | 0 | 2019 | 2526 | 79.9% |
| ov_SC07_011 | 1934 | 1543 | 0 | 2014 | 2450 | 82.2% |
+23 -3
View File
@@ -109,9 +109,20 @@ conditional) · main-EXE/B9 + GLM/B6 + resident's 14 walls (P30) · behemoths B7
Task 3) + plumbing/DIFF — the plain-sweep tail is drained. **R22 clean-fleet 140/140 byte-identical;
audit-binaries OK; dedup-check 1840/0; main 143dbb89.** Fleet: **instr 70.2→70.4% · distinct-code
51.9→52.3% · fn-count 84.73→84.94%**; 0 NON_MATCHING linked (G4).
- **NEXT:** (b) **Arm A** — the -O0 cluster carve tool (9 families / 1,233 members / ~24k ins;
adapt `rollout_whale_o0.py` for the multi-file carve of 0x13410..0x14834; the swing verdict's
whole-binary confirmation); (c) teach `family_sweep` per-member opt-level awareness (the general lever).
- **✅ ARM A: swing verdict CONFIRMED as a BANKED FACT (2026-07-16).** Built `tools/rollout_o0_cluster.py`
(the -O0-cluster carve, adapting `rollout_whale_o0.py`) + the Makefile `O0_CLUSTER_OBJS` -O0 wildcard.
On **ov_SC07_010 the carve is byte-neutral and `family_sweep --hseq` banked 9/9 of the -O0 exemplar
families' members whole-binary** (R22 clean-fleet 140/140) — so the -O0 cluster members DO bank at -O0,
not just masked-MATCH (§52b candidate → fact; Phase-20's "func_8013B7AC overlay-local" also refuted).
**BUT the fleet-scale carve hits a splat-integration wall:** the same carve on 006/007/011 byte-shifts the
whole image (+0x20 data-symbol shift, 34% diff) from a CLEAN build — a splat `%lo` re-disassembly
sensitivity (boundaries verified correct), the Phase-20 "-O0 split infra" wall now characterized. **Full
-O0 fleet rollout (~1,233 / ~0.6pp) DEFERRED** on ROI: 3/4 sampled overlays walled + 134 overlays have the
cluster inside the jr_801380E0 carve + bigger levers wait. Details → `docs/decision-log.md`. (c) opt-level
awareness is moot — the -O0-ness is per-FILE via the Makefile rule; `family_sweep`/`stub_map` already
route to the carved split. Hindsight fix logged: mirror the whale's shared-header `_o0b` shape.
- **TASK 2 SUBSTANTIVELY COMPLETE.** Arm B tail (748 members) + Arm A (verdict = banked fact). NEXT: **Task 3
(core-cracks + B3 near-misses)** and/or **Task 6 (tiny-IMM mega-pools ~5,566 members)** — the bigger levers.
---
@@ -136,3 +147,12 @@ conditional) · main-EXE/B9 + GLM/B6 + resident's 14 walls (P30) · behemoths B7
fleet instr 70.2→70.4% / distinct 51.9→52.3% / fn-count 84.73→84.94%; audit-binaries OK, dedup 1840/0,
main 143dbb89, 0 NON_MATCHING (G4). Verified pure-reduction (0 new/dup stubs, R14/H5). Logs
`.run/armB_tail_{substantial,mid,tiny,checkall}.log`. NEXT: Arm A (-O0 cluster carve) + item (c).
- **2026-07-16 — Task 2 (Arm A) DONE = swing verdict → banked fact + wall characterized.** New tool
`tools/rollout_o0_cluster.py` + Makefile `O0_CLUSTER_OBJS` -O0 wildcard. Carved 4 SC07 tail overlays;
byte-neutrality gate (clean R22): **ov_SC07_010 byte-identical, 006/007/011 FAIL** (+0x20 splat
data-symbol shift, boundaries verified — the Phase-20 splat "-O0 split infra" wall, not the compiler).
Kept 010's carve, reverted the 3; `family_sweep --hseq --only <9 -O0 exemplars> --band all` banked
**9/9 -O0 members in ov_SC07_010** whole-binary → **the -O0 cluster masked-MATCHes ARE real banks
(§52b fact)**. R22 clean-fleet **140/140 byte-identical**; fleet 70.4% instr / 52.3% distinct / 84.94%
fn-count; dedup 1840/0; main 143dbb89. Full -O0 fleet rollout DEFERRED (splat wall on 3/4 + jr-embedded
on 134 + ROI vs Task 3/6). R31 writeup in `docs/decision-log.md`. Logs `.run/armA_*`.
File diff suppressed because it is too large Load Diff
+104
View File
@@ -0,0 +1,104 @@
#include "common.h"
#include "../shared/engine_core.h"
void func_8013B568(s32 arg0) {
extern s32 D_80182714;
D_80182714 = arg0;
}
INCLUDE_ASM("asm/ov_SC07_010/nonmatchings/ov_SC07_010_o0", func_8013B598);
INCLUDE_ASM("asm/ov_SC07_010/nonmatchings/ov_SC07_010_o0", func_8013B6A0);
typedef struct { s32 f0; u8 pad[0x18]; } E_3B7AC; /* sizeof 0x1C stride */
void func_8013B7AC(int a0) {
extern E_3B7AC D_801A9420[];
D_801A9420[a0].f0 = 0; }
void func_8013B7F4(s32 a0, s32 a1) {
extern s32 D_801A953C;
func_8013B83C(a0, a1, D_801A953C);
}
INCLUDE_ASM("asm/ov_SC07_010/nonmatchings/ov_SC07_010_o0", func_8013B83C);
typedef struct { char pad_8013BC7C[0x68]; s32 f68; } S_8013BC7C;
void func_8013BC7C(void *arg0) {
extern void (*D_80181F78[])(void *);
D_80181F78[((S_8013BC7C *)arg0)->f68](arg0);
}
/* func_8013BCDC (ov_SC01_077_o0, -O0): guarded indirect call through D_80182714.
* D_80182714 is declared `extern s32` in the TU (canonical-sig layer) and holds a
* function pointer; read it as an s32, test non-null, cast to fn-ptr and call.
* Frame residual: target frame is 0x30 (saves $ra/$fp/$s0 at 0x28/0x24/0x20) — 16
* bytes of var_size above the minimal 0x20. At -O0 (no DCE) an unused 16-byte local
* reserves exactly that var region (cookbook §42 lever 3, frame-pad induction) with
* zero body instructions. 22/22 byte-identical via rtu_match. */
void func_8013BCDC(void) {
extern s32 D_80182714;
s32 pad[4];
if (D_80182714 != 0) {
((void (*)(void))D_80182714)();
}
}
void func_8013BD34(s32 a0) {
extern s32 D_801A94D8;
func_8013BD74(&D_801A94D8, a0);
}
INCLUDE_ASM("asm/ov_SC07_010/nonmatchings/ov_SC07_010_o0", func_8013BD74);
INCLUDE_ASM("asm/ov_SC07_010/nonmatchings/ov_SC07_010_o0", func_8013C08C);
INCLUDE_ASM("asm/ov_SC07_010/nonmatchings/ov_SC07_010_o0", func_8013C0F8);
void func_8013C360(s32 a0) {
s32 *p;
u32 i;
p = (s32 *)(a0 + 0x10);
for (i = 0; i < *(u32 *)(a0 + 8); i++) {
*(s32 *)(*(s32 *)p) = *(s32 *)((s32)p + 4);
p = (s32 *)((s32)p + 0xC);
}
}
INCLUDE_ASM("asm/ov_SC07_010/nonmatchings/ov_SC07_010_o0", func_8013C414);
void func_8013C938(void) {
extern s32 D_801A94D8;
D_801A94D8 = 1;
}
void func_8013C964(void) {
extern s32 D_801A94D8;
D_801A94D8 = 0;
}
File diff suppressed because it is too large Load Diff
+132
View File
@@ -0,0 +1,132 @@
#!/usr/bin/env python3
"""Phase-29 T2 Arm A: carve the -O0 cluster (vram 0x8013B568..0x8013C98C, file 0x13410..0x14834)
into its own -O0 object per single-file overlay, so the h_seq family members of those 9 matched
-O0 exemplars can bank whole-binary (they masked-MATCH only at -O0 — the Task-1 swing verdict).
Background (the swing verdict, 2026-07-16): `family_sweep --hseq` compiled the -O0 cluster members
at -O2 (member stub files are -O2), so their remapped bodies masked-MATCH but fail the whole-binary
gate. gcc-2.7.2 has no per-function optimize pragma (opt is per-FILE), so — exactly like the whale
(rollout_whale_o0.py, §38) and ov_SC01_077's own _o0 split (Phase-19 §18) — each overlay must
compile the cluster in its own -O0 TU. This tool builds that split; the family sweep then fills it.
The cluster is a contiguous run of 16 fns (all INCLUDE_ASM stubs in an unswept overlay). We carve
<ov>.c into 3 objects (a single object's .text can't be split around a middle object):
- <ov>.c = before the cluster (0x0..0x13410), keeps name + nonmatchings/<ov> asm paths
- <ov>_o0.c = the -O0 cluster (0x13410..0x14834), asm paths -> nonmatchings/<ov>_o0
- <ov>_o2b.c = after the cluster (0x14834..data tail), asm paths -> nonmatchings/<ov>_o2b
The Makefile O0_CLUSTER_OBJS wildcard -O0-compiles every <ov>_o0.o. Byte-NEUTRAL by construction:
INCLUDE_ASM pastes the ORIGINAL bytes regardless of -O0/-O2, so a freshly-carved (all-stub) overlay
stays byte-identical — gate it before sweeping. Idempotent (skips carved overlays). The whole-binary
byte-gate (make check-all) is the sole arbiter (G3/P9). Usage:
rollout_o0_cluster.py <ov> [<ov> ...] # single-file overlays only (files=1); whale/jr-carved
# overlays keep the cluster inside jr_801380E0 (not handled)
"""
import sys, re, os
BASE = 0x80128158
LO_FOFF, HI_FOFF = 0x13410, 0x14834 # cluster vram 0x8013B568 .. 0x8013C98C
START_FN = "func_8013B568" # first fn of the cluster (0x13410)
LAST_FN = "func_8013C964" # last fn of the cluster (just below 0x14834)
CLUSTER_FNS = [ # the 16 contiguous -O0 fns (from ov_SC01_077_o0.c)
"func_8013B568", "func_8013B598", "func_8013B6A0", "func_8013B7AC",
"func_8013B7F4", "func_8013B83C", "func_8013BC7C", "func_8013BCDC",
"func_8013BD34", "func_8013BD74", "func_8013C08C", "func_8013C0F8",
"func_8013C360", "func_8013C414", "func_8013C938", "func_8013C964",
]
def header_end(lines):
"""Header = the leading #include block (+ trailing blanks). Stops at the first non-#include,
non-blank line (a DEFINE_func macro / decl), so the low-address dedup macros stay in `before`."""
last_inc = -1
for i, l in enumerate(lines):
s = l.strip()
if s.startswith("#include"):
last_inc = i
elif s and last_inc >= 0:
break
he = last_inc + 1
while he < len(lines) and lines[he].strip() == "":
he += 1
return he
def unit_start(lines, fn):
"""Line index where `fn`'s definition/stub begins (an INCLUDE_ASM stub or a `... fn(` def line)."""
for i, l in enumerate(lines):
if re.search(rf'INCLUDE_ASM\("[^"]*",\s*{fn}\)\s*;', l):
return i, "stub"
if re.search(rf'\b{fn}\s*\(', l) and not l.lstrip().startswith(("//", "*", "extern")) \
and "INCLUDE_ASM" not in l and (l.rstrip().endswith("{") or ")" in l and ";" not in l):
return i, "def"
return None, None
def split_overlay(ov):
cdir = f"src/{ov}"
main_c = f"{cdir}/{ov}.c"
o0_c, o2b_c = f"{cdir}/{ov}_o0.c", f"{cdir}/{ov}_o2b.c"
yaml = f"config/splat.{ov}.yaml"
if os.path.exists(o0_c):
return f"SKIP {ov} (o0 exists)"
if not (os.path.exists(main_c) and os.path.exists(yaml)):
return f"SKIP {ov} (missing .c or yaml)"
lines = open(main_c).read().split("\n")
# every cluster fn must be present in THIS file (single-file overlay) — else the cluster is
# carved elsewhere (jr-embedded overlay) and this tool must not touch it.
missing = [fn for fn in CLUSTER_FNS if not any(re.search(rf'\b{fn}\b', l) for l in lines)]
if missing:
return f"SKIP {ov} (cluster fns not in base .c: {missing[:3]}... — jr-embedded?)"
si, _ = unit_start(lines, START_FN)
li, lkind = unit_start(lines, LAST_FN)
if si is None or li is None:
return f"ERROR {ov}: boundary fn not found (start={si}, last={li})"
if lkind != "stub":
return f"SKIP {ov} ({LAST_FN} is matched inline — cluster not all-stub; carve by hand)"
# end = first non-blank line after the LAST_FN stub (keeps the after-region fn's forward externs
# with the after-region, not the cluster).
ei = li + 1
while ei < len(lines) and lines[ei].strip() == "":
ei += 1
if not (si < li < ei):
return f"ERROR {ov}: bad boundary order si={si} li={li} ei={ei}"
header = "\n".join(lines[:header_end(lines)]).rstrip("\n")
before, cluster, after = lines[:si], lines[si:ei], lines[ei:]
OLD = f'nonmatchings/{ov}"'
open(main_c, "w").write("\n".join(before).rstrip("\n") + "\n")
cluster_body = "\n".join(cluster).replace(OLD, f'nonmatchings/{ov}_o0"')
open(o0_c, "w").write(header + "\n\n" + cluster_body.rstrip("\n") + "\n")
after_body = "\n".join(after).replace(OLD, f'nonmatchings/{ov}_o2b"')
open(o2b_c, "w").write(header + "\n\n" + after_body.rstrip("\n") + "\n")
# carve the yaml code subseg [0x0, c, <ov>] -> before / o0 cluster / o2b after
y = open(yaml).read()
pat = re.compile(r'^(\s*)- \[0x0, c, ' + re.escape(ov) + r'\].*$', re.M)
m = pat.search(y)
if not m:
return f"ERROR {ov}: '- [0x0, c, {ov}]' not found in yaml"
ind = m.group(1)
repl = (f'{ind}- [0x0, c, {ov}] # -O2 before -O0 cluster (file 0x0..0x{LO_FOFF:X})\n'
f'{ind}- [0x{LO_FOFF:X}, c, {ov}_o0] # -O0 cluster 0x8013B568..0x8013C98C (0x{LO_FOFF:X}..0x{HI_FOFF:X})\n'
f'{ind}- [0x{HI_FOFF:X}, c, {ov}_o2b] # -O2 after -O0 cluster (0x{HI_FOFF:X}..data tail)')
open(yaml, "w").write(pat.sub(lambda _m: repl, y, count=1))
ncl = sum(1 for l in cluster if "INCLUDE_ASM" in l)
return f"OK {ov}: before {len(before)} / cluster {len(cluster)} ({ncl} stubs) / after {len(after)} lines"
if __name__ == "__main__":
targets = sys.argv[1:]
if not targets:
print(__doc__)
sys.exit(2)
n_ok = 0
for ov in targets:
r = split_overlay(ov)
if r.startswith("OK"):
n_ok += 1
print(r)
print(f"--- {n_ok}/{len(targets)} carved ---")