Files
BFM-decomp/tools/reconcile_decls.py
T
Drew T 4aae5e7589 fix(phase-26a): A3d — retire the fleet-majority oracle: it was WRONG for the TU 16% of the time, on both banking paths
R33 applied to the worst finding in the audit: this oracle was not fixed, it was RETIRED.

    reconcile_decls asks "what does the FLEET call this symbol?"
    C asks           "what does THIS TRANSLATION UNIT declare?"

The engine is loosely typed -- the same address is legitimately declared with incompatible types in
different overlays -- so a single fleet-wide answer is WRONG FOR SOME TU BY CONSTRUCTION. And it is
worse than a silent skip: it writes an ACTIVELY WRONG declaration into the draft, which then
collides with the very TU it was meant to conform to.

MEASURED across ov_SC01_077's 12 TUs, against what cpp says each TU really declares:

    the fleet oracle AGREES with the TU ................ 2883
    the fleet oracle CONFLICTS with it (cc1 REJECTS)  ..  548    <- 16%
    the TU declares it, the oracle has NO answer ......   357

and it was LIVE ON BOTH BANKING PATHS:
  * gate_stage      -- rewrote 60 of 196 drafts in the current batch
  * jtbl_family_bank -- EVERY SIBLING of the ×134 family sweep, the project's economic engine.
    A poisoned decl means that sibling silently does not bank, and the loss is invisible: the sweep
    simply reports a smaller number. The irony is exact -- that function's own docstring already
    knew the conflicting symbols are PER-OVERLAY, which is precisely why a FLEET oracle could never
    have been right.

reconcile_tu.py (written in Phase 26 but NEVER WIRED) now supersedes it, rebuilt on cdecl:
  * ask cpp what the TU declares (macro-injected DEFINE_func_* externs included -- a raw scan
    cannot see them, §8c / §51g LAW 7);
  * ask cc1 whether the draft's decl can coexist (cdecl.compatible, validated against the real
    gcc-2.7.2 front end on 1,485 live pairs -- NOT the C standard, NOT modern gcc; §51g LAW 9);
  * NOT declared -> leave the draft alone (its extern types are load-bearing: %lo-folding, access
    width, alignment); compatible -> nothing; CONFLICTING -> the TU wins + cast at every USE so the
    draft's intended access survives byte-for-byte;
  * derives WHICH TU from corpus.stubs() rather than a hand-passed --src-file (§51g LAW 10).
  * handles the fn-ptr kind NATIVELY -- which is why it supersedes rather than patches: teaching
    reconcile_decls' parser to see `extern void (*D_x[])(void);` would have ARMED its fn-ptr-blind
    data_access_subs to rewrite a call-through `D_x[i]()` into `((u8 *)D_x)[i]()`. Fixing the regex
    would have detonated a dormant bug.

AND THE NULL RESULT, AGAIN, REPORTED AS SUCH (P9/R14): on the 196 never-banked historical drafts the
new oracle banks EXACTLY AS MANY AS THE OLD ONE -- zero. That tail fails on CODEGEN, not on decl
plumbing. The two disagree on 45 of 196 drafts and the outcome does not move. This is a CORRECTNESS
fix (548 wrong declarations removed from two live pipelines, protecting all FUTURE drafts and every
future family sweep), not a banking win, and it is not being sold as one. Three nulls in one session.

reconcile_decls.py is kept as EVIDENCE, marked RETIRED, with no live caller.

  R22 clean-fleet: make clean + extract-all + check-all -> 136 passed, 0 failed of 136
  src/ untouched (0 changes)   reconcile_tu: 0 coverage defects over 196 drafts
  NOTE: the family-sweep path gets its real exercise at Task 8 -- watch the per-sibling bank rate.
2026-07-14 12:53:14 -06:00

260 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""RETIRED (Phase 26-A, R33) — superseded by tools/reconcile_tu.py. DO NOT RE-WIRE.
This tool asked the wrong question, and no regex fix could have saved it:
reconcile_decls asks "what does the FLEET call this symbol?"
C asks "what does THIS TRANSLATION UNIT declare?"
The engine is loosely typed — the same address is legitimately declared with incompatible types in
different overlays — so a single fleet-wide answer is **wrong for some TU by construction**. And it
is worse than a silent skip: it writes an ACTIVELY WRONG declaration into the draft, which then
collides with the very TU it was meant to conform to. Measured across ov_SC01_077's 12 TUs:
agrees with the TU's own declaration ....... 2883
CONFLICTS with it (cc1 REJECTS the result) . 548 <- 16%
TU declares it, this oracle has NO answer .. 357
and it was live on both banking paths: it rewrote 60 of 196 drafts in gate_stage, and every sibling
in the ×134 jtbl_family_bank sweep — the project's economic engine.
Its own docstring already knew the symbols are PER-OVERLAY. That is exactly why a FLEET oracle
could never have been right.
KEPT AS EVIDENCE, NOT AS CODE. Its `data_access_subs` also has no fn-ptr kind, so teaching its parser
to see `extern void (*D_x[])(void);` would have ARMED it to rewrite a call-through `D_x[i]()` into
`((u8 *)D_x)[i]()` — a dormant transform that "fixing the regex" would have detonated. reconcile_tu
handles the kind natively, which is why it SUPERSEDES this rather than patching it.
Original docstring follows.
"""
"""reconcile_decls.py — the DATA-symbol analog of cast_call_sites.py (Phase 24 T7b, cookbook §33).
A freshly-matched giant / wave draft byte-matches STANDALONE with its own guessed decls, but to bank
×1 in the overlay TU (and then propagate ×134 via a shared `DEFINE_func_*` macro) its callee/data
externs must be FLEET-CANONICAL — otherwise `conflicting types` vs engine_core.h / sibling decls
(a COMPILE error, not a byte miss). `cast_call_sites.py` handles this for callee FUNCTIONS
(decl->canonical + fn-ptr cast at the call). This tool does the same for DATA symbols `D_XXXX`:
rewrite the draft's `extern <T_draft> D_x...;` -> the fleet-canonical decl, and inject a byte-neutral
cast at each ACCESS so the load/store opcode is unchanged. gcc-2.7.2 folds the compile-time
pointer/scalar cast of a known symbol, so the body bytes are identical.
This is a PURE draft-text transform (like sig_unify / cast_call_sites / canon_resident_calls): the
whole-binary `harvest_verify` byte-gate remains the sole arbiter (G3/P9) — a wrong reconcile just
fails the gate and reverts, so it trial-and-errors safely. It does NOT touch callee FUNCTION decls
(that's cast_call_sites/sig_unify — compose them). It changes only the draft's OWN data externs +
the access sites; it never edits the fleet's canonical decls.
Byte-neutral cast taxonomy (proven by hand on func_80129CF8; the intended type is what the DRAFT
declared, the canonical is the fleet's — cast every use to reproduce the intended access):
draft array Ed[] vs canonical array Ec[] -> decl Ec[]; D_x[i] -> ((Ed*)D_x)[i]; D_x -> (Ed*)D_x
draft array Ed[] vs canonical struct/scalar -> decl canon; D_x -> (Ed*)&D_x (address-of the object)
draft scalar Td vs canonical scalar Tc -> decl Tc; D_x -> *(Td*)&D_x (forces the Td-width opcode)
draft ptr P* D_x vs canonical scalar Tc -> decl Tc; write D_x=v -> D_x=(Tc)(v); read D_x -> (P*)D_x
Pipeline (cookbook §33):
draft -> canon_resident_calls -> cast_call_sites -> reconcile_decls -> sig_unify -> harvest_verify --chunk 1
(callee funcs) (data symbols) (def sig) (the byte-gate)
Usage:
tools/reconcile_decls.py --overlay ov_SC01_077 --src-file src/ov_SC01_077/ov_SC01_077_a.c \
--in .run/drafts-giant --out .run/drafts-giant-rc
tools/reconcile_decls.py --print-canon D_80126948 # inspect the oracle's pick for one symbol
"""
import argparse, os, re, glob, importlib.util, collections
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
def _load(mod, rel):
spec = importlib.util.spec_from_file_location(mod, os.path.join(REPO, rel))
m = importlib.util.module_from_spec(spec)
spec.loader.exec_module(m)
return m
_ght = _load('ght', 'tools/gen_harvest_targets.py') # reuse collect_data_decls / the D_ regexes
# `extern <type> D_XXXX;` or `extern <type> D_XXXX[];` (optional trailing /* comment */). <type> may
# carry a trailing `*` (pointer) which we fold into the element type.
DATA_DECL_LINE_RE = re.compile(
r'^([ \t]*)extern\s+([A-Za-z_][\w \t]*?)\s*(\*?)\s*\b(D_[0-9A-Fa-f]+)\b\s*(\[\s*\])?\s*;'
r'[ \t]*(?:/\*[^\n]*\*/)?[ \t]*$')
# ---------------------------------------------------------------- the canonical-type ORACLE (T2)
def canonical_data_map(extra_src=None):
"""Fleet-majority canonical decl per data symbol. Returns {D_name: 'extern <decl>;'}.
Precedence: an engine_core.h macro decl is AUTHORITATIVE (it's the shared/propagated set every
overlay co-instantiates — conforming to it is what avoids the per-overlay macro conflict). Else
the plurality vote across all overlays + resident (ties -> the lexicographically-first spelling,
deterministic). This is the picker the design-doc flagged as missing (gen_harvest_targets read
only engine_core.h + ONE overlay, first-seen-wins, and never saw a fleet disagreement)."""
ec = os.path.join(REPO, 'src/shared/engine_core.h')
# 1. engine_core.h — authoritative (a symbol declared inside any DEFINE_ macro)
ec_decls = _ght.collect_data_decls([ec]) # {D_name: 'extern ...;'} first-seen
# 2. plurality across the fleet (all overlays + resident) for symbols engine_core.h lacks
votes = collections.defaultdict(collections.Counter)
fleet = (glob.glob(os.path.join(REPO, 'src/ov_*/*.c'))
+ [os.path.join(REPO, 'src/resident/resident.c')])
if extra_src:
fleet.append(os.path.join(REPO, extra_src))
for p in fleet:
if not os.path.exists(p):
continue
txt = open(p).read()
for m in _ght.DATA_DECL_RE.finditer(txt):
decl = re.sub(r'\s+', ' ', m.group(1)).strip()
nm = re.search(r'D_[0-9A-Fa-f]+', decl)
if nm:
votes[nm.group(0)][f'extern {decl};'] += 1
canon = {}
names = set(ec_decls) | set(votes)
for nm in names:
if nm in ec_decls:
canon[nm] = ec_decls[nm] # authoritative
else:
# plurality; tie -> lexicographically-first decl spelling (deterministic)
best = sorted(votes[nm].items(), key=lambda kv: (-kv[1], kv[0]))[0][0]
canon[nm] = best
return canon
def parse_data_decl(decl):
"""'extern u8 D_x[];' -> (name, elem='u8', is_array=True, is_ptr=False).
'extern struct BigCopy D_x;' -> (name, 'struct BigCopy', False, False).
'extern s32 *D_x;' -> (name, 's32', False, is_ptr=True)."""
m = DATA_DECL_LINE_RE.match(decl) or DATA_DECL_LINE_RE.match(' ' + decl.strip())
if not m:
return None
_indent, base, star, name, arr = m.group(1), m.group(2), m.group(3), m.group(4), m.group(5)
return name, re.sub(r'\s+', ' ', base).strip(), bool(arr), bool(star)
def _norm_type(elem, is_array, is_ptr):
"""Spelling-insensitive key for 'do these decls differ' — collapse int-family aliases (same
width+conv, casting them is a no-op), normalise whitespace. Array vs ptr vs scalar are distinct."""
e = re.sub(r'\s+', ' ', elem).strip()
e = re.sub(r'\b(s32|u32|int|unsigned int|unsigned|long|unsigned long|u_long)\b', 'int', e)
kind = 'arr' if is_array else ('ptr' if is_ptr else 'sca')
return (e, kind)
def _base_ptr_expr(name, ielem, canon_is_array):
"""The draft's intended array/base pointer, expressed under the canonical storage decl.
canonical array -> the array decays: (Ed*)D_x
canonical struct/scalar -> take its address: (Ed*)&D_x"""
return f'(({ielem} *){name})' if canon_is_array else f'(({ielem} *)&{name})'
def data_access_subs(name, idecl, cdecl):
"""One (compiled-regex, replacement-fn) that reproduces the DRAFT's intended access to `name`
under the CANONICAL storage decl in a SINGLE pass (re.sub never re-scans its own output, so a
symbol appearing in several forms on one line can't double-wrap). [] if type-compatible, None if
unparseable. The regex captures an optional leading `&` and a following `[` to pick the form."""
ip = parse_data_decl(idecl)
cp = parse_data_decl(cdecl)
if not ip or not cp:
return None # unparseable -> caller logs, leaves as-is
_, ielem, iarr, iptr = ip
_, celem, carr, cptr = cp
if _norm_type(ielem, iarr, iptr) == _norm_type(celem, carr, cptr):
return [] # already compatible
rx = re.compile(rf'(&?)\b{re.escape(name)}\b(\s*\[)?')
if iarr:
base = _base_ptr_expr(name, ielem, carr) # (Ed*)D_x or (Ed*)&D_x (canon array vs not)
def repl(m):
amp, idx = m.group(1), m.group(2)
if idx: # D_x[i] / &D_x[i] -> [&]base[i]
return f'{amp}{base}{idx}'
return base # bare D_x / &D_x -> base (array address IS the base ptr)
else:
# scalar (signedness/width) OR ptr-vs-scalar: force the intended-width/type access via a
# cast-lvalue `*(<intended> *)&D_x` (valid as both lvalue and rvalue; ptr-intended -> `ielem **`).
star = ' *' if iptr else ''
acc = f'(*({ielem}{star} *)&{name})'
def repl(m):
amp, idx = m.group(1), m.group(2)
return f'{amp}{acc}{idx or ""}'
return [(rx, repl)]
# ---------------------------------------------------------------- the transform (T3)
def transform(text, canon):
"""Return (new_text, n_data_reconciled, notes). For each data symbol the draft declares with a
type differing from the fleet-canonical: rewrite the decl -> canonical + cast every access."""
lines = text.split('\n')
plan = {} # D_name -> (canonical_decl_line, [subs])
decl_idx = {} # D_name -> line index of its decl
notes = []
for i, ln in enumerate(lines):
m = DATA_DECL_LINE_RE.match(ln)
if not m:
continue
indent, name = m.group(1), m.group(4)
idecl = ln.strip()
if name not in canon:
continue # no fleet decl -> draft's is the only one, keep
cdecl = canon[name]
subs = data_access_subs(name, idecl, cdecl)
if subs is None:
notes.append(f'{name}: UNPARSEABLE decl, left as-is'); continue
if not subs:
continue # already compatible -> nothing to do
plan[name] = (f'{indent}{cdecl}', subs)
decl_idx[name] = i
if not plan:
return text, 0, notes
out = []
for i, ln in enumerate(lines):
dname = next((n for n, j in decl_idx.items() if j == i), None)
if dname is not None:
out.append(plan[dname][0]) # replace the decl line with the canonical decl
continue
if DATA_DECL_LINE_RE.match(ln): # some OTHER data decl -> never cast in a decl
out.append(ln); continue
for name, (_c, subs) in plan.items(): # body line -> rewrite this symbol's accesses
for rx, rep in subs:
ln = rx.sub(rep, ln)
out.append(ln)
return '\n'.join(out), len(plan), notes
def main():
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument('--overlay', default='ov_SC01_077')
ap.add_argument('--src-file', dest='src_file', default=None,
help='overlay .c whose local decls also vote (default: none; the fleet scan '
'already covers all overlays)')
ap.add_argument('--in', dest='indir')
ap.add_argument('--out', dest='outdir')
ap.add_argument('--print-canon', metavar='D_XXXX', help='print the oracle pick for one symbol and exit')
a = ap.parse_args()
canon = canonical_data_map(a.src_file)
if a.print_canon:
print(canon.get(a.print_canon, f'(no fleet decl for {a.print_canon})'))
return
if not (a.indir and a.outdir):
ap.error('need --in and --out (or --print-canon)')
os.makedirs(os.path.join(REPO, a.outdir), exist_ok=True)
drafts = touched = total = 0
for p in sorted(glob.glob(os.path.join(REPO, a.indir, '*.c'))):
new, k, notes = transform(open(p).read(), canon)
open(os.path.join(REPO, a.outdir, os.path.basename(p)), 'w').write(new)
drafts += 1
if k:
touched += 1; total += k
for n in notes:
print(f' [{os.path.basename(p)}] {n}')
print(f'canonical data decls: {len(canon)}; drafts: {drafts}; '
f'reconciled: {touched} draft(s), {total} data symbol(s)')
if __name__ == '__main__':
main()