mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-30 07:31:59 -04:00
feat(resolver/aprop): decl-from-use wired into the A-prop drafter; TU-BROKEN classification; dup-def→extern candidates (P31 S61 follow-up, all zero-token)
aprop_autodraft: where the seed carries no decl, infer a minimal extern from the MEMBER'S OWN target .s (decl_from_use, negative-controlled 97.4%/4,702) and place it at BLOCK scope via insert_decls — file scope collides with the fleet's per-function loose-typing (the ov_SC04_018 lesson). Strictly additive: runs only where the old path refused; genuine refusals keep the old behaviour with the class named. integration_resolver: (1) a CC1/CPP verdict is billed to the DRAFT only after the split TU passes a TU-alone compile probe WITHOUT the draft — else TU-BROKEN, no demotion, auto-reopened when the TU's hash changes (S61: one broken TU was billed to 39 drafts; one-off sweep over the CC1 stock: 1 broken TU of 55, 39 verdicts reclassified). (2) dup-def→extern demotion: a draft that DEFINES data a still-stubbed sibling .s in the same TU also emits dies at the assembler with 'symbol already defined' — invisible to rtu (INCLUDE_ASM neutralized). Single-line file-scope defs whose symbol a sibling .s emits are demoted to extern as an ADDITIONAL candidate (new sha, so the ledger's unchanged-skip does not hide it). Smoke test: md_MAIN_003/func_800D3204 demoted D_800D3200 → rtu MATCH (12 ins); the whole-binary SHA remains the sole arbiter.
This commit is contained in:
@@ -517,6 +517,34 @@ def build_draft(body, seed_name, member_name, renames, seed_text, dest_text,
|
||||
missing.append(s)
|
||||
continue
|
||||
decls.append(re.sub(rf'\b{re.escape(old)}\b', s, d))
|
||||
if missing and member_asm:
|
||||
# DECL-FROM-USE (P31 S61 follow-up). The seed has no decl, but the member's OWN target .s
|
||||
# states how the symbol is accessed (widths, sign, index scale). Infer a MINIMAL C89 extern
|
||||
# (negative-controlled at 97.4% kind-agreement over 4,702 banked triples) and place it at
|
||||
# BLOCK SCOPE via insert_decls — file-scope decls conflict with the fleet's per-function
|
||||
# loose-typing convention (the ov_SC04_018 lesson). Genuine refusals (STRUCT, conflicting
|
||||
# width, function symbols, no evidence) keep the old refusal, now with the class named.
|
||||
# Strictly additive: this branch runs only where the old code refused outright.
|
||||
try:
|
||||
import decl_from_use as _dfu
|
||||
_ev = _dfu.collect(member_asm)
|
||||
_blanked = _dfu.blank_decl_statements(new_body)
|
||||
_got, _still = {}, []
|
||||
for _s in missing:
|
||||
try:
|
||||
_d = _dfu.infer(_s, _ev, _dfu.usage_forms(_blanked, _s))
|
||||
_got[_s] = _d["decl"]
|
||||
except _dfu.Refuse as _r:
|
||||
_still.append("%s(%s)" % (_s, _r.cls))
|
||||
except Exception as _e:
|
||||
_still.append("%s(ERR:%s)" % (_s, type(_e).__name__))
|
||||
if _got and not _still:
|
||||
new_body = _dfu.insert_decls(new_body, _got)
|
||||
missing = []
|
||||
else:
|
||||
missing = _still or missing
|
||||
except Exception as _e:
|
||||
missing = ["(decl_from_use failed: %r)" % _e] + missing
|
||||
if missing:
|
||||
# R32: a draft missing a declaration is a KNOWN-BAD draft. Don't spend a build on it.
|
||||
return None, "no seed decl for " + ",".join(missing[:3])
|
||||
|
||||
@@ -79,7 +79,8 @@ REJECTS = ".run/reloc_rejects.jsonl"
|
||||
STAGE_DEFAULT = ".run/sweep_resolver"
|
||||
STAGEABLE_RELOC = ("AGREE", "UNRESOLVED", "COMPILE-FAIL") # COMPILE-FAIL = standalone-only (§ rtu_second_chance)
|
||||
FINAL_NEG = ("DIFF", "CC1", "CPP", "MASPSX", "AS", "SYMBOL-MISMATCH", "GATE-REJECTED",
|
||||
"NEEDS-TU-EDIT", "NO-OBJ-FN", "UNKNOWN", "RELOC-ERR", "TIMEOUT", "EDIT-FAIL")
|
||||
"NEEDS-TU-EDIT", "NO-OBJ-FN", "UNKNOWN", "RELOC-ERR", "TIMEOUT", "EDIT-FAIL",
|
||||
"TU-BROKEN") # re-opened automatically when the split TU (split_sha) changes
|
||||
|
||||
_print_lock = threading.Lock()
|
||||
_stage_lock = threading.Lock()
|
||||
@@ -301,6 +302,71 @@ def stub_index(b):
|
||||
# ------------------------------------------------------------------------------------------
|
||||
# judge one item
|
||||
# ------------------------------------------------------------------------------------------
|
||||
_tu_ok_cache = {}
|
||||
|
||||
|
||||
def _tu_broken(binary, stub, split_sha):
|
||||
"""True + first error when the split TU fails the TU-alone compile WITH NO DRAFT SPLICED.
|
||||
A CC1 verdict then belongs to the TREE, not the draft (S61: one broken TU was billed to 39
|
||||
drafts as `undeclared`). Cached per (binary, split_sha)."""
|
||||
key = (binary, split_sha)
|
||||
if key not in _tu_ok_cache:
|
||||
try:
|
||||
import decl_from_use as _dfu
|
||||
ok, errs = _dfu.tu_compiles(binary, stub.path)
|
||||
_tu_ok_cache[key] = (not ok, (errs[0] if errs else "")[:200])
|
||||
except Exception as e:
|
||||
_tu_ok_cache[key] = (False, "tu-probe failed: %r" % e)
|
||||
return _tu_ok_cache[key]
|
||||
|
||||
|
||||
_DATA_DEF_RE = re.compile(r"^(?P<lead>(?:const\s+|static\s+|volatile\s+)*[A-Za-z_]\w*(?:\s*\*+)?\s+)"
|
||||
r"(?P<sym>[A-Za-z_]\w*_8[0-9A-Fa-f]{7})(?P<arr>\s*\[[^\]]*\])?"
|
||||
r"\s*(?:=[^;]*)?;\s*$", re.M)
|
||||
|
||||
|
||||
def dup_def_demote(binary, fn, stub, body, work):
|
||||
"""The duplicate-symbol class (S61 live probe: assembler `symbol 'D_...' is already defined`):
|
||||
the draft DEFINES data at file scope that a still-stubbed sibling .s in the SAME TU also emits.
|
||||
rtu is blind to it (INCLUDE_ASM neutralized), so it surfaces only at the real build. Demote such
|
||||
defs to extern (single-line defs only; the sibling .s keeps emitting the bytes; the whole-binary
|
||||
SHA stays the sole arbiter). Returns a new candidate path, or None when nothing applies."""
|
||||
try:
|
||||
tu = open(stub.path, errors="replace").read()
|
||||
except OSError:
|
||||
return None
|
||||
sibs = []
|
||||
for m in re.finditer(r'INCLUDE_ASM\("([^"]+)",\s*(\w+)\)', tu):
|
||||
if m.group(2) != fn:
|
||||
sibs.append(os.path.join(m.group(1), m.group(2) + ".s"))
|
||||
if not sibs:
|
||||
return None
|
||||
emitted = set()
|
||||
for sp in sibs:
|
||||
try:
|
||||
for dm in re.finditer(r"^(?:dlabel|glabel)\s+([A-Za-z_]\w*)", open(sp, errors="replace").read(), re.M):
|
||||
emitted.add(dm.group(1))
|
||||
except OSError:
|
||||
continue
|
||||
# only lines at brace depth 0 are file-scope defs
|
||||
out, changed, depth = [], [], 0
|
||||
for line in body.split("\n"):
|
||||
m = _DATA_DEF_RE.match(line) if depth == 0 else None
|
||||
if m and m.group("sym") in emitted and not line.lstrip().startswith("extern"):
|
||||
out.append("extern " + m.group("lead").lstrip() + m.group("sym") + (m.group("arr") or "") + ";"
|
||||
+ " /* dup-def demoted: a sibling .s still emits it (S61) */")
|
||||
changed.append(m.group("sym"))
|
||||
else:
|
||||
out.append(line)
|
||||
depth += line.count("{") - line.count("}")
|
||||
if not changed:
|
||||
return None
|
||||
os.makedirs(work, exist_ok=True)
|
||||
pth = os.path.join(work, fn + ".dupfix.c")
|
||||
open(pth, "w").write("\n".join(out))
|
||||
return pth
|
||||
|
||||
|
||||
def resolve(item, a, ledger, stage):
|
||||
b, fn, stub = item["binary"], item["fn"], item["stub"]
|
||||
work = os.path.join(RUN, "work", "%s__%s" % (b, fn))
|
||||
@@ -311,7 +377,13 @@ def resolve(item, a, ledger, stage):
|
||||
src_file = stub.path
|
||||
rows, best = [], None
|
||||
staged = None
|
||||
cands = list(item["candidates"])
|
||||
for kind, draft in item["candidates"]:
|
||||
if os.path.exists(draft):
|
||||
dd = dup_def_demote(b, fn, stub, open(draft, errors="replace").read(), os.path.join(work, "dupfix"))
|
||||
if dd:
|
||||
cands.append(("dupfix:" + kind, dd))
|
||||
for kind, draft in cands:
|
||||
row = {"ts": time.strftime("%Y-%m-%d %H:%M:%S"), "run": a.run_id, "binary": b, "fn": fn,
|
||||
"kind": kind, "draft": draft, "draft_sha": sha(draft), "split_sha": split_sha,
|
||||
"stored_closeness": item.get("stored_closeness")}
|
||||
@@ -365,6 +437,11 @@ def resolve(item, a, ledger, stage):
|
||||
else:
|
||||
v = {"verdict": "RELOC-" + str(st), "nins": v["nins"], "ndiff": 0,
|
||||
"note": str(rv.get("note") or rv.get("caveat") or "")[:200]}
|
||||
if not staged and v["verdict"] in ("CC1", "CPP"):
|
||||
_broken, _terr = _tu_broken(b, stub, split_sha)
|
||||
if _broken:
|
||||
v = {"verdict": "TU-BROKEN", "nins": v.get("nins"), "ndiff": None,
|
||||
"note": "the split TU fails WITHOUT the draft — a tree condition, not this draft: " + _terr}
|
||||
row.update(verdict=("STAGED" if staged else v["verdict"]), ndiff=v.get("ndiff"),
|
||||
nins=v.get("nins"), note=v.get("note", ""), via=via, body=(staged[0] if staged else body),
|
||||
reloc=(staged[2] if staged else None))
|
||||
@@ -671,6 +748,7 @@ def main():
|
||||
n_dem = 0
|
||||
for r in final.values():
|
||||
if r["verdict"] in ("DIFF", "CC1", "CPP", "SYMBOL-MISMATCH", "NEEDS-TU-EDIT"):
|
||||
# TU-BROKEN is deliberately absent: a tree condition must not demote the draft
|
||||
if r["verdict"] == "DIFF" and r.get("ndiff") == r.get("stored_closeness"):
|
||||
continue
|
||||
backlog.append_record({
|
||||
|
||||
Reference in New Issue
Block a user