mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-10-07 09:27:55 -04:00
fix(phase-31): gate_main conflict check reads the destination TU + is per-file (S52-7)
resolve_conflicts() had two defects, both found by the wave-J/K/L draft recovery:
(a) THE SYMBOL TABLE STARTED EMPTY -- only draft-vs-draft was compared, so a draft contradicting
a declaration ALREADY IN the .c reached the rebuild and surfaced only as a compile error and
a bisect. src/800.c carries 'extern void func_8001C9D0(void);' (from banked func_8001C2C4)
while three wave-J drafts declared it (s32)/(void *). The TU now seeds the table, and the
drop report names whether the clash is with the TU itself or an earlier draft.
(b) ONE NAMESPACE FOR ALL FILES -- 'seen' was global across the slate, so two drafts landing in
DIFFERENT .c files could not legally disagree about a symbol. Separate TUs are separate
namespaces; the table is now keyed per destination file (R39: over-refusal discards good work).
On the 11 recovered drafts the new check named 7 real TU conflicts that the old one missed
entirely. All 7 were repaired by adopting the TU's declaration verbatim and casting at the use
site -- including a NEW variant: when the TU's prototype takes no argument and the call must pass
one, cast through a function pointer, ((void (*)(s32))func_8001C9D0)(a0). All 11 re-verified
MATCH afterwards, so the cast is byte-identical in every case.
R39 NC: a synthetic draft re-contradicting the TU is still dropped; the repaired slate is 11/11.
This commit is contained in:
+44
-3
@@ -81,13 +81,53 @@ def sha():
|
||||
return r.stdout.split()[0] if r.returncode == 0 and r.stdout else None
|
||||
|
||||
def resolve_conflicts(slate):
|
||||
kept, dropped, seen = [], [], {}
|
||||
"""Drop drafts whose externs contradict (a) the destination TU's OWN existing declarations,
|
||||
or (b) an earlier draft landing in the SAME file.
|
||||
|
||||
Two defects fixed here after the S52 recovery pass (both cost verified-correct drafts):
|
||||
|
||||
(a) THE TABLE STARTED EMPTY. Only draft-vs-draft was compared, so a draft contradicting a
|
||||
declaration ALREADY IN the .c sailed through to the rebuild and only surfaced as a
|
||||
compile error + bisect. Real case: src/800.c carries `extern void func_8001C9D0(void);`
|
||||
(from banked func_8001C2C4) while three wave-J drafts declared it (s32) / (void *).
|
||||
The TU is the arbiter (wave law 2) -- so the TU seeds the table.
|
||||
(b) ONE NAMESPACE FOR ALL FILES. `seen` was global across the slate, so two drafts landing in
|
||||
DIFFERENT .c files could not legally disagree about a symbol -- but they can; separate
|
||||
TUs are separate namespaces. Now keyed per destination file (R39: a refusal check that
|
||||
discards good work is worse than one that lets a failure through).
|
||||
|
||||
Recovery for a real (a)-class drop is the call-site cast: adopt the TU's declaration verbatim
|
||||
and cast at the use site -- including through a function pointer when the TU's prototype takes
|
||||
no argument and your call passes one: ((void (*)(s32))func_8001C9D0)(a0) (byte-identical;
|
||||
verified on all 3 of the above)."""
|
||||
stubs = {st.symbol: st for st in corpus.stubs('main').values()}
|
||||
kept, dropped = [], []
|
||||
seen_by_file, from_tu = {}, {}
|
||||
|
||||
def table(path):
|
||||
if path not in seen_by_file:
|
||||
t = {}
|
||||
try:
|
||||
for d in DECL.findall(open(path).read()):
|
||||
s = sym_of(d)
|
||||
if s: t[s] = typesig(d)
|
||||
except OSError:
|
||||
pass
|
||||
seen_by_file[path] = t
|
||||
from_tu[path] = set(t) # so the report can say WHO it clashed with
|
||||
return seen_by_file[path]
|
||||
|
||||
for e in slate:
|
||||
st = stubs.get(e['fn'])
|
||||
path = st.path if st else '<unknown>'
|
||||
seen = table(path)
|
||||
ds = [(sym_of(d), typesig(d)) for d in DECL.findall(open(e['draft']).read())]
|
||||
ds = [(s, t) for s, t in ds if s]
|
||||
clash = [(s, seen[s], t) for s, t in ds if s in seen and seen[s] != t]
|
||||
if clash:
|
||||
dropped.append({'fn': e['fn'], 'symbol': clash[0][0],
|
||||
sym = clash[0][0]
|
||||
dropped.append({'fn': e['fn'], 'symbol': sym, 'file': path,
|
||||
'against': 'the TU itself' if sym in from_tu.get(path, ()) else 'an earlier draft',
|
||||
'kept': str(clash[0][1]), 'this': str(clash[0][2])})
|
||||
continue
|
||||
for s, t in ds: seen[s] = t
|
||||
@@ -175,7 +215,8 @@ def main():
|
||||
kept, dropped = resolve_conflicts(slate)
|
||||
print(f"slate {len(slate)} -> {len(kept)} compatible, {len(dropped)} dropped for in-TU decl conflict")
|
||||
for d in dropped:
|
||||
print(f" DROP {d['fn']}: {d['symbol']} kept={d['kept']} this={d['this']}")
|
||||
print(f" DROP {d['fn']}: {d['symbol']} clashes with {d.get('against','?')} "
|
||||
f"in {d.get('file','?')} kept={d['kept']} this={d['this']}")
|
||||
if dropped:
|
||||
print(" (dropped drafts are usually CORRECT -- recover with a cast-at-use: adopt the")
|
||||
print(" other declaration verbatim and adapt at the use site, e.g. (&D_x)[i].)")
|
||||
|
||||
Reference in New Issue
Block a user