Files
BFM-decomp/.run/w6_alias_repro.py
T
Drew T 2483fc902a fix(tools): jr_isolate_all was SILENTLY DELETING asm-label-alias definitions during a repartition
ROOT CAUSE (byte-witnessed, P30 S38 — the fifth tool with this same blindness).
A function banked under the §37/§73 DEFINITION-SIDE ASM-LABEL ALIAS form is spelled with a private
C identifier and bound to its real symbol by a GNU asm label:

    void aF8018A860(s32, s16 *, u8 *, u8 *) __asm__("func_80183AF8");   <- decl, stays in preamble
    void aF8018A860(s32, s16 *, u8 *, u8 *) { ... }                     <- THIS emits func_80183AF8

overlay_src_split.addr_of() resolves `func_<hex>` arithmetically and everything else through `syms`.
`aF8018A860` matches NEITHER, so it returned None — and partition() keeps only items with a
resolved address, so the definition was dropped from EVERY region. The file was then rewritten
without it and nothing said so. One carve of ov_SC02_028 deleted the definitions emitting BOTH
func_80183AF8 and func_80184268; the overlay stopped linking with `undefined reference`, and six
wave-6 drafts were written off against that as a plumbing/compiler wall.

TWO FIXES:
 - CAUSE: overlay_src_split now builds an asm-label alias map from the source and resolves a
   definition through its EMITTED SYMBOL rather than its C name (verified: aF8018A860 -> 0x80183AF8,
   aF8018AFD0 -> 0x80184268 — exactly the two symbols the link was missing).
 - SILENCE: partition() and jr_isolate_all._partition() now REFUSE to rewrite a file when any
   construct's address does not resolve (R32), instead of discarding it. That guard alone would
   have surfaced this the first time it happened.

RESULT: 3 of the 6 alias-class wave-6 drafts bank immediately, for ZERO agent tokens —
func_801884D8 (137 ins) · func_80180B04 (251) · func_801380E0 (438). R22 clean-fleet 140/140.
The other 3 (the three LARGEST: 557/513/710 ins) have a second, size-correlated cause — open.

NOTE FOR THE FLYWHEEL: family_remap._alias_decl_for ALREADY handled this exact form, and its
docstring records the identical lesson ("that blindness was the WHOLE of the h_seq sweep's 137 'no
matched unit' skips. The tool, not the compiler (R35)"). The fix was never propagated. The alias
form needs ONE shared oracle, the way §134 comment-masking ended up on cdecl._mask — five tools
have now independently rediscovered it.
2026-08-04 18:30:01 -06:00

89 lines
3.8 KiB
Python

#!/usr/bin/env python3
"""Reproduce the wave-6 ALIAS-CLASS link failure and INSPECT the post-carve tree, then revert.
harvest_verify does carve -> build -> revert, so the interesting state (where the asm-label alias
definition ended up relative to its callers) is destroyed before anyone can look at it. This does
the same carve, dumps the evidence, builds to capture the real linker error, and reverts.
READ THE EVIDENCE, DO NOT GUESS: the hypothesis under test is that `jr_isolate_all` repartitions the
TU and separates the call site of `func_<X>` from the DEFINITION that emits it — which in this
overlay is a §37/§124 definition-side asm-label alias:
void aF8018A860(...) __asm__("func_80183AF8"); /* decl binds the symbol */
void aF8018A860(...) { ... } /* THIS is what emits func_80183AF8 */
A tool that moves code by ADDRESS cannot see that the symbol emitted at 0x80183AF8 is spelled
`aF8018A860` in the source.
Usage: w6_alias_repro.py <ov> <fn> <callee_sym>
"""
import sys, os, re, glob, subprocess, shutil
OV, FN, CALLEE = sys.argv[1], sys.argv[2], sys.argv[3]
PY = '.venv/bin/python'
sys.path.insert(0, 'tools')
import corpus
def sh(cmd, **kw):
return subprocess.run(cmd, capture_output=True, text=True, **kw)
def snapshot(tag):
print(f"\n----- {tag} -----")
files = sorted(glob.glob(f'src/{OV}/*.c'))
print(f" TUs in src/{OV}/: {len(files)}")
for f in files:
t = open(f).read()
defs = len(re.findall(r'__asm__\s*\(\s*"' + re.escape(CALLEE) + r'"\s*\)', t))
refs = len(re.findall(r'\b' + re.escape(CALLEE) + r'\b', t))
stub = len(re.findall(r'INCLUDE_ASM\([^)]*\b' + re.escape(CALLEE) + r'\b', t))
if defs or refs or stub:
print(f" {os.path.basename(f):<46} asm-label-binds={defs} textual-refs={refs} INCLUDE_ASM={stub}")
st = corpus.stubs(OV)
addr = int(FN.split('_')[1], 16)
rec = st.get(addr)
if not rec:
sys.exit(f"{FN} is not a live stub in {OV}")
tu, stub_line = rec.path, f'INCLUDE_ASM("{rec.asm_dir}", {rec.symbol});'
draft = open(f'.run/w6/{OV}/{FN}.c').read()
orig = open(tu).read()
assert orig.count(stub_line) == 1, "stub line not unique"
snapshot("BEFORE the carve (HEAD)")
print(f"\n[1] splicing the draft over the stub in {tu}")
open(tu, 'w').write(orig.replace(stub_line, draft, 1))
print(f"[2] jtbl_carve {OV} --func {FN}")
r = sh([PY, 'tools/jtbl_carve.py', OV, '--func', FN])
out = (r.stdout or '') + (r.stderr or '')
print(f" rc={r.returncode} {out.strip().splitlines()[-1][:130] if out.strip() else ''}")
if r.returncode:
print(f"[3] carve refused -> jr_isolate_all --only {FN} (the §61b ladder: isolate WITH the body spliced)")
r2 = sh([PY, 'tools/jr_isolate_all.py', OV, '--only', FN])
print(f" isolate rc={r2.returncode}")
r3 = sh(['make', '--no-print-directory', 'extract', f'BINARY={OV}'])
print(f" extract rc={r3.returncode}")
r = sh([PY, 'tools/jtbl_carve.py', OV, '--func', FN])
print(f" carve retry rc={r.returncode}")
sh(['make', '--no-print-directory', 'extract', f'BINARY={OV}'])
snapshot("AFTER the carve/isolation — WHERE DID THE ALIAS DEFINITION GO?")
print(f"\n[4] make build BINARY={OV} — the real linker error")
rb = sh(['make', 'build', f'BINARY={OV}'])
allout = (rb.stdout or '') + (rb.stderr or '')
hits = [l for l in allout.splitlines()
if 'undefined reference' in l or 'multiple definition' in l or re.search(r'\.[ch]:\d+.*error', l)]
print(f" build rc={rb.returncode}, {len(hits)} hard diagnostic line(s):")
for l in hits[:10]:
print(" ", l.strip()[:190])
print("\n[5] reverting")
sh(['git', 'checkout', '--', f'src/{OV}', 'config'])
sh(['git', 'clean', '-fdq', f'src/{OV}'])
sh(['make', '--no-print-directory', 'extract', f'BINARY={OV}'])
print(" reverted + re-extracted (R22 corollary: a reverted config needs a re-extract)")