feat(phase-30): tools/o0_subsplit.py — the T2 carve-within-a-carve driver; +3 banked in ov_SC03_015

Promotes the proven probe (commit:1266) into a real tool, and validates it FIRST-TRY on a
fresh overlay.

tools/o0_subsplit.py <ov> --lo <vram> --hi <vram>:
  - derives the range's contents from the SOURCE ANCHORS (overlay_src_split.parse_overlay_c:
    `asm` = unmatched stub, `define`/`def`/`nonmatch` = already matched), NEVER from an asm
    scan -- a matched fn emits no .s, which is exactly the blindness that made the range look
    like a clean contiguous run (SS126 / SS124's shape);
  - computes the -O0 bound as (address range MINUS already-matched bodies) and emits ONE
    sub-region per maximal run of unmatched anchors (K matched islands => K+1 regions);
  - names each `<ov>_o0<letter>` picking free suffixes, so the widened Makefile glob selects
    them; refuses loudly if it runs out or if the range spans >1 object or is already -O0;
  - honours the one-carve-per-region law (forces a cut at every already-banked jr in the
    object) and reuses jr_isolate_all's plan/build_new_config/ascending-unique validation
    verbatim, so carve-repoint + source-repartition stay on the proven path;
  - warns (does not refuse) when a stub in an -O0 run lacks the frame-pointer prologue --
    the byte-gate is the arbiter, not the heuristic.

VALIDATION on ov_SC03_015 (untouched by the manual probe): the tool independently derived the
SAME structure found by hand on ov_SC03_014 -- 2 matched -O2 islands (func_80184440,
func_801848E4), 2 -O0 regions (8 + 7 fns), same 5 cuts. Sub-split -> BYTE-IDENTICAL. Then 3
drafts, each global DERIVED FROM THAT OVERLAY'S OWN ASM (%hi operand) rather than copied:
3/3 match_one --o0 MATCH (22 ins), 3/3 through the whole-binary gate.

BANKED this commit: func_801846E4 / func_8018473C / func_80184794 in ov_SC03_015 (6 across
the two overlays now). The other 24 stubs in the region are undrafted -- the route makes them
DRAFTABLE (they were un-bankable at any effort before); drafting them is crack-wave work.

R22 CLEAN-FLEET: extract-all 139/139 (+main); check-all 140 passed, 0 failed of 140.
cookbook SS126 (the address-range-is-not-an-optimization-region law + the probe ladder).
This commit is contained in:
Drew T
2026-07-31 08:41:54 -06:00
parent 45d26cc413
commit d2b48b7680
9 changed files with 11929 additions and 647 deletions
+4
View File
@@ -119,6 +119,10 @@ segments:
- [0x52354, c, ov_SC03_015_jr_8017A4AC]
- [0x52cd4, c, ov_SC03_015_jr_8017AE2C]
- [0x56a24, c, ov_SC03_015_jr_8017EB7C]
- [0x5bb98, c, ov_SC03_015_o0c]
- [0x5c2e8, c, ov_SC03_015_jr_80184440]
- [0x5c31c, c, ov_SC03_015_o0d]
- [0x5c78c, c, ov_SC03_015_jr_801848E4]
- [0x633c8, data, tail]
- [0xc0050, .rodata, ov_SC03_015] # Phase-26 §8 jtbl-rodata carve (jtbl_carve.py)
- [0xc012c, .rodata, ov_SC03_015_jr_8012ACE0] # Phase-26 §8 jtbl-rodata carve (jtbl_carve.py)
+12 -4
View File
@@ -2,7 +2,7 @@
> **Generated by `tools/cookbook_index.py` — do not hand-edit** (R33). Regenerate after adding a cookbook section.
>
> `docs/matching-cookbook.md` is ~716 KB / 332 sections. Grepping it blind is how three P30 wave-1 agents each "discovered" an idiom that was already written down. **Start here, then read the section.** A section appears under every symptom it addresses.
> `docs/matching-cookbook.md` is ~716 KB / 336 sections. Grepping it blind is how three P30 wave-1 agents each "discovered" an idiom that was already written down. **Start here, then read the section.** A section appears under every symptom it addresses.
**How to use:** name what you SEE in the diff (a stolen delay slot, an extra `la`, a swapped register pair, a `conflicting types` error), find that symptom below, read those sections first. If nothing fits, THEN grind — and add a section when you win.
@@ -217,7 +217,7 @@
- **§3-Do** — NOT "strip the duplicate typedef" — it breaks the extern that uses it <sub>L8030</sub>
- **§121** — Synthesise externs for macro-DEFINED callees from the macro's own definition head (Phase 29 T95) <sub>L8054</sub>
### jump tables & switches (17)
### jump tables & switches (18)
- **§8** — rodata island (compiler jump tables) — the `.data→.rodata→.data` sandwich (Phase 7) <sub>L320</sub>
- **§8a** — rodata island in a flat OVERLAY — the tail sandwich, per matched jr-function (Phase 26 — PoC PROVEN) <sub>L342</sub>
@@ -236,8 +236,9 @@
- **§97** — The gate's own tree hygiene: a refused carve, an unchecked recovery, and a snapshot that captured a dirty tree (Phase 29 SESSION-22) <sub>L7059</sub>
- **§105** — A gate's revert must survive an EXCEPTION, not just a failure (Phase 29 T53, `jtbl_family_bank`) <sub>L7424</sub>
- **§125** — Split the CARVE from the BODY before calling a jr residue a wall — and measure it by SHA from a CLEAN tree (P30 SESSION-28; **this section's first draft was WRONG and the method caught it**) <sub>L8199</sub>
- **§126** — The carve-within-a-carve: an ADDRESS RANGE is not an OPTIMIZATION REGION (P30 T2, byte-proven end-to-end) <sub>L8257</sub>
### optimisation level (-O0/-O2) (7)
### optimisation level (-O0/-O2) (8)
- **§6** — Per-module optimization mixing — the -O0 boot module (Phase 7) <sub>L246</sub>
- **Detecting** — the opt level (do this first) <sub>L254</sub>
@@ -246,6 +247,7 @@
- **§38** — The WHALE `func_80144B9C` (770 ins): the -O0 struct-assign memcpy idiom + the -O0 reach-134 ×134 rollout (Phase 24 T7 §G, cheap Opus — no Fable5, no calls.c) <sub>L2520</sub>
- **§39** — The ×1→×134 giant-endgame: propagate a matched **-O2** giant via the NATIVE DEFINE-macro path (Phase 24 T7 §G close, 2026-07-08) <sub>L2530</sub>
- **§116** — Optimization level is a property of the FILE, not the function: read a family 0/N against the member's stub HOME (Phase 29 T79) <sub>L7870</sub>
- **§3-The** — finding: opt level is per FILE, so the file's contents must be opt-HOMOGENEOUS <sub>L8272</sub>
### family propagation & sweeps (65)
@@ -488,7 +490,7 @@
- **§125** — Split the CARVE from the BODY before calling a jr residue a wall — and measure it by SHA from a CLEAN tree (P30 SESSION-28; **this section's first draft was WRONG and the method caught it**) <sub>L8199</sub>
- **§3-The** — meta-lesson <sub>L8250</sub>
### (unbucketed — title matched no symptom vocabulary) (86)
### (unbucketed — title matched no symptom vocabulary) (88)
- **§3-How** — to use this <sub>L30</sub>
- **§1** — Idiom catalog (asm pattern → C that produces it) <sub>L39</sub>
@@ -576,6 +578,8 @@
- **§3-The** — method (keep this) <sub>L8206</sub>
- **§3-The** — instrument rules that make its answer trustworthy (this is where I failed) <sub>L8216</sub>
- **§3-The** — corrected results (each SHA-verified, from a clean tree, restore re-verified) <sub>L8231</sub>
- **§3-The** — instrument trap that hid it (and it is §124's shape again) <sub>L8283</sub>
- **§3-The** — mechanics <sub>L8292</sub>
## All sections, in order
@@ -912,3 +916,7 @@
- **§3-The** — corrected results (each SHA-verified, from a clean tree, restore re-verified) <sub>L8231</sub>
- **§3-Two** — further notes worth keeping <sub>L8241</sub>
- **§3-The** — meta-lesson <sub>L8250</sub>
- **§126** — The carve-within-a-carve: an ADDRESS RANGE is not an OPTIMIZATION REGION (P30 T2, byte-proven end-to-end) <sub>L8257</sub>
- **§3-The** — finding: opt level is per FILE, so the file's contents must be opt-HOMOGENEOUS <sub>L8272</sub>
- **§3-The** — instrument trap that hid it (and it is §124's shape again) <sub>L8283</sub>
- **§3-The** — mechanics <sub>L8292</sub>
+53
View File
@@ -8253,3 +8253,56 @@ same failure one level up: **a verdict from the wrong *measurement* manufactures
efficiently.** R35 says fix the instrument before trusting its measurement — and *my own diagnostic
script is an instrument*, subject to the same rule as the tools it audits. The saving grace is that
the method in this section is what refuted the section's own first conclusion, one build at a time.
## §126 — The carve-within-a-carve: an ADDRESS RANGE is not an OPTIMIZATION REGION (P30 T2, byte-proven end-to-end)
A 4th `-O0` region was found *inside* an `-O2` jr split (`0x80183CF0..0x80184920`, ov_SC03_014 +
ov_SC03_015). Banking it needs the containing object sub-split into pre/`-O0`/post — the
"carve within a carve" the roadmap had flagged as blocked on the Arm-A splat `%lo +0x20` defect.
**It is not blocked.** Four probes, each isolating exactly one variable, SHA vs `config/check.<ov>.sha`
from a clean tree:
| probe | isolated | result |
|---|---|---|
| 1 | sub-split at arbitrary addresses, everything still `-O2` | **BYTE-NEUTRAL** — the re-carve does not shift `%lo`; Arm-A does not bite |
| 2 | same split, middle region routed `-O0` | diverged (two variables changed at once — inconclusive) |
| 3 | probe-1's *name*, only the `-O0` flag added | diverged ⇒ **the FLAG, not the subseg name** |
| 4 | `-O0` regions cut to EXCLUDE matched bodies | **BYTE-IDENTICAL — route proven** |
### The finding: opt level is per FILE, so the file's contents must be opt-HOMOGENEOUS
§116 says opt level is a property of the FILE. The corollary nobody had needed until now: when you
select a region **by address range**, you get everything in that range — including functions that are
already **MATCHED**, whose bodies expand from `engine_core.h` as `DEFINE_func_*()` instantiations and
are compiled `-O2`. Flipping the file recompiles them, and they stop matching. Here two matched bodies
(`func_80184440`, `func_801848E4`) sat *interleaved* among the 15 `-O0` stubs. Cut around them —
`[lo..matched)`, matched stays `-O2`, `[after..hi)` — and the image is byte-identical.
**So the region bound is: (address range) MINUS (already-matched bodies)**, and a range with K
interleaved matched functions needs K+1 `-O0` sub-regions, not one.
### The instrument trap that hid it (and it is §124's shape again)
I first derived "15 contiguous `-O0` functions, clean cut" by scanning `asm/<ov>/nonmatchings/**/*.s`
for the frame-pointer prologue (`addu $fp,$sp,$zero` / `21F0A003`). **A MATCHED function emits no
`.s`** — splat writes none, because its `.c` carries real C. So that scan is structurally blind to
precisely the bodies that break the flip, and it reported a clean run where the range was mixed.
**Derive the region's contents from the SOURCE anchors (`INCLUDE_ASM` stubs *and* `DEFINE_func_*()`
instantiations, in address order), never from an asm-file scan.** `corpus.stubs` gives the stubs; the
`DEFINE_func_` instantiations in the region `.c` give the matched ones.
### The mechanics
- **Cuts:** `jr_isolate_all`'s `plan()` / `build_new_config()` already accept arbitrary cut vrams —
region naming is purely positional, so nothing new is needed for the split itself. Inject the cut
list and reuse its source-repartition, carve-repoint and ascending/unique validation verbatim.
- **The one-carve-per-region law still applies:** every already-banked jr in the object must ALSO be
a cut, or two carve owners share one object and its single contiguous `.rodata` must host both.
- **Naming + the Makefile:** name each `-O0` sub-region `<ov>_o0<letter>` and let ONE widened rule
select them — the glob is now `$(wildcard src/ov_*/ov_*_o0?.c)` (was `_o0b`). **A missed `-O0` rule
is SILENT:** the region compiles `-O2` and every residual it produces is a pure artifact (§116).
`corpus.o0_sources()` parses this rule and resolves `?` via glob, so the `-O0` oracle stays honest.
- **Verify the routing, don't assume it:** `corpus.is_o0("src/<ov>/<ov>_o0c.c")` must return True
before you read a single residual from that region.
### Method note
Probe 2 changed the name *and* the flag and was therefore uninterpretable. Probe 3 — same name as the
proven-neutral probe 1, flag only — is what produced the answer. **One variable per probe**, and keep
the previous probe's proven-neutral configuration as the control.
-643
View File
@@ -4298,646 +4298,3 @@ void func_80183B58(s32 param_1, s16 *param_2) {
*(s32 *)(param_1 + 0x40) = (s32)svec_out.vy;
*(s32 *)(param_1 + 0x44) = (s32)svec_out.vz;
}
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80183CF0);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80183D50);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80183F28);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80184028);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80184058);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80184264);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_801842E0);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80184354);
DEFINE_func_80184440() /* dedup: shared engine-core @0x80184440 (src/shared) */
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80184474);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80184538);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_801846E4);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_8018473C);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80184794);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_801847EC);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80184868);
DEFINE_func_801848E4() /* dedup: shared engine-core @0x801848E4 (src/shared) */
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80184920);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80184A08);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80184D1C);
extern void func_800183E0(s32 a0);
void func_80184DA4(s32 param_1) {
extern u8 D_801CF780[];
/* [T51] scoped in from file scope: a file-scope decl of these symbols constrains every
LATER function in this TU, which blocks a byte-true decl of a different type.
Declaration-only move (cookbook §103); the whole-binary byte-gate is the arbiter. */
extern u16 D_800B99DA;
extern u8 D_8018FE40[];
*(u8 *)(*(s32 *)(param_1 + 0xCC) + 0x27) = 0x8A;
func_800183E0((s32)&D_801CF780[D_8018FE40[D_800B99DA & 0x1F] * 16]);
}
extern void func_800183E0(s32 a0);
void func_80184DF8(s32 param_1) {
extern u8 D_801CF680[];
/* [T51] scoped in from file scope: a file-scope decl of these symbols constrains every
LATER function in this TU, which blocks a byte-true decl of a different type.
Declaration-only move (cookbook §103); the whole-binary byte-gate is the arbiter. */
extern u16 D_800B99DA;
extern u8 D_8018FE40[];
*(u8 *)(*(s32 *)(param_1 + 0xD0) + 0x27) = 0x8B;
func_800183E0((s32)&D_801CF680[D_8018FE40[D_800B99DA & 0x1F] * 16]);
}
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80184E4C);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80184F14);
extern void (*D_80190008[])(void);
void func_80185144(void *a0) {
D_80190008[*(u16 *)((s32)a0 + 0x2)]();
}
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80185180);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80185380);
DEFINE_func_8018553C() /* dedup: shared engine-core @0x8018553C (src/shared) */
extern s32 D_801EAEE4;
s32 func_80185578(void) {
return 0x2 - D_801EAEE4;
}
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_8018558C);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_801855A0);
DEFINE_func_801856B4() /* dedup: shared engine-core @0x801856B4 (src/shared) */
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_801856C4);
extern s32 func_8001AAA0(s32 arg);
s32 func_80185744(void) {
return func_8001AAA0(0x51) != 0;
}
extern s32 func_8001AAA0(s32 arg);
s32 func_80185764(void) {
return func_8001AAA0(0x52) != 0;
}
extern s32 func_8001AAA0(s32 arg);
s32 func_80185784(void) {
return func_8001AAA0(0x56) != 0;
}
extern s32 func_80184868(s32);
s32 func_801857A4(void) {
return (u32)((s32 (*)(void))func_80184868)() != 0;
}
extern void func_800D0C48(s32 arg);
void func_801857C4(void) {
func_800D0C48(0x1);
}
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_801857E4);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80185858);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_801858D4);
extern s32 func_80184868(s32 arg);
void func_8018597C(void *a0) {
if (func_80184868((s32)a0)) {
*(s16 *)((s32)a0 + 0x2) = 0x3;
}
}
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_801859B0);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80185A0C);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80185B44);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80185EF8);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_801863B8);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_8018655C);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_801867F8);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80186A74);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80186C4C);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80187090);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80187300);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_801874C0);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80187884);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80187A98);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80187CA8);
extern void (*D_80191890[])(void);
void func_80187D80(void *a0) {
D_80191890[*(u16 *)((s32)a0 + 0x2)]();
}
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80187DBC);
DEFINE_func_80187E28() /* dedup: shared engine-core @0x80187E28 (src/shared) */
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80187E3C);
extern void func_80187EE8(s32 a0);
extern s32 func_8012BEE8(s32 a0);
extern void func_8012C218(void*);
void func_80187EA8(s32 a0) {
func_80187EE8(a0);
if (func_8012BEE8(a0)) {
((void (*)(s32))func_8012C218)(a0);
}
}
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80187EE8);
extern void func_80131E00(struct S80131E00 *a0, s32 a1);
void func_801880F8(void *arg0) {
((void (*)(s32, s32))func_80131E00)((s32)arg0, 0x11);
}
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80188118);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80188168);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_801881CC);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80188290);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80188350);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_801883AC);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80188404);
DEFINE_func_80188470() /* dedup: shared engine-core @0x80188470 (src/shared) */
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80188478);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_8018858C);
extern void (*D_801918B0[])(void);
void func_80188634(void *a0) {
D_801918B0[*(u16 *)((s32)a0 + 0x2)]();
}
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80188670);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80188828);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_801889B4);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80188A80);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80188B94);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80188C0C);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80188C60);
s32 func_80188CA0(s32 arg0) {
return *(u16*)(arg0 + 0x2) == 0x6;
}
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80188CB4);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80188D64);
extern s32 func_8018473C(void);
void func_80188D84(void) {
func_8018473C();
}
extern void func_8013CAE8(void);
void func_80188DA4(void) {
func_8013CAE8();
}
extern void (*D_80191B84[])(void);
void func_80188DC4(void *a0) {
D_80191B84[*(u16 *)((s32)a0 + 0x2)]();
}
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80188E00);
DEFINE_func_80188F18() /* dedup: shared engine-core @0x80188F18 (src/shared) */
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80188F28);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80188F80);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80188FB4);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80189050);
extern s32 func_8017BEBC(void);
void func_801890B8(void) {
func_8017BEBC();
}
extern s32 func_8017BFA0(void);
void func_801890D8(void) {
func_8017BFA0();
}
DEFINE_func_801890F8() /* dedup: shared engine-core @0x801890F8 (src/shared) */
DEFINE_func_80189104() /* dedup: shared engine-core @0x80189104 (src/shared) */
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80189110);
extern void func_8002D4C8(s32 arg0, s32 arg1);
void func_80189160(void *a0) {
*(s16 *)((s32)a0 + 0x2) = 0x4;
func_8002D4C8(0x87C, 0);
}
DEFINE_func_8018918C() /* dedup: shared engine-core @0x8018918C (src/shared) */
// @class: struct
// @stuck: none — MATCH expected; param_1 survives jal in $s0, table fp-call with %lo-fold
extern void func_801891E8(u8 *a0);
extern void (*D_80191BD4[])(int);
void func_8018919C(int param_1)
{
((void (*)(void))func_801891E8)();
D_80191BD4[*(unsigned short *)(param_1 + 2)](param_1);
}
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_801891E8);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80189238);
DEFINE_func_801892D4() /* dedup: shared engine-core @0x801892D4 (src/shared) */
DEFINE_func_801892DC() /* dedup: shared engine-core @0x801892DC (src/shared) */
DEFINE_func_801892E4() /* dedup: shared engine-core @0x801892E4 (src/shared) */
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80189310);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_801893BC);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_8018942C);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_801894BC);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_8018950C);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_801895BC);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80189630);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80189680);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_8018973C);
extern void func_80189110(void);
void func_801897A4(void) {
func_80189110();
}
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_801897C4);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80189824);
DEFINE_func_801898BC() /* dedup: shared engine-core @0x801898BC (src/shared) */
extern void (*D_80191C0C[])(void);
void func_80189970(void *a0) {
D_80191C0C[*(u16 *)((s32)a0 + 0x2)]();
}
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_801899AC);
// @class: plumbing
// @stuck: none — MATCH expected; simple if/else, param saved in $s0 across call
extern void func_8012C1B8(void);
extern void func_8012CAE4(void *a0);
extern void func_8001C214(int, int);
extern int D_80191BFC;
void func_801899F4(int param_1)
{
int v0;
v0 = ((int (*)(void))func_8012C1B8)();
*(int *)(param_1 + 0x20) = v0;
if (v0 == 0) {
((void (*)(int))func_8012CAE4)(param_1);
} else {
func_8001C214(v0, 0);
*(int *)(param_1 + 0x58) = (int)&D_80191BFC;
*(short *)(param_1 + 0x5c) = 0x80;
*(unsigned short *)(param_1 + 2) += 1;
}
}
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80189A60);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80189AC4);
extern void (*D_801E3298[])(void);
void func_80189B3C(void *a0) {
D_801E3298[*(u16 *)((s32)a0 + 0x2)]();
}
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80189B78);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80189C4C);
DEFINE_func_80189C74() /* dedup: shared engine-core @0x80189C74 (src/shared) */
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80189C7C);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80189DC8);
extern void func_8012C218(void *a0);
void func_80189EE8(void *arg0) {
*(short *)((char *)arg0 + 0x2) = 2;
((void (*)(s32 *))func_8012C218)(*(s32 **)((char *)arg0 + 0xcc));
}
void func_80189F14(void *a0) {
*(short *)(*(int *)((char *)a0 + 0x6c) + 0xfe) = 1;
}
void func_80189F24(void *a0) {
*(short *)(*(int *)((char *)a0 + 0x6c) + 0xfe) = 2;
}
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80189F34);
extern s32 D_80126B58;
extern void func_8014ADA8(s32 a0, s32 a1);
void func_80189F74(void * arg0) {
s32 temp_v0;
temp_v0 = *(s32 *)((s32)arg0 + 0xdc);
((void (*)(s32 *, s32))func_8014ADA8)((s32 *)&D_80126B58, *(u16 *)(temp_v0 + 0xc));
}
extern s16 func_80174764(void);
s32 func_80189FA8(void) {
return ((u32)(((s32 (*)(void))func_80174764)() ^ 1)) < 1;
}
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_80189FCC);
extern void (*D_801E32C0[])(void);
void func_8018A030(void *a0) {
D_801E32C0[*(u16 *)((s32)a0 + 0x2)]();
}
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_8018A06C);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_8018A0F4);
/* func_8018A1B4 — guarded state-kick: if the s16 at 0x98 is clear, set the
* state word at 0x02 to 1, hand the entity to func_8012A828 with one of two
* script tables selected by the s16 flag at 0xFE (== 1 -> D_801E3980, else
* D_801E3760), then clear that flag.
*
* §71 sibling-first: func_8018A06C (same TU, 0x148 bytes earlier) is the same
* two-arm `func_8012A828(entity, D_801E3980 / D_801E3760)` selector and pins
* the widths: `lh` at 0x98/0xFE, `sh` at 0x02.
*/
extern void func_8012A828(s32 a0, void *a1);
void func_8018A1B4(void *a0) {
extern u8 D_801E3980[];
extern u8 D_801E3760[];
if (*(s16 *)((s32)a0 + 0x98) == 0) {
*(s16 *)((s32)a0 + 0x2) = 1;
if (*(s16 *)((s32)a0 + 0xFE) == 1) {
func_8012A828((s32)a0, D_801E3980);
} else {
func_8012A828((s32)a0, D_801E3760);
}
*(s16 *)((s32)a0 + 0xFE) = 0;
}
}
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_8018A224);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_8018A31C);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_8018A390);
extern void (*D_801E3A24[])(void);
void func_8018A5B0(void *a0) {
D_801E3A24[*(u16 *)((s32)a0 + 0x2)]();
}
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_8018A5EC);
void func_8018A680(void) {
extern s16 D_801EADEC;
s16 *p = &D_801EADEC;
s32 i = 0xF;
do {
*p = 0;
p = (s16 *)((s32)p + 0xe);
i--;
} while (i >= 0);
}
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_8018A6A4);
extern s16 D_801EADE4;
extern s16 D_801EADE6;
extern s16 D_801EADE8;
extern s16 D_801EADEA;
void func_8018A744(s32 arg0, s32 arg1, s32 arg2) {
D_801EADE4 = *(u16 *)((s32)arg0 + 0x0);
D_801EADE6 = *(u16 *)((s32)arg0 + 0x2);
D_801EADE8 = arg1;
D_801EADEA = arg2;
}
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_8018A774);
extern s16 D_801EADEA;
void func_8018A7EC(s32 arg0) {
*(s16 *)((s32)&D_801EADEA + 0x0) += arg0;
}
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_8018A808);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_8018A860);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_8018A934);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_8018A970);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_8018A9BC);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_8018AA98);
void func_8018AD5C(void) {
extern s16 D_801EACE4;
s16 *p = &D_801EACE4;
s32 i = 0xF;
do {
*p = 0;
p = (s16 *)((s32)p + 0x10);
i--;
} while (i >= 0);
}
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_8018AD80);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_8018ADA8);
extern void func_8018AE9C(void *a0, void *a1, s16 a2);
extern void func_8018AFD0(s32 a0, s16 a1, u16 a2);
void func_8018AE30(void *arg0, void *arg1, s32 arg2, s32 arg3) {
/* The target frame is 0x30: 0x10 outgoing-arg area + 0x10 var_size +
* 0x10 saved regs (s0/s1/s2/ra at 0x20..0x2C). gcc-2.7.2 allocates a
* declared aggregate even when it is never referenced, so this 16-byte
* local reproduces the original's var_size exactly. */
s32 unused[4];
s32 i;
s32 j;
func_8018AE9C(arg0, arg1, arg2);
for (i = 0, j = 0; i < 0x10; i++, j += 0x100) {
func_8018AFD0(i, j, arg3);
}
}
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_8018AE9C);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_8018AF0C);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_8018AF84);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_8018AFD0);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_8018B0EC);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_8018B128);
INCLUDE_ASM("asm/ov_SC03_015/nonmatchings/ov_SC03_015_jr_8017EB7C", func_8018B23C);
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
File diff suppressed because it is too large Load Diff
+183
View File
@@ -0,0 +1,183 @@
#!/usr/bin/env python3
"""P30 T2 — the CARVE WITHIN A CARVE: route an `-O0` address range inside an `-O2` object.
Some `-O0` regions sit INSIDE an object that is otherwise `-O2` (a jr split, or the main body).
gcc-2.7.2 has no per-function optimize pragma, so opt level is per FILE (§116): the range must be
cut into its own object(s) and named so the Makefile's `-O0` wildcard selects it.
WHAT THIS TOOL EXISTS TO GET RIGHT (cookbook §126, byte-proven):
**An address range is not an optimization region.** A range selected by address contains everything
in it — including already-MATCHED functions, whose bodies expand from engine_core.h as
`DEFINE_func_*()` instantiations (or sit inline) and are compiled `-O2`. Flipping the FILE to `-O0`
recompiles those too and they stop matching. So the `-O0` bound is
(address range) MINUS (already-matched bodies)
and a range with K interleaved matched functions needs **K+1** `-O0` sub-regions, not one.
The contents are derived from the SOURCE ANCHORS (`overlay_src_split.parse_overlay_c`: `asm` =
unmatched stub, `define`/`def`/`nonmatch` = matched), NEVER from an asm-file scan — a matched
function emits no `.s`, so a scan of `asm/**/*.s` is structurally blind to exactly the bodies that
break the flip. That blindness is what made the range look like a clean contiguous run (§124's shape).
The split itself reuses `jr_isolate_all` verbatim (plan / build_new_config / the ascending-unique
validation / repoint_overlays_mk): region naming there is purely positional, so only the cut list is
injected. That keeps source-repartition and `.rodata` carve-repoint semantics on the proven path,
including the one-carve-per-region law (every already-banked jr in the object must also be a cut).
The whole-binary byte-gate remains the sole arbiter (G3/P9). After running this:
make extract BINARY=<ov> && make build BINARY=<ov> -> must be BYTE-IDENTICAL
(the split is byte-neutral by construction; a divergence means a bound is wrong), then bank drafts
into the `_o0*` files and gate again, then a full R22 (config changed => T2 blast radius).
Usage:
tools/o0_subsplit.py <ov> --lo 0x80183CF0 --hi 0x80184920 [--dry-run]
"""
import argparse
import os
import re
import string
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import jr_isolate_all as J # noqa: E402
import overlay_src_split as oss # noqa: E402
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
MATCHED_KINDS = {"define", "def", "nonmatch"} # already carry real C => must stay -O2
def free_letters(ov, n):
"""n unused `<ov>_o0<letter>` suffixes (b is the whale's, so start at c and skip taken ones)."""
taken = {m.group(1) for m in
(re.fullmatch(rf"{re.escape(ov)}_o0(\w?)\.c", f)
for f in os.listdir(os.path.join(REPO, "src", ov))) if m}
out = []
for ch in string.ascii_lowercase[2:]: # c, d, e, ...
if ch not in taken:
out.append(ch)
if len(out) == n:
return out
sys.exit(f"o0_subsplit: ran out of free _o0<letter> suffixes for {ov}")
def main():
ap = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("ov")
ap.add_argument("--lo", required=True, help="first vram of the -O0 range (inclusive)")
ap.add_argument("--hi", required=True, help="first vram AFTER the -O0 range (exclusive)")
ap.add_argument("--dry-run", action="store_true")
a = ap.parse_args()
ov, lo, hi = a.ov, int(a.lo, 16), int(a.hi, 16)
p = J.plan(ov)
def obj_of(v):
for s, e, nm, li, ind in p["obj_ranges"]:
if s <= v and (e is None or v < e):
return nm
return None
nm = obj_of(lo)
if nm is None or obj_of(max(lo, hi - 1)) != nm:
sys.exit(f"o0_subsplit: [{a.lo}, {a.hi}) does not lie inside ONE code object "
f"(lo in {nm}, hi-1 in {obj_of(max(lo, hi - 1))})")
if nm.endswith(J.O0_SUFFIX) or re.search(r"_o0\w?$", nm):
sys.exit(f"o0_subsplit: {nm} is ALREADY an -O0 object — nothing to route.")
# ---- derive the range's contents FROM THE SOURCE (never an asm scan) --------------------
srcpath = os.path.join(REPO, f"src/{ov}/{nm}.c")
_hdr, items = oss.parse_overlay_c(open(srcpath).read(), oss.load_ov_syms(ov))
inrange = sorted([it for it in items if it[0] is not None and lo <= it[0] < hi],
key=lambda it: it[0])
if not inrange:
sys.exit(f"o0_subsplit: no addressed source anchors in [{a.lo}, {a.hi}) of {nm}")
stubs = [it for it in inrange if it[2] == "asm"]
matched = [it for it in inrange if it[2] in MATCHED_KINDS]
print(f"o0_subsplit {ov}: object {nm}")
print(f" range 0x{lo:08X}..0x{hi:08X}: {len(inrange)} anchors "
f"= {len(stubs)} unmatched stub(s) + {len(matched)} ALREADY-MATCHED (must stay -O2)")
for ad, nam, kind, _t in matched:
print(f" -O2 island: 0x{ad:08X} {nam} [{kind}]")
if not stubs:
sys.exit("o0_subsplit: every anchor in the range is already matched — nothing to route.")
# ---- maximal runs of consecutive UNMATCHED anchors = the -O0 sub-regions ----------------
runs, cur = [], []
for it in inrange:
if it[2] == "asm":
cur.append(it)
else:
if cur:
runs.append(cur)
cur = []
if cur:
runs.append(cur)
letters = free_letters(ov, len(runs))
cuts, renames = set(), {}
for run, ch in zip(runs, letters):
start = run[0][0]
after = next((it[0] for it in inrange if it[0] > run[-1][0]), hi)
cuts.add(start)
cuts.add(after) # close the region (the next anchor, or hi)
renames[start] = f"o0{ch}"
print(f" -O0 region {ov}_o0{ch}: 0x{start:08X}..0x{after:08X} ({len(run)} fns)")
# sanity (this scan IS valid for stubs — only MATCHED fns lack a .s): every -O0 candidate
# should carry the frame-pointer prologue. Warn, don't refuse: the byte-gate is the arbiter.
import corpus
nofp = []
for run in runs:
for ad, nam, _k, _t in run:
ap_ = corpus.asm_path(ov, nam)
if ap_ and os.path.exists(os.path.join(REPO, ap_)):
txt = open(os.path.join(REPO, ap_)).read()
if not re.search(r'addu\s+\$fp,\s*\$sp,\s*\$zero', txt):
nofp.append(nam)
if nofp:
print(f" ⚠ {len(nofp)} stub(s) in the -O0 runs lack the frame-pointer prologue "
f"({nofp[:5]}{'…' if len(nofp) > 5 else ''}) — check the bounds; gate decides.")
# ---- the one-carve-per-region law: every already-banked jr here must also be cut --------
_alljr, banked = J.jr_inventory(ov)
banked_here = [x for x in banked if obj_of(x) == nm]
allcuts = sorted(cuts | set(banked_here))
print(f" banked jr in object: {[hex(x) for x in banked_here]} (forced cuts)")
print(f" final cuts: {[hex(c) for c in allcuts]}")
_orig = J.subseg_name
J.subseg_name = lambda o, vram: (f"{o}_{renames[vram]}" if vram in renames else _orig(o, vram))
p["per_obj"] = {nm: allcuts}
cfg_lines, new_files, carve_renames = J.build_new_config(ov, p)
# jr_isolate_all's fail-loud ascending/unique validation, verbatim
code_re = re.compile(r'^\s*- \[(0x[0-9A-Fa-f]+), c, (\w+)\]')
seen_off, seen_nm = -1, set()
for ln in cfg_lines:
m = code_re.match(ln)
if not m:
continue
off, n2 = int(m.group(1), 16), m.group(2)
if off <= seen_off or n2 in seen_nm:
sys.exit(f"o0_subsplit: REFUSING corrupt config — [{hex(off)}, {n2}] "
f"{'out of order' if off <= seen_off else 'duplicate'}")
seen_off, seen_nm = off, seen_nm | {n2}
print(f" -> {len(new_files)} region .c files; carve repoints: {carve_renames or '(none)'}")
if a.dry_run:
print(" [dry-run] nothing written.")
return
open(os.path.join(REPO, f"config/splat.{ov}.yaml"), "w").write("\n".join(cfg_lines) + "\n")
for path, content in new_files.items():
open(path, "w").write(content)
J.repoint_overlays_mk(carve_renames, dry=False)
print(f" wrote config + region files + overlays.mk.\n"
f" NEXT: make extract BINARY={ov} && make build BINARY={ov} -> must be BYTE-IDENTICAL "
f"(the split is byte-neutral by construction; a divergence means a bound is wrong).")
if __name__ == "__main__":
main()