import json
import os
import re
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
ASM_DIR = ROOT / "target" / "asm"
PROBES_MANIFEST = ROOT / "scripts" / "asm" / "probes" / "Cargo.toml"
def req(need=None, need_any=None, forbid=None):
return {"tier": "req", "need": need or {}, "need_any": need_any or [],
"forbid": forbid or []}
def info(note, need=None):
return {"tier": "info", "note": note, "need": need or {}, "need_any": [],
"forbid": []}
X86_ROWS = [
("baseline", "x86-64", [], []),
("v2", "x86-64-v2", [], []),
("v3", "x86-64-v3", [], []),
("v4", "x86-64-v4", [], []),
("native", "native", [], []),
("v3c", "x86-64-v3", ["-C", "llvm-args=-fp-contract=fast"], []),
("v2f", "x86-64-v2", [], ["--features", "use_libm_fma"]),
("v3f", "x86-64-v3", [], ["--features", "use_libm_fma"]),
]
ANDF = r"v?and(n)?(ps|pd)|vpand"
ORF = r"v?or(ps|pd)|vpor|vpternlog"
XORF = r"v?xor(ps|pd)|vpxor|vpternlog"
R2 = 2
EXPECT_X86 = {
"probe_add_f64x8": {
"baseline": req(forbid=["%ymm", "%zmm"]),
"v2": req(need={r"v?addpd": 4}),
"v3": req(need={r"v?addpd": R2}),
"v4": req(need={r"v?addpd": 1}),
"native": req(need={r"v?addpd": 1}),
},
"probe_mul_f64x8": {
"v2": req(need={r"v?mulpd": 4}),
"v3": req(need={r"v?mulpd": R2}),
"v4": req(need={r"v?mulpd": 1}),
"native": req(need={r"v?mulpd": 1}),
},
"probe_div_f64x8": {
"v2": req(need={r"v?divpd": 4}),
"v3": req(need={r"v?divpd": R2}),
"v4": req(need={r"v?divpd": 1}),
"native": req(need={r"v?divpd": 1}),
},
"probe_neg_f64x8": {
"v2": req(need={r"v?xor(ps|pd)": 4}),
"v3": req(need={r"v?xor(ps|pd)": R2}),
"v4": req(need={r"v?xor(ps|pd)": 1}),
"native": req(need={r"v?xor(ps|pd)": 1}),
},
"probe_min_f64x8": {
"v2": req(need={r"v?minpd": 4}),
"v3": req(need={r"v?minpd": R2}),
"v4": req(need={r"v?minpd": 1}),
"native": req(need={r"v?minpd": 1}),
},
"probe_max_f64x8": {
"v2": req(need={r"v?maxpd": 4}),
"v3": req(need={r"v?maxpd": R2}),
"v4": req(need={r"v?maxpd": 1}),
"native": req(need={r"v?maxpd": 1}),
},
"probe_copysign_f64x8": {
"v2": req(need={ANDF: 1, ORF: 1}),
"v3": req(need={ANDF: 1, ORF: 1}),
"v4": req(need_any=[[ANDF, r"vpternlog"], [ORF, r"vpternlog"]]),
"native": req(need_any=[[ANDF, r"vpternlog"], [ORF, r"vpternlog"]]),
},
"probe_sqrt_f64x8": {
"v2": req(need={r"v?sqrtpd": 4}),
"v3": req(need={r"v?sqrtpd": R2}),
"v4": req(need={r"v?sqrtpd": 1}),
"native": req(need={r"v?sqrtpd": 1}),
},
"probe_abs_f64x8": {
"v2": req(need={r"v?and(ps|pd)": 4}),
"v3": req(need={r"v?and(ps|pd)": R2}),
"v4": req(need={r"v?and(ps|pd)": 1}),
"native": req(need={r"v?and(ps|pd)": 1}),
},
"probe_floor_f64x8": {
"baseline": info("SSE2 lacks vector rounding: scalar libm calls"),
"v2": req(need={r"v?roundpd": 4}),
"v3": req(need={r"v?roundpd": R2}),
"v4": req(need={r"v?(roundpd|rndscalepd)": 1}),
"native": req(need={r"v?(roundpd|rndscalepd)": 1}),
},
"probe_mul_add_f64x8": {
"baseline": req(forbid=["vfmadd"]), "v2": req(need={r"v?mulpd": 4, r"v?addpd": 4}),
"v3": req(need={"vfmadd": R2}),
"v4": req(need={"vfmadd": 1}),
"native": req(need={"vfmadd": 1}),
"v3c": req(need={"vfmadd": R2}),
"v2f": info("use_libm_fma: scalar libm fma fallback "
"(no FMA ISA; ~4.6x slower than separated)", need={"callq": 0}),
"v3f": req(need={"vfmadd": R2}),
},
"probe_fma_from_f64x8": {
"baseline": req(forbid=["vfmadd"]),
"v2": req(need={r"v?mulpd": 4, r"v?addpd": 4}),
"v3": req(need={"vfmadd": R2}),
"v4": req(need={"vfmadd": 1}),
"native": req(need={"vfmadd": 1}),
"v3c": req(need={"vfmadd": R2}),
"v2f": info("use_libm_fma: scalar libm fma fallback", need={"callq": 0}),
"v3f": req(need={"vfmadd": R2}),
},
"probe_mul_add_f64x16": {
"baseline": req(forbid=["vfmadd"]),
"v2": req(need={r"v?mulpd": 8, r"v?addpd": 8}),
"v3": req(need={"vfmadd": 4}),
"v4": req(need={"vfmadd": 2}),
"native": req(need={"vfmadd": 2}),
"v3c": req(need={"vfmadd": 4}),
"v2f": info("use_libm_fma: scalar libm fma fallback", need={"callq": 0}),
"v3f": req(need={"vfmadd": 4}),
},
"probe_add_f32x16": {
"v2": req(need={r"v?addps": 4}),
"v3": req(need={r"v?addps": R2}),
"v4": req(need={r"v?addps": 1}),
"native": req(need={r"v?addps": 1}),
},
"probe_mul_add_f32x16": {
"baseline": req(forbid=["vfmadd"]),
"v2": req(need={r"v?mulps": 4, r"v?addps": 4}),
"v3": req(need={"vfmadd": R2}),
"v4": req(need={"vfmadd": 1}),
"native": req(need={"vfmadd": 1}),
"v3c": req(need={"vfmadd": R2}),
"v2f": info("use_libm_fma: scalar libm fmaf fallback", need={"callq": 0}),
"v3f": req(need={"vfmadd": R2}),
},
"probe_add_i64x8": {
"v2": req(need={r"v?paddq": 2}),
"v3": req(need={r"v?paddq": R2}),
"v4": req(need={r"v?paddq": 1}),
"native": req(need={r"v?paddq": 1}),
},
"probe_bitwise_i32x16": {
"v2": req(need={ANDF: 1, ORF: 1, XORF: 1}),
"v3": req(need={ANDF: 1, ORF: 1, XORF: 1}),
"v4": req(need={ANDF: 1, ORF: 1, XORF: 1}),
"native": req(need={ANDF: 1, ORF: 1, XORF: 1}),
},
"probe_shift_i64x8": {
"v2": info("no vector i64 arithmetic shift before AVX-512: "
"scalarized to GPR shlq/sarq (semantics verified)"),
"v3": req(need={r"v?psll": 1, r"v?psr(l|a)": 1}),
"v4": req(need={r"v?psll": 1, r"v?psr(l|a)": 1}),
"native": req(need={r"v?psll": 1, r"v?psr(l|a)": 1}),
},
"probe_cmp_select_f64x8": {
"v2": req(need={r"v?cmp": 1, r"v?blendv": 1}),
"v3": req(need={r"v?cmp": 1, r"v?blendv": 1}),
"v4": req(need={r"v?cmp": 1}, need_any=[[r"\{%k", r"v?blend"]]),
"native": req(need={r"v?cmp": 1}, need_any=[[r"\{%k", r"v?blend"]]),
},
"probe_eq_any_all_f64x8": {
"v2": req(need={r"v?cmp": 1}),
"v3": req(need={r"v?cmp": 1}),
"v4": req(need={r"v?cmp": 1}),
"native": req(need={r"v?cmp": 1}),
},
"probe_slice_io_unaligned": {
"v2": req(need={r"v?movupd": 1, r"v?mulpd": 4}),
"v3": req(need={r"v?movupd": 1, r"v?mulpd": R2}),
"v4": req(need={r"v?movupd": 1, r"v?mulpd": 1}),
"native": req(need={r"v?movupd": 1, r"v?mulpd": 1}),
},
"probe_slice_io_aligned": {
"v2": req(need={r"v?movapd": 1, r"v?addpd": 4}),
"v3": req(need={r"v?movapd": 1, r"v?addpd": R2}),
"v4": req(need={r"v?movapd": 1, r"v?addpd": 1}),
"native": req(need={r"v?movapd": 1, r"v?addpd": 1}),
},
"probe_store_slice_partial": {
row: info("runtime tail length: no stable canonical form "
"(masked move not guaranteed)")
for row in ("v2", "v3", "v4", "native")
},
"probe_reduce_sum_f64x8": {
row: info("strict lane order: v3 = scalar vaddsd chain; wider rows "
"fold with vaddpd", need={r"v?add": 0})
for row in ("v2", "v3", "v4", "native")
},
"probe_expr_fma_f64x8": {
"v3": req(forbid=["vfmadd"]), "v3c": req(need={"vfmadd": 1}), "v3f": req(forbid=["vfmadd"]),
},
"probe_f16_add_f16x8": {
row: info("known-scalar on x86: half soft-float (no f16 arith ISA)")
for row in ("v2", "v3", "v4", "native")
},
"probe_complex_mul_c64x8": {
row: info("vectorized: vmulpd/vshufpd/vaddsubpd family")
for row in ("v2", "v3", "v4", "native")
},
}
AARCH64_ROWS = [
("generic", "generic", [], []),
("native", "native", [], []),
("genc", "generic", ["-C", "llvm-args=-fp-contract=fast"], []),
("genf", "generic", [], ["--features", "use_libm_fma"]),
]
V4 = 4
SELF = r"(bif|bsl|bit)\.16b"
LIBM_FMA = r"\bbl\s+_?fma"
EXPECT_AARCH64 = {
"probe_add_f64x8": {
"generic": req(need={r"fadd\.2d": V4}),
"native": req(need={r"fadd\.2d": V4}),
},
"probe_mul_f64x8": {
"generic": req(need={r"fmul\.2d": V4}),
"native": req(need={r"fmul\.2d": V4}),
},
"probe_div_f64x8": {
"generic": req(need={r"fdiv\.2d": V4}),
"native": req(need={r"fdiv\.2d": V4}),
},
"probe_neg_f64x8": {
"generic": req(need={r"fneg\.2d": V4}),
"native": req(need={r"fneg\.2d": V4}),
},
"probe_min_f64x8": {
"generic": req(need={r"fminnm": V4}),
"native": req(need={r"fminnm": V4}),
},
"probe_max_f64x8": {
"generic": req(need={r"fmaxnm": V4}),
"native": req(need={r"fmaxnm": V4}),
},
"probe_copysign_f64x8": {
"generic": req(need={SELF: 1}),
"native": req(need={SELF: 1}),
},
"probe_sqrt_f64x8": {
"generic": req(need={r"fsqrt\.2d": V4}),
"native": req(need={r"fsqrt\.2d": V4}),
},
"probe_abs_f64x8": {
"generic": req(need={r"fabs\.2d": V4}),
"native": req(need={r"fabs\.2d": V4}),
},
"probe_floor_f64x8": {
"generic": req(need={r"frint": V4}),
"native": req(need={r"frint": V4}),
},
"probe_mul_add_f64x8": {
"generic": req(need={r"fmla\.2d": V4}, forbid=[LIBM_FMA]),
"native": req(need={r"fmla\.2d": V4}, forbid=[LIBM_FMA]),
"genc": req(need={r"fmla\.2d": V4}),
"genf": req(need={r"fmla\.2d": V4}, forbid=[LIBM_FMA]),
},
"probe_fma_from_f64x8": {
"generic": req(need={r"fmla\.2d": V4}, forbid=[LIBM_FMA]),
"native": req(need={r"fmla\.2d": V4}, forbid=[LIBM_FMA]),
"genc": req(need={r"fmla\.2d": V4}),
"genf": req(need={r"fmla\.2d": V4}, forbid=[LIBM_FMA]),
},
"probe_mul_add_f64x16": {
"generic": req(need={r"fmla\.2d": 8}, forbid=[LIBM_FMA]),
"native": req(need={r"fmla\.2d": 8}, forbid=[LIBM_FMA]),
"genc": req(need={r"fmla\.2d": 8}),
"genf": req(need={r"fmla\.2d": 8}, forbid=[LIBM_FMA]),
},
"probe_add_f32x16": {
"generic": req(need={r"fadd\.4s": V4}),
"native": req(need={r"fadd\.4s": V4}),
},
"probe_mul_add_f32x16": {
"generic": req(need={r"fmla\.4s": V4}, forbid=[LIBM_FMA]),
"native": req(need={r"fmla\.4s": V4}, forbid=[LIBM_FMA]),
"genc": req(need={r"fmla\.4s": V4}),
"genf": req(need={r"fmla\.4s": V4}, forbid=[LIBM_FMA]),
},
"probe_add_i64x8": {
"generic": req(need={r"add\.2d": V4}),
"native": req(need={r"add\.2d": V4}),
},
"probe_bitwise_i32x16": {
"generic": req(need={r"and\.16b": 1, r"orr\.16b": 1, r"eor\.16b": 1}),
"native": req(need={r"and\.16b": 1, r"orr\.16b": 1, r"eor\.16b": 1}),
},
"probe_shift_i64x8": {
"generic": req(need={r"[su]shl\.2d": V4}),
"native": req(need={r"[su]shl\.2d": V4}),
},
"probe_cmp_select_f64x8": {
row: info("scalarized: branch-free per-lane fcmp/fcsel "
"(no NEON fcmgt+bsl)", need={"fcmp": 0, "fcsel": 0})
for row in ("generic", "native")
},
"probe_eq_any_all_f64x8": {
row: info("scalarized: branch-free per-lane fcmp/csel",
need={"fcmp": 0, "csel": 0})
for row in ("generic", "native")
},
"probe_slice_io_unaligned": {
"generic": req(need={r"ld[rp]\s+q": 1, r"fmul\.2d": V4}),
"native": req(need={r"ld[rp]\s+q": 1, r"fmul\.2d": V4}),
},
"probe_slice_io_aligned": {
"generic": req(need={r"ld[rp]\s+q": 1, r"fadd\.2d": V4}),
"native": req(need={r"ld[rp]\s+q": 1, r"fadd\.2d": V4}),
},
"probe_store_slice_partial": {
row: info("runtime tail length: no stable canonical form "
"(loop-shaped stores not guaranteed)")
for row in ("generic", "native")
},
"probe_reduce_sum_f64x8": {
row: info("strict lane order: scalar fadd d chain",
need={r"fadd\sd": 0})
for row in ("generic", "native")
},
"probe_expr_fma_f64x8": {
"generic": req(forbid=["fmla"]), "native": req(forbid=["fmla"]),
"genc": req(need={r"fmla\.2d": V4}), "genf": req(forbid=["fmla"]),
},
"probe_f16_add_f16x8": {
"generic": info("soft-float via half: GPR bit work "
"(no f16 arith at armv8.0 generic)"),
"native": info("native-but-scalar: per-lane scalar fadd h "
"(half's intrinsic path does not auto-vectorize)",
need={r"fadd\sh": 0}),
},
"probe_complex_mul_c64x8": {
row: info("vectorized: fmul/ext-shuffle/fsub/fadd family "
"(no fcnmla contraction)", need={r"fmul\.2d": 0})
for row in ("generic", "native")
},
}
ARCHES = {
"x86_64": {"rows": X86_ROWS, "expect": EXPECT_X86},
"aarch64": {"rows": AARCH64_ROWS, "expect": EXPECT_AARCH64},
}
def die(msg, code=2):
print(msg, file=sys.stderr)
sys.exit(code)
def sh(cmd, env=None, **kw):
try:
return subprocess.run(cmd, check=True, capture_output=True,
text=True, env=env, **kw)
except subprocess.CalledProcessError as e:
die(f"command failed: {' '.join(cmd)}\n{e.stdout}\n{e.stderr}")
def host_arch():
vv = sh(["rustc", "-vV"]).stdout
m = re.search(r"^host: (\S+)", vv, re.M)
return m.group(1).split("-")[0] if m else None
def host_triple():
vv = sh(["rustc", "-vV"]).stdout
m = re.search(r"^host: (\S+)", vv, re.M)
return m.group(1) if m else None
HOST_BASELINE = {"x86_64": "x86-64", "aarch64": "generic"}
def compile_rows(arch, rows):
ASM_DIR.mkdir(parents=True, exist_ok=True)
env = {**os.environ, "CARGO_TARGET_DIR": str(ROOT / "target" / "asm-build")}
env.pop("RUSTFLAGS", None)
triple = host_triple()
host_cfg = json.dumps(["-C", f"target-cpu={HOST_BASELINE[arch]}"])
out = {}
for name, cpu, extra, cargo_extra in rows:
asm_path = ASM_DIR / f"{arch}-{name}.s"
target_cfg = json.dumps(["-C", f"target-cpu={cpu}", *extra])
sh(["cargo", "rustc",
"--manifest-path", str(PROBES_MANIFEST),
"--release",
"-Ztarget-applies-to-host", "-Zhost-config",
"--config", f"target.{triple}.rustflags={target_cfg}",
"--config", f"host.rustflags={host_cfg}",
*cargo_extra,
"--", "--emit", f"asm={asm_path}"], env=env)
out[name] = asm_path
return out
def split_bodies(asm_path):
bodies = {}
current, buf = None, []
for line in Path(asm_path).read_text().splitlines():
m = re.fullmatch(r"_?(probe_\w+):", line)
if m:
if current:
bodies[current] = "\n".join(buf)
current, buf = m.group(1), []
elif current is not None:
if line.startswith(".globl") or line.startswith(".size"):
bodies[current] = "\n".join(buf)
current = None
else:
buf.append(line)
if current:
bodies[current] = "\n".join(buf)
return bodies
def count(body, pattern):
return len(re.findall(pattern, body, re.IGNORECASE))
def evaluate(cell, body):
for pat, n in cell["need"].items():
have = count(body, pat)
if have < n:
return False, f"need >= {n} x /{pat}/, found {have}"
for group in cell["need_any"]:
if not any(count(body, pat) > 0 for pat in group):
return False, f"need any of {group}"
for pat in cell["forbid"]:
if count(body, pat) > 0:
return False, f"forbidden /{pat}/ present"
return True, ""
def load_row(arch, row):
path = ASM_DIR / f"{arch}-{row}.s"
if not path.exists():
die(f"{path} not built yet; run the script once first")
return split_bodies(path)
def cmd_dump(probe, row, arch):
body = load_row(arch, row).get(probe)
if body is None:
die(f"{probe} not found in {ASM_DIR}/{arch}-{row}.s")
print(f"// {probe} @ {arch} {row}\n{body}")
def cmd_hist(row, arch):
bodies = load_row(arch, row)
for probe in sorted(bodies):
hist = {}
for line in bodies[probe].splitlines():
m = re.match(r"\s+([a-z][a-z0-9.]*)", line)
if m:
hist[m.group(1)] = hist.get(m.group(1), 0) + 1
top = " ".join(f"{k}x{v}" for k, v in
sorted(hist.items(), key=lambda kv: -kv[1])[:6])
print(f"{probe}\n {top}")
def run_checks(arch, expect, rows):
row_order = [name for name, *_ in rows]
asm_paths = compile_rows(arch, rows)
all_bodies = {row: split_bodies(path) for row, path in asm_paths.items()}
n_probes = len(all_bodies[row_order[0]])
for row in row_order:
present = len(all_bodies[row])
flag = "" if present == n_probes else " <-- MISSING BODIES"
print(f"{arch}-{row}: {present}/{n_probes} probe bodies{flag}")
if not expect:
print(f"\n{arch}: expectation table not populated — informational "
"mode, nothing is asserted.")
print("author one with: python3 scripts/check_asm.py "
f"--hist [{row_order[0]}] {arch} and --dump PROBE ROW {arch}")
return 0
results = {} failures = []
notes = []
for probe, cells in expect.items():
for row in row_order:
cell = cells.get(row)
if cell is None:
results[(probe, row)] = ("-", "")
continue
body = all_bodies[row].get(probe)
if body is None:
results[(probe, row)] = ("miss", "probe body not found")
if cell["tier"] == "req":
failures.append((probe, row, "probe body not found"))
continue
if cell["tier"] == "info":
extra = "".join(f" [{pat}={count(body, pat)}]"
for pat in cell["need"])
results[(probe, row)] = ("info", extra)
notes.append(f"{probe}@{row}: {cell['note']}{extra}")
continue
ok, detail = evaluate(cell, body)
results[(probe, row)] = ("ok" if ok else "miss", detail)
if not ok:
failures.append((probe, row, detail))
name_w = max(len(p) for p in expect) + 1
header = "probe".ljust(name_w) + "".join(c.ljust(10) for c in row_order)
print("\n" + header)
print("-" * len(header))
for probe in expect:
cells = []
for row in row_order:
status, _ = results[(probe, row)]
cells.append({"ok": "ok", "miss": "MISS",
"info": "info", "-": "-"}.get(status, "?").ljust(10))
print(probe.ljust(name_w) + "".join(cells))
print()
for probe, row, detail in failures:
print(f"REQUIRED MISS: {probe}@{row}: {detail}")
print()
for note in notes:
print(f"note: {note}")
print(f"\nREQUIRED misses: {len(failures)} "
f"(informational notes: {len(notes)})")
print("assembly artifacts: " + str(ASM_DIR.relative_to(ROOT)))
print("inspect one body: python3 scripts/check_asm.py "
"--dump <probe> [row] [arch]")
return 1 if failures else 0
def main():
args = sys.argv[1:]
arch_kw = None
if "--arch" in args:
i = args.index("--arch")
if i + 1 >= len(args):
die("--arch needs a value")
arch_kw = args[i + 1]
args = args[:i] + args[i + 2:]
host = host_arch()
arch = arch_kw or host
if arch not in ARCHES:
die(f"unknown arch {arch!r}; known: {', '.join(ARCHES)}. "
"Add an ARCHES entry (rows + expectation table) to extend.")
if arch_kw and arch_kw != host:
print(f"{arch_kw}: cross-emission is not wired up (cargo cross "
f"artifacts can be metadata-only stubs); scaffolding only. "
f"Run on an {arch_kw} host to compile and report.")
return 0
spec = ARCHES[arch]
rows = spec["rows"]
if args and args[0] == "--dump":
if len(args) < 2:
die("--dump PROBE [ROW] [ARCH]")
cmd_dump(args[1], args[2] if len(args) > 2 else rows[0][0], arch)
return 0
if args and args[0] == "--hist":
cmd_hist(args[1] if len(args) > 1 else rows[0][0], arch)
return 0
if args:
die(f"unrecognized arguments: {' '.join(args)}")
sys.exit(run_checks(arch, spec["expect"], rows))
if __name__ == "__main__":
main()