mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-26 13:33:34 -04:00
cd03c67652
§332 states the class is "6 FUNCTIONS FLEET-WIDE, NONE BANKABLE FROM C" and names
TWO of them. §332a then says, correctly, "Filter before drafting" -- but a filter
needs the LIST, and the rest were never written down, so the draw kept handing them
to agents. A COUNT WITHOUT AN ENUMERATION CANNOT DRIVE A FILTER.
Measured cost of that gap today: main/func_80061FA8 -- a fable agent produced C
that oracle_reorder proves BYTE-CORRECT (0 diffs / 103 ins) and that the pinned
triple still cannot emit. 92,684 tokens to rediscover a documented class. Plus
main/func_8005F0C8 at 289k tokens, the same story via §188.
The sweep is now the list: 10 functions, 1,027 instructions, derived from 1,378
open-stub .s files with 0 unreadable.
TWO DEFECTS IN MY OWN DETECTOR, both caught by demanding it reproduce members I
already knew -- the same rule I have been applying to every other tool today:
* It returned a confident 0 across all 1,378 files. The .s lines carry a
slash-star offset/addr/bytes star-slash comment prefix, and my regex anchored
the mnemonic at start-of-line, so it matched NOTHING. A sweep returning 0 must
prove it CAN return non-zero before the 0 means anything.
* Widened, it found 6 but MISSED func_8005DBD8, which §332a names. Its delay slot
holds a store through %lo -- the tail of a lui-%hi / store-%lo MACRO, not a la.
Same mechanism, different mnemonic: ANY %lo in a delay slot is the second half
of an assembler macro that gcc emits as one atomic insn, so C can never put it
there.
IT PAID FOR ITSELF WITHIN MINUTES: main/func_8005D734 is in the list, and I had
escalated it to Fable at closeness 8 twenty minutes earlier. The sweep's site for
it is EXACTLY the residual that agent described. That escalation could never
succeed and has been stopped.
Ledger: .run/S68_walls_332.txt (--emit-exclude form, ready for draw_waves).
114 lines
5.0 KiB
Python
114 lines
5.0 KiB
Python
#!/usr/bin/env python3
|
|
"""wall_sweep.py — enumerate the §332 maspsx `la`-in-a-delay-slot walls, fleet-wide. (P31 S68)
|
|
|
|
WHY THIS EXISTS. Cookbook §332 states the class is "6 FUNCTIONS FLEET-WIDE, NONE BANKABLE FROM C"
|
|
and names TWO of them (`func_80062144`, `func_8005DBD8`). §332a then says, correctly, *"Filter
|
|
before drafting"* — but a filter needs the LIST, and the other four were never written down. So the
|
|
draw kept handing them to agents:
|
|
|
|
S68 `main/func_80061FA8` — a FABLE agent produced C that `oracle_reorder` proves BYTE-CORRECT
|
|
(0 diffs / 103 ins) and that the pinned triple still cannot emit: two §332 `la`-in-jal-slot
|
|
sites plus one §188 epilogue. 92,684 tokens to rediscover a known class.
|
|
|
|
**A count without an enumeration cannot drive a filter.** The detection is mechanical, so this
|
|
turns the sentence into a list.
|
|
|
|
THE TELL. gcc-2.7.2's `movsi_internal` emits a symbolic address load as ONE atomic RTL insn of
|
|
length 2 (the `lui`+`addiu %lo(...)` pair; this MIPS backend has no HIGH/LO_SUM define_split). A
|
|
delay slot holding the SECOND HALF of such a pair therefore cannot be produced from C — the
|
|
assembler filled it, and maspsx cannot reproduce that.
|
|
So: a delay slot (the line after an unconditional jump / jal / branch) whose text is an
|
|
`addiu <reg>,<reg>,%lo(<sym>)`.
|
|
|
|
COVERAGE (R32). The denominator is every `.s` under the asm tree for every OPEN stub — printed, with
|
|
the count of files that could not be read. A hit list that does not state what it scanned is not
|
|
evidence.
|
|
|
|
tools/wall_sweep.py # report
|
|
tools/wall_sweep.py --emit-exclude # binary:fn lines, ready for draw_waves --exclude
|
|
"""
|
|
import argparse
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
os.chdir(REPO)
|
|
sys.path.insert(0, os.path.join(REPO, "tools"))
|
|
import corpus # noqa: E402
|
|
import progress # noqa: E402
|
|
|
|
# THE .s LINES CARRY A COMMENT PREFIX: ` /* 52854 80062054 3771010C */ jal func_X`.
|
|
# My first version anchored the mnemonic at ^\s* and therefore matched NOTHING — the sweep
|
|
# returned a confident 0 across 1,378 files while a function I already KNEW had two of these sat in
|
|
# the scan set. That null was caught only because the rule "a sweep returning 0 must prove it can
|
|
# return non-zero" was applied to my own tool (R32/R40). Skip the comment before matching.
|
|
_PFX = r"^\s*(?:/\*[^*]*\*/)?\s*"
|
|
BRANCH = re.compile(_PFX + r"(jal|j|jr|b|beq|bne|beqz|bnez|blez|bgez|bltz|bgtz|bc\w+)\s")
|
|
# WIDER THAN `addiu ...,%lo(` ALONE. The first version matched only the `la` second half and so
|
|
# MISSED func_8005DBD8, which §332a names — its slot holds `sw $v0, %lo(D_800729A0)($at)`, the
|
|
# second half of a `lui $at,%hi / sw %lo($at)` STORE macro. Same mechanism, different mnemonic: any
|
|
# `%lo(` in a delay slot is the tail of an assembler macro pair that gcc emits as ONE atomic insn,
|
|
# so C can never put it there. Iterating the detector against a KNOWN member is what exposed the
|
|
# narrowness — a hit list that cannot reproduce the members you already know is not evidence.
|
|
LA_LO = re.compile(_PFX + r"\S+\s+.*%lo\(")
|
|
|
|
|
|
def scan(path):
|
|
"""[(lineno, branch_text, slot_text)] for every delay slot holding a macro tail (%lo)."""
|
|
try:
|
|
with open(path, errors="replace") as fh:
|
|
lines = fh.readlines()
|
|
except OSError:
|
|
return None
|
|
hits = []
|
|
for i, ln in enumerate(lines[:-1]):
|
|
if BRANCH.match(ln) and LA_LO.match(lines[i + 1]):
|
|
hits.append((i + 1, ln.strip(), lines[i + 1].strip()))
|
|
return hits
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser(description=__doc__,
|
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
ap.add_argument("--emit-exclude", action="store_true",
|
|
help="print `binary:fn` lines only, for draw_waves --exclude")
|
|
a = ap.parse_args()
|
|
|
|
scanned = unreadable = 0
|
|
walls = []
|
|
for b in progress.BINARIES:
|
|
try:
|
|
stubs = corpus.stubs(b)
|
|
except Exception as e:
|
|
print(" oracle refused %s: %r" % (b, e), file=sys.stderr)
|
|
continue
|
|
for s in stubs.values():
|
|
p = os.path.join(s.asm_dir, s.symbol + ".s")
|
|
if not os.path.exists(p):
|
|
unreadable += 1
|
|
continue
|
|
scanned += 1
|
|
hits = scan(p)
|
|
if hits is None:
|
|
unreadable += 1
|
|
elif hits:
|
|
walls.append((b, s.symbol, corpus.s_ins_count(p), hits))
|
|
|
|
if a.emit_exclude:
|
|
for b, fn, _n, _h in sorted(walls):
|
|
print("%s:%s" % (b, fn))
|
|
return 0
|
|
|
|
print("scanned %d open stub .s file(s); %d unreadable/absent" % (scanned, unreadable))
|
|
print("§332 `la`-in-a-delay-slot WALLS: %d function(s)\n" % len(walls))
|
|
for b, fn, n, hits in sorted(walls):
|
|
print(" %-14s %-18s %4d ins %d site(s)" % (b, fn, n, len(hits)))
|
|
for ln, br, slot in hits[:2]:
|
|
print(" line %-6d %-28s | slot: %s" % (ln, br, slot))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|