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.
This commit is contained in:
Drew T
2026-07-14 12:52:41 -06:00
parent f4502f11bb
commit 4aae5e7589
5 changed files with 218 additions and 213 deletions
+24
View File
@@ -659,6 +659,30 @@ On approval → `/model opus` + `/effort xHigh` (Tasks 0–4; ALL Fable5 via `Ag
## Log
- **2026-07-14 (session 10, A3d — RETIRE the fleet-majority oracle; both banking paths; Max):** R33 applied to
the audit's worst finding: **not fixed, RETIRED.** `reconcile_decls` asks *"what does the FLEET call this
symbol?"*; C asks *"what does THIS TU declare?"*. The engine is loosely typed, so one fleet-wide answer is
**wrong for some TU by construction** — and worse than a skip, because it writes an **actively wrong decl**
into the draft that then collides with the very TU it was meant to conform to. **Measured** across
ov_SC01_077's 12 TUs vs what cpp says each TU really declares: **agrees 2,883 · CONFLICTS 548 (16%, cc1
rejects the result) · absent 357.** And it was live on **both** banking paths — `gate_stage` (rewrote **60 of
196** drafts) and **`jtbl_family_bank`, i.e. EVERY SIBLING of the ×134 family sweep, the project's economic
engine**. A poisoned decl means that sibling silently doesn't bank and the sweep just 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, **never wired**) now supersedes it, rebuilt on cdecl: cpp for what
the TU declares (macro-injected externs included), **cc1** for whether the draft's decl can coexist, TU-derived
from `corpus.stubs()`, and it **handles the fn-ptr kind NATIVELY** — which is *why* it supersedes rather than
patches: teaching the old 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.**
**NULL RESULT AGAIN (P9/R14):** on the 196 never-banked drafts the new oracle banks **exactly as many as the
old — zero**; the two disagree on 45 of 196 and the outcome does not move. That tail fails on **codegen**, not
plumbing. This is a **correctness** fix (548 wrong decls 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` kept as EVIDENCE, marked RETIRED, **no live caller**.
**R22 clean-fleet 136/136; src/ untouched; reconcile_tu 0 coverage defects over 196 drafts.**
⚠ **The family-sweep path gets its real exercise at Task 8 — watch the per-sibling bank rate.**
- **2026-07-14 (session 10, A3c — first consumer migration: `cast_call_sites` onto the cdecl oracle; Max):**
Added **`cdecl.compatible()`** — *"will cc1 accept these two declarations of one name?"*, the predicate four
tools each half-implement and get wrong (`norm_sig`/`_norm_type` collapse the int family, so a **signedness**
+17 -5
View File
@@ -189,11 +189,23 @@ def _run_gate_locked(drafts, binary, src, asm, out, good_sha, propagate, source_
cast_extra = (["--src-file", src_file] if src_file else None)
d1 = _xform("canon_resident_calls.py", binary, drafts, "-cn")
d1 = _xform("cast_call_sites.py", binary, d1, "-cast", extra=cast_extra)
# data-symbol analog of cast_call_sites: rewrite each loose D_XXXX extern -> fleet-canonical +
# byte-neutral access cast (§33, T7b). No-op/idempotent on drafts without a data-decl conflict, so
# it can't regress the wave; the byte-gate is still the sole arbiter. Chiefly unblocks GIANTS
# (hoisted global base arrays) but composes harmlessly for every draft.
d1 = _xform("reconcile_decls.py", binary, d1, "-rc", extra=cast_extra)
# data-symbol analog of cast_call_sites: rewrite each loose D_XXXX extern -> canonical + a
# byte-neutral access cast (§33, T7b). No-op/idempotent without a data-decl conflict; the
# byte-gate is still the sole arbiter.
#
# Phase 26-A: reconcile_decls.py -> reconcile_tu.py (R33 — retire the oracle, don't fix it).
# reconcile_decls asked "what does the FLEET call this symbol?"; C asks "what does THIS TU
# declare?". The engine is loosely typed, so one fleet-wide answer is wrong for some TU BY
# CONSTRUCTION — and worse than a skip, because it writes an ACTIVELY WRONG decl into the draft.
# Measured across ov_SC01_077's 12 TUs: 2,883 of its answers agree with the TU, 548 CONFLICT
# (cc1 rejects the result), 357 are absent — and it was rewriting 60 of 196 live drafts.
# reconcile_tu asks cpp what the TU declares (macro-injected externs included) and cc1 whether
# the draft's decl can coexist; where it cannot, the TU wins and every USE is cast so the
# draft's intended access is preserved byte-for-byte. It also handles the fn-ptr kind natively,
# which is why it SUPERSEDES reconcile_decls rather than patching it: teaching the old parser to
# see `extern void (*D_x[])(void);` would ARM its fn-ptr-blind data_access_subs to rewrite a
# call-through `D_x[i]()` into `((u8 *)D_x)[i]()`.
d1 = _xform("reconcile_tu.py", binary, d1, "-rc", extra=cast_extra)
verified = _gate1(binary, src, asm, out, good_sha, d1, verified_out, failed_out)
d = d1
+11 -2
View File
@@ -71,7 +71,16 @@ def revert(ov, cf=None, keep_regions=None):
def recover(body, to_ov, cf, func):
"""The §20/§24 recovery pass, run against THIS sibling's TU: `cast_call_sites` (rewrite a callee decl
that conflicts with its real engine_core.h definition to the canonical type, and cast at the call
site — codegen-neutral) then `reconcile_decls` (the DATA-symbol analog).
site — codegen-neutral) then `reconcile_tu` (the DATA-symbol analog).
Phase 26-A: `reconcile_decls` -> `reconcile_tu` here TOO, and this is the path that mattered most.
This is the ×134 family sweep — the project's economic engine — and it was running every sibling
through the FLEET-MAJORITY oracle, which is measurably wrong for the TU **16% of the time**
(across ov_SC01_077's 12 TUs: 2,883 answers agree, **548 CONFLICT** — cc1 rejects the result —
and 357 are absent). A poisoned declaration means that sibling silently does not bank, and the
loss is invisible: the sweep just reports a smaller number. The irony is exact — the docstring
below already knew the symbols are PER-OVERLAY, which is precisely why a FLEET-wide oracle could
never have been right.
It must be redone PER SIBLING: the conflicting symbols are largely PER-OVERLAY (`D_801812A4` in
ov_SC01_000 vs `D_800D4F8C` in ov_SC01_077), so the exemplar's recovered decls do not transfer —
@@ -84,7 +93,7 @@ def recover(body, to_ov, cf, func):
open(os.path.join(REPO, d_in, f"{func}.c"), "w").write(body)
sh(f"python3 tools/cast_call_sites.py --overlay {to_ov} --src-file {cf} --in {d_in} --out {d_mid}")
stage2 = d_mid if os.path.exists(os.path.join(REPO, d_mid, f"{func}.c")) else d_in
sh(f"python3 tools/reconcile_decls.py --overlay {to_ov} --src-file {cf} --in {stage2} --out {d_out}")
sh(f"python3 tools/reconcile_tu.py --overlay {to_ov} --src-file {cf} --in {stage2} --out {d_out}")
for d in (d_out, d_mid):
p = os.path.join(REPO, d, f"{func}.c")
if os.path.exists(p):
+29
View File
@@ -1,4 +1,33 @@
#!/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
+137 -206
View File
@@ -1,257 +1,188 @@
#!/usr/bin/env python3
"""reconcile_tu.py — conform a draft's DATA declarations to what the TARGET TU can actually SEE.
The successor to `reconcile_decls.py` for the templating/banking path. Two things are wrong with that
tool, and the second is structural, not a typo (Phase-26 session-8 scanner audit):
THE SUCCESSOR TO reconcile_decls.py, and the reason is structural, not a typo.
1. ITS REGEX IS BLIND TO FUNCTION POINTERS. `DATA_DECL_LINE_RE` wants `extern <type-words> D_x[];`,
so the `(` in
extern void (*D_801DA75C)(void); <- fn-ptr scalar
extern void (*D_801812A4[])(void *); <- fn-ptr array (a dispatch table)
breaks the type run and the line never matches. The tool then SILENTLY SKIPS exactly the symbols
that are failing and reports success — the classic silent skip. (Blocking func_8017A4AC, 536 ins
x134 = 287 KB, today.)
reconcile_decls asks "what does the FLEET call this symbol?"
C asks "what does THIS TRANSLATION UNIT declare?"
2. ITS ORACLE ASKS THE WRONG QUESTION. It elects a canonical decl by FLEET MAJORITY (engine_core.h
first-seen, else a plurality vote across all overlays). But this engine is loosely typed: 34.4% of
fleet symbols carry >= 2 mutually incompatible decl spellings, so a single fleet-wide answer is
**provably wrong for some TU by construction** — and it is worse than a skip, because it hands back
an ACTIVELY WRONG decl (measured: 3,717 symbols) that then collides with the very macro it was
supposed to conform to.
Those are different questions, and only the second one has an answer. This 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,
because it hands back an ACTIVELY WRONG declaration that then collides with the very TU it was
supposed to conform to. Measured on ov_SC01_077's 12 TUs (Phase 26-A):
The question is never "what does the fleet call this symbol". It is **"what can THIS TU see"** — because
that, and only that, is what gcc compares the draft's decl against. So:
the fleet oracle AGREES with the TU's own declaration ...... 2883
the fleet oracle CONFLICTS with it (cc1 REJECTS the result) . 548 <- 16%
the TU declares it and the fleet oracle has NO answer ....... 357
* Reconstruct the TU's VISIBLE file-scope decl environment above the insertion point, from BOTH
sources (the §8c law — a decl can be invisible to any col-0 text scan):
(a) col-0 `extern ...;` lines in the .c, and
(b) MACRO-INJECTED externs — a `DEFINE_func_*()` / `SETTER()` / `RETCONST()` invocation expands
at FILE scope, so its leading `extern`s are genuine file-scope declarations of the invoking
TU even though they live in engine_core.h (1,801 macros / ~1,462 symbols).
* If the symbol has NO visible decl -> no conflict is possible; leave the draft's decl alone (and let
`scope_data_externs` (§8d) demote it, so it establishes no global the TU's own later block-scope
externs would then have to agree with).
* If it HAS one and the draft agrees -> nothing to do.
* If it HAS one and the draft disagrees -> the TU's decl WINS (it is the environment; we are the
guest). Rewrite the draft's decl to the visible one and CAST AT EVERY USE so the access the draft
intended is preserved. gcc-2.7.2 folds a compile-time cast of a known symbol, so the emitted bytes
and it is live: it rewrote 60 of 196 drafts in the current batch.
WHAT THIS DOES INSTEAD
======================
* Ask cpp what the TU actually declares (`cdecl.tu_scope` — so macro-injected `DEFINE_func_*`
externs are visible; a raw text scan cannot see them, §8c / cookbook §51g LAW 7).
* Ask cc1 whether the draft's declaration can coexist with it (`cdecl.compatible`, validated
against the real gcc-2.7.2 front end on 1,485 live pairs — NOT against the C standard and NOT
against modern gcc, which give different answers; §51g LAW 9).
* NO visible declaration -> no conflict is possible -> LEAVE THE DRAFT ALONE. Its extern types are
load-bearing (%lo-folding, access width, alignment all key off the declared type), and
scope_data_externs (§8d) will demote it to block scope so it establishes no global that the TU's
own later block-scope externs would have to agree with.
* A visible, COMPATIBLE declaration -> nothing to do.
* A visible, CONFLICTING declaration -> **the TU wins** (it is the environment; we are the guest).
Rewrite the draft's decl to the TU's, and CAST AT EVERY USE so the access the draft intended is
preserved exactly. gcc-2.7.2 folds a compile-time cast of a known symbol, so the emitted bytes
are unchanged — and the whole-binary byte-gate (G3/P9) remains the sole arbiter either way.
COVERAGE ASSERTION (the rule ratified 2026-07-14: a scanner over the corpus must assert its own
coverage; a silent skip is a DEFECT, not a no-op). Every line in the draft that LOOKS like an extern
declaration of a `D_` symbol must be parsed by one of the forms below. Any that is not is reported
LOUDLY (and `--strict` exits non-zero) instead of being quietly ignored.
WHICH TU? DERIVED, NEVER HAND-PASSED (§51g LAW 10)
==================================================
`--src-file` was an OPTIONAL flag defaulting to `src/<ov>/<ov>.c`. ov_SC01_077 has **263 open stubs
across 12 TUs and only 13 of them are in the main .c**, so 95% of drafts were being reconciled
against a translation unit that would never compile them. The INCLUDE_ASM line is self-describing;
`corpus.stubs()` reads it. Ask, don't assume.
Usage:
from reconcile_tu import fix
body, notes = fix(body, tu_text, insert_pos, self_fn)
tools/reconcile_tu.py --body draft.c --tu src/ov_X/ov_X.c --func func_Y [--out out.c] [--strict]
tools/reconcile_tu.py --overlay ov_SC01_077 --in <drafts> --out <dir> [--src-file <tu.c>]
from reconcile_tu import fix; body, notes = fix(body, tu_path, fn)
"""
import argparse
import glob
import importlib.util
import os
import re
import sys
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
ENGINE_CORE = os.path.join(REPO, 'src/shared/engine_core.h')
# ---- the three declaration FORMS a D_ symbol can take -------------------------------------------
# plain data: `extern u8 D_x[];` `extern struct S D_x;` `extern s32 *D_x;`
PLAIN_RE = re.compile(
r'^([ \t]*)extern\s+([A-Za-z_][\w \t]*?)\s*(\*?)\s*\b(D_[0-9A-Fa-f]+)\b\s*(\[[^\]]*\])?\s*;'
r'[ \t]*(?:/\*.*?\*/)?[ \t]*\\?[ \t]*$')
# fn-ptr scalar: `extern void (*D_x)(void);` fn-ptr array: `extern void (*D_x[])(void *);`
FNPTR_RE = re.compile(
r'^([ \t]*)extern\s+([A-Za-z_][\w \t\*]*?)\s*\(\s*\*\s*\b(D_[0-9A-Fa-f]+)\b\s*(\[[^\]]*\])?\s*\)'
r'\s*\(([^;]*)\)\s*;[ \t]*(?:/\*.*?\*/)?[ \t]*\\?[ \t]*$')
# the COVERAGE ORACLE — deliberately over-approximating: anything that looks like an extern of a D_ sym
CANDIDATE_RE = re.compile(r'^[ \t]*extern\b[^;]*\bD_[0-9A-Fa-f]+\b[^;]*;')
# a macro invocation at file scope, whose expansion injects file-scope externs (§8c)
MACRO_CALL_RE = re.compile(r'^(DEFINE_func_[0-9A-Fa-f]+|SETTER|RETCONST)\s*\(')
def parse_decl(line):
"""-> (sym, decl_text, kind, elem, params) or None.
kind in {'plain','array','ptr','fnptr','fnarr'}; params only for the fn-ptr kinds."""
m = FNPTR_RE.match(line)
if m:
ret, sym, arr, params = m.group(2), m.group(3), m.group(4), m.group(5)
return (sym, line.strip().rstrip('\\').strip(),
'fnarr' if arr else 'fnptr',
re.sub(r'\s+', ' ', ret).strip(), re.sub(r'\s+', ' ', params).strip())
m = PLAIN_RE.match(line)
if m:
base, star, sym, arr = m.group(2), m.group(3), m.group(4), m.group(5)
kind = 'array' if arr else ('ptr' if star else 'plain')
return (sym, line.strip().rstrip('\\').strip(), kind,
re.sub(r'\s+', ' ', base).strip(), None)
return None
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
def _norm(kind, elem, params):
"""Spelling-insensitive identity: int-family aliases are byte-identical to cast between."""
e = re.sub(r'\b(s32|u32|int|unsigned int|unsigned|long|unsigned long|u_long)\b', 'int', elem or '')
p = re.sub(r'\b(s32|u32|int|unsigned int|unsigned|long)\b', 'int', params or '')
return (kind, re.sub(r'\s+', ' ', e).strip(), re.sub(r'\s+', ' ', p).strip())
cdecl = _load('cdecl', 'tools/cdecl.py')
corpus = _load('corpus', 'tools/corpus.py')
# ---- the TU-VISIBLE oracle ----------------------------------------------------------------------
_MACRO_EXTERNS = None
def _macro_externs():
"""{macro_name: [extern lines]} — the file-scope decls each engine_core.h macro INJECTS into its
invoking TU. Invisible to any col-0 scan of the .c, and load-bearing (§8c)."""
global _MACRO_EXTERNS
if _MACRO_EXTERNS is not None:
return _MACRO_EXTERNS
_MACRO_EXTERNS = {}
if not os.path.exists(ENGINE_CORE):
return _MACRO_EXTERNS
lines = open(ENGINE_CORE).read().splitlines()
hdr = re.compile(r'^#define\s+(DEFINE_func_[0-9A-Fa-f]+|SETTER|RETCONST)\b')
i = 0
while i < len(lines):
m = hdr.match(lines[i])
if not m:
i += 1
continue
name, ext, j = m.group(1), [], i
while j < len(lines):
s = lines[j].strip().rstrip('\\').strip()
if s.startswith('extern'):
ext.append(s if s.endswith(';') else s + ';')
if not lines[j].rstrip().endswith('\\'):
break
j += 1
_MACRO_EXTERNS.setdefault(name, []).extend(ext)
i = j + 1
return _MACRO_EXTERNS
def tu_visible(tu_text, pos):
"""{sym: (decl_text, kind, elem, params)} for every DATA symbol declared at FILE scope ABOVE `pos`.
Both §8c sources: col-0 externs in the .c, AND the externs injected by macro invocations above pos.
(All visible decls of one symbol must already be mutually compatible — the TU compiles today — so
the FIRST one is a sound canonical.)"""
seen = {}
macros = _macro_externs()
for ln in tu_text[:pos].split('\n'):
cand = None
mm = MACRO_CALL_RE.match(ln)
if mm:
for e in macros.get(mm.group(1), []):
p = parse_decl(e)
if p and p[0] not in seen:
seen[p[0]] = p[1:]
continue
if ln[:1] not in (' ', '\t'): # col-0 only: block-scope decls are not the environment
cand = parse_decl(ln)
if cand and cand[0] not in seen:
seen[cand[0]] = cand[1:]
return seen
def tu_for(overlay, fn, override=None):
"""The TU this draft is spliced into — DERIVED from the corpus (§51g LAW 10)."""
if override:
return override
try:
st = corpus.stubs(overlay).get(int(fn[5:], 16))
if st:
return os.path.join(REPO, st.path)
except Exception:
pass
return os.path.join(REPO, f'src/{overlay}/{overlay}.c')
# ---- the byte-neutral access casts ---------------------------------------------------------------
def _cast_subs(sym, dkind, delem, dparams, ckind):
"""(regex, repl) reproducing the DRAFT's intended access to `sym` under the TU's canonical storage.
gcc folds a compile-time cast of a known symbol, so the emitted access is unchanged."""
def _elem(d):
"""The element/base type the DRAFT believes the symbol has."""
return (d.base + (' *' if d.kind == 'ptr' else '')).strip()
def _cast_sub(d, tu):
"""(regex, repl) reproducing the DRAFT's intended access to the symbol under the TU's storage.
gcc folds a compile-time cast of a known symbol, so the emitted access is unchanged.
NOTE the fn-ptr arms. reconcile_decls' `data_access_subs` has no fn-ptr kind, so the moment its
parser is taught to SEE `extern void (*D_x[])(void);` it would happily rewrite a call-through
`D_x[i]()` into `((u8 *)D_x)[i]()` — a dormant transform that fixing the parser would ARM. This
tool handles the kind natively, which is why it supersedes that one rather than patching it."""
sym = d.name
rx = re.compile(rf'(&?)\b{re.escape(sym)}\b(\s*\[)?')
c_is_arr = ckind in ('array', 'fnarr')
c_arr = tu.kind in ('array', 'fnptr_array')
e = _elem(d)
if dkind == 'fnptr': # draft: void (*D_x)(P) ; TU: some scalar
acc = f'((({delem} (*)({dparams})){sym}))'
return [(rx, lambda m: f'{m.group(1)}{acc}{m.group(2) or ""}')]
if dkind == 'fnarr': # draft: void (*D_x[])(P) ; TU: scalar/array
base = f'(({delem} (**)({dparams})){"" if c_is_arr else "&"}{sym})'
return [(rx, lambda m: f'{m.group(1)}{base}{m.group(2) or ""}')]
if dkind == 'array': # draft: E D_x[]
base = f'(({delem} *){"" if c_is_arr else "&"}{sym})'
return [(rx, lambda m: f'{m.group(1)}{base}{m.group(2) or ""}')]
# draft: a scalar / struct / pointer -> force the intended width+type via a cast-lvalue
star = ' *' if dkind == 'ptr' else ''
acc = f'(*({delem}{star} *)&{sym})'
return [(rx, lambda m: f'{m.group(1)}{acc}{m.group(2) or ""}')]
if d.kind == 'fnptr': # draft: void (*D_x)(P)
acc = f'(({e} (*)({", ".join(d.params or [])})){sym})'
elif d.kind == 'fnptr_array': # draft: void (*D_x[])(P)
acc = f'(({e} (**)({", ".join(d.params or [])})){"" if c_arr else "&"}{sym})'
elif d.kind == 'array': # draft: E D_x[]
acc = f'(({e} *){"" if c_arr else "&"}{sym})'
else: # scalar / struct / pointer
acc = f'(*({e} *)&{sym})' if not c_arr else f'(*({e} *){sym})'
return rx, lambda m: f'{m.group(1)}{acc}{m.group(2) or ""}'
def fix(body, tu_text, insert_pos, self_fn=None):
"""Conform the draft's file-scope DATA decls to what the TU can SEE above `insert_pos`.
Returns (new_body, notes). notes carries the coverage report — UNPARSED lines are DEFECTS."""
vis = tu_visible(tu_text, insert_pos)
lines = body.split('\n')
def fix(body, tu_path, fn):
"""Conform the draft's DATA decls to the TU. Returns (new_body, notes)."""
full = cdecl.tu_scope(tu_path) # CONFLICT domain: a decl BELOW still conflicts
above = set(cdecl.tu_scope(tu_path, above=fn)) # ORDER, for cc1's no-prototype rule
plan, decl_at, notes = {}, {}, []
unparsed = []
for i, ln in enumerate(lines):
if not CANDIDATE_RE.match(ln):
plan, notes = {}, []
for st in cdecl.split_statements(body):
try:
ds = cdecl.parse(st.text)
except cdecl.CDeclError as e:
notes.append(f'!! UNPARSED (a coverage defect, not a no-op): {st.text[:60]} -> {e}')
continue
p = parse_decl(ln)
if not p:
unparsed.append((i + 1, ln.strip())) # COVERAGE DEFECT — never silently skip
continue
sym, _dtext, dkind, delem, dparams = p
if sym not in vis:
continue # no visible decl -> no conflict possible
cdecl, ckind, celem, cparams = vis[sym]
if _norm(dkind, delem, dparams) == _norm(ckind, celem, cparams):
continue # already agrees
plan[sym] = (cdecl, _cast_subs(sym, dkind, delem, dparams, ckind))
decl_at[sym] = i
notes.append(f'{sym}: draft {dkind}({delem}{"/" + dparams if dparams else ""}) '
f'-> TU-visible `{cdecl}` + cast at use')
if unparsed:
notes.append(f'!! COVERAGE DEFECT: {len(unparsed)} extern line(s) of a D_ symbol did not parse '
f'— a silent skip is a defect, not a no-op: ' +
'; '.join(f'L{n}: {t[:60]}' for n, t in unparsed[:3]))
for d in ds:
if d.is_definition or d.kind == 'func' or d.storage == 'typedef':
continue
tu = full.get(d.name)
if tu is None:
continue # not declared here -> no conflict is possible
a, b = (tu, d) if d.name in above else (d, tu)
if cdecl.compatible(a, b):
continue # cc1 accepts both -> leave the draft's types
plan[d.name] = (tu, d, st)
notes.append(f'{d.name}: draft {d.type!r} vs TU {tu.type!r} -> TU wins + cast at use')
if not plan:
return body, notes
out = []
for i, ln in enumerate(lines):
sym = next((s for s, j in decl_at.items() if j == i), None)
if sym is not None:
indent = re.match(r'^[ \t]*', ln).group(0)
out.append(indent + plan[sym][0]) # the TU's decl replaces the draft's
out, done = [], set()
for line in body.split('\n'):
hit = next((s for s, (_t, _d, st) in plan.items()
if st.text.split('\n')[0].strip() == line.strip() and s not in done), None)
if hit: # the draft's decl line -> the TU's declaration
done.add(hit)
indent = re.match(r'^[ \t]*', line).group(0)
out.append(indent + plan[hit][0].declaration())
continue
if CANDIDATE_RE.match(ln): # never cast inside another decl line
out.append(ln)
if re.match(r'\s*(extern|typedef)\b', line):
out.append(line) # never cast inside a declaration line
continue
for _s, (_c, subs) in plan.items():
for rx, rep in subs:
ln = rx.sub(rep, ln)
out.append(ln)
for _s, (tu, d, _st) in plan.items(): # cast every USE to the draft's intended view
rx, rep = _cast_sub(d, tu)
line = rx.sub(rep, line)
out.append(line)
return '\n'.join(out), notes
def main():
ap = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument('--body', required=True)
ap.add_argument('--tu', required=True)
ap.add_argument('--func', required=True)
ap.add_argument('--out')
ap.add_argument('--overlay', default='ov_SC01_077')
ap.add_argument('--in', dest='indir', required=True)
ap.add_argument('--out', dest='outdir', required=True)
ap.add_argument('--src-file', dest='src_file', default=None,
help='override the derived TU (normally unnecessary — it is derived per draft)')
ap.add_argument('--strict', action='store_true', help='exit non-zero on a coverage defect')
a = ap.parse_args()
tu = open(a.tu).read()
m = re.search(rf'INCLUDE_ASM\("[^"]*",\s*{re.escape(a.func)}\);', tu)
pos = m.start() if m else len(tu)
new, notes = fix(open(a.body).read(), tu, pos, a.func)
vis = tu_visible(tu, pos)
print(f'TU-visible data symbols above the insertion point: {len(vis)} '
f'({len(_macro_externs())} macros scanned for injected externs)')
for n in notes:
print(' ' + n)
if a.out:
open(a.out, 'w').write(new)
print(f'wrote {a.out}')
if a.strict and any(n.startswith('!!') for n in notes):
override = a.src_file and os.path.join(REPO, a.src_file)
os.makedirs(os.path.join(REPO, a.outdir), exist_ok=True)
drafts = touched = syms = defects = 0
for p in sorted(glob.glob(os.path.join(REPO, a.indir, '*.c'))):
fn = os.path.basename(p)[:-2]
new, notes = fix(open(p).read(), tu_for(a.overlay, fn, override), fn)
open(os.path.join(REPO, a.outdir, os.path.basename(p)), 'w').write(new)
drafts += 1
n = sum(1 for x in notes if not x.startswith('!!'))
defects += sum(1 for x in notes if x.startswith('!!'))
if n:
touched += 1
syms += n
print(f'drafts: {drafts}; reconciled: {touched} draft(s), {syms} data symbol(s); '
f'coverage defects: {defects}')
if a.strict and defects:
sys.exit(1)