mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-26 13:33:34 -04:00
05a4aeaf41
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.
105 lines
4.5 KiB
Python
105 lines
4.5 KiB
Python
#!/usr/bin/env python3
|
|
"""test_o0_detect.py — negative control for match_one's -O0 auto-detection (P31 S59).
|
|
|
|
WHY IT EXISTS. `match_one --o0` has always existed, and nothing in the drafting path ever passed
|
|
it: `api_draft.match_one()` — the oracle every wave agent iterates against — builds a fixed argv
|
|
without it. An agent handed an -O0 target was shown an -O2 compile of its own C and a mismatch on
|
|
every instruction, feedback that cannot converge for a reason invisible in the diff. S59 made the
|
|
opt level auto-detected. This is the control on that detector.
|
|
|
|
THE DETECTION LAW (cookbook §6/§18, byte-proven): gcc-2.7.2 keeps a frame pointer at -O0 and omits
|
|
it at -O2, so an -O0 function opens with `sw $fp, N($sp)` + `addu $fp, $sp, $zero` (word 21F0A003).
|
|
|
|
THREE CHECKS:
|
|
NC-1 COVERAGE — every .s under asm/ that carries the -O0 signature anywhere is detected as -O0,
|
|
either by its prologue or by its subseg being an -O0 object. A miss compiles an -O0 target
|
|
at -O2, which is the whole defect.
|
|
NC-2 NO FALSE POSITIVES OUTSIDE THE -O0 OBJECTS — a file the signature grep does NOT find may
|
|
only be called -O0 if it lives in a subseg the Makefile compiles -O0 (`boot`, `*_o0*`).
|
|
Calling an -O2 function -O0 is the same defect with the sign flipped.
|
|
NC-3 THE PROLOGUE RULE IS ANCHORED AT THE FUNCTION — the two md_MAIN_011 files whose .s opens
|
|
with a migrated jump table / .asciz blob must still be detected. They are the reason the
|
|
scan starts at `glabel`, not at the top of the file.
|
|
|
|
Run: .venv/bin/python tools/test_o0_detect.py
|
|
"""
|
|
import glob
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
|
|
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
# Import detect_o0 without executing match_one's CLI (it parses argv at import time).
|
|
_SRC = open(os.path.join(REPO, "tools/match_one.py")).read()
|
|
_FN = _SRC[_SRC.index("def detect_o0"):_SRC.index("_sub = os.path.basename")]
|
|
_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? 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))
|
|
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():
|
|
os.chdir(REPO)
|
|
files = glob.glob("asm/**/*.s", recursive=True)
|
|
grep = set(subprocess.run(
|
|
["bash", "-c", "grep -rl 'addu .*\\$fp, *\\$sp\\|21F0A003' asm/ --include=*.s"],
|
|
capture_output=True, text=True).stdout.split())
|
|
print(f"R32 denominator: {len(files)} .s files under asm/")
|
|
print(f"carrying the -O0 signature anywhere in the file: {len(grep)}")
|
|
|
|
detected = {p for p in files if detect_o0(p) or sub_is_o0(p)}
|
|
missed = sorted(grep - detected)
|
|
print(f"detected as -O0 (prologue OR -O0 subseg): {len(detected)}")
|
|
|
|
# NC-1
|
|
if missed:
|
|
FAIL.append(f"NC-1 COVERAGE: {len(missed)} signature-carrying files not detected: {missed[:5]}")
|
|
print(f"NC-1 coverage : {len(grep) - len(missed)}/{len(grep)} signature files detected")
|
|
|
|
# NC-2
|
|
fp = sorted(p for p in detected - grep if not sub_is_o0(p))
|
|
if fp:
|
|
FAIL.append(f"NC-2 FALSE POSITIVES: {len(fp)} files called -O0 with no signature and no -O0 subseg: {fp[:5]}")
|
|
extra_in_o0 = len(detected - grep) - len(fp)
|
|
print(f"NC-2 false positives : 0 outside -O0 objects ({extra_in_o0} inside them, which the "
|
|
f"Makefile does compile -O0)")
|
|
|
|
# NC-3
|
|
anchored = ["asm/md_MAIN_011/nonmatchings/md_MAIN_011/func_800CF28C.s",
|
|
"asm/md_MAIN_011/nonmatchings/md_MAIN_011/func_800D04F4.s"]
|
|
for p in anchored:
|
|
if not os.path.exists(p):
|
|
print(f"NC-3 skipped ({p} absent — asm/ not extracted?)")
|
|
break
|
|
if not detect_o0(p):
|
|
FAIL.append(f"NC-3 {p}: rodata-first .s not detected — the scan is not anchored at glabel")
|
|
else:
|
|
print(f"NC-3 rodata-first : {len(anchored)}/{len(anchored)} detected (scan anchored at glabel)")
|
|
|
|
if FAIL:
|
|
print("\nFAILED:")
|
|
for f in FAIL:
|
|
print(" -", f)
|
|
return 1
|
|
print("\nALL CHECKS PASS")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|