mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-10-01 15:43:06 -04:00
fix(phase-29): T60 — reconcile_def_sig name bug fixed (verdicts moved); 0 banked, three causes separated
Tool fix + a sharper diagnosis. NO BANKS — the three "header-conflict" families share a SYMPTOM, not
a cause.
THE FIX (cookbook §109): reconcile_def_sig now conforms the canonical TYPES and keeps the BODY's
parameter names, parsed with cdecl (base/params/pnames, R33 — not a regex). Two re-render traps
handled: `void*` + `a1` -> `void *a1` (cdecl glues stars to the type), and an EMPTY parameter list is
handed back verbatim because `(void)` and `()` both parse to params==[] and are DIFFERENT
declarations (§99 no-prototype). Unit-tested across 6 shapes incl. both void forms and an arity
mismatch; falls back to the wholesale canonical string for fn-ptr/array params.
THE FIX IS REAL, AND THE PROOF IS THAT THE VERDICTS MOVED:
func_8016163C `param_1 undeclared` -> DIFF (plumbing CLEARED; codegen left)
func_8014D610 `param_1 undeclared` -> `void value not ignored` (the HEADER is wrong)
func_80156044 unchanged -> `conflicting types for func_80155FF8` (WRONG LEVER — callee conflict)
TWO FINDINGS UNDER THAT:
1. The §85 return-axis precondition applies to reconcile_def_sig and NOTHING CHECKS IT. Conforming a
def's return to the canonical `void` is only safe when no caller consumes the return.
func_8014D610's callers do, so engine_core.h's `void` contradicts the byte truth and conforming
yields `void value not ignored`. The HEADER is the wrong artifact; correcting it is fleet-shared
blast radius (§61/§63), not a sweep-time fix.
2. func_80156044 was never the def-signature class — its conflict is on the CALLEE func_80155FF8
(decl 2 lines above the splice). That is cast_call_sites / canon_sig_reconcile territory.
HONEST ACCOUNTING: re-swept all three with the fix -> 0/411, tree clean throughout. The lever is now
correct (it no longer manufactures a false compile failure) but it was ONE of three causes, not the
cause. My T59 write-up grouped them as a single ~30,000-instruction block; that grouping was WRONG,
and what disproved it was re-reading each verdict after the fix rather than re-running the batch and
reporting the total.
No src/ or config/ change: no bank, no metric move.
This commit is contained in:
@@ -7587,3 +7587,43 @@ both are already in hand at the call site.
|
||||
> (§107), off by default, or subtly broken — never the compiler. **Before diagnosing a family as
|
||||
> hard, enumerate the levers the invocation actually enabled, then read one member's real cc1
|
||||
> output.** A sweep's `0/N` is a statement about the harness.
|
||||
|
||||
---
|
||||
|
||||
## §109 — Conforming a definition to a shared header: fix the NAMES, then check the RETURN precondition (Phase 29 T60)
|
||||
|
||||
`reconcile_def_sig` substituted the canonical header decl **wholesale — types AND parameter names** —
|
||||
while the body kept the exemplar's names. Its docstring called this a "rare name mismatch" the gate
|
||||
would reject; both halves were wrong. An exemplar drafted with the `param_N` convention hits it
|
||||
**every time**, and the outcome is not a rejected match but the **whole family booking as a compile
|
||||
failure**, indistinguishable from a compiler wall:
|
||||
|
||||
```
|
||||
canonical : void func_8014D610(s32 a0, void *a1, void *a2)
|
||||
draft body: ... param_1 ... -> `param_1' undeclared (first use this function)
|
||||
```
|
||||
|
||||
**Fix: conform the TYPES, keep the BODY's names** — both are in hand at the call site. Parse with
|
||||
`cdecl` (`base` = return type, `params` = types, `pnames` = names), not a regex. Two traps in the
|
||||
re-render: `void*` + `a1` must become `void *a1` (cdecl glues the stars to the type), and an **empty
|
||||
parameter list must be handed back verbatim** — `(void)` and `()` both parse to `params == []`, and
|
||||
they are different declarations (§99: `()` is the no-prototype form).
|
||||
|
||||
**The fix is real but it is not sufficient, and the proof is that the verdicts MOVED:**
|
||||
|
||||
| family | before | after | what is left |
|
||||
|---|---|---|---|
|
||||
| `func_8016163C` | `param_1 undeclared` | **DIFF** | plumbing fully cleared; genuine codegen |
|
||||
| `func_8014D610` | `param_1 undeclared` | `void value not ignored as it ought to be` | **the header is wrong** |
|
||||
| `func_80156044` | *unchanged* | `conflicting types for func_80155FF8` | **wrong lever** — a CALLEE conflict |
|
||||
|
||||
> **The §85 return-axis precondition applies here too, and nothing checks it.** Conforming a
|
||||
> definition's return type to the canonical `void` is only safe when **no caller consumes the
|
||||
> return**. `func_8014D610`'s callers do — so the header's `void` contradicts the byte truth, and
|
||||
> conforming to it produces `void value not ignored`. The header is the thing that is wrong; changing
|
||||
> it is fleet-shared blast radius. `reconcile_def_sig` should test that precondition before promoting
|
||||
> or demoting a return type, exactly as §85 requires of `conform_decls`.
|
||||
|
||||
> **And a verdict that changes is the signal to re-route, not to push harder.** One of these three
|
||||
> is now a codegen question, one is a header-correctness question, and one was never the def
|
||||
> signature at all. They shared a symptom, not a cause — three families, three levers.
|
||||
|
||||
@@ -8130,3 +8130,53 @@ T56 a lever unreachable from the sweep path · T57 a lever off by default · T59
|
||||
4. **Probe the distinct-code +0 anomaly** (4 data points, no identified variable).
|
||||
5. **~50 more eligible families** — regenerate `family_hseq.py` first (the map predates T56–T58), and
|
||||
select exemplars with **`corpus.stubs`**, not a name-grep.
|
||||
|
||||
## 🔧 T60 — `reconcile_def_sig`'s name bug FIXED (verified by moved verdicts); **0 banked**, three causes separated
|
||||
|
||||
Tool fix + a sharper diagnosis. **No banks** — the three "header-conflict" families turned out to
|
||||
share a *symptom*, not a cause.
|
||||
|
||||
### THE FIX (§109)
|
||||
`reconcile_def_sig` now conforms the canonical **TYPES** and keeps the **BODY's parameter names**,
|
||||
parsed with `cdecl` (`base`/`params`/`pnames`, R33 — not a regex). Two re-render traps handled:
|
||||
`void*` + `a1` → `void *a1` (cdecl glues stars to the type), and an **empty parameter list is handed
|
||||
back verbatim** because `(void)` and `()` both parse to `params == []` and are *different*
|
||||
declarations (§99 no-prototype). Unit-tested across 6 shapes incl. both void forms and an arity
|
||||
mismatch; falls back to the wholesale canonical string for fn-ptr/array params it cannot re-render.
|
||||
|
||||
### THE FIX IS REAL — AND THE PROOF IS THAT THE VERDICTS MOVED
|
||||
| family | before | after | what is actually left |
|
||||
|---|---|---|---|
|
||||
| `func_8016163C` | `param_1 undeclared` | **DIFF** | plumbing fully cleared — genuine codegen |
|
||||
| `func_8014D610` | `param_1 undeclared` | `void value not ignored as it ought to be` | **the header is wrong** |
|
||||
| `func_80156044` | *unchanged* | `conflicting types for func_80155FF8` | **wrong lever** — a CALLEE conflict |
|
||||
|
||||
### THE TWO FINDINGS UNDER THAT
|
||||
1. **The §85 return-axis precondition applies to `reconcile_def_sig`, and nothing checks it.**
|
||||
Conforming a def's return type to the canonical `void` is only safe when **no caller consumes the
|
||||
return**. `func_8014D610`'s callers do — so `engine_core.h`'s `void` contradicts the byte truth,
|
||||
and conforming to it yields `void value not ignored`. **The header is the wrong artifact here**,
|
||||
and correcting it is fleet-shared blast radius (§61/§63) — not a sweep-time fix.
|
||||
2. **`func_80156044` was never the def-signature class.** Its conflict is on the CALLEE
|
||||
`func_80155FF8` (decl 2 lines above the splice). `--fix-def-sig` cannot touch it; that is
|
||||
`cast_call_sites` / `canon_sig_reconcile` territory.
|
||||
|
||||
### HONEST ACCOUNTING
|
||||
Re-swept all three with the fix: **0/411**, tree clean throughout. The lever is now correct — it no
|
||||
longer manufactures a false compile failure — but it was **one of three causes, not the cause**. My
|
||||
T59 write-up grouped them as a single 30,000-instruction block; that grouping was **wrong**, and the
|
||||
thing that disproved it was re-reading each verdict after the fix rather than re-running the batch
|
||||
and reporting the total.
|
||||
|
||||
## ▶ NEXT (ranked, all measured)
|
||||
1. **`func_80156044`** (137) — a CALLEE decl conflict (`func_80155FF8`); route to `cast_call_sites` /
|
||||
`canon_sig_reconcile`, not `--fix-def-sig`. Cheapest of the three.
|
||||
2. **`func_8016163C`** (137) — now a clean **DIFF**: measure closeness and route per §31. Likely the
|
||||
`s32`→`void` return demotion costing an instruction (§85) — if so, it needs the header corrected,
|
||||
which merges it with item 3.
|
||||
3. **`func_8014D610`** (137) — `engine_core.h` declares it `void` but callers consume the return.
|
||||
Correcting the header is fleet-shared (§61/§63, R22-mandatory); size the blast radius first.
|
||||
4. **Add the §85 return precondition to `reconcile_def_sig`** — it should refuse to demote a return
|
||||
type when a caller consumes it, instead of emitting code that cannot compile.
|
||||
5. `0x80143d28` (136) `ApplyMatrixSV`; `0x801457a4` (137) the original DIFF; the distinct-code +0
|
||||
probe; ~50 more families (regenerate the map; select with `corpus.stubs`).
|
||||
|
||||
+60
-5
@@ -30,6 +30,7 @@ import scope_tu_externs as STU # §103 — the TU-side decl-scope lever
|
||||
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, os.path.join(REPO, 'tools'))
|
||||
import corpus # the derived corpus oracle (Phase 26-A)
|
||||
import cdecl # the coverage-asserting C-declaration parser (Phase 26-A) — used by _merge_sig
|
||||
PY = ".venv/bin/python"
|
||||
_OV_LOCKS = __import__("collections").defaultdict(_th.Lock) # two splits of ONE overlay build the same binary
|
||||
SWEEP = ".run/sweep"
|
||||
@@ -80,10 +81,25 @@ def header_sig_map():
|
||||
|
||||
def reconcile_def_sig(draft, to_func, smap):
|
||||
"""If `to_func` is forward-declared in a shared header, rewrite the draft's DEFINITION signature
|
||||
(return type + param list) to that canonical decl, so the member TU stops seeing conflicting types.
|
||||
Body param names come from the header decl (auto-generated a0/a1/... on both sides, so they align in
|
||||
the common case; a rare name mismatch makes the body reference an undeclared name → the gate rejects
|
||||
it, never a false bank). Returns the draft unchanged if the fn isn't in a header or no def is found."""
|
||||
to the canonical TYPES while KEEPING THE BODY'S PARAMETER NAMES, so the member TU stops seeing
|
||||
conflicting types without the body losing the identifiers it references.
|
||||
|
||||
THE NAME BUG THIS FIXES (Phase 29 T59/T60). The first cut substituted the canonical decl string
|
||||
WHOLESALE — types AND names. Its docstring called a name mismatch "rare", and said the gate would
|
||||
reject it. Both were wrong in the way that matters: an exemplar drafted with the `param_N`
|
||||
convention hits it EVERY time, and the result is not a rejected match but a whole family booking
|
||||
as a compile failure — indistinguishable from a compiler wall:
|
||||
|
||||
canonical : void func_8014D610(s32 a0, void *a1, void *a2)
|
||||
draft body: ... param_1 ... -> `param_1' undeclared (first use this function)
|
||||
|
||||
Three families / 411 members sat at 0/N on exactly this. The types are what cc1 compares; the
|
||||
names are the body's own business, and both are in hand right here.
|
||||
|
||||
Parsed with `cdecl`, not a regex (R33): `base` is the return type, `params` the types, `pnames`
|
||||
the names. Falls back to the wholesale canonical string when a canonical param type is one this
|
||||
cannot safely re-render (a function-pointer or array parameter) — honest, and no worse than before.
|
||||
Returns the draft unchanged if the fn isn't in a header or no def is found."""
|
||||
canon = smap.get(to_func)
|
||||
if not canon:
|
||||
return draft
|
||||
@@ -92,7 +108,46 @@ def reconcile_def_sig(draft, to_func, smap):
|
||||
m = pat.search(draft)
|
||||
if not m:
|
||||
return draft
|
||||
return draft[:m.start()] + m.group(1) + canon + " {" + draft[m.end():]
|
||||
return draft[:m.start()] + m.group(1) + _merge_sig(m.group(0), canon, to_func) + " {" + draft[m.end():]
|
||||
|
||||
|
||||
def _render_param(ctype, name):
|
||||
"""`void*` + `a1` -> `void *a1`; `s32` + `a0` -> `s32 a0`. cdecl renders a param type with its
|
||||
stars glued on, which is not how a declarator is written."""
|
||||
stars = len(ctype) - len(ctype.rstrip('*'))
|
||||
return f"{ctype.rstrip('*')} {'*' * stars}{name}" if name else ctype
|
||||
|
||||
|
||||
def _merge_sig(def_line, canon, to_func):
|
||||
"""Canonical TYPES + the definition's own parameter NAMES -> a signature string (no trailing brace).
|
||||
|
||||
Positional: name[i] comes from the draft where it has one, else from the canonical decl. An arity
|
||||
mismatch is not smoothed over — the canonical arity governs the declaration (it is what callers
|
||||
see), and if the draft had MORE parameters its extra names simply are not declared, so the body
|
||||
references an undeclared identifier and the byte-gate rejects it. That is the correct outcome for
|
||||
a genuinely disagreeing signature; it must not be papered into a false bank (G3/P9)."""
|
||||
try:
|
||||
cd = cdecl.parse(canon.rstrip('; ') + ';')[0]
|
||||
dd = cdecl.parse(def_line.strip().lstrip('\n').rstrip('{ \t\n') + ';')[0]
|
||||
except Exception:
|
||||
return canon
|
||||
ctypes = cd.params or []
|
||||
if any(('(' in t or '[' in t) for t in ctypes): # fn-ptr / array param: cannot re-render safely
|
||||
return canon
|
||||
if not ctypes:
|
||||
# `(void)` and `()` both parse to params==[] — and they are NOT the same declaration
|
||||
# (§99: `()` is the no-prototype form, which changes argument promotion). There are no names
|
||||
# to preserve here anyway, so hand back the canonical string and keep whichever form the
|
||||
# header actually wrote.
|
||||
return canon
|
||||
dnames = list(dd.pnames or [])
|
||||
cnames = list(cd.pnames or [])
|
||||
out = []
|
||||
for i, t in enumerate(ctypes):
|
||||
nm = (dnames[i] if i < len(dnames) and dnames[i] else
|
||||
(cnames[i] if i < len(cnames) and cnames[i] else f"a{i}"))
|
||||
out.append(_render_param(t, nm))
|
||||
return f"{cd.base} {to_func}({', '.join(out)})"
|
||||
|
||||
|
||||
def draft_def_ref(draft, to_func):
|
||||
|
||||
Reference in New Issue
Block a user