blob: 4eb77cf359bba8771648120be189eaeb887ce1cf [file] [edit]
#!/usr/bin/env python3
"""A utility to wrap utils/update_cc_test_checks.py for updating CHECK lines in
libclc .cl tests for a given architecture.
The script accepts an architecture argument to determine the triple and check
prefix. Supported arch values: amdgpu, amdgcn, nvptx64, spirv, spirv64.
The script does 3 things:
1. Replaces %target, %cpu, and %check_prefix in the .cl file for the arch.
2. Runs update_cc_test_checks.py to update CHECK lines.
3. Reverts the .cl file back to using %target, %cpu, and %check_prefix.
Usage:
% libclc/test/update_libclc_tests.py amdgpu
"""
import argparse
import os
import re
import shutil
import subprocess
import sys
from concurrent.futures import ProcessPoolExecutor, as_completed
from pathlib import Path
ARCH_TO_TRIPLE = {
"amdgpu": "amdgcn-amd-amdhsa-llvm",
"amdgcn": "amdgcn-amd-amdhsa-llvm",
"nvptx64": "nvptx64-nvidia-cuda",
"spirv": "spirv-unknown-mesa3d",
"spirv64": "spirv64-unknown-mesa3d",
}
ARCH_TO_CPU = {
"amdgpu": "gfx900",
"amdgcn": "gfx900",
"nvptx64": "",
"spirv": "",
"spirv64": "",
}
ARCH_TO_REQUIRES = {
"amdgpu": "amdgpu-registered-target",
"amdgcn": "amdgpu-registered-target",
"nvptx64": "nvptx-registered-target",
"spirv": "spirv-registered-target",
"spirv64": "spirv-registered-target",
}
SCRIPT_DIR = Path(__file__).parent
REPO_ROOT = SCRIPT_DIR.parent.parent
UPDATE_SCRIPT = REPO_ROOT / "llvm" / "utils" / "update_cc_test_checks.py"
def find_cl_files(test_dir: Path):
return list(test_dir.rglob("*.cl"))
def replace_in_file(path: Path, triple: str, cpu: str, check_prefix: str):
content = path.read_bytes()
content = content.replace(b"%target", triple.encode())
if cpu:
content = content.replace(b"%cpu", cpu.encode())
content = content.replace(b"%check_prefix", check_prefix.encode())
content = content.replace(b"%libclc_lib", b"")
path.write_bytes(content)
def _run_line_indices(content: bytes) -> list:
RUN_MARKERS = (b"// RUN:", b"; RUN:")
return [
i
for i, l in enumerate(content.splitlines(keepends=True))
if any(l.lstrip().startswith(m) for m in RUN_MARKERS)
]
def file_requires_feature(path: Path, feature: str) -> bool:
try:
text = path.read_text(encoding="utf-8", errors="replace")
except Exception:
return False
for line in text.splitlines():
stripped = line.strip().lstrip("//").strip()
if stripped.startswith("REQUIRES:"):
rest = stripped[len("REQUIRES:") :]
features = [f.strip() for f in re.split(r",|\|\|", rest)]
if feature in features:
return True
return False
def process_file(
cl_file: Path, triple: str, cpu: str, check_prefix: str, clang: Path
) -> bool:
original = cl_file.read_bytes()
orig_lines = original.splitlines(keepends=True)
saved_run = {i: orig_lines[i] for i in _run_line_indices(original)}
replace_in_file(cl_file, triple, cpu, check_prefix)
cmd = [
sys.executable,
str(UPDATE_SCRIPT),
"--clang",
str(clang),
str(cl_file),
]
print(f" update: {cl_file.relative_to(REPO_ROOT)}")
result = subprocess.run(cmd, capture_output=True, text=True)
ok = result.returncode == 0
if not ok:
print(f" FAILED: {result.stderr.strip()}", file=sys.stderr)
updated = cl_file.read_bytes()
updated_lines = updated.splitlines(keepends=True)
for i, line in saved_run.items():
updated_lines[i] = line
cl_file.write_bytes(b"".join(updated_lines))
return ok
def main():
parser = argparse.ArgumentParser(
description="Update libclc FileCheck assertions for a given arch."
)
parser.add_argument(
"arch",
choices=list(ARCH_TO_TRIPLE.keys()),
help="Target arch: amdgpu, amdgcn, nvptx64, spirv, spirv64",
)
parser.add_argument(
"--clang-binary",
default=None,
help="The clang binary used to generate the test case (default: clang from PATH)",
)
args = parser.parse_args()
if args.clang_binary:
clang = Path(args.clang_binary)
if not clang.is_file():
print(f"Error: clang binary not found: {clang}", file=sys.stderr)
sys.exit(1)
else:
clang_in_path = shutil.which("clang")
if not clang_in_path:
print(
"Error: clang not found in PATH; use --clang-binary to specify.",
file=sys.stderr,
)
sys.exit(1)
clang = Path(clang_in_path)
arch = args.arch.lower()
triple = ARCH_TO_TRIPLE[arch]
cpu = ARCH_TO_CPU[arch]
# check_prefix matches REQUIRES feature: uppercase of canonical arch name
# amdgpu -> AMDGCN (same triple as amdgcn), others uppercased
if arch == "amdgpu":
check_prefix = "AMDGCN"
else:
check_prefix = arch.upper()
requires_feature = ARCH_TO_REQUIRES[arch]
cl_files = find_cl_files(SCRIPT_DIR)
target_files = [f for f in cl_files if file_requires_feature(f, requires_feature)]
if not target_files:
print(f"No .cl files found with REQUIRES: {requires_feature}")
return
print(
f"arch={arch} triple={triple} cpu={cpu} check_prefix={check_prefix} requires={requires_feature} clang={clang}"
)
print(f"Processing {len(target_files)} file(s)...")
failed = []
num_workers = max(1, os.cpu_count() // 2)
with ProcessPoolExecutor(max_workers=num_workers) as executor:
futures = {
executor.submit(process_file, f, triple, cpu, check_prefix, clang): f
for f in target_files
}
for future in as_completed(futures):
if not future.result():
failed.append(futures[future])
if failed:
print(f"\n{len(failed)} file(s) failed:", file=sys.stderr)
for f in failed:
print(f" {f}", file=sys.stderr)
sys.exit(1)
else:
print(f"Done. Updated {len(target_files)} file(s).")
if __name__ == "__main__":
main()