# IR-95 poll-budget census, v2 (loop-anchored). doyle 2026-09-10, releases#294.
# Run from the repo root:  python census.py <outdir>
# NOTE: chr(92) is used for the backslash literal on purpose -- this script is
# authored through a shell heredoc that collapses doubled backslashes.
import io,re,subprocess,collections,sys
SP=sys.argv[1]; BS=chr(92)
files=subprocess.run(['git','ls-files','*.rs'],capture_output=True,text=True).stdout.split()
corpus=[f for f in files if '/tests/' in f or f.startswith('xtask/')]
LOOP=re.compile(r'^\s*(for\s+_?\w*\s+in\s+0\.\.[0-9_]+|while\s+Instant::now\(\)\s*<|loop\s*\{|while\s+\w+\.elapsed\(\))')
SLEEP=re.compile(r'sleep\(|sleep_until|thread::sleep|tokio::time::sleep')
CLOCKCODE=re.compile(r'(\.elapsed\(\)|\.as_millis\(\)|\.as_secs_f|\b(iters|iterations|attempts|polls)\b\s*[,)=])')
FAILV=re.compile(r'(panic!|unreachable!|bail!|assert!|assert_eq!|expect\()')
def strip_lits(s):
    """Blank out string literals: a panic message must not credit itself with a clock."""
    out=[];inq=False;esc=False
    for ch in s:
        if esc: esc=False; continue
        if ch=='"': inq=not inq; continue
        if inq:
            if ch==BS: esc=True
            continue
        out.append(ch)
    return ''.join(out)
rows=[]
for f in corpus:
    try: L=io.open(f,'r',encoding='utf-8',errors='replace').read().split('\n')
    except Exception: continue
    for i,l in enumerate(L):
        if not LOOP.search(l): continue
        if not SLEEP.search('\n'.join(L[i:i+40])): continue   # a POLL, not a spin
        tail='\n'.join(L[i:i+60])
        if not FAILV.search(tail): continue                    # that can FAIL
        rows.append((f,i+1,bool(CLOCKCODE.search(strip_lits(tail))),l.strip()[:80]))
tot=len(rows);cl=sum(1 for r in rows if r[2])
print(f"POLL LOOPS THAT WAIT AND CAN FAIL (loop-anchored, {len(corpus)} files scanned): {tot}")
print(f"  emit a measured clock on the failure path : {cl}")
print(f"  emit NO clock                             : {tot-cl}   <-- see VALIDATION.md: 13 more recovered from the CLOCK bucket; NOT a floor")
per=collections.Counter(f for f,n,h,t in rows if not h)
print("\nTOP FILES BY BLIND POLL LOOPS:")
for f,c in per.most_common(10): print(f"  {c:3d}  {f}")
io.open(SP+'/loop-anchored-sites.tsv','w',encoding='utf-8').write('\n'.join(f"{'CLOCK' if h else 'BLIND'}\t{f}:{n}\t{t}" for f,n,h,t in rows))
