makepad/libs/relax/compiler/bench_rt/interp/tailcheck.py
Admin b22a714840 makepad run: the Rust JIT (libs/relax) and makepad-remote
The JIT that runs makepad apps from source: lexer, front end, type checker,
lazy borrow checker (Gandalf; `--check-first` opts in), codegen, session and
warm/cold caches keyed by compiler stamp, provided crates bound late,
startup images, live patching. User-facing name is `makepad run` (binary
`makepad`, MAKEPAD_RUN_* env, ~/.makepad-run); libs/relax stays the internal
library name. `makepad check` reports rustc-style diagnostics with rustc
error codes and checks a whole workspace (`--workspace`). Runs on macOS,
Linux, Windows and Android. makepad-remote: one tool schema for apps and the
WM (app/1): grabs, captures, traces, frame profiles and tweaks.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 12:26:21 +02:00

34 lines
2.1 KiB
Python

#!/usr/bin/env python3
"""Every interpreter handler must end in a tail jump (a missed tail call grows the native stack per
instruction). usage: tailcheck.py <makepad binary>. Disassembles with otool (macOS) or objdump and
lists handlers (relax_codegen::interp::h_*) that dispatch with a call (`blr`) instead of a tail jump
(`br`): a handler that ends the chain (returns, h_call's unwinding exit) may `ret`, but the next
handler is never called."""
import re, subprocess, sys
exe = sys.argv[1]
if sys.platform == "darwin":
dis = subprocess.run(["otool", "-tV", exe], capture_output=True, text=True).stdout
else:
dis = subprocess.run(["objdump", "-d", "--no-show-raw-insn", exe], capture_output=True, text=True).stdout
fns, cur = {}, None
for line in dis.splitlines():
m = re.match(r"^(?:[0-9a-f]+ <)?(_?_R\w+)>?:$", line)
if m:
cur = m.group(1)
fns[cur] = []
elif cur:
parts = line.split("\t")
if len(parts) > 1:
fns[cur].append(parts[1].strip() if sys.platform == "darwin" else parts[-1].split()[0] if parts[-1].split() else "")
hs = {n: o for n, o in fns.items() if re.search(r"6interp(\d+vx)?\d+h_", n)}
# a handler that does not end the chain must reach the next one with `br`; one that also
# calls something (`bl` / `blr`, a frame) must still not dispatch by call: no `blr` to the next
# handler is visible statically, so require the tail `br` and no `ret` outside the known exits
bad = [n for n, o in hs.items() if not re.search(r"h_ret|h_trap|h_resume|h_call$|h_const_call$", n) and ("br" not in o or "ret" in o)]
bad += [n for n, o in hs.items() if re.search(r"h_call$|h_const_call$", n) and "br" not in o]
# no handler calls through a register: that is how a lost tail dispatch looks (`blr` to the next
# handler); out-of-line helpers are called with `bl`
bad += [n for n, o in hs.items() if "blr" in o and n not in bad]
sizes = sorted(len(o) for o in hs.values())
print("%d handlers, median %d instructions; not tail-calling: %s" % (len(hs), sizes[len(sizes) // 2] if sizes else 0, bad or "none"))
sys.exit(1 if bad or not hs else 0)