Files
BFM-decomp/tools/reorder_passthrough.py
T
Drew T 067f25f682 feat(build): §332b — per-object REORDER path for the 800c2/800c3 PsyQ island
Those two objects were originally assembled in REORDER mode (the assembler filled
the delay slots). maspsx force-emits `.set noreorder`, making that unreachable, so
a whole class there read as a permanent compiler wall (§332) when the property
belongs to the OBJECT, not the toolchain.

For REORDER_TUS only, swap maspsx for tools/reorder_passthrough.py + `as -O2` --
the pipeline tools/oracle_reorder.py already proved byte-exact (0 diffs on
func_80061FA8 where the pinned path gives 57). Everything else is untouched.

Verified:
  * branch selection BOTH ways: 800c3 -> reorder_passthrough, 800.c -> maspsx
  * tools/reorder_passthrough.py --selftest, incl. a negative control (a line
    merely CONTAINING "move", e.g. `jal remove_thing`, must not be rewritten)
  * BYTE-INERT: main rebuilds BYTE-IDENTICAL via verify_binary (§384, re-extracts)

Note the first patch used `ifeq ($(filter $*,...))`, which make evaluates at PARSE
time when $* is empty -- it would have silently always taken the maspsx branch.
`$(if ...)` expands per-target, which is why the rule already uses that form for
JTBL_PADS.
2026-09-01 20:32:12 -06:00

47 lines
2.4 KiB
Python

#!/usr/bin/env python3
"""reorder_passthrough.py — the §332b assembler-REORDER path, for the objects that were built that way.
THE PINNED PATH is `cc1 -> maspsx -> as -O1`, and maspsx force-emits `.set noreorder` after every
`.ent`. That is correct for the whole fleet EXCEPT the `800c2` / `800c3` PsyQ island, whose ORIGINAL
objects were assembled in REORDER mode: the assembler itself filled delay slots. `as` cannot be asked
to do that once `.set noreorder` is in the stream, so a whole class of functions there looked like a
permanent compiler wall (§332) when it is really a per-OBJECT assembler MODE.
This filter is the maspsx replacement for those objects, and it does exactly what the proven oracle
(`tools/oracle_reorder.py`, measured 0 diffs on func_80061FA8 where the pinned path gave 57) does:
take cc1's output and rewrite `move` into the `addu rd, rs, $0` form, leaving `.set noreorder` absent
so `as -O2` performs the original reorder-mode slot filling.
WHY NOT MASPSX WITH A FLAG: maspsx's other transforms (div expansion, li expansion, macro handling)
are not needed by these two objects — §332b measured the swap BYTE-INERT across the whole of both —
and adding a mode to a pinned submodule to serve two objects is the larger, less reversible change.
cc1 ... | python3 tools/reorder_passthrough.py | as -O2 ...
The whole-binary byte gate remains the sole arbiter (G3/P9): if this were wrong for any object it is
built into, main's SHA1 changes and the build fails.
"""
import re
import sys
# `move $a, $b` -> `addu $a, $b, $0`. gcc-2.7.2 emits the macro; the original objects carry the
# expanded form. Identical semantics, and it is what oracle_reorder.py proved byte-exact.
_MOVE = re.compile(r"\tmove\t(\$\w+),(\$\w+)")
def convert(text):
return _MOVE.sub(lambda m: "\taddu\t%s,%s,$0" % (m.group(1), m.group(2)), text)
if __name__ == "__main__":
if "--selftest" in sys.argv:
got = convert("\tmove\t$v0,$a0\n\tnop\n\tmove\t$s1,$zero\n")
want = "\taddu\t$v0,$a0,$0\n\tnop\n\taddu\t$s1,$zero,$0\n"
ok = got == want
# negative control: a line that merely CONTAINS "move" must be untouched
neg = convert("\tjal\tremove_thing\n")
ok &= neg == "\tjal\tremove_thing\n"
print("reorder_passthrough selftest:", "OK" if ok else "FAILED")
sys.exit(0 if ok else 1)
sys.stdout.write(convert(sys.stdin.read()))