from __future__ import annotations
import argparse
import pathlib
import platform
import re
import subprocess
import sys
REPORT_LIMIT = 20
SYMBOL_DEF = re.compile(r"^([A-Za-z_$][A-Za-z0-9_$.]*):\s*$")
BRANCH = re.compile(r"^\s+(call|callq|jmp|jmpq|bl|b)\s+([^\s;#]+)")
CORE_ARCH = re.compile(r"9core_arch")
AV1_SIMD = re.compile(r"8av1_simd")
GENERIC_KERNEL_MODULE = re.compile(
r"8av1_simd|11vorbis_simd|8vp9_simd|14vorbis_encoder4simd|11zvidlib_vp84simd"
)
VECTOR_TYPE = re.compile(r"6vector3(x86|arm)")
SIMD_PACKAGES = (
"zvidlib-av1",
"zvidlib-color",
"zvidlib-hevc-decoder",
"zvidlib-hevc-encoder",
"zvidlib-vorbis-decoder",
"zvidlib-vorbis-encoder",
"zvidlib-vp8",
"zvidlib-vp9-decoder",
"zvidlib-vp9-encoder",
)
DISAMBIGUATOR = re.compile(r"Cs[0-9A-Za-z]+_")
LEGACY_HASH = re.compile(r"^h[0-9a-f]{16}$")
IDENTIFIER = re.compile(r"[A-Za-z_][A-Za-z0-9_]*$")
def readable(symbol: str) -> str:
trimmed = DISAMBIGUATOR.sub("", symbol)
parts = []
index = 0
while index < len(trimmed):
if not trimmed[index].isdigit() or trimmed[index] == "0":
index += 1
continue
if index and trimmed[index - 1] == "B":
index += 1
continue
end = index
while end < len(trimmed) and trimmed[end].isdigit():
end += 1
length = int(trimmed[index:end])
if trimmed[end : end + 1] == "_":
end += 1
name = trimmed[end : end + length]
if len(name) != length or not IDENTIFIER.match(name):
index += 1
continue
if not LEGACY_HASH.match(name):
parts.append(name)
index = end + length
if not parts:
return symbol
return "::".join(parts)
def _clean(target: str) -> str:
target = target.lstrip("*")
for suffix in ("@PLT", "@plt", "@GOTPCREL", "(%rip)"):
target = target.replace(suffix, "")
return target
def is_core_arch(symbol: str) -> bool:
return bool(CORE_ARCH.search(symbol))
def is_outlined_kernel(symbol: str) -> bool:
return bool(GENERIC_KERNEL_MODULE.search(symbol) and VECTOR_TYPE.search(symbol))
def analyze(lines) -> tuple[list[str], int]:
violations = []
enclosing = "<file scope>"
av1_symbols = 0
intrinsic_calls: dict[tuple[str, str], int] = {}
for line in lines:
definition = SYMBOL_DEF.match(line)
if definition:
symbol = definition.group(1)
if symbol.startswith("L"):
continue
enclosing = symbol
if AV1_SIMD.search(symbol):
av1_symbols += 1
if is_outlined_kernel(symbol):
violations.append(
f"outlined generic kernel: {readable(symbol)}\n"
f" a kernel the `#[target_feature]` wrapper did not absorb, so its "
f"body was compiled at the baseline instruction set"
)
elif is_core_arch(symbol):
violations.append(
f"out-of-line intrinsic: {readable(symbol)}\n"
f" an intrinsic emitted as a real function instead of an instruction"
)
continue
branch = BRANCH.match(line)
if branch:
target = _clean(branch.group(2))
if is_core_arch(target) or is_outlined_kernel(target):
key = (enclosing, target)
intrinsic_calls[key] = intrinsic_calls.get(key, 0) + 1
for (caller, callee), count in sorted(intrinsic_calls.items()):
plural = "" if count == 1 else "s"
violations.append(
f"{readable(caller)}\n"
f" branches to {readable(callee)} {count} time{plural}"
)
return violations, av1_symbols
def check(args: argparse.Namespace) -> int:
paths = [pathlib.Path(p) for p in args.asm]
missing = [str(p) for p in paths if not p.is_file()]
if missing:
print(f"no such assembly file: {', '.join(missing)}", file=sys.stderr)
return 2
violations: list[str] = []
av1_symbols = 0
for path in paths:
with path.open(encoding="utf-8", errors="replace") as handle:
found, seen = analyze(handle)
violations.extend(f"{path}: {v}" for v in found)
av1_symbols += seen
if av1_symbols == 0:
print(
"found no `av1_simd` symbols in "
f"{', '.join(str(p) for p in paths)}: this is not the crate's assembly, "
"and a clean result from it would mean nothing",
file=sys.stderr,
)
return 2
if violations:
print(
f"{len(violations)} sign(s) of a SIMD kernel compiled without the "
"instruction set its `#[target_feature]` wrapper declares:\n",
file=sys.stderr,
)
for violation in violations[:REPORT_LIMIT]:
print(f" {violation}", file=sys.stderr)
if len(violations) > REPORT_LIMIT:
print(
f" ... and {len(violations) - REPORT_LIMIT} more",
file=sys.stderr,
)
print(
"\nEvery kernel reached from `simd_entry_points!` must be "
"`#[inline(always)]` so it is absorbed into the wrapper that enables "
"its instruction set; see issue #336.",
file=sys.stderr,
)
return 1
print(
f"no out-of-line intrinsics and no outlined generic kernels "
f"({av1_symbols} `av1_simd` symbols inspected)"
)
return 0
def build(args: argparse.Namespace) -> int:
architecture = (args.target or platform.machine()).lower()
if not architecture.startswith(("x86_64", "amd64")):
print(
f"host is {platform.machine()}; skipping. The check is x86_64-only: "
"NEON is in the aarch64 baseline, so a kernel that lost its "
"`#[target_feature]` is still compiled to NEON there and the defect "
"leaves no trace. Pass `--target x86_64-...` to cross-compile it here."
)
return 0
target_dir = pathlib.Path(args.target_dir)
for package in args.packages:
command = [
"cargo",
"rustc",
"--package",
package,
"--lib",
"--release",
"--target-dir",
str(target_dir),
]
if args.target:
command += ["--target", args.target]
command += [
"--",
"--emit=asm,link",
"-C",
"codegen-units=1",
"-C",
"symbol-mangling-version=v0",
]
print(f"$ {' '.join(command)}", flush=True)
completed = subprocess.run(command, check=False)
if completed.returncode != 0:
print(f"the assembly build of {package} failed", file=sys.stderr)
return completed.returncode
prefix = f"{args.target}/" if args.target else ""
produced = sorted(target_dir.glob(f"{prefix}release/deps/zvidlib*.s"))
if not produced:
print(
f"the build produced no assembly under "
f"{target_dir}/{prefix}release/deps",
file=sys.stderr,
)
return 2
args.asm = [str(p) for p in produced]
return check(args)
def main(argv=None) -> int:
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
sub = parser.add_subparsers(dest="command", required=True)
checker = sub.add_parser("check", help="inspect assembly files already emitted")
checker.add_argument("--asm", nargs="+", required=True)
checker.set_defaults(func=check)
builder = sub.add_parser("build", help="emit the crate's assembly, then check it")
builder.add_argument("--target-dir", default="target/simd-feature-check")
builder.add_argument(
"--target",
help="cross-compile for this triple instead of building for the host",
)
builder.add_argument(
"--packages",
nargs="*",
default=list(SIMD_PACKAGES),
help="the workspace crates whose assembly is checked",
)
builder.set_defaults(func=build)
args = parser.parse_args(argv)
return args.func(args)
if __name__ == "__main__":
sys.exit(main())