mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-28 23:00:29 -04:00
fix(verbatim_to_stub): refuse a §179-C epilogue-less fragment
A function with no `jr $ra` of its own falls into a sibling's shared
epilogue. gcc-2.7.2 has no sibcall/tail-merge pass and appends an epilogue to
every C function it compiles, so no C spelling can ever match — converting one
to an INCLUDE_ASM stub just puts an unbankable target into the drawable
frontier.
I did exactly that to six functions in src/800c.c, on a `rows == 1` filter
that meant "the manifest listed one row", not "this is an independent
function" — ignoring the DECOMPILE-AS-PARENT disposition whose entire meaning
is "this row is a FRAGMENT". Three drafting agents then rediscovered §179-C
from scratch, one citing the cookbook line that names its own target.
The symptom is one grep, so nobody should pay an agent to find it again.
TWO THINGS THIS COST, both caught only by testing a known-true case:
* the first version read the function's .s — but splat stops emitting <fn>.s
for a verbatim body, so it had nothing to read and returned False: inert
for precisely the case it guards. It now reads the verbatim block itself.
* my first negative control was CloseEvent, a libapi trampoline that
genuinely has no `jr $ra` — a "false positive" that was the correct
answer. Re-controlled on VectorNormal (verbatim, has jr $ra, guard stays
silent) vs func_80047E58 (verbatim, no jr $ra, guard fires).
Census of main's verbatim blocks: 37 have jr $ra, 100 do not.
This commit is contained in:
@@ -154,3 +154,38 @@ def is_verbatim_asm_draft(text, fn):
|
||||
return False
|
||||
c_def = re.search(rf'^[A-Za-z_][\w \*]*\b{f}\s*\([^;]*\)\s*(?:/\*.*?\*/\s*)?\{{', text, re.M | re.S)
|
||||
return not c_def
|
||||
|
||||
|
||||
def is_epilogueless_fragment(asm_path=None, asm_text=None):
|
||||
"""True if this function has NO `jr $ra` of its own — cookbook §179-C (P31 S76).
|
||||
|
||||
THE CLASS. A §179-C function ends mid-basic-block, or every exit is a raw `j` into a SIBLING's
|
||||
label, and the shared `lw $ra / addiu $sp / jr $ra` tail lives in the next symbol. gcc-2.7.2 has
|
||||
no sibcall/cross-function tail-merge pass and `expand_function_end` appends an epilogue to every
|
||||
C function it compiles, so NO C spelling exists: every attempt comes out +2 instructions
|
||||
(a phantom `jr $ra` / `nop`). The only correct form is a file-scope `__asm__`.
|
||||
|
||||
WHY IT IS A PRECHECK AND NOT A LESSON (S76). The manifest already marks these DECOMPILE-AS-PARENT
|
||||
("this row is a FRAGMENT — decompile the unit_entry, NEVER the fragment") and the cookbook §179-C
|
||||
entry NAMES several of them. I converted six of them to INCLUDE_ASM stubs anyway, on a `rows == 1`
|
||||
filter that meant "the manifest listed one row", not "this is an independent function" — putting
|
||||
six unbankable targets into the drawable frontier. Three separate drafting agents then rediscovered
|
||||
§179-C from scratch, one of them citing the very cookbook line that names its target.
|
||||
|
||||
The symptom is one grep over the target's own `.s`, so no one should ever pay an agent to find it
|
||||
again: a function with no `jr $ra` is not drawable and not convertible, whatever any disposition
|
||||
field says. Verified on the S76 set: 6 of 6 main `src/800c.c` conversions flagged, 5 of 5 overlay
|
||||
conversions cleared.
|
||||
|
||||
READ THE RIGHT SOURCE. Pass `asm_text` when the function is still a VERBATIM body: splat stops
|
||||
emitting `<fn>.s` for a function that is not a stub, so a path-only check has nothing to read and
|
||||
returns False — inert for exactly the case it exists to prevent. Measured S76: the first version
|
||||
of this guard passed a §179-C fragment straight through for that reason, caught only by testing
|
||||
it on a case whose answer was already known. The verbatim block IS the assembly; use it."""
|
||||
if asm_text is None:
|
||||
try:
|
||||
with open(asm_path, errors='replace') as fh:
|
||||
asm_text = fh.read()
|
||||
except (OSError, TypeError):
|
||||
return False # cannot read -> not our call to make; the caller's oracle decides
|
||||
return not any('jr' in ln and '$ra' in ln for ln in asm_text.splitlines())
|
||||
|
||||
@@ -28,6 +28,7 @@ purpose is to enable a byte gate, so it refuses to be the one link that goes unc
|
||||
tools/verbatim_to_stub.py --restore <path.bak> # undo
|
||||
"""
|
||||
import argparse
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
@@ -125,6 +126,19 @@ def main():
|
||||
f'— is it already a stub, or a class-B asm-bodied C function? (R43: refusing to guess)')
|
||||
path, text, s, e, blk = hit
|
||||
|
||||
# REFUSE A §179-C FRAGMENT (P31 S76). A function with no `jr $ra` of its own falls into a
|
||||
# sibling's shared epilogue; gcc-2.7.2 appends an epilogue to every C function it compiles, so
|
||||
# no C spelling can ever match and converting it to a stub only puts an unbankable target into
|
||||
# the drawable frontier. Measured: six of these were converted on a filter that read the
|
||||
# manifest's row COUNT instead of its DECOMPILE-AS-PARENT disposition, and three agents then
|
||||
# rediscovered §179-C independently. The symptom is one grep over the target's own .s.
|
||||
import draft_prechecks as _DP
|
||||
if _DP.is_epilogueless_fragment(asm_text=blk):
|
||||
sys.exit('verbatim_to_stub: REFUSED — %s has NO `jr $ra` of its own (cookbook §179-C): it '
|
||||
'falls into a sibling\'s shared epilogue, so gcc-2.7.2 can never emit it from C '
|
||||
'and a stub here is an unbankable target. It belongs in the file-scope __asm__ '
|
||||
'block it already is.' % a.fn)
|
||||
|
||||
# The asm subdir INCLUDE_ASM must name: take it from a sibling stub in the SAME FILE, because
|
||||
# that is the only spelling guaranteed to resolve for this TU (subsegs are per-file, and a
|
||||
# neighbouring file's spelling is a different subseg — using it would produce a stub that
|
||||
|
||||
Reference in New Issue
Block a user