mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-30 07:31:59 -04:00
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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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% |
|
||||
|
||||
@@ -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
@@ -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
@@ -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 ---")
|
||||
Reference in New Issue
Block a user