diff --git a/Makefile b/Makefile index 8c3e961701..864ed0dd14 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/tools/build_wave_atlas.py b/tools/build_wave_atlas.py index 67b0550ec9..7fc15d1267 100644 --- a/tools/build_wave_atlas.py +++ b/tools/build_wave_atlas.py @@ -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))): diff --git a/tools/corpus.py b/tools/corpus.py index 6412930c35..55c0136d3a 100644 --- a/tools/corpus.py +++ b/tools/corpus.py @@ -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//.c`, and + main's subsegs live at the tree root as `src/.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 diff --git a/tools/match_one.py b/tools/match_one.py index 0c7335eefa..d582fbc095 100644 --- a/tools/match_one.py +++ b/tools/match_one.py @@ -97,12 +97,21 @@ def detect_o0(spath): _sub = os.path.basename(a.asm_subdir.rstrip('/')) +# The BINARY, from the asm tree layout: asm//nonmatchings/ for every alias except main, +# which splat writes at the tree root as asm/nonmatchings/. +_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: diff --git a/tools/test_o0_detect.py b/tools/test_o0_detect.py index 1ed8f631b9..ea0d97577b 100644 --- a/tools/test_o0_detect.py +++ b/tools/test_o0_detect.py @@ -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():