From d4d35ec738c596f79f1070cdae6caac987d426de Mon Sep 17 00:00:00 2001 From: Drew T <50529377+Druthulu@users.noreply.github.com> Date: Mon, 10 Aug 2026 21:56:31 -0600 Subject: [PATCH] feat(phase-30 S47-F2): auto-alias conflicting data externs (partial: 2/44); real cause recorded MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 44 data-symbol conflicting-types failures are NOT the cdFileLocTable duplicate-typedef class. They are genuine per-view type differences: D_80078EB4 is s16 at 2,409 sites and u16 at 1,341; D_800AE620 is Blk20/s32/Mat32. For data the declared type drives the load (lh vs lhu), so canonicalising would rewrite thousands of already-banked sites' codegen. The answer is one type PER VIEW — the §37 asm-label alias the fleet already hand-writes for D_800AE620 (9 sites). Shipped: scope_data_externs now auto-aliases a conflicting extern — extern s16 aD80078EB4 __asm__("D_80078EB4"); keeping the draft's own type (byte-truth for that body) while the private C name makes collision impossible and the asm label pins the emitted symbol, so codegen is unchanged. Fires only where the TU declares that symbol with a DIFFERENT type text; same-type and already-aliased drafts are untouched (4 controls, 2 of them negative). Applied on both scope paths — a staged draft's externs arrive indented, so a demote-path-only fix reached 1 of 44. R22 clean-fleet: check-all 213 passed / 0 failed of 213. WHY ONLY 2 BANKED — the collision is DRAFT-vs-DRAFT, not draft-vs-TU. The failing draft declares `extern s32 D_80114F24;` and ov_MAIN_012.c declares that symbol nowhere; the error lands at the splice point. family_sweep stages every member of an (overlay, split) group into one TU before gating, so two templated bodies with different views of one symbol collide with each other. scope_data_fix sees one draft plus the pre-splice TU and structurally cannot see the others. The real fix belongs in the staging loop, which knows the whole group: alias any data symbol declared with >=2 distinct types across the drafts staged together. Not attempted here. Three wrong inferences on this one task before reading a failing draft: scoped as the typedef class; aliased only the demote path against the file's own comment; targeted the wrong collision. --- phase-ends/CURRENT_PHASE.md | 26 +++++ src/ov_MAIN_012/ov_MAIN_012.c | 120 +++++++++++++++++++++- src/ov_SC06_018/ov_SC06_018_jr_8017C24C.c | 11 +- tools/scope_data_externs.py | 78 +++++++++++++- 4 files changed, 229 insertions(+), 6 deletions(-) diff --git a/phase-ends/CURRENT_PHASE.md b/phase-ends/CURRENT_PHASE.md index 9c69a08cc5..1abdd099df 100644 --- a/phase-ends/CURRENT_PHASE.md +++ b/phase-ends/CURRENT_PHASE.md @@ -426,6 +426,32 @@ sibling lacks. **Now the largest class, and a different mechanism from every dec (D_801202A0 11 · func_8001534C 4) · `14` arity (too many/too few args) · `6` func_80145CEC. **Every former unknown turned out cheap** — declarations, arity, redefinitions. Not one wall. +## ⚠ TASK F2 (data-symbol conflicts) — **PARTIAL: 2 of 44.** Mechanism correct, target wrong. +**NOT the cdFileLocTable class.** These are genuine PER-VIEW type differences, not duplicate +typedefs: `D_80078EB4` is `s16` at 2,409 sites and `u16` at 1,341; `D_800AE620` is +`Blk20`/`s32`/`Mat32`. For DATA the declared type drives the load (`lh` vs `lhu`), so canonicalising +would rewrite thousands of BANKED sites' codegen — the memcpy trap at 100× scale. The answer is one +type PER VIEW: the §37 asm-label alias, which the fleet already hand-writes for `D_800AE620` (9×). +**Shipped:** `scope_data_externs` auto-aliases a conflicting extern (`extern s16 aD80078EB4 +__asm__("D_80078EB4")`) — keeps the draft's own type (byte-truth for that body), private C name +makes collision impossible, asm label pins the emitted symbol ⇒ codegen unchanged. Fires ONLY where +the TU declares that symbol with a different type text. 4 controls incl. 2 negatives. R22 **213/213**. + +**WHY IT ONLY BANKED 2 — the collision is DRAFT-vs-DRAFT, not draft-vs-TU.** Evidence (finally +read, after two wrong inferences): the failing draft declares `extern s32 D_80114F24;` and +`ov_MAIN_012.c` declares that symbol **nowhere**; the error lands at line 24538 = the splice point. +The sweep stages EVERY member of an (overlay,split) group into one TU before gating, so two +templated bodies with different views of one symbol collide with each other. `scope_data_fix` sees +one draft + the PRE-SPLICE TU, so it structurally cannot see the others. +**→ The real fix belongs in the sweep's STAGING LOOP** (it knows the whole group): alias any data +symbol declared with ≥2 distinct types across the drafts being staged together. Not attempted. + +**MY ERROR LEDGER ON THIS ONE TASK (3):** (1) scoped it as the typedef class — wrong; (2) put the +alias only on the demote path when the file's OWN comment says staged drafts arrive INDENTED — 1/44; +(3) the collision is not the one the mechanism targets — 2/44. Each caught by measurement, but I +burned three attempts on a 44-member class by inferring the shape instead of reading ONE failing +draft, which took 90 seconds when I finally did it. + ## ▶ RESUME HERE — D (and the leftovers below) **C is DONE.** For reference, the command was: `.venv/bin/python tools/dedup_extend.py --binaries ov_SC03_107,ov_MAIN_012,ov_SC02_037` diff --git a/src/ov_MAIN_012/ov_MAIN_012.c b/src/ov_MAIN_012/ov_MAIN_012.c index d2cb9544ad..4ea3153108 100644 --- a/src/ov_MAIN_012/ov_MAIN_012.c +++ b/src/ov_MAIN_012/ov_MAIN_012.c @@ -16564,7 +16564,125 @@ void func_80166654(void *a0) { } -INCLUDE_ASM("asm/ov_MAIN_012/nonmatchings/ov_MAIN_012", func_80166690); + + /* 12-byte sub-table record (stride 0xC) */ + + +extern s32 func_80017758(void *a0, void *a1); + +s32 func_80166690(s32 param_1, s32 param_2) +{ + + extern Blk20 aD800AE620 __asm__("D_800AE620"); + extern u8 D_80184418[]; + Frame_80166690 c; + Blk20 blk; + Rec_80166690 *sub; + Rec_80166690 *pv; + u8 *rec; + int i6; + int lt2; + int p2; + unsigned int uVar7, uVar8, uVar9; + int iVar3; + unsigned int w9, w7, w, t, w3b; + int w3; + short sVar10; + register unsigned int zr __asm__("$0"); + + blk = aD800AE620; + i6 = (short)param_1; + c.tag = 0x50000000; + rec = &D_80184418[i6 * 0xC4]; + sVar10 = 0; + if (rec[0] == 0) { + return 0; + } + sub = (Rec_80166690 *)(rec + 4); + uVar9 = rec[1]; + uVar8 = rec[3]; + uVar7 = uVar8 >> 1; + iVar3 = rec[2]; + w3 = iVar3 - 1; + iVar3 = w3 + zr; + if (w3 != -1) { + lt2 = i6 < 2; + p2 = (short)param_2; + do { + pv = &sub[(short)uVar9]; + c.v0 = pv->h[0]; + c.v1 = pv->h[1]; + c.v2 = pv->h[2]; + c.w0 = pv->h[3]; + c.w1 = pv->h[4]; + c.w2 = pv->h[5]; + sVar10 = sVar10 + 1; + + if (lt2) { + if (p2 == 0) { + t = uVar8 + zr; + c.c12 = t; c.c02 = t; + t = uVar7 + zr; + } else { + w = ((int)(uVar8 << 16) >> 17) + ((int)(uVar8 << 16) >> 19); + uVar7 = w + zr; + c.c12 = w; c.c02 = w; + t = uVar8 + zr; + } + c.c11 = t; c.c01 = t; c.c10 = t; c.c00 = t; + } else { + t = uVar8 + zr; + c.c10 = t; c.c00 = t; + t = uVar7 + zr; + c.c12 = t; c.c02 = t; c.c11 = t; c.c01 = t; + } + + w9 = uVar9 - 1; + uVar9 = w9 + zr; + if ((int)(w9 << 16) < 0) { + uVar9 = 0xF; + } + pv = &sub[(short)uVar9]; + c.v3 = pv->h[0]; + c.v4 = pv->h[1]; + c.v5 = pv->h[2]; + c.w3 = pv->h[3]; + c.w4 = pv->h[4]; + uVar8 = uVar8 - 0x10; + w7 = uVar7 - 0x10; + uVar7 = w7 + zr; + c.w5 = pv->h[5]; + if ((int)(w7 << 16) < 0) { + uVar7 = 0; + } + + if (lt2) { + if (p2 == 0) { + t = uVar8 + zr; + c.c32 = t; c.c22 = t; + t = uVar7 + zr; + } else { + t = uVar7 + zr; + c.c32 = t; c.c22 = t; + t = uVar8 + zr; + } + c.c31 = t; c.c21 = t; c.c30 = t; c.c20 = t; + } else { + t = uVar8 + zr; + c.c30 = t; c.c20 = t; + t = uVar7 + zr; + c.c32 = t; c.c22 = t; c.c31 = t; c.c21 = t; + } + + __asm__ __volatile__("" :: "r"(iVar3)); + func_80017758(&c, &blk); + w3b = iVar3 - 1; + iVar3 = w3b + zr; + } while ((short)w3b != -1); + } + return (short)sVar10; +} + extern void func_801484B0(s32 a0, s32 a1); extern void func_80148534(s32 a0, s32 a1); diff --git a/src/ov_SC06_018/ov_SC06_018_jr_8017C24C.c b/src/ov_SC06_018/ov_SC06_018_jr_8017C24C.c index b7c6c5317a..504214aac6 100644 --- a/src/ov_SC06_018/ov_SC06_018_jr_8017C24C.c +++ b/src/ov_SC06_018/ov_SC06_018_jr_8017C24C.c @@ -7078,7 +7078,16 @@ INCLUDE_ASM("asm/ov_SC06_018/nonmatchings/ov_SC06_018_jr_8017C24C", func_8018540 INCLUDE_ASM("asm/ov_SC06_018/nonmatchings/ov_SC06_018_jr_8017C24C", func_80185440); -INCLUDE_ASM("asm/ov_SC06_018/nonmatchings/ov_SC06_018_jr_8017C24C", func_801854C0); + +extern void func_8012A828(s32, void*); + extern short aD801CC358 __asm__("D_801CC358"); + void func_801854C0(short *a0) { + *(short *)((char *)a0 + 0x2) = 0x1D; + *(short *)((char *)a0 + 0x34) = 0; + *(int *)((char *)a0 + 0x1c) = 0xf; + ((void (*)(s32 *, s32))func_8012A828)((s32 *)a0, (int)&aD801CC358); + } + // @class: schedule // @stuck: none — MATCH (181 ins, relocation-masked) diff --git a/tools/scope_data_externs.py b/tools/scope_data_externs.py index 84064a902b..420969b964 100644 --- a/tools/scope_data_externs.py +++ b/tools/scope_data_externs.py @@ -93,6 +93,31 @@ def _body_open_brace(body, func): _ASM_LABEL_RE = re.compile(r'__asm__\s*\(\s*"([^"]+)"\s*\)') +def _norm_ws(s): + return re.sub(r'\s+', ' ', s.strip()) + + +def _decl_type_text(line, sym): + """The declared TYPE of `sym` in one `extern` line — everything between `extern` and the + declarator, plus any array suffix. None if the line does not declare it.""" + m = re.search(rf'^\s*extern\s+(.*?)\b{re.escape(sym)}\b\s*(\[[^\]]*\])?', line) + return (m.group(1) + (m.group(2) or '')) if m else None + + +def _file_scope_data_types(tu_text): + """{sym -> declared type text} for every file-scope data extern ANYWHERE in the TU. + + `_file_scope_data_syms` answers "declared ABOVE the insertion point", which is the right + question for the drop/demote decision and the WRONG one for conflict detection: a file-scope + declaration BELOW the body must still agree with a block-scope extern inside it.""" + out = {} + for ln in FILE_EXTERN_RE.findall(tu_text): + d = DATA_SYM_RE.search(ln) + if d: + out.setdefault(d.group(0), _decl_type_text(ln, d.group(0))) + return out + + def is_asm_alias(ln): """True for a §37/§84 ASM-LABEL ALIAS: `extern T ident… __asm__("SYM");` with ident != SYM. @@ -121,9 +146,11 @@ def fix(body, tu_text, insert_pos, func): qualifies, when the body declares no data externs, or when the opening brace can't be located.""" above = _file_scope_data_syms(tu_text[:insert_pos]) + tu_types = _file_scope_data_types(tu_text) demote = [] # (line_text, sym) dropped = [] # syms the TU ALREADY declares above us — redundant, see below keep_lines = [] + aliased = [] # (sym, alias) — renamed at the end, both scope paths for ln in body.split('\n'): # BLOCK-SCOPE TOO, not just col-0 (Phase 29 SESSION-22). §8d demotes these externs on the way # in, so by the time a sibling draft is STAGED they are already indented — and a col-0-only @@ -135,6 +162,16 @@ def fix(body, tu_text, insert_pos, func): if d and d.group(0) in above and not is_asm_alias(ln): dropped.append(d.group(0)) continue + # THE ALIAS MUST REACH THIS BRANCH TOO (P30 S47). A staged draft's externs are ALREADY + # indented (see the note above), so they arrive here, not in the col-0 demote path — + # and a first cut that aliased only demoted lines fired on 1 member out of 44. + if d and not is_asm_alias(ln): + _mine, _theirs = _decl_type_text(ln, d.group(0)), tu_types.get(d.group(0)) + if _mine and _theirs and _norm_ws(_mine) != _norm_ws(_theirs): + _al = f'aD{d.group(0)[2:]}' + ln = (re.sub(rf'\b{re.escape(d.group(0))}\b', _al, ln, count=1) + .rstrip().rstrip(';') + f' __asm__("{d.group(0)}");') + aliased.append((d.group(0), _al)) keep_lines.append(ln) continue if FILE_EXTERN_RE.match(ln): # col-0 extern (match => anchored at col 0) @@ -161,17 +198,50 @@ def fix(body, tu_text, insert_pos, func): dropped.append(d.group(0)) continue keep_lines.append(ln) - if not demote and not dropped: + if not demote and not dropped and not aliased: return body, [] + def _rename(txt): + """Point every USE at the alias; never touch the asm label (it names the real symbol).""" + for _s, _a in aliased: + txt = re.sub(rf'\b{re.escape(_s)}\b', _a, txt) + txt = txt.replace(f'__asm__("{_a}")', f'__asm__("{_s}")') + return txt + stripped = '\n'.join(keep_lines) if not demote: # drops only — no block to place - return stripped, dropped + return _rename(stripped), dropped at = _body_open_brace(stripped, func) if at is None: # can't place them safely -> leave the body alone return body, [] - block = ''.join(f' {ln}\n' for ln, _ in demote) - return stripped[:at] + '\n' + block + stripped[at:], [s for _, s in demote] + dropped + + # AUTO-ALIAS A DEMOTED EXTERN THE TU DECLARES WITH A DIFFERENT TYPE (P30 S47). + # Demoting to block scope does NOT avoid a conflict: C requires a block-scope `extern` to agree + # with any file-scope declaration of the same object in the TU, wherever it sits. And for DATA + # the type is not cosmetic — it drives the load: `s16` vs `u16` is `lh` vs `lhu`. The fleet + # genuinely reads one address at several widths (§16 loose typing): D_80078EB4 is `s16` at 2,409 + # sites and `u16` at 1,341, D_800AE620 is `Blk20`/`s32`/`Mat32`. Canonicalising would rewrite + # thousands of BANKED sites' codegen, so the answer is not one type — it is one type PER VIEW. + # That is the §37 asm-label alias, and the fleet already hand-writes it for exactly this symbol: + # extern Mtx8_8017C910_8017C910 aD800AE620 __asm__("D_800AE620"); (9 sites) + # Emitting it automatically keeps the draft's own type — byte-truth for THIS body — while the + # private C name makes a collision impossible. Codegen is unchanged: the asm label fixes the + # emitted symbol, so the same load reaches the same address. + # Scoped deliberately: only fires where the TU actually declares the symbol with a DIFFERENT + # type text, so a member that never had a conflict is untouched. + block_lines = [] + for ln, sym in demote: + mine, theirs = _decl_type_text(ln, sym), tu_types.get(sym) + if mine and theirs and _norm_ws(mine) != _norm_ws(theirs): + alias = f'aD{sym[2:]}' + block_lines.append(re.sub(rf'\b{re.escape(sym)}\b', alias, ln, count=1) + .rstrip().rstrip(';') + f' __asm__("{sym}");') + aliased.append((sym, alias)) + else: + block_lines.append(ln) + block = ''.join(f' {b}\n' for b in block_lines) + return _rename(stripped[:at] + '\n' + block + stripped[at:]), \ + [s for _, s in demote] + dropped def main():