From e99c823cb436f4268c8ec5841b45a835517444b6 Mon Sep 17 00:00:00 2001 From: Drew T <50529377+Druthulu@users.noreply.github.com> Date: Fri, 31 Jul 2026 07:47:47 -0600 Subject: [PATCH] fix(phase-30): extract_unit was blind to the asm-label ALIAS definition form (the 137 "no matched unit") MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The h_seq sweep's 137 "no matched unit for func" skips were ONE exemplar x 137 same-address members, not 137 distinct failures: func_8016191C @ ov_SC01_077, band=mid, non-jr, 24 ins, ALL 137 members still INCLUDE_ASM stubs => 3,288 ins left on the floor by a tool lookup miss (R35 again). ROOT CAUSE (and it refutes the SESSION-27 checkpoint's own diagnosis, R14): the exemplar is NOT "banked in a different binary". It is banked in ov_SC01_077 under the SS37/SS73 asm-label alias — the byte-true body conflicts with the fleet-canonical decl on BOTH SS73 axes (return void vs s32 AND params void*/s32 vs int/unsigned), so it was banked zero-touch as int aF8016191C(int, unsigned int) __asm__("func_8016191C"); extract_unit only ever matched a definition head literally NAMED func_, so it returned None, and every caller reads None as "not matched". FIX (tool-only, T0 blast radius): - _alias_decl_for(): resolve `(...) __asm__("func_");` -> . - extract_unit(): accept the alias identifier as the definition head, re-derived PER FILE so one file's alias can never leak into the next. - carry the alias DECLARATION into the unit (without it the sibling emits the symbol aF8016191C and the body never lands at func_); the backscan now walks PAST the alias line so the fn's own preceding externs are carried exactly as for a plain definition, with a start<=alias_ln<=end guard against double-emit. - R32: an alias decl with no findable definition now REFUSES LOUDLY instead of falling through to _macro_unit and reporting "not matched" — the silent-skip class this fix exists to delete. Rejected the alternative the banking agent suggested (widen engine_core.h's decl void->s32, drop the alias): it addresses only SS73's RETURN axis while decl and body also disagree on PARAMS, and it is a T2 fleet-shared edit where the alias is T0. VERIFIED: unit extracts with externs + exactly one asm label; remap_hseq produces a sibling draft; rtu_match 3/3 MATCH (24 ins) in the real TU (ov_SC01_000/001/004). make tools-health RC=0. The whole-binary byte-gate remains the sole arbiter (G3/P9). --- tools/family_remap.py | 58 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/tools/family_remap.py b/tools/family_remap.py index 3bb1fc5df..e16483eef 100644 --- a/tools/family_remap.py +++ b/tools/family_remap.py @@ -669,13 +669,52 @@ def _foreign_defs(unit_text, addr): return out +def _alias_decl_for(lines, addr): + """(alias_ident, decl_line_index) when the function is defined under the §37/§73 ASM-LABEL ALIAS + form, else (None, None). + + A body whose byte-true signature conflicts with the fleet-canonical decl (BOTH §73 axes at once — + return AND params) is banked zero-touch by giving the definition a different C identifier and + binding the emitted SYMBOL with a GNU asm label: + + extern void func_8016191C(void *a0, s32 a1); <- the fleet canon, in engine_core.h + int aF8016191C(int param_1, unsigned int param_2) __asm__("func_8016191C"); + int aF8016191C(int param_1, unsigned int param_2) { ... } + + `extract_unit` matched only a definition head literally NAMED func_, so it was structurally + blind to this form and returned None — which every caller reads as "not matched". MEASURED + (Phase 30): that blindness was the WHOLE of the h_seq sweep's 137 "no matched unit" skips — one + exemplar (func_8016191C @ ov_SC01_077) × 137 same-address members, 3,288 ins, every member still + an INCLUDE_ASM stub and otherwise sweep-ready. The tool, not the compiler (R35). + + The alias DECLARATION must travel with the unit: without it the sibling TU emits the symbol + `aF8016191C` and the function never lands at func_.""" + rx = re.compile(rf'^\s*[A-Za-z_][\w \*]*?\b([A-Za-z_]\w*)\s*\([^;]*\)\s*' + rf'__asm__\s*\(\s*"func_{addr:08X}"\s*\)\s*;', re.I) + for j, ln in enumerate(lines): + m = rx.match(ln) + if m and m.group(1).lower() != f"func_{addr:08x}": + return m.group(1), j + return None, None + + def extract_unit(ov, addr): """the matched inline def + its contiguous preceding extern/blank/comment lines, from the overlay src. - func_ names are UPPERCASE-hex in src (func_8013DBE4); match case-insensitively to be safe.""" - pat = re.compile(rf'^\s*[A-Za-z_][\w \*]*\bfunc_{addr:08X}\s*\(', re.I) + func_ names are UPPERCASE-hex in src (func_8013DBE4); match case-insensitively to be safe. + + Also resolves the §37/§73 ASM-LABEL ALIAS definition form (see `_alias_decl_for`): the accepted + definition name becomes the alias identifier, and the alias declaration line is carried into the + unit so the sibling still emits the func_ symbol.""" + plain = re.compile(rf'^\s*[A-Za-z_][\w \*]*\bfunc_{addr:08X}\s*\(', re.I) for cf in sorted(glob.glob(f"src/{ov}/{ov}*.c")): lines = open(cf).read().split("\n") + alias_ident, alias_ln = _alias_decl_for(lines, addr) + # RE-DERIVE per file: `pat` must never leak an earlier file's alias into a later one. + pat = (re.compile(rf'^\s*[A-Za-z_][\w \*]*\b{re.escape(alias_ident)}\s*\(', re.I) + if alias_ident else plain) for i, ln in enumerate(lines): + if alias_ident and i == alias_ln: + continue # the alias DECLARATION is not the definition # A DECLARATION ends in `;` — but m2c writes them with a trailing comment # (`M2C_UNK func_80178D40(s32, s32); /* extern */`), so the raw line ends in `*/` and the # old `endswith(";")` guard let it through as a DEFINITION. The forward brace-scan then ran @@ -698,7 +737,7 @@ def extract_unit(ov, addr): # those belong to the FILE, not to the first function under them. Absorbing one makes # the unit drag ~140 unrelated externs into every sibling — several naming types the # sibling's TU lacks — and the whole family gate-fails (Phase 26 session 6). - while j >= 0 and (lines[j].strip() == "" or + while j >= 0 and (lines[j].strip() == "" or j == alias_ln or lines[j].lstrip().startswith(("extern", "//", "/*", "*", "typedef"))): if _DECL_LAYER_END.search(lines[j]): break @@ -724,6 +763,12 @@ def extract_unit(ov, addr): end = k break unit_text = "\n".join(lines[start:end + 1]) + if alias_ident and not (start <= alias_ln <= end): + # The backscan walks PAST the alias line (so the fn's own preceding externs are + # carried, exactly as for a plain definition) and normally sweeps it into + # [start:end]. If some layout kept it out, carry it explicitly and FIRST — the asm + # label is what binds this body to the func_ symbol in the sibling TU. + unit_text = lines[alias_ln].strip() + "\n" + unit_text extra = _foreign_defs(unit_text, addr) if extra: # R32: a unit that defines a function OTHER than its target cannot be templated — @@ -737,6 +782,13 @@ def extract_unit(ov, addr): if macros: unit_text = "\n".join(macros) + "\n" + unit_text return unit_text, cf + if alias_ident: + # R32: we PROVED the function is defined here (the asm label binds the symbol) but could + # not find the alias identifier's definition. Falling through to _macro_unit would report + # "not matched" — the silent skip this whole fix exists to delete. Refuse loudly instead. + print(f"[extract_unit] func_{addr:08X} in {ov}: {cf} declares the asm-label alias " + f"`{alias_ident}` but no definition of it was found — refusing (R32).") + return None, None return _macro_unit(addr)