diff --git a/tools/wave_judge.py b/tools/wave_judge.py index 3f1dcac052..dec6c5095d 100644 --- a/tools/wave_judge.py +++ b/tools/wave_judge.py @@ -1,14 +1,15 @@ #!/usr/bin/env python3 -"""T4 judge — per-arm whole-binary gate (G3/P9, exit codes checked per R53), tree reset between arms, +"""Wave judge (tools/wave_judge.py; born as the T4 judge) — per-arm whole-binary gate (G3/P9, exit codes checked per R53), tree reset between arms, then the union (best arm per fn) gated once and left in the tree for the commit. Reads targets from .run/t4/targets.json and drafts from .run/t4//.c. Writes .run/t4/judge.json. Usage: judge.py [arms...] (default haiku sonnet opus ds)""" import sys, os, json, glob, subprocess, shutil, time REPO = '/home/musashi/bfm-decomp'; os.chdir(REPO) +WAVEDIR = os.environ.get('WAVE', '.run/t4') # the wave's dir: /targets.json, //.c sys.path.insert(0, 'tools'); import corpus UNION = '--union' in sys.argv ARMS = [a for a in sys.argv[1:] if not a.startswith('--')] or ['haiku', 'sonnet', 'opus', 'ds'] -targets = json.load(open('.run/t4/targets.json')) +targets = json.load(open(WAVEDIR + '/targets.json')) T = {t['name']: t for t in targets} def sh(cmd, timeout=3600): return subprocess.run(cmd, capture_output=True, text=True, timeout=timeout) @@ -22,10 +23,10 @@ def reset_tree(): r = sh(['git', 'status', '--porcelain', '--', 'src/', 'config/']) assert not r.stdout.strip(), 'tree not clean after reset:\n' + r.stdout def stage(arm, fns): - d = '.run/t4/stage_%s' % arm; shutil.rmtree(d, ignore_errors=True) + d = WAVEDIR + '/stage_%s' % arm; shutil.rmtree(d, ignore_errors=True) n = 0 for fn in fns: - src = '.run/t4/%s/%s.c' % (arm, fn) + src = WAVEDIR + '/%s/%s.c' % (arm, fn) if not os.path.exists(src) or os.path.getsize(src) == 0: continue b = T[fn]['binary']; os.makedirs('%s/%s' % (d, b), exist_ok=True) shutil.copy(src, '%s/%s/%s.c' % (d, b, fn)); n += 1 @@ -34,10 +35,10 @@ def gate(arm, fns): before = open_fns() d, n = stage(arm, fns) if n == 0: return {}, 'no drafts' - bins = ','.join(sorted({T[f]['binary'] for f in fns if os.path.exists('.run/t4/%s/%s.c' % (arm, f))})) + bins = ','.join(sorted({T[f]['binary'] for f in fns if os.path.exists(WAVEDIR + '/%s/%s.c' % (arm, f))})) t0 = time.time() r = sh(['flock', '.run/auto/draw.lock', '.venv/bin/python', 'tools/sweep_parallel.py', '--drafts', d, '--only', bins, '-j', '4'], 7200) - open('.run/t4/gate_%s.log' % arm, 'w').write(r.stdout + r.stderr) + open(WAVEDIR + '/gate_%s.log' % arm, 'w').write(r.stdout + r.stderr) # banked = INCLUDE_ASM lines the gate removed (git diff; immune to corpus's per-process cache) d = sh(['git', 'diff', '-U0', '--', 'src/']).stdout import re as _re @@ -47,14 +48,14 @@ def gate(arm, fns): def main(): reset_tree() res = {'arms': {}, 'fns': {}} - if os.path.exists('.run/t4/judge.json'): - old = json.load(open('.run/t4/judge.json')); res['arms'] = old.get('arms', {}) + if os.path.exists(WAVEDIR + '/judge.json'): + old = json.load(open(WAVEDIR + '/judge.json')); res['arms'] = old.get('arms', {}) for a, v in res['arms'].items(): for f in v.get('banked', []): res['fns'].setdefault(f, []).append(a) for arm in ARMS: fns = [t['name'] for t in targets] banked, note = gate(arm, fns) - res['arms'][arm] = {'banked': sorted(banked), 'n_banked': len(banked), 'drafts': sum(1 for f in fns if os.path.exists('.run/t4/%s/%s.c' % (arm, f))), 'note': note} + res['arms'][arm] = {'banked': sorted(banked), 'n_banked': len(banked), 'drafts': sum(1 for f in fns if os.path.exists(WAVEDIR + '/%s/%s.c' % (arm, f))), 'note': note} print('%-7s banked %2d / %2d drafts (%s)' % (arm, len(banked), res['arms'][arm]['drafts'], note), flush=True) for f in banked: res['fns'].setdefault(f, []).append(arm) reset_tree() @@ -63,20 +64,20 @@ def main(): pick = {} for f, arms in res['fns'].items(): pick[f] = sorted(arms, key=lambda a: order.index(a) if a in order else 99)[0] - ud = '.run/t4/stage_union'; shutil.rmtree(ud, ignore_errors=True) + ud = WAVEDIR + '/stage_union'; shutil.rmtree(ud, ignore_errors=True) for f, a in pick.items(): b = T[f]['binary']; os.makedirs('%s/%s' % (ud, b), exist_ok=True) - shutil.copy('.run/t4/%s/%s.c' % (a, f), '%s/%s/%s.c' % (ud, b, f)) + shutil.copy(WAVEDIR + '/%s/%s.c' % (a, f), '%s/%s/%s.c' % (ud, b, f)) if pick and UNION: bins = ','.join(sorted({T[f]['binary'] for f in pick})) r = sh(['flock', '.run/auto/draw.lock', '.venv/bin/python', 'tools/sweep_parallel.py', '--drafts', ud, '--only', bins, '-j', '4'], 7200) - open('.run/t4/gate_union.log', 'w').write(r.stdout + r.stderr) + open(WAVEDIR + '/gate_union.log', 'w').write(r.stdout + r.stderr) d = sh(['git', 'diff', '-U0', '--', 'src/']).stdout import re as _re removed = set(_re.findall(r'^-INCLUDE_ASM\("[^"]+",\s*(func_\w+)\);', d, _re.M)) res['union'] = {'picked': pick, 'banked': sorted(f for f in pick if f in removed), 'rc': r.returncode} print('union: %d picked, %d banked (rc=%d) — LEFT IN TREE for the commit' % (len(pick), len(res['union']['banked']), r.returncode)) - json.dump(res, open('.run/t4/judge.json', 'w'), indent=1) + json.dump(res, open(WAVEDIR + '/judge.json', 'w'), indent=1) # per-band table (R41: denominators) band = lambda n: '<=50' if n <= 50 else ('51-120' if n <= 120 else '>120') for arm in res['arms']: