diff --git a/tools/dedup_propagate.py b/tools/dedup_propagate.py index 3f1889708..e07eb9d33 100644 --- a/tools/dedup_propagate.py +++ b/tools/dedup_propagate.py @@ -293,8 +293,22 @@ def main(): ssig = load_sig(src) ctext = source_text(src) # scan main + split files for matched defs reg = registered_addrs() # skip functions already shared (additive + resumable) + # -O0 split-file functions are OVERLAY-LOCAL (§18/§20, cont.4): -O0 codegen embeds per-overlay + # %lo data addresses, so their bytes diverge per overlay even though the relocation-MASKED + # h_exact falsely reports reach-134. Cross-overlay-propagating one fails the byte-gate and, + # under the all-or-nothing batch revert, poisons every CLEAN match in the batch (cont.4: + # func_8013C360 reverted 10 good ×134 matches). Never auto-propagate them — detect by def-site + # in the -O0 split file (*_o0.c). (--addr still forces them, for an explicit override.) + o0_skip = set() + for _p, _tag in overlay_files(src): + if _p.name.endswith("_o0.c"): + _o0txt = _p.read_text() + for _ad in ssig: + _s = find_site(_o0txt, src, _ad) + if _s and _s[0] == "def": + o0_skip.add(_ad) for addr in sorted(ssig): - if addr in reg: + if addr in reg or addr in o0_skip: continue site = find_site(ctext, src, addr) if not site or site[0] != "def": # only functions matched (inline def) in the source diff --git a/tools/gate_stage.py b/tools/gate_stage.py index b8b11ee5f..2c01a8599 100644 --- a/tools/gate_stage.py +++ b/tools/gate_stage.py @@ -120,10 +120,24 @@ def _run_gate_locked(drafts, binary, src, asm, out, good_sha, propagate, source_ # 5 propagate the banked matches fleet-wide propagated = 0 + prop_error = None if verified and propagate: before = _dedup_group_count() - sh([PY, "tools/dedup_propagate.py", "--auto-from", binary, "--min-reach", "2"], timeout=3600) + pr = sh([PY, "tools/dedup_propagate.py", "--auto-from", binary, "--min-reach", "2"], timeout=3600) propagated = max(0, _dedup_group_count() - before) + # Surface a REAL failure: dedup_propagate exits non-zero on a byte-gate revert (a false-reach + # straggler poisoned the all-or-nothing batch) — distinct from the benign "nothing to propagate" + # empty-plan no-op. A swallowed revert previously hid a real ×134 gain (cont.4); never again. + out = (pr.stdout or "") + (pr.stderr or "") + if pr.returncode != 0 and "nothing to propagate" not in out: + errlog = os.path.join(REPO, ".run/auto/last_propagate_error.log") + try: + open(errlog, "w").write(out) + except OSError: + pass + prop_error = [l for l in out.strip().splitlines() if l.strip()][-3:] or [f"exit {pr.returncode}"] + print(f"[gate] WARNING: dedup_propagate exited {pr.returncode} ({propagated} groups added); " + f"see {errlog}", file=sys.stderr) # 6 log every non-match to the backlog (closeness + RESIDUAL class + best draft for the human). # The drafter stamps `// @class: ` and `// @stuck: ` into the draft (so the @@ -171,7 +185,8 @@ def _run_gate_locked(drafts, binary, src, asm, out, good_sha, propagate, source_ commit_sha = sh(["git", "rev-parse", "--short", "HEAD"]).stdout.strip() return {"drafts": len(draft_fns), "banked": len(verified), "propagated": propagated, - "near": near, "failed": failed, "fleet_pct": fp, "verified": verified, "commit": commit_sha} + "near": near, "failed": failed, "fleet_pct": fp, "verified": verified, + "prop_error": prop_error, "commit": commit_sha} def main():