diff --git a/tools/draft_prechecks.py b/tools/draft_prechecks.py index 70728d086..b13189312 100644 --- a/tools/draft_prechecks.py +++ b/tools/draft_prechecks.py @@ -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 `.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()) diff --git a/tools/verbatim_to_stub.py b/tools/verbatim_to_stub.py index bd4c7d082..6e2c23dc8 100644 --- a/tools/verbatim_to_stub.py +++ b/tools/verbatim_to_stub.py @@ -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 # 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