Files
BFM-decomp/tools/gate_triage.py
T
Drew T 137c418bc7 docs(phase-31): S71 — the 64 standalone matches priced honestly; 12 banked, 52 in four named lanes
* gate 1 (all 64 across 33 binaries): 12 banked — main 11 + ov_SC07_006 1.
* gate 2 tested "a bad draft kills its binary's good ones" by re-staging only the 25
  that recover_integration --probe-only called MATCH in their real TU: 0 banked.
  An honest null — that probe compiles and diffs bytes but never LINKS or CARVES,
  so it is a third oracle with its own blind spot.
* triage (25/25 accounted): CARVE-REFUSED 10, undefined-reference 4, DIFF 3,
  CC1-FAIL-no-diagnostic 2, PARSE 1; gate 1 adds 7 func-decl / 4 data-decl /
  6 type-decl conflicts.
* R37 probe of the carve class: 6 of 8 are one refusal — a subseg would host
  NON-CONTIGUOUS .rodata carves — whose named remedy is jr_isolate_all (§8b).

tools/restage_matching.py — rebuild a gate plan from probe verdicts.
tools/gate_triage.py — route a gate's verdicts to the lane each one names (R47).
2026-09-02 00:39:18 -06:00

94 lines
4.7 KiB
Python

#!/usr/bin/env python3
"""Route a parallel_gate result set to the repair lane its OWN verdict names (§376/§378, R47).
A gate that banks 0 is not a codegen verdict. `match_one` compiles a draft ALONE; the TU it must
live in already carries declarations written for its call sites, and C rejects the pair. Each of
those refusals names its own fix, and the fixes are byte-neutral (a declaration emits no code), so
the whole-binary gate stays the sole arbiter (G3/P9).
conflicting types for `f` -> cast_self_callers / fix_arity_callers --any-proto (§376/§378)
too few|many arguments to `f` -> fix_arity_callers --any-proto (§376)
undefined reference to D_* -> aprop_symfix (stale seed symbol) (§171)
redeclared / previous decl -> the draft redeclares what the TU owns (§378c)
R47: consume EVERY verdict layer — the class alone is not the verdict, the diagnostic text is.
R41: every count is printed against the number of drafts staged.
"""
import argparse, collections, json, os, re, sys
LANES = [
('CARVE', re.compile(r'CARVE-REFUSED'), 'jtbl_carve: probe it — the '
'dominant sub-class is NON-CONTIGUOUS .rodata in one subseg, fixed by jr_isolate_all (§8b)'),
('NO-DIAG', re.compile(r'CC1-FAIL\(no-diagnostic\)'), 'the cc1 error was not captured — '
'recompile the TU to get it'),
('ARITY', re.compile(r'too (few|many) arguments to function'), '§376 fix_arity_callers --any-proto'),
('CONFLICT', re.compile(r'conflicting types for'), '§378 cast_self_callers / fix_arity_callers'),
('REDECL', re.compile(r'redeclared|previous declaration|previously declared'), '§378c draft redeclares a TU-owned decl'),
('UNDEF-D', re.compile(r'undefined reference to `?D_[0-9A-Fa-f]{8}'), '§171 aprop_symfix (stale seed symbol)'),
('UNDEF-FN', re.compile(r'undefined reference to `?func_[0-9A-Fa-f]{8}'), 'missing callee definition/decl'),
('UNDEF', re.compile(r'undefined reference to'), 'unresolved symbol'),
('IMPLICIT', re.compile(r'implicit declaration'), 'missing extern'),
('PARSE', re.compile(r'parse error|syntax error'), 'draft does not parse in-TU'),
('DIFF', re.compile(r'\bDIFF\b|closeness'), 'real codegen residual'),
]
def lane_of(text):
for name, rx, _ in LANES:
if rx.search(text):
return name
return 'OTHER'
def main():
ap = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument('--results', default='.run/pgate_results.json')
ap.add_argument('--plan', default=None, help='the gate plan, to assert the denominator (R32)')
ap.add_argument('--out', default='.run/gate_triage.json')
a = ap.parse_args()
res = json.load(open(a.results))
staged = None
if a.plan:
staged = 0
for job in json.load(open(a.plan)):
staged += len([f for f in os.listdir(job['drafts']) if f.endswith('.c')])
rows, banked = [], []
for r in res:
b = r['binary']
for fn in r.get('banked') or []:
banked.append((b, fn))
for v in r.get('verdicts') or []:
fn, _, text = v.partition('\t')
rows.append({'binary': b, 'fn': fn.strip(), 'lane': lane_of(text),
'verdict': text.strip()})
if not (r.get('verdicts') or r.get('banked')) and r.get('tail'):
rows.append({'binary': b, 'fn': '(binary)', 'lane': lane_of(r['tail']),
'verdict': r['tail'].strip()[-200:]})
seen = {(x['binary'], x['fn']) for x in rows} | set(banked)
print('== gate triage (R41) ==')
if staged is not None:
print('drafts staged : %d' % staged)
# R32: a verdict for every staged draft, or say how many went unaccounted.
print('accounted (banked+verdict): %d / %s' % (len(seen), staged))
print('banked : %d' % len(banked))
c = collections.Counter(x['lane'] for x in rows)
for lane, n in c.most_common():
why = next((w for nm, _, w in LANES if nm == lane), '')
print(' %-9s %4d %s' % (lane, n, why))
json.dump({'banked': banked, 'rows': rows, 'staged': staged},
open(a.out, 'w'), indent=1)
print('rows -> %s' % a.out)
for lane, _, _ in LANES:
ex = [x for x in rows if x['lane'] == lane][:3]
for x in ex:
print(' %-9s %-14s %-16s %s' % (lane, x['binary'], x['fn'], x['verdict'][:110]))
return 0
if __name__ == '__main__':
sys.exit(main())