diff --git a/clang.be0 b/clang.be0 index 221a442be..825412e63 100755 --- a/clang.be0 +++ b/clang.be0 @@ -1,10 +1,10 @@ #!/usr/bin/env beesh -# BEE_VERSION clang-22.1.8-0 +# BEE_VERSION clang-23.1.3-0 #SRCURL[0]="https://github.com/llvm/llvm-project/releases/download/llvmorg-${PKGVERSION}/clang-${PKGVERSION}.src.tar.xz" #SRCURL[0]="https://github.com/llvm/llvm-project/releases/download/llvmorg-${PKGVERSION}/llvm-project-${PKGVERSION}.src.tar.xz" -SRCURL[0]="https://beehive.molgen.mpg.de/69065494ebcb1150b2a329c1b3db7584/llvm-project-22.1.8.src.tar.xz" +SRCURL[0]="https://beehive.molgen.mpg.de/d7e227fe471d80d5e2d411127a0123b1/llvm-project-23.1.3.src.tar.xz" # build_in_sourcedir diff --git a/compiler-rt.be0 b/compiler-rt.be0 index a0b80db52..ef3f17a0c 100755 --- a/compiler-rt.be0 +++ b/compiler-rt.be0 @@ -1,9 +1,9 @@ #!/usr/bin/env beesh -# BEE_VERSION compiler-rt-22.1.8-0 +# BEE_VERSION compiler-rt-23.1.3-0 #SRCURL[0]="https://github.com/llvm/llvm-project/releases/download/llvmorg-${PKGVERSION}/llvm-project-${PKGVERSION}.src.tar.xz" -SRCURL[0]="https://beehive.molgen.mpg.de/69065494ebcb1150b2a329c1b3db7584/llvm-project-22.1.8.src.tar.xz" +SRCURL[0]="https://beehive.molgen.mpg.de/d7e227fe471d80d5e2d411127a0123b1/llvm-project-23.1.3.src.tar.xz" # PATCHURL+=() @@ -20,9 +20,12 @@ sourcesubdir_append compiler-rt #} mee_configure() { + # A standalone build installs to ${PREFIX} by default, but clang only + # searches its resource directory ${LIBDIR}/clang/. bee_configure \ -DCMAKE_BUILD_TYPE=Release \ -DCOMPILER_RT_STANDALONE_BUILD=ON \ + -DCOMPILER_RT_INSTALL_PATH=${LIBDIR}/clang/${PKGVERSION%%.*} \ -DCOMPILER_RT_INCLUDE_TESTS=OFF \ -DLLVM_CMAKE_DIR=$(llvm-config --cmakedir) \ -DLLVM_CONFIG_PATH=$(command -v llvm-config) \ diff --git a/libclc.be0 b/libclc.be0 deleted file mode 100755 index 7dcbcb1fb..000000000 --- a/libclc.be0 +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env beesh - -# BEE_VERSION libclc-22.1.8-0 - -# Since LLVM 19 upstream only publishes the monorepo tarball, so libclc is -# built from the llvm-project source subdirectory, like clang and compiler-rt. -#SRCURL[0]="https://github.com/llvm/llvm-project/releases/download/llvmorg-${PKGVERSION}/libclc-${PKGVERSION}.src.tar.xz" -#SRCURL[0]="https://github.com/llvm/llvm-project/releases/download/llvmorg-${PKGVERSION}/llvm-project-${PKGVERSION}.src.tar.xz" -SRCURL[0]="https://beehive.molgen.mpg.de/69065494ebcb1150b2a329c1b3db7584/llvm-project-22.1.8.src.tar.xz" - -# PATCHURL+=() - -# build_in_sourcedir - -sourcesubdir_append libclc - -#mee_extract() { -# bee_extract "${@}" -#} - -#mee_patch() { -# bee_patch "${@}" -#} - -mee_configure() { - bee_configure \ - -DLIBCLC_TARGETS_TO_BUILD="amdgcn--;amdgcn-amd-amdhsa;clspv--;clspv64--;r600--;nvptx64--;nvptx64--nvidiacl;nvptx64-nvidia-cuda;amdgcn-mesa-mesa3d;spirv-mesa3d-;spirv64-mesa3d-" -} - -#mee_build() { -# start_cmd ninja -v -C ${B} ${BEE_NINJAFLAGS} "${@}" -#} - -#mee_install() { -# DESTDIR=${D} start_cmd ninja -C ${B} install "${@}" -#} diff --git a/lld.be0 b/lld.be0 index 51f45c886..9124b3ecf 100755 --- a/lld.be0 +++ b/lld.be0 @@ -1,9 +1,9 @@ #!/usr/bin/env beesh -# BEE_VERSION lld-22.1.8-0 +# BEE_VERSION lld-23.1.3-0 #SRCURL[0]="https://github.com/llvm/llvm-project/releases/download/llvmorg-${PKGVERSION}/llvm-project-${PKGVERSION}.src.tar.xz" -SRCURL[0]="https://beehive.molgen.mpg.de/69065494ebcb1150b2a329c1b3db7584/llvm-project-22.1.8.src.tar.xz" +SRCURL[0]="https://beehive.molgen.mpg.de/d7e227fe471d80d5e2d411127a0123b1/llvm-project-23.1.3.src.tar.xz" # PATCHURL+=() diff --git a/llvm.be0 b/llvm.be0 index 681da7bbd..9a284267a 100755 --- a/llvm.be0 +++ b/llvm.be0 @@ -1,9 +1,9 @@ #!/usr/bin/env beesh -# BEE_VERSION llvm-22.1.8-0 +# BEE_VERSION llvm-23.1.3-0 #SRCURL[0]="https://github.com/llvm/llvm-project/releases/download/llvmorg-${PKGVERSION}/llvm-project-${PKGVERSION}.src.tar.xz" -SRCURL[0]="https://beehive.molgen.mpg.de/69065494ebcb1150b2a329c1b3db7584/llvm-project-22.1.8.src.tar.xz" +SRCURL[0]="https://beehive.molgen.mpg.de/d7e227fe471d80d5e2d411127a0123b1/llvm-project-23.1.3.src.tar.xz" # PATCHURL+=() diff --git a/mesa-libclc.be0 b/mesa-libclc.be0 new file mode 100755 index 000000000..a38c82a2a --- /dev/null +++ b/mesa-libclc.be0 @@ -0,0 +1,36 @@ +#!/usr/bin/env beesh + +# BEE_VERSION mesa-libclc-22.1.8.3-0 + +# Mesa's fork of libclc, pinned to a state that passes the OpenCL CTS with +# rusticl. Mesa prefers it (mesa-libclc.pc) over upstream libclc (libclc.pc). +#SRCURL[0]="https://gitlab.freedesktop.org/karolherbst/mesa-libclc/-/archive/${PKGVERSION}/mesa-libclc-${PKGVERSION}.tar.gz" +SRCURL[0]="https://beehive.molgen.mpg.de/b643e5da5335d299f06b65790ad8e277/mesa-libclc-22.1.8.3.tar.gz" + +# PATCHURL+=() + +# build_in_sourcedir + +#mee_extract() { +# bee_extract "${@}" +#} + +#mee_patch() { +# bee_patch "${@}" +#} + +mee_configure() { + bee_configure \ + -DCMAKE_BUILD_TYPE=Release \ + -DLLVM_CMAKE_DIR=$(llvm-config --cmakedir) \ + -Wno-dev \ + -G Ninja +} + +mee_build() { + start_cmd ninja -v -C ${B} ${BEE_NINJAFLAGS} "${@}" +} + +mee_install() { + DESTDIR=${D} start_cmd ninja -C ${B} install "${@}" +} diff --git a/mesalib.be0 b/mesalib.be0 index 47f562876..3dc0e9fdf 100755 --- a/mesalib.be0 +++ b/mesalib.be0 @@ -1,6 +1,6 @@ #!/usr/bin/env beesh -# BEE_VERSION mesalib-26.2.2-0 +# BEE_VERSION mesalib-26.2.4-0 ## this file was created by bee init and should be executed to build a ## bee-package. (Additional hints are located at the end of this file.) @@ -10,7 +10,7 @@ ## downloaded. Version variables may be used to simplify reuse of this bee-file. #SRCURL[0]="https://archive.mesa3d.org/mesa-${PKGVERSION}.tar.xz" -SRCURL[0]="https://beehive.molgen.mpg.de/4f9e0c96c913cdd9e8998453e248714e/mesa-26.2.2.tar.xz" +SRCURL[0]="https://beehive.molgen.mpg.de/d8ee81c058acce4f7ce7e5be1917ed2e/mesa-26.2.4.tar.xz" ############################################################################### ## Add URLs/pathes to patch files to the PATCHURL array. diff --git a/scripts/smoke-tests/llvm-smoke-test b/scripts/smoke-tests/llvm-smoke-test new file mode 100755 index 000000000..252e4c854 --- /dev/null +++ b/scripts/smoke-tests/llvm-smoke-test @@ -0,0 +1,407 @@ +#! /usr/bin/env python3 + +# Smoke-test the installed LLVM toolchain: llvm, clang, compiler-rt, lld and +# spirv-llvm-translator. +# +# scripts/smoke-tests/llvm-smoke-test +# TMPDIR=/dev/shm scripts/smoke-tests/llvm-smoke-test \ +# --prefix /src/mariux/beeroot/packages/llvm-23.1.3-0.x86_64.bee.tar.bz2 \ +# --prefix /src/mariux/beeroot/packages/clang-23.1.3-0.x86_64.bee.tar.bz2 \ +# --prefix /src/mariux/beeroot/packages/compiler-rt-23.1.3-0.x86_64.bee.tar.bz2 \ +# --prefix /src/mariux/beeroot/packages/lld-23.1.3-0.x86_64.bee.tar.bz2 \ +# --prefix /src/mariux/beeroot/packages/spirv-llvm-translator-23.1.2-0.x86_64.bee.tar.bz2 +# +# The five packages are built separately, standalone, against each other, so +# each can be fine on its own and still not fit the others: compiler-rt 22.1.8-0 +# installed its runtimes to /usr/lib/linux, where clang never looks, and +# nothing noticed until `-fsanitize=address' failed to link. So this compiles, +# links and runs small programs through every package: +# +# - the versions agree: clang, llvm-config, ld.lld and the libLLVM that +# llvm-spirv links (the translator's major version has to match LLVM's), +# - clang finds its resource directory, headers and compiler-rt runtimes, +# - C and C++ (libstdc++, exceptions) with the default linker, +# - lld, also with ThinLTO, and compiler-rt's builtins via --rtlib, +# - AddressSanitizer, UndefinedBehaviorSanitizer and ThreadSanitizer catch +# what they should, +# - profiling and coverage with llvm-profdata and llvm-cov, +# - OpenCL C to SPIR-V and back with llvm-spirv, as Mesa's rusticl does. +# +# --prefix (repeatable) takes the image directory of a build, or a .bee.tar.bz2 +# package, which is unpacked below $TMPDIR. They are mounted read-only over /usr +# in a user namespace, so packages can be checked before they are installed, +# without root: clang derives its resource directory from where its binary is, +# so compiler-rt has to show up below the same /usr as clang. +# +# Failures exit non-zero. ASan and TSan reserve terabytes of address space, which +# hosts with vm.overcommit_memory=2 (MXQ nodes and servers) refuse; there the +# programs are only linked, and that is reported as WARN. + +import argparse +import os +import re +import shlex +import shutil +import subprocess +import sys +import tempfile + +# How --prefix survives the re-exec into the mount namespace. +PREFIX_VARIABLE = "LLVM_SMOKE_TEST_PREFIX" + +SOURCES = { + "hello.c": r""" +#include +int main(void) { puts("hello"); return 0; } +""", + "hello.cc": r""" +#include +#include +#include +int main() { + try { + throw std::runtime_error("hello"); + } catch (const std::exception &e) { + std::cout << std::string(e.what()) << std::endl; + } + return 0; +} +""", + "lto-main.c": r""" +#include +int twice(int); +int main(void) { printf("hello %d\n", twice(21)); return 0; } +""", + "lto-twice.c": r""" +int twice(int x) { return 2 * x; } +""", + "asan.c": r""" +#include +int main(void) { int *p = malloc(4 * sizeof *p); int x = p[4]; free(p); return x; } +""", + "ubsan.c": r""" +#include +int main(int argc, char **argv) { int x = INT_MAX; x += argc; return x == 0; } +""", + "tsan.c": r""" +#include +static int counter; +static void *work(void *arg) { for (int i = 0; i < 100000; i++) counter++; return arg; } +int main(void) { + pthread_t a, b; + pthread_create(&a, 0, work, 0); + pthread_create(&b, 0, work, 0); + pthread_join(a, 0); + pthread_join(b, 0); + return 0; +} +""", + "profile.c": r""" +#include +static int odd(int x) { return x & 1; } +int main(int argc, char **argv) { printf("%d\n", odd(argc) ? 1 : 2); return 0; } +""", + "kernel.cl": r""" +kernel void k(global float *out) { + size_t i = get_global_id(0); + out[i] = sqrt((float)i) + clamp((float)i, 1.0f, 2.0f); +} +""", +} + +status = {"fail": 0, "warn": 0} + + +def report(state, message): + if state != "ok": + status[state] += 1 + print("%-5s %s" % (state.upper(), message)) + + +def run(command, **kwargs): + """Run command, capturing its output, never raising for its exit status.""" + try: + return subprocess.run(command, capture_output=True, text=True, + timeout=600, **kwargs) + except (OSError, subprocess.TimeoutExpired) as error: + return subprocess.CompletedProcess(command, 127, "", str(error)) + + +def last_line(process): + """The line of the output that says what went wrong.""" + lines = (process.stderr or process.stdout).strip().splitlines() + for line in lines: + # The linker says why before clang says that it failed. + if re.search(r"error|cannot find|undefined reference", line) and \ + "linker command failed" not in line: + return line.strip() + return lines[-1] if lines else "exit status %d" % process.returncode + + +# ---------------------------------------------------------------- --prefix + +def unpack(items, workdir): + """Return the directories to overlay, unpacking package archives.""" + lowers = [] + for number, item in enumerate(items): + if os.path.isdir(item): + path = os.path.abspath(item.rstrip("/")) + elif os.path.isfile(item): + path = os.path.join(workdir, str(number)) + os.mkdir(path) + print(" unpacking %s" % item, flush=True) + process = subprocess.run(["tar", "-C", path, "-xf", item], + capture_output=True, text=True) + if process.returncode: + sys.exit("cannot unpack %s: %s" % (item, process.stderr)) + else: + sys.exit("no such directory or package: %s" % item) + if ":" in path or "," in path: + sys.exit("cannot overlay %s: overlayfs separates layers by ':'" + " and options by ','" % path) + if not os.path.isdir(path + "/usr"): + sys.exit("%s has no usr/" % item) + lowers.append(path) + return lowers + + +def run_in_overlay(lowers): + """Run this script again with lowers mounted over /usr.""" + layers = ":".join([lower + "/usr" for lower in lowers] + ["/usr"]) + setup = "set -e\nmount -t overlay overlay -o lowerdir=%s /usr\nexec \"$@\"" \ + % shlex.quote(layers) + environment = dict(os.environ) + environment[PREFIX_VARIABLE] = ":".join(lowers) + command = ["unshare", "--user", "--map-root-user", "--mount", + "sh", "-c", setup, "sh", sys.executable, + os.path.abspath(__file__)] + sys.argv[1:] + try: + return subprocess.run(command, env=environment).returncode + except OSError as error: + sys.exit("cannot re-run in a mount namespace (%s); install the" + " packages and run without --prefix" % error) + + +# ----------------------------------------------------------------- checks + +def major(version): + return version.split(".")[0] if version else None + + +def check_versions(): + """Return clang's major version, after comparing it with the others.""" + clang = re.search(r"clang version (\S+)", run(["clang", "--version"]).stdout) + clang = clang.group(1) if clang else None + llvm = run(["llvm-config", "--version"]).stdout.strip() or None + lld = re.search(r"LLD (\S+)", run(["ld.lld", "--version"]).stdout) + lld = lld.group(1) if lld else None + spirv = None + path = shutil.which("llvm-spirv") + if path: + linked = re.search(r"libLLVM\.so\.(\S+)", run(["ldd", path]).stdout) + spirv = linked.group(1) if linked else None + found = {"clang": clang, "llvm-config": llvm, "ld.lld": lld, + "llvm-spirv (libLLVM)": spirv} + summary = ", ".join("%s %s" % (name, version or "missing") + for name, version in found.items()) + if not clang: + report("fail", "no clang: " + summary) + return None + if None in found.values(): + report("fail", "missing: " + summary) + elif all(major(version) == major(clang) for version in found.values()): + report("ok", summary) + else: + report("fail", "versions disagree: " + summary) + return major(clang) + + +def check_resource_dir(version): + resource = run(["clang", "-print-resource-dir"]).stdout.strip() + expected = "/usr/lib/clang/%s" % version + if resource != expected: + report("fail", "resource directory is %s, not %s" % (resource, expected)) + if not os.path.exists(resource + "/include/stddef.h"): + report("fail", "no %s/include/stddef.h: clang's headers are missing" + % resource) + runtime = run(["clang", "-print-runtime-dir"]).stdout.strip() + runtimes = os.listdir(runtime) if os.path.isdir(runtime) else [] + if any(name.startswith("libclang_rt.asan") for name in runtimes): + report("ok", "runtime directory %s holds %d compiler-rt files" + % (runtime, len(runtimes))) + else: + report("fail", "no compiler-rt runtimes in clang's runtime directory" + " (clang -print-runtime-dir: %s)" % runtime) + + +def build(name, command, directory): + """Run a compile command; report and return False if it fails.""" + process = run(command, cwd=directory) + if process.returncode: + report("fail", "%s: %s failed: %s" + % (name, " ".join(command[:1]), last_line(process))) + return False + return True + + +def check_program(name, command, directory, expect="hello", binary="./a.out"): + if not build(name, command, directory): + return + process = run([binary], cwd=directory) + if process.returncode or expect not in process.stdout: + report("fail", "%s: %s printed %r, exit status %d" + % (name, binary, process.stdout.strip(), process.returncode)) + else: + report("ok", name) + + +def check_lld(version, directory): + name = "lld links and runs" + if not build(name, ["clang", "-fuse-ld=lld", "-o", "lld", "hello.c"], + directory): + return + comment = run(["readelf", "-p", ".comment", "lld"], cwd=directory).stdout + if "LLD %s." % version not in comment: + report("fail", "%s: .comment names no LLD %s" % (name, version)) + return + process = run(["./lld"], cwd=directory) + if process.returncode or "hello" not in process.stdout: + report("fail", "%s: ./lld exit status %d" % (name, process.returncode)) + else: + report("ok", "%s, .comment names LLD %s" % (name, version)) + + +def strict_overcommit(): + try: + with open("/proc/sys/vm/overcommit_memory") as sysctl: + return sysctl.read().strip() == "2" + except OSError: + return False + + +def check_sanitizer(name, flags, source, expect, directory): + output = name.lower() + if not build(name, ["clang", "-g"] + flags + ["-o", output, source], + directory): + return + if strict_overcommit() and name != "UBSan": + report("warn", "%s: linked, but not run: vm.overcommit_memory=2 refuses" + " its shadow memory" % name) + return + process = run(["./" + output], cwd=directory) + if expect in process.stderr: + report("ok", "%s reports %s" % (name, expect)) + else: + report("fail", "%s: expected %r, got exit status %d: %s" + % (name, expect, process.returncode, last_line(process))) + + +def check_profile(directory): + name = "profiling and coverage" + if not build(name, ["clang", "-fprofile-instr-generate", + "-fcoverage-mapping", "-o", "profile", "profile.c"], + directory): + return + steps = ([["./profile"], {"LLVM_PROFILE_FILE": "profile.profraw"}], + [["llvm-profdata", "merge", "-o", "profile.profdata", + "profile.profraw"], {}], + [["llvm-cov", "report", "./profile", + "-instr-profile=profile.profdata"], {}]) + for command, environment in steps: + process = run(command, cwd=directory, + env=dict(os.environ, **environment)) + if process.returncode: + report("fail", "%s: %s failed: %s" + % (name, command[0], last_line(process))) + return + if "TOTAL" in process.stdout: + report("ok", "%s: compiler-rt profile, llvm-profdata, llvm-cov" % name) + else: + report("fail", "%s: llvm-cov report printed no TOTAL" % name) + + +def check_spirv(directory): + name = "OpenCL C to SPIR-V" + if not shutil.which("llvm-spirv"): + report("fail", "%s: no llvm-spirv" % name) + return + if not build(name, ["clang", "-c", "-target", "spir64-unknown-unknown", + "-cl-std=CL2.0", "-emit-llvm", "-Xclang", + "-finclude-default-header", "-o", "kernel.bc", + "kernel.cl"], directory): + return + if not build(name, ["llvm-spirv", "kernel.bc", "-o", "kernel.spv"], + directory): + return + with open(os.path.join(directory, "kernel.spv"), "rb") as spirv: + if spirv.read(4) != b"\x03\x02\x23\x07": + report("fail", "%s: kernel.spv has no SPIR-V magic number" % name) + return + if shutil.which("spirv-val") and \ + not build(name, ["spirv-val", "kernel.spv"], directory): + return + if build(name, ["llvm-spirv", "-r", "kernel.spv", "-o", "back.bc"], + directory) and \ + build(name, ["llvm-dis", "back.bc"], directory): + report("ok", "%s and back with llvm-spirv%s" + % (name, ", spirv-val accepts it" + if shutil.which("spirv-val") else "")) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--prefix", action="append", default=[], + metavar="DIR|PACKAGE", + help="test the image directory DIR of a build, or the" + " .bee.tar.bz2 PACKAGE, instead of what is" + " installed; may be repeated") + arguments = parser.parse_args() + + if arguments.prefix and PREFIX_VARIABLE not in os.environ: + workdir = tempfile.mkdtemp(prefix="llvm-smoke-") + try: + return run_in_overlay(unpack(arguments.prefix, workdir)) + finally: + shutil.rmtree(workdir, ignore_errors=True) + if PREFIX_VARIABLE in os.environ: + print(" testing, over /usr: %s" + % os.environ[PREFIX_VARIABLE].replace(":", " ")) + + version = check_versions() + if not version: + return 1 + directory = tempfile.mkdtemp(prefix="llvm-smoke-") + try: + for name, text in SOURCES.items(): + with open(os.path.join(directory, name), "w") as source: + source.write(text) + check_resource_dir(version) + check_program("C", ["clang", "hello.c"], directory) + check_program("C++ with exceptions", ["clang++", "hello.cc"], directory) + check_lld(version, directory) + check_program("ThinLTO with lld", + ["clang", "-flto=thin", "-fuse-ld=lld", "-O2", + "lto-main.c", "lto-twice.c"], directory, + expect="hello 42") + check_program("compiler-rt builtins (--rtlib=compiler-rt)", + ["clang", "--rtlib=compiler-rt", "--unwindlib=libgcc", + "hello.c"], directory) + check_sanitizer("ASan", ["-fsanitize=address"], "asan.c", + "heap-buffer-overflow", directory) + check_sanitizer("UBSan", ["-fsanitize=undefined"], "ubsan.c", + "signed integer overflow", directory) + check_sanitizer("TSan", ["-fsanitize=thread"], "tsan.c", + "data race", directory) + check_profile(directory) + check_spirv(directory) + finally: + shutil.rmtree(directory, ignore_errors=True) + + print("%s (%d failed, %d warned)" + % ("FAIL" if status["fail"] else "PASS", status["fail"], + status["warn"])) + return 1 if status["fail"] else 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/smoke-tests/mesa-smoke-test b/scripts/smoke-tests/mesa-smoke-test new file mode 100755 index 000000000..4017ef51c --- /dev/null +++ b/scripts/smoke-tests/mesa-smoke-test @@ -0,0 +1,818 @@ +#! /usr/bin/env python3 + +# Smoke-test an installed Mesa: OpenGL, Vulkan and OpenCL (rusticl). +# +# scripts/smoke-tests/mesa-smoke-test +# scripts/smoke-tests/mesa-smoke-test --rusticl-enable llvmpipe,radeonsi +# TMPDIR=/dev/shm scripts/smoke-tests/mesa-smoke-test \ +# --prefix /src/mariux/beeroot/packages/llvm-23.1.3-0.x86_64.bee.tar.bz2 \ +# --prefix /src/mariux/beeroot/packages/clang-23.1.3-0.x86_64.bee.tar.bz2 \ +# --prefix /src/mariux/beeroot/packages/spirv-llvm-translator-23.1.2-0.x86_64.bee.tar.bz2 \ +# --prefix /src/mariux/beeroot/packages/mesa-libclc-22.1.8.3-0.x86_64.bee.tar.bz2 \ +# --prefix /src/mariux/beeroot/packages/mesalib-26.2.4-0.x86_64.bee.tar.bz2 +# +# mesalib.be0 links Mesa against llvm, clang, spirv-llvm-translator and +# mesa-libclc, and a version bump of any of them can break a driver without +# breaking the build. So this checks what a desktop and a compute user would hit: +# +# - every Mesa library resolves its shared libraries (ldd), +# - OpenGL ES on every device EGL lists, the software rasterizer (llvmpipe) +# and each GPU, by drawing a shaded triangle into a framebuffer and +# comparing the pixels; llvmpipe compiles that shader with LLVM's JIT, +# - GLX on an Xvfb of its own, the path Xfce takes, +# - each Mesa Vulkan driver: create an instance and a device on every GPU it +# drives, talking to the driver directly, as hosts need not have the loader, +# - OpenCL: build and run a kernel calling libclc builtins on every rusticl +# device, and complain when rusticl does not find Mesa's libclc fork. +# +# The EGL, Vulkan and OpenCL checks are restricted to Mesa's own vendor files, so +# that NVIDIA's proprietary driver, where installed, is not what gets tested. +# +# --prefix (repeatable) takes the image directory of a build, or a .bee.tar.bz2 +# package, which is unpacked below $TMPDIR. They are mounted read-only over /usr +# (and /etc) in a user namespace, so packages can be checked before they are +# installed, without root; the drivers find each other only under the paths +# compiled into them, which LD_LIBRARY_PATH alone does not cover. +# +# Failures of what the packages provide exit non-zero; what depends on the +# machine (no GPU, no render node, no Xvfb) is reported as WARN. + +import argparse +import ctypes +import glob +import json +import math +import os +import shlex +import shutil +import subprocess +import sys +import tempfile +import time + +# How --prefix survives the re-exec into the mount namespace. +PREFIX_VARIABLE = "MESA_SMOKE_TEST_PREFIX" + +# Mesa's own vendor files, see the comment at the top. +EGL_VENDOR = "/usr/share/glvnd/egl_vendor.d/50_mesa.json" +RUSTICL_ICD = "/etc/OpenCL/vendors/rusticl.icd" +OPENCL_LOADER = "/usr/lib/libOpenCL.so.1" +VULKAN_ICD_DIRS = ("/usr/share/vulkan/icd.d", "/etc/vulkan/icd.d") + +# Logged by rusticl (src/gallium/frontends/rusticl/core/device.rs). +LIBCLC_MISSING = "Libclc failed to load" +LIBCLC_UPSTREAM = "Patched Mesa libclc not detected" + +status = {"fail": 0, "warn": 0} + + +def report(state, message): + if state != "ok": + status[state] += 1 + print("%-5s %s" % (state.upper(), message)) + + +# ---------------------------------------------------------------- --prefix + +def unpack(items, workdir): + """Return the directories to overlay, unpacking package archives.""" + lowers = [] + for number, item in enumerate(items): + if os.path.isdir(item): + path = os.path.abspath(item.rstrip("/")) + elif os.path.isfile(item): + path = os.path.join(workdir, str(number)) + os.mkdir(path) + print(" unpacking %s" % item, flush=True) + process = subprocess.run(["tar", "-C", path, "-xf", item], + capture_output=True, text=True) + if process.returncode: + sys.exit("cannot unpack %s: %s" % (item, process.stderr)) + else: + sys.exit("no such directory or package: %s" % item) + if ":" in path or "," in path: + sys.exit("cannot overlay %s: overlayfs separates layers by ':'" + " and options by ','" % path) + lowers.append(path) + return lowers + + +def run_in_overlay(lowers): + """Run this script again with lowers mounted over /usr and /etc.""" + setup = ["set -e"] + for directory in ("usr", "etc"): + layers = [lower + "/" + directory for lower in lowers + if os.path.isdir(lower + "/" + directory)] + if layers: + setup.append("mount -t overlay overlay -o lowerdir=%s /%s" + % (shlex.quote(":".join(layers + ["/" + directory])), + directory)) + setup.append('exec "$@"') + environment = dict(os.environ) + environment[PREFIX_VARIABLE] = ":".join(lowers) + command = ["unshare", "--user", "--map-root-user", "--mount", + "sh", "-c", "\n".join(setup), "sh", sys.executable, + os.path.abspath(__file__)] + sys.argv[1:] + try: + return subprocess.run(command, env=environment).returncode + except OSError as error: + sys.exit("cannot re-run in a mount namespace (%s); install the" + " packages and run without --prefix" % error) + + +# --------------------------------------------------------------- children +# +# Every check that loads a driver runs in a child process of this script, so +# that each gets its own environment, a crash fails one check instead of the +# run, and what the driver logs to stderr can be looked at. A child prints one +# line of JSON. + +def child(kind, environment=None, timeout=300): + """Run check kind in a child; return (result or None, stderr).""" + env = dict(os.environ) + env.update(environment or {}) + try: + process = subprocess.run( + [sys.executable, os.path.abspath(__file__), "--child", kind], + env=env, capture_output=True, text=True, timeout=timeout) + except subprocess.TimeoutExpired: + return None, "timed out after %d s" % timeout + for line in reversed(process.stdout.splitlines()): + if line.startswith("{"): + return json.loads(line), process.stderr + return None, (process.stderr.strip().splitlines() or + ["exit status %d" % process.returncode])[-1] + + +def egl_devices(egl): + """Return EGL's devices (EGL_EXT_device_enumeration), or raise.""" + egl.eglQueryString.restype = ctypes.c_char_p + egl.eglQueryString.argtypes = [ctypes.c_void_p, ctypes.c_int] + extensions = (egl.eglQueryString(None, 0x3055) or b"").split() # EXTENSIONS + for extension in (b"EGL_EXT_device_enumeration", b"EGL_EXT_platform_device"): + if extension not in extensions: + raise RuntimeError("EGL lacks %s" % extension.decode()) + egl.eglGetProcAddress.restype = ctypes.c_void_p + egl.eglGetProcAddress.argtypes = [ctypes.c_char_p] + query_devices = ctypes.CFUNCTYPE( + ctypes.c_uint, ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p)( + egl.eglGetProcAddress(b"eglQueryDevicesEXT")) + devices, count = (ctypes.c_void_p * 16)(), ctypes.c_int() + if not query_devices(16, devices, ctypes.byref(count)): + raise RuntimeError("eglQueryDevicesEXT failed") + return devices[:count.value] + + +def child_egl_devices(): + """List EGL's devices: the software one, and a DRM node for each GPU.""" + egl = ctypes.CDLL("libEGL.so.1") + try: + devices = egl_devices(egl) + except RuntimeError as error: + return {"error": str(error)} + query_string = ctypes.CFUNCTYPE(ctypes.c_char_p, ctypes.c_void_p, + ctypes.c_int)( + egl.eglGetProcAddress(b"eglQueryDeviceStringEXT")) + result = [] + for index, device in enumerate(devices): + extensions = (query_string(device, 0x3055) or b"").split() + node = None + if b"EGL_EXT_device_drm_render_node" in extensions: + node = query_string(device, 0x3377) # DRM_RENDER_NODE_FILE_EXT + if not node and b"EGL_EXT_device_drm" in extensions: + node = query_string(device, 0x3233) # DRM_DEVICE_FILE_EXT + result.append({"index": index, + "software": b"EGL_MESA_device_software" in extensions, + "node": node.decode() if node else None}) + return {"devices": result} + + +def child_gl(): + """Draw a shaded triangle with OpenGL ES 3 on EGL device + $MESA_SMOKE_EGL_DEVICE (EGL_EXT_platform_device), into a framebuffer object. + + An error before the context is current carries "setup", as it may just + mean that Mesa has no driver for the device. + """ + c_int, c_uint, c_void_p = ctypes.c_int, ctypes.c_uint, ctypes.c_void_p + egl = ctypes.CDLL("libEGL.so.1") + gl = ctypes.CDLL("libGLESv2.so.2") + egl.eglGetPlatformDisplay.restype = c_void_p + egl.eglGetPlatformDisplay.argtypes = [c_uint, c_void_p, c_void_p] + egl.eglInitialize.argtypes = [c_void_p, c_void_p, c_void_p] + egl.eglChooseConfig.argtypes = [c_void_p, c_void_p, c_void_p, c_int, + c_void_p] + egl.eglCreateContext.restype = c_void_p + egl.eglCreateContext.argtypes = [c_void_p, c_void_p, c_void_p, c_void_p] + egl.eglMakeCurrent.argtypes = [c_void_p, c_void_p, c_void_p, c_void_p] + egl.eglGetError.restype = c_int + gl.glGetString.restype = ctypes.c_char_p + gl.glCreateShader.restype = c_uint + gl.glCreateProgram.restype = c_uint + gl.glShaderSource.argtypes = [c_uint, c_int, c_void_p, c_void_p] + gl.glVertexAttribPointer.argtypes = [c_uint, c_int, c_uint, ctypes.c_ubyte, + c_int, c_void_p] + gl.glReadPixels.argtypes = [c_int, c_int, c_int, c_int, c_uint, c_uint, + c_void_p] + gl.glBindAttribLocation.argtypes = [c_uint, c_uint, ctypes.c_char_p] + + def fail(message, setup=False): + return {"error": "%s (EGL error 0x%x, GL error 0x%x)" + % (message, egl.eglGetError(), gl.glGetError()), + "setup": setup} + + try: + device = egl_devices(egl)[int(os.environ["MESA_SMOKE_EGL_DEVICE"])] + except (RuntimeError, IndexError) as error: + return fail("no such EGL device (%s)" % error, setup=True) + display = egl.eglGetPlatformDisplay(0x313F, device, None) # DEVICE_EXT + if not display or not egl.eglInitialize(display, None, None): + return fail("eglInitialize failed", setup=True) + # EGL_SURFACE_TYPE 0: any config, the context renders into an FBO. + attributes = (c_int * 5)(0x3033, 0, 0x3040, 0x0040, 0x3038) + config, count = c_void_p(), c_int() + if not egl.eglChooseConfig(display, attributes, ctypes.byref(config), 1, + ctypes.byref(count)) or count.value < 1: + return fail("no OpenGL ES 3 config", setup=True) + egl.eglBindAPI(0x30A0) # EGL_OPENGL_ES_API + context = egl.eglCreateContext(display, config, None, + (c_int * 3)(0x3098, 3, 0x3038)) + if not context or not egl.eglMakeCurrent(display, None, None, context): + return fail("cannot make an OpenGL ES 3 context current", setup=True) + + size = 64 + framebuffer, renderbuffer = c_uint(), c_uint() + gl.glGenFramebuffers(1, ctypes.byref(framebuffer)) + gl.glBindFramebuffer(0x8D40, framebuffer) + gl.glGenRenderbuffers(1, ctypes.byref(renderbuffer)) + gl.glBindRenderbuffer(0x8D41, renderbuffer) + gl.glRenderbufferStorage(0x8D41, 0x8058, size, size) # GL_RGBA8 + gl.glFramebufferRenderbuffer(0x8D40, 0x8CE0, 0x8D41, renderbuffer) + if gl.glCheckFramebufferStatus(0x8D40) != 0x8CD5: + return fail("framebuffer incomplete") + + sources = { + 0x8B31: b"attribute vec2 pos;\n" + b"void main() { gl_Position = vec4(pos, 0.0, 1.0); }\n", + 0x8B30: b"precision highp float;\n" + b"void main() {\n" + b" gl_FragColor = vec4(gl_FragCoord.xy / 64.0,\n" + b" 0.5 + 0.5 * sin(gl_FragCoord.x), 1.0);\n" + b"}\n", + } + program = gl.glCreateProgram() + for kind, text in sources.items(): + shader = gl.glCreateShader(kind) + pointer = ctypes.c_char_p(text) + gl.glShaderSource(shader, 1, ctypes.byref(pointer), None) + gl.glCompileShader(shader) + compiled = c_int() + gl.glGetShaderiv(shader, 0x8B81, ctypes.byref(compiled)) + if not compiled.value: + return fail("shader does not compile") + gl.glAttachShader(program, shader) + gl.glBindAttribLocation(program, 0, b"pos") + gl.glLinkProgram(program) + linked = c_int() + gl.glGetProgramiv(program, 0x8B82, ctypes.byref(linked)) + if not linked.value: + return fail("program does not link") + gl.glUseProgram(program) + + triangle = (ctypes.c_float * 6)(-1, -1, 3, -1, -1, 3) + gl.glViewport(0, 0, size, size) + gl.glEnableVertexAttribArray(0) + gl.glVertexAttribPointer(0, 2, 0x1406, 0, 0, triangle) + gl.glDrawArrays(0x0004, 0, 3) + pixels = (ctypes.c_ubyte * (size * size * 4))() + gl.glReadPixels(0, 0, size, size, 0x1908, 0x1401, pixels) + if gl.glGetError(): + return fail("drawing failed") + + wrong = 0 + for y in range(size): + for x in range(size): + want = ((x + .5) / size, (y + .5) / size, + .5 + .5 * math.sin(x + .5)) + got = pixels[(y * size + x) * 4:(y * size + x) * 4 + 3] + if any(abs(g - w * 255) > 3 for g, w in zip(got, want)): + wrong += 1 + return {"renderer": gl.glGetString(0x1F01).decode(), + "version": gl.glGetString(0x1F02).decode(), + "wrong": wrong, "pixels": size * size} + + +class VkApplicationInfo(ctypes.Structure): + _fields_ = [("sType", ctypes.c_uint32), ("pNext", ctypes.c_void_p), + ("pApplicationName", ctypes.c_char_p), + ("applicationVersion", ctypes.c_uint32), + ("pEngineName", ctypes.c_char_p), + ("engineVersion", ctypes.c_uint32), + ("apiVersion", ctypes.c_uint32)] + + +class VkInstanceCreateInfo(ctypes.Structure): + _fields_ = [("sType", ctypes.c_uint32), ("pNext", ctypes.c_void_p), + ("flags", ctypes.c_uint32), + ("pApplicationInfo", ctypes.POINTER(VkApplicationInfo)), + ("enabledLayerCount", ctypes.c_uint32), + ("ppEnabledLayerNames", ctypes.c_void_p), + ("enabledExtensionCount", ctypes.c_uint32), + ("ppEnabledExtensionNames", ctypes.c_void_p)] + + +class VkDeviceQueueCreateInfo(ctypes.Structure): + _fields_ = [("sType", ctypes.c_uint32), ("pNext", ctypes.c_void_p), + ("flags", ctypes.c_uint32), + ("queueFamilyIndex", ctypes.c_uint32), + ("queueCount", ctypes.c_uint32), + ("pQueuePriorities", ctypes.POINTER(ctypes.c_float))] + + +class VkDeviceCreateInfo(ctypes.Structure): + _fields_ = [("sType", ctypes.c_uint32), ("pNext", ctypes.c_void_p), + ("flags", ctypes.c_uint32), + ("queueCreateInfoCount", ctypes.c_uint32), + ("pQueueCreateInfos", ctypes.POINTER(VkDeviceQueueCreateInfo)), + ("enabledLayerCount", ctypes.c_uint32), + ("ppEnabledLayerNames", ctypes.c_void_p), + ("enabledExtensionCount", ctypes.c_uint32), + ("ppEnabledExtensionNames", ctypes.c_void_p), + ("pEnabledFeatures", ctypes.c_void_p)] + + +class VkPhysicalDeviceProperties(ctypes.Structure): + # Only the head is read; the tail (limits, sparse properties: 532 bytes) + # just has to fit. + _fields_ = [("apiVersion", ctypes.c_uint32), + ("driverVersion", ctypes.c_uint32), + ("vendorID", ctypes.c_uint32), ("deviceID", ctypes.c_uint32), + ("deviceType", ctypes.c_uint32), + ("deviceName", ctypes.c_char * 256), + ("pipelineCacheUUID", ctypes.c_uint8 * 16), + ("tail", ctypes.c_uint64 * 128)] + + +class VkPhysicalDeviceProperties2(ctypes.Structure): + _fields_ = [("sType", ctypes.c_uint32), ("pNext", ctypes.c_void_p), + ("properties", VkPhysicalDeviceProperties)] + + +class VkPhysicalDeviceDriverProperties(ctypes.Structure): + _fields_ = [("sType", ctypes.c_uint32), ("pNext", ctypes.c_void_p), + ("driverID", ctypes.c_uint32), + ("driverName", ctypes.c_char * 256), + ("driverInfo", ctypes.c_char * 256), + ("conformanceVersion", ctypes.c_uint8 * 4)] + + +def child_vulkan(): + """Create a device on every GPU of the driver named in $MESA_SMOKE_ICD. + + Without the loader the driver is asked for its entry points directly, + through the interface the loader itself uses (vk_icdGetInstanceProcAddr). + """ + driver = ctypes.CDLL(os.environ["MESA_SMOKE_ICD"]) + negotiated = ctypes.c_uint32(5) + if hasattr(driver, "vk_icdNegotiateLoaderICDInterfaceVersion"): + driver.vk_icdNegotiateLoaderICDInterfaceVersion( + ctypes.byref(negotiated)) + gipa = driver.vk_icdGetInstanceProcAddr + gipa.restype = ctypes.c_void_p + gipa.argtypes = [ctypes.c_void_p, ctypes.c_char_p] + + def function(instance, name, *argtypes): + address = gipa(instance, name) + if not address: + raise RuntimeError("no %s" % name.decode()) + return ctypes.CFUNCTYPE(ctypes.c_int32, *argtypes)(address) + + application = VkApplicationInfo(sType=0, pApplicationName=b"mesa-smoke-test", + apiVersion=(1 << 22) | (3 << 12)) + info = VkInstanceCreateInfo(sType=1, + pApplicationInfo=ctypes.pointer(application)) + instance = ctypes.c_void_p() + result = function(None, b"vkCreateInstance", ctypes.c_void_p, + ctypes.c_void_p, ctypes.c_void_p)( + ctypes.byref(info), None, ctypes.byref(instance)) + if result: + return {"error": "vkCreateInstance returned %d" % result} + enumerate_devices = function(instance, b"vkEnumeratePhysicalDevices", + ctypes.c_void_p, ctypes.c_void_p, + ctypes.c_void_p) + properties2 = function(instance, b"vkGetPhysicalDeviceProperties2", + ctypes.c_void_p, ctypes.c_void_p) + create_device = function(instance, b"vkCreateDevice", ctypes.c_void_p, + ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p) + destroy_device = function(instance, b"vkDestroyDevice", ctypes.c_void_p, + ctypes.c_void_p) + count = ctypes.c_uint32(16) + handles = (ctypes.c_void_p * 16)() + enumerate_devices(instance, ctypes.byref(count), handles) + devices = [] + for handle in handles[:count.value]: + driver_properties = VkPhysicalDeviceDriverProperties(sType=1000196000) + properties = VkPhysicalDeviceProperties2( + sType=1000059001, + pNext=ctypes.cast(ctypes.pointer(driver_properties), + ctypes.c_void_p)) + properties2(handle, ctypes.byref(properties)) + priority = ctypes.c_float(1) + queue = VkDeviceQueueCreateInfo(sType=2, queueCount=1, + pQueuePriorities=ctypes.pointer(priority)) + create = VkDeviceCreateInfo(sType=3, queueCreateInfoCount=1, + pQueueCreateInfos=ctypes.pointer(queue)) + device = ctypes.c_void_p() + result = create_device(handle, ctypes.byref(create), None, + ctypes.byref(device)) + if not result: + destroy_device(device, None) + api = properties.properties.apiVersion + devices.append({ + "name": properties.properties.deviceName.decode(), + "driver": "%s %s" % (driver_properties.driverName.decode(), + driver_properties.driverInfo.decode()), + "api": "%d.%d.%d" % (api >> 22, (api >> 12) & 0x3ff, api & 0xfff), + "create": result}) + return {"interface": negotiated.value, "devices": devices} + + +KERNEL = b""" +kernel void k(global float *out) { + size_t i = get_global_id(0); + float x = (float)i; + out[i] = sqrt(x) + sin(x) + clamp(x, 1.0f, 2.0f) + (float)popcount((uint)i); +} +""" + + +def child_opencl(): + """Build and run KERNEL on every device of the rusticl platform.""" + # By path: the /pkg python wrapper can put CUDA's lib/ on the library path, + # and the Khronos ICD loader there finds no rusticl on machdoch. + cl = ctypes.CDLL(OPENCL_LOADER) + c_void_p, c_uint, c_size_t = ctypes.c_void_p, ctypes.c_uint, ctypes.c_size_t + for name, restype in (("clCreateContext", c_void_p), + ("clCreateCommandQueueWithProperties", c_void_p), + ("clCreateProgramWithSource", c_void_p), + ("clCreateBuffer", c_void_p), + ("clCreateKernel", c_void_p)): + getattr(cl, name).restype = restype + cl.clGetDeviceIDs.argtypes = [c_void_p, ctypes.c_uint64, c_uint, c_void_p, + c_void_p] + cl.clGetPlatformInfo.argtypes = [c_void_p, c_uint, c_size_t, c_void_p, + c_void_p] + cl.clGetDeviceInfo.argtypes = [c_void_p, c_uint, c_size_t, c_void_p, + c_void_p] + cl.clCreateContext.argtypes = [c_void_p, c_uint, c_void_p, c_void_p, + c_void_p, c_void_p] + cl.clCreateCommandQueueWithProperties.argtypes = [c_void_p, c_void_p, + c_void_p, c_void_p] + cl.clCreateProgramWithSource.argtypes = [c_void_p, c_uint, c_void_p, + c_void_p, c_void_p] + cl.clBuildProgram.argtypes = [c_void_p, c_uint, c_void_p, c_void_p, + c_void_p, c_void_p] + cl.clGetProgramBuildInfo.argtypes = [c_void_p, c_void_p, c_uint, c_size_t, + c_void_p, c_void_p] + cl.clCreateBuffer.argtypes = [c_void_p, ctypes.c_uint64, c_size_t, + c_void_p, c_void_p] + cl.clCreateKernel.argtypes = [c_void_p, ctypes.c_char_p, c_void_p] + cl.clSetKernelArg.argtypes = [c_void_p, c_uint, c_size_t, c_void_p] + cl.clEnqueueNDRangeKernel.argtypes = [c_void_p, c_void_p, c_uint, c_void_p, + c_void_p, c_void_p, c_uint, c_void_p, + c_void_p] + cl.clEnqueueReadBuffer.argtypes = [c_void_p, c_void_p, c_uint, c_size_t, + c_size_t, c_void_p, c_uint, c_void_p, + c_void_p] + + def text(query, handle, key): + buffer = ctypes.create_string_buffer(4096) + query(handle, key, 4096, buffer, None) + return buffer.value.decode(errors="replace").strip() + + platforms = (c_void_p * 8)() + count = c_uint() + if cl.clGetPlatformIDs(8, platforms, ctypes.byref(count)) or not count.value: + return {"error": "no OpenCL platform"} + results = [] + for platform in platforms[:count.value]: + if text(cl.clGetPlatformInfo, platform, 0x0902) != "rusticl": + continue + devices = (c_void_p * 8)() + found = c_uint() + if cl.clGetDeviceIDs(platform, 0xFFFFFFFF, 8, devices, + ctypes.byref(found)): + found.value = 0 + for device in devices[:found.value]: + entry = {"name": text(cl.clGetDeviceInfo, device, 0x102B), + "version": text(cl.clGetDeviceInfo, device, 0x102F)} + results.append(entry) + handle = c_void_p(device) + error = ctypes.c_int() + context = cl.clCreateContext(None, 1, ctypes.byref(handle), None, + None, ctypes.byref(error)) + queue = cl.clCreateCommandQueueWithProperties( + context, device, None, ctypes.byref(error)) + source = ctypes.c_char_p(KERNEL) + program = cl.clCreateProgramWithSource( + context, 1, ctypes.byref(source), None, ctypes.byref(error)) + if cl.clBuildProgram(program, 1, ctypes.byref(handle), None, None, + None): + log = ctypes.create_string_buffer(8192) + cl.clGetProgramBuildInfo(program, device, 0x1183, 8192, log, + None) + entry["error"] = "build failed: " + \ + log.value.decode(errors="replace").strip() + continue + n = 1024 + buffer = cl.clCreateBuffer(context, 1 << 1, n * 4, None, + ctypes.byref(error)) + kernel = cl.clCreateKernel(program, b"k", ctypes.byref(error)) + memory = c_void_p(buffer) + cl.clSetKernelArg(kernel, 0, ctypes.sizeof(memory), + ctypes.byref(memory)) + work = c_size_t(n) + out = (ctypes.c_float * n)() + if cl.clEnqueueNDRangeKernel(queue, kernel, 1, None, + ctypes.byref(work), None, 0, None, + None) or \ + cl.clEnqueueReadBuffer(queue, buffer, 1, 0, n * 4, out, 0, None, + None): + entry["error"] = "running the kernel failed" + continue + wrong = 0 + for i in range(n): + want = math.sqrt(i) + math.sin(i) + min(max(i, 1), 2) + \ + bin(i).count("1") + if abs(out[i] - want) > 1e-3 * max(1, abs(want)): + wrong += 1 + entry["wrong"], entry["values"] = wrong, n + return {"devices": results} + + +CHILDREN = {"egl-devices": child_egl_devices, "gl": child_gl, + "vulkan": child_vulkan, "opencl": child_opencl} + + +# ----------------------------------------------------------------- checks + +def check_libraries(): + """Every Mesa library has to resolve its shared libraries. + + Starts from the entry points and follows the libgallium they load, rather + than globbing for it: with --prefix the overlay still shows the installed, + older libgallium-.so, which nothing uses any more. + """ + queue = sorted(glob.glob("/usr/lib/libvulkan_*.so") + + glob.glob("/usr/lib/dri/*_dri.so")[:1] + + ["/usr/lib/libEGL_mesa.so.0", "/usr/lib/libGLX_mesa.so.0", + "/usr/lib/libRusticlOpenCL.so.1"]) + checked, llvm, broken = [], set(), False + while queue: + library = queue.pop(0) + if library in checked: + continue + checked.append(library) + if not os.path.exists(library): + report("warn", "%s is not installed" % library) + continue + output = subprocess.run(["ldd", library], capture_output=True, + text=True).stdout + for line in output.splitlines(): + fields = line.split() + if "not found" in line: + report("fail", "%s: %s not found" % (library, fields[0])) + broken = True + elif fields and fields[0].startswith("libgallium-") and \ + len(fields) > 2: + queue.append(fields[2]) + elif fields and fields[0].startswith(("libLLVM.so", "libclang-cpp.so", + "libLLVMSPIRVLib.so")): + llvm.add(fields[0]) + if not broken: + report("ok", "%d Mesa libraries resolve their shared libraries, using %s" + % (len(checked), ", ".join(sorted(llvm)) or "no LLVM")) + + +def check_libclc(): + """Mesa's libclc fork, which mesalib finds through mesa-libclc.pc.""" + pc = "/usr/share/pkgconfig/mesa-libclc.pc" + if not os.path.exists(pc): + report("warn", "mesa-libclc is not installed (no %s)" % pc) + return + with open(pc) as pkgconfig: + variables = dict(line.strip().split("=", 1) for line in pkgconfig + if "=" in line and ":" not in line.split("=")[0]) + directory = variables.get("libexecdir", "") + missing = [name for name in ("spirv-mesa3d-.spv", "spirv64-mesa3d-.spv") + if not os.path.exists(os.path.join(directory, name))] + if missing: + report("fail", "mesa-libclc: %s missing in %s" + % (", ".join(missing), directory)) + else: + report("ok", "mesa-libclc provides its SPIR-V libraries in %s" + % directory) + + +def check_gl(): + """Draw on every EGL device; DRI_PRIME would pick only one GPU.""" + environment = {"__EGL_VENDOR_LIBRARY_FILENAMES": EGL_VENDOR} + listing, stderr = child("egl-devices", environment) + if not listing or "error" in listing: + report("fail", "OpenGL ES: cannot list EGL devices: %s" + % (listing["error"] if listing else stderr)) + return + devices = listing["devices"] + if not any(device["software"] for device in devices): + report("fail", "OpenGL ES: EGL lists no software device (llvmpipe)") + if not any(not device["software"] for device in devices): + report("warn", "OpenGL ES (hardware): EGL lists no GPU" + " (no render node?)") + for device in devices: + label = ("software" if device["software"] else "hardware, %s" + % (device["node"] or "EGL device %d" % device["index"])) + result, stderr = child("gl", dict(environment, MESA_SMOKE_EGL_DEVICE= + str(device["index"]))) + if not result or "error" in result: + # A GPU Mesa has no driver for, like NVIDIA's with its own driver. + setup = result and result.get("setup") + state = "warn" if setup and not device["software"] else "fail" + report(state, "OpenGL ES (%s): %s" + % (label, result["error"] if result else stderr)) + continue + name = "%s, %s" % (result["renderer"], result["version"]) + if not device["software"] and "llvmpipe" in result["renderer"]: + report("warn", "OpenGL ES (%s): no hardware driver, got %s" + % (label, name)) + elif result["wrong"]: + report("fail", "OpenGL ES (%s) %s: %d of %d pixels wrong" + % (label, name, result["wrong"], result["pixels"])) + else: + report("ok", "OpenGL ES (%s) draws a shaded triangle: %s" + % (label, name)) + + +def start_xvfb(): + """Return (display, process) for a private X server, or (None, None).""" + if not shutil.which("Xvfb") or not shutil.which("xdpyinfo"): + return None, None + for number in range(77, 97): + if os.path.exists("/tmp/.X%d-lock" % number): + continue + display = ":%d" % number + process = subprocess.Popen( + ["Xvfb", display, "-nolisten", "tcp", "-screen", "0", "640x480x24"], + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + for _ in range(50): + time.sleep(.2) + if process.poll() is not None: + break + if subprocess.run(["xdpyinfo", "-display", display], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL).returncode == 0: + return display, process + process.kill() + return None, None + + +def check_glx(): + if not shutil.which("glxinfo"): + report("warn", "GLX: no glxinfo") + return + display, xvfb = start_xvfb() + if not display: + report("warn", "GLX: cannot start Xvfb") + return + try: + environment = dict(os.environ, DISPLAY=display, + __GLX_VENDOR_LIBRARY_NAME="mesa") + output = subprocess.run(["glxinfo", "-B"], env=environment, + capture_output=True, text=True, + timeout=60).stdout + finally: + xvfb.terminate() + xvfb.wait() + fields = dict(line.strip().split(": ", 1) for line in output.splitlines() + if ": " in line) + renderer = fields.get("OpenGL renderer string") + if fields.get("direct rendering") == "Yes" and renderer: + report("ok", "GLX on Xvfb: %s, %s" + % (renderer, fields.get("OpenGL core profile version string"))) + else: + report("fail", "GLX on Xvfb: no direct rendering context\n%s" % output) + + +def vulkan_drivers(): + """Mesa's Vulkan drivers, from the ICD manifests.""" + drivers = {} + for directory in VULKAN_ICD_DIRS: + for manifest in sorted(glob.glob(directory + "/*.json")): + try: + with open(manifest) as file: + library = json.load(file)["ICD"]["library_path"] + except (OSError, ValueError, KeyError) as error: + report("fail", "%s: %s" % (manifest, error)) + continue + if os.path.basename(library).startswith("libvulkan_"): + drivers[os.path.basename(library)] = library + return drivers + + +def check_vulkan(): + drivers = vulkan_drivers() + if not drivers: + report("warn", "Vulkan: no Mesa driver installed") + return + devices = 0 + for name, library in sorted(drivers.items()): + result, stderr = child("vulkan", {"MESA_SMOKE_ICD": library}) + if not result or "error" in result: + report("fail", "Vulkan %s: %s" + % (name, result["error"] if result else stderr)) + continue + if not result["devices"]: + report("ok", "Vulkan %s: loads, drives no GPU of this host" % name) + for device in result["devices"]: + devices += 1 + line = "Vulkan %s: %s, %s, Vulkan %s" % ( + name, device["name"], device["driver"], device["api"]) + if device["create"]: + report("fail", "%s: vkCreateDevice returned %d" + % (line, device["create"])) + else: + report("ok", line + ", device created") + if not devices: + report("warn", "Vulkan: no GPU of this host is driven by Mesa") + + +def check_opencl(drivers): + if not os.path.exists(RUSTICL_ICD): + report("fail", "OpenCL: %s is not installed" % RUSTICL_ICD) + return + result, stderr = child("opencl", {"OCL_ICD_VENDORS": RUSTICL_ICD, + "RUSTICL_ENABLE": drivers}) + if not result or "error" in result: + report("fail", "OpenCL: %s" % (result["error"] if result else stderr)) + return + if LIBCLC_MISSING in stderr: + report("fail", "OpenCL: rusticl finds no libclc") + elif LIBCLC_UPSTREAM in stderr: + report("warn", "OpenCL: rusticl uses upstream libclc, not Mesa's fork" + " (install mesa-libclc)") + if not result["devices"]: + report("warn", "OpenCL: rusticl offers no device for %s" % drivers) + for device in result["devices"]: + line = "OpenCL %s (%s)" % (device["name"], device["version"]) + if "error" in device: + report("fail", "%s: %s" % (line, device["error"])) + elif device["wrong"]: + report("fail", "%s: %d of %d results wrong" + % (line, device["wrong"], device["values"])) + else: + report("ok", "%s runs a kernel calling libclc builtins" % line) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--prefix", action="append", default=[], + metavar="DIR|PACKAGE", + help="test the image directory DIR of a build, or the" + " .bee.tar.bz2 PACKAGE, instead of what is" + " installed; may be repeated") + parser.add_argument("--rusticl-enable", default="llvmpipe,iris,radeonsi", + metavar="DRIVERS", + help="RUSTICL_ENABLE, the Gallium drivers to offer as" + " OpenCL devices (default: %(default)s)") + parser.add_argument("--child", choices=sorted(CHILDREN), + help=argparse.SUPPRESS) + arguments = parser.parse_args() + + if arguments.child: + print(json.dumps(CHILDREN[arguments.child]())) + return 0 + + if arguments.prefix and PREFIX_VARIABLE not in os.environ: + workdir = tempfile.mkdtemp(prefix="mesa-smoke-") + try: + return run_in_overlay(unpack(arguments.prefix, workdir)) + finally: + shutil.rmtree(workdir, ignore_errors=True) + if PREFIX_VARIABLE in os.environ: + print(" testing, over /usr and /etc: %s" + % os.environ[PREFIX_VARIABLE].replace(":", " ")) + + check_libraries() + check_libclc() + check_gl() + check_glx() + check_vulkan() + check_opencl(arguments.rusticl_enable) + + print("%s (%d failed, %d warned)" + % ("FAIL" if status["fail"] else "PASS", status["fail"], + status["warn"])) + return 1 if status["fail"] else 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/spirv-llvm-translator.be0 b/spirv-llvm-translator.be0 index 47e2e0b50..75947f6d8 100755 --- a/spirv-llvm-translator.be0 +++ b/spirv-llvm-translator.be0 @@ -1,9 +1,9 @@ #!/usr/bin/env beesh -# BEE_VERSION spirv-llvm-translator-22.1.6-0 +# BEE_VERSION spirv-llvm-translator-23.1.2-0 #SRCURL[0]="https://github.com/KhronosGroup/SPIRV-LLVM-Translator/archive/v${PKGVERSION}/SPIRV-LLVM-Translator-${PKGVERSION}.tar.gz" -SRCURL[0]="https://beehive.molgen.mpg.de/4e302a4bab696f81369211c2b6204ba1/SPIRV-LLVM-Translator-22.1.6.tar.gz" +SRCURL[0]="https://beehive.molgen.mpg.de/2f163a32cb716012c3f19bb9a754a0e4/SPIRV-LLVM-Translator-23.1.2.tar.gz" # PATCHURL+=()