Files
Drew T cd03c67652 feat(walls): wall_sweep.py — ENUMERATE the §332 delay-slot macro walls, 10 fns / 1,027 ins
§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).
2026-08-31 19:59:27 -06:00

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())