feat(o0): md_MAIN_011 compiles -O0 — 21 functions / 4,321 ins unlocked, byte-proven

Its entire code subseg is the -O0 run and the .c is stub-only, so this is the boot
precedent: a whole-object CC1FLAGS override, no splat change, no carve, and none of
the 18-P29 re-disassembly risk. Proven byte-neutral by a CLEAN per-binary rebuild —
build dirs deleted, re-extract, rebuild:
  sha1 80731bac0ddd6b3e354f43b2c179582b12590752 == config/check.md_MAIN_011.sha

Landed with the coupling fix it requires, or the 21 would have stayed invisible.
Three tools decided -O0-ness from the subseg NAME ('_o0' in it, or 'boot'); this
object keeps its plain name, so match_one would have warned 'cannot bank' about
functions that now bank and the wave draw would have kept refusing to draw them. All
three now ask corpus.o0_subseg(), which derives the answer from the Makefile itself
(R33: a name is a convention, the Makefile is ground truth).

Verified end to end: match_one compiles md_MAIN_011 targets at -O0 with the
cannot-bank warning correctly gone, the wave draw emits cards for them, and
test_o0_detect still passes 167/167 coverage with 0 false positives.
This commit is contained in:
Drew T
2026-08-24 12:43:40 -06:00
parent c17b35fb37
commit 05a4aeaf41
5 changed files with 47 additions and 5 deletions
+10
View File
@@ -724,6 +724,16 @@ $(WHALE_O0B_OBJS): CC1FLAGS := -quiet -O0 -G0 -mips1 -mcpu=3000 -mgas -msoft-flo
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
# md_MAIN_011 IS AN ENTIRELY -O0 MODULE (P31 S59, census in .run/s59_o0/). All 21 functions in its
# single code subseg carry the -O0 frame-pointer prologue (`sw $fp` + `addu $fp,$sp,$zero`), the
# .c is stub-only, and no -O0 glob matches `src/md_*/` at all — so its functions were unbankable no
# matter how good a draft was, and the wave draw now refuses to draw them (build_wave_atlas's
# `o0-in-an-O2-object` skip). Whole-object override, the `boot` precedent (§6): no splat change, no
# carve, and therefore none of the §18-P29 re-disassembly risk. Byte-neutral while the file is
# stub-only — proven by gating md_MAIN_011 when this landed. `corpus.o0_sources()` parses this rule,
# so every -O0-aware tool picks the object up without a name convention.
build/src/md_MAIN_011/md_MAIN_011.o: 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
+4 -2
View File
@@ -273,8 +273,10 @@ def _o0_unbankable(spath):
`addu $fp,$sp,$zero` inside the function's first instructions, anchored at `glabel` so a
migrated jump table or .asciz blob ahead of the code is not read as the prologue."""
seg = os.path.basename(os.path.dirname(str(spath)))
if '_o0' in seg or seg == 'boot':
return False # the build compiles this object -O0
parts = os.path.normpath(str(spath)).split(os.sep)
binary = 'main' if (len(parts) > 1 and parts[1] == 'nonmatchings') else parts[1]
if corpus.o0_subseg(binary, seg):
return False # the Makefile compiles this object -O0
try:
head, started = [], False
for ln in open(os.path.join(REPO, str(spath))):
+15
View File
@@ -298,6 +298,21 @@ def o0_sources():
return frozenset(out)
def o0_subseg(binary, subseg):
"""Is the OBJECT built from this asm subseg compiled at -O0?
Derived from the Makefile via o0_sources() (R33), not from the subseg NAME. Three tools used to
ask `'_o0' in name or name == 'boot'` — a convention, and it broke the moment P31 S59 gave
md_MAIN_011 a whole-object -O0 rule while it kept its plain name: `match_one` would have warned
"this cannot bank" about 21 functions that now bank fine, and the wave draw would have kept
refusing to draw them. A name is a convention; the Makefile is the ground truth.
The subseg name is the .c basename by construction (splat writes `src/<bin>/<subseg>.c`, and
main's subsegs live at the tree root as `src/<subseg>.c`)."""
rel = f"src/{subseg}.c" if binary == "main" else f"src/{binary}/{subseg}.c"
return is_o0(rel)
def is_o0(src_path):
"""Is this .c compiled at -O0? `src_path` is repo-relative (a Stub.path) or absolute."""
rel = os.path.relpath(src_path, REPO) if os.path.isabs(src_path) else src_path
+10 -1
View File
@@ -97,12 +97,21 @@ def detect_o0(spath):
_sub = os.path.basename(a.asm_subdir.rstrip('/'))
# The BINARY, from the asm tree layout: asm/<bin>/nonmatchings/<sub> for every alias except main,
# which splat writes at the tree root as asm/nonmatchings/<sub>.
_parts = os.path.normpath(a.asm_subdir).split(os.sep)
_bin = 'main' if (len(_parts) > 1 and _parts[1] == 'nonmatchings') else (_parts[1] if len(_parts) > 1 else 'main')
# TWO ORACLES, and they answer different questions (R34). The PROLOGUE says what the target bytes
# were compiled as; the SUBSEG NAME says what this build will compile the C as (Makefile:697/702/
# 716/724 give `boot` and every `*_o0*.c` object -O0 flags). Either one alone is wrong somewhere:
# `boot/start.s` has no ordinary prologue yet is built -O0, and an -O0 function sitting in an -O2
# subseg has the prologue but cannot bank until it is carved.
_sub_o0 = ('_o0' in _sub) or _sub == 'boot'
try:
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import corpus as _corpus
_sub_o0 = _corpus.o0_subseg(_bin, _sub) # the Makefile is the ground truth, not the name
except Exception:
_sub_o0 = ('_o0' in _sub) or _sub == 'boot' # fallback: the old name convention
_tell_o0 = detect_o0('%s/%s.s' % (a.asm_subdir, a.fn))
_o0 = a.o0 or (not a.no_auto_o0 and (_tell_o0 or _sub_o0))
if _o0 and not a.o0:
+8 -2
View File
@@ -38,13 +38,19 @@ _NS = {"re": re, "os": os}
exec(_FN, _NS)
detect_o0 = _NS["detect_o0"]
sys.path.insert(0, os.path.join(REPO, "tools"))
import corpus # noqa: E402
FAIL = []
def sub_is_o0(path):
"""Does the BUILD compile this subseg -O0? (Makefile:697/702/716/724)"""
"""Does the BUILD compile this subseg -O0? Asked of the Makefile, never of the name (R33) —
md_MAIN_011 keeps its plain name and is compiled -O0 by an explicit whole-object rule."""
sub = os.path.basename(os.path.dirname(path))
return ("_o0" in sub) or sub == "boot"
parts = os.path.normpath(path).split(os.sep)
binary = "main" if (len(parts) > 1 and parts[1] == "nonmatchings") else parts[1]
return corpus.o0_subseg(binary, sub)
def main():