From 258a985e3383efcf94b1630ab17a8215774ff3cb Mon Sep 17 00:00:00 2001 From: Simeon David Schaub Date: Tue, 14 Jul 2026 23:16:01 +0200 Subject: [PATCH] test khronos translator as backend Requires https://github.com/JuliaPackaging/Yggdrasil/pull/14212 and https://github.com/JuliaGPU/GPUCompiler.jl/pull/879 to pass. Deletes one float atomics test I added before we had proper support for float atomics and which is not really relevant anymore. (ref https://github.com/KhronosGroup/SPIRV-LLVM-Translator/issues/3404) --- .github/workflows/Test.yml | 34 ++++++++++++++++++---------------- src/compiler/compilation.jl | 33 ++++++++++++++++++++++++++++++++- test/atomics.jl | 31 ------------------------------- test/runtests.jl | 11 ++++++++--- 4 files changed, 58 insertions(+), 51 deletions(-) diff --git a/.github/workflows/Test.yml b/.github/workflows/Test.yml index d98555c6..e2f58b3b 100644 --- a/.github/workflows/Test.yml +++ b/.github/workflows/Test.yml @@ -13,7 +13,7 @@ concurrency: jobs: test: - name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ matrix.memory_backend }} - PoCL ${{ matrix.pocl }} + name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ matrix.memory_backend }} - PoCL ${{ matrix.pocl }} - ${{ matrix.llvm_to_spirv_backend }} runs-on: ${{ matrix.os }} timeout-minutes: 100 permissions: # needed to allow julia-actions/cache to proactively delete old caches that it has created @@ -31,23 +31,25 @@ jobs: matrix: include: # full memory-backend coverage on the primary platform - - { os: ubuntu-24.04, arch: x64, version: '1.12', pocl: jll, memory_backend: usm } - - { os: ubuntu-24.04, arch: x64, version: '1.12', pocl: jll, memory_backend: svm } - - { os: ubuntu-24.04, arch: x64, version: '1.12', pocl: jll, memory_backend: buffer } + - { os: ubuntu-24.04, arch: x64, version: '1.12', pocl: jll, memory_backend: usm, llvm_to_spirv_backend: llvm } + - { os: ubuntu-24.04, arch: x64, version: '1.12', pocl: jll, memory_backend: svm, llvm_to_spirv_backend: llvm } + - { os: ubuntu-24.04, arch: x64, version: '1.12', pocl: jll, memory_backend: buffer, llvm_to_spirv_backend: llvm } # oldest supported Julia - - { os: ubuntu-24.04, arch: x64, version: '1.10', pocl: jll, memory_backend: usm } + - { os: ubuntu-24.04, arch: x64, version: '1.10', pocl: jll, memory_backend: usm, llvm_to_spirv_backend: llvm } # Julia version coverage on the primary platform - - { os: ubuntu-24.04, arch: x64, version: '1.11', pocl: jll, memory_backend: usm } - - { os: ubuntu-24.04, arch: x64, version: '1.13-nightly', pocl: jll, memory_backend: usm } + - { os: ubuntu-24.04, arch: x64, version: '1.11', pocl: jll, memory_backend: usm, llvm_to_spirv_backend: llvm } + - { os: ubuntu-24.04, arch: x64, version: '1.13-nightly', pocl: jll, memory_backend: usm, llvm_to_spirv_backend: llvm } # other platforms, one job each (backends rotated for extra cross-coverage) - - { os: ubuntu-24.04-arm, arch: arm64, version: '1.12', pocl: jll, memory_backend: svm } - - { os: windows-2022, arch: x64, version: '1.12', pocl: jll, memory_backend: usm } + - { os: ubuntu-24.04-arm, arch: arm64, version: '1.12', pocl: jll, memory_backend: svm, llvm_to_spirv_backend: llvm } + - { os: windows-2022, arch: x64, version: '1.12', pocl: jll, memory_backend: usm, llvm_to_spirv_backend: llvm } # upcoming PoCL release (pocl_next_jll) on every platform - - { os: ubuntu-24.04, arch: x64, version: '1.12', pocl: next, memory_backend: usm } - - { os: ubuntu-24.04-arm, arch: arm64, version: '1.12', pocl: next, memory_backend: svm } - - { os: windows-2022, arch: x64, version: '1.12', pocl: next, memory_backend: buffer } + - { os: ubuntu-24.04, arch: x64, version: '1.12', pocl: next, memory_backend: usm, llvm_to_spirv_backend: llvm } + - { os: ubuntu-24.04-arm, arch: arm64, version: '1.12', pocl: next, memory_backend: svm, llvm_to_spirv_backend: llvm } + - { os: windows-2022, arch: x64, version: '1.12', pocl: next, memory_backend: buffer, llvm_to_spirv_backend: llvm } # upstream PoCL HEAD, built from source - - { os: ubuntu-24.04, arch: x64, version: '1.12', pocl: local, memory_backend: usm } + - { os: ubuntu-24.04, arch: x64, version: '1.12', pocl: local, memory_backend: usm, llvm_to_spirv_backend: llvm } + # khronos backend for LLVM -> SPIRV translation + - { os: ubuntu-24.04, arch: x64, version: '1.12', pocl: jll, memory_backend: usm, llvm_to_spirv_backend: khronos } steps: - name: Checkout OpenCL.jl uses: actions/checkout@v7 @@ -144,7 +146,7 @@ jobs: uses: julia-actions/julia-runtest@v1 if: runner.os != 'Windows' with: - test_args: "--quickfail --verbose --platform=${{ case(matrix.pocl == 'next', 'pocl_next', 'pocl') }}" + test_args: "--quickfail --verbose --platform=${{ case(matrix.pocl == 'next', 'pocl_next', 'pocl') }} --llvm_to_spirv_backend=${{ matrix.llvm_to_spirv_backend }}" - name: Setup BusyBox if: runner.os == 'Windows' @@ -156,13 +158,13 @@ jobs: run: | using Pkg Pkg.activate(".") - Pkg.test(; test_args=`--quickfail --verbose --platform=${{ matrix.pocl == 'next' && 'pocl_next' || 'pocl' }}`) + Pkg.test(; test_args=`--quickfail --verbose --platform=${{ matrix.pocl == 'next' && 'pocl_next' || 'pocl' }} --llvm_to_spirv_backend=${{ matrix.llvm_to_spirv_backend }}`) - name: Upload compilation dumps if: always() uses: actions/upload-artifact@v4 with: - name: compilation-dumps-${{ matrix.version }}-${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.memory_backend }}-pocl-${{ matrix.pocl }} + name: compilation-dumps-${{ matrix.version }}-${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.memory_backend }}-pocl-${{ matrix.pocl }}-${{ matrix.llvm_to_spirv_backend }} path: ${{ runner.temp }}/opencl-compilation-dumps/ if-no-files-found: ignore diff --git a/src/compiler/compilation.jl b/src/compiler/compilation.jl index 348cdf8c..20c2b901 100644 --- a/src/compiler/compilation.jl +++ b/src/compiler/compilation.jl @@ -185,7 +185,7 @@ const SPIRV_VERSION = v"1.4" # create GPUCompiler objects target = SPIRVCompilerTarget(; version=SPIRV_VERSION, supports_fp16, supports_fp64, - validate=true, extensions=spirv_ext, kwargs...) + extensions=spirv_ext, backend=llvm_to_spirv_backend(), kwargs...) params = OpenCLCompilerParams(; sub_group_size, features=device_features(dev), program_backend=backend) CompilerConfig(target, params; kernel, name, always_inline) @@ -217,6 +217,37 @@ function run_and_collect(cmd) return proc, log end +""" + llvm_to_spirv_backend() -> Symbol + +The requested backend for compiling LLVM IR to SPIRV for the current task (`:llvm` (default), or `:khronos`). +""" +llvm_to_spirv_backend() = get(task_local_storage(), :CLLLVMToSPIRVBackend, :llvm)::Symbol + +""" + llvm_to_spirv_backend!(mode::Symbol) + llvm_to_spirv_backend!(f::Function, mode::Symbol) + +Select how LLVM IR is compiled to SPIRV for the current task: (`:llvm` (default), or `:khronos`). +The second form applies `mode` only for the duration of `f`. +""" +function llvm_to_spirv_backend!(mode::Symbol) + mode in (:llvm, :khronos) || + throw(ArgumentError("invalid LLVM to SPIRV backend $mode (expected :llvm, :khronos)")) + task_local_storage(:CLLLVMToSPIRVBackend, mode) + return mode +end + +function llvm_to_spirv_backend!(f::Base.Callable, mode::Symbol) + old = llvm_to_spirv_backend() + llvm_to_spirv_backend!(mode) + try + f() + finally + llvm_to_spirv_backend!(old) + end +end + # How kernels are fed to the driver: # - `:auto` — SPIR-V (clCreateProgramWithIL) when the device supports IL programs, otherwise # OpenCL C source translated from the SPIR-V via spirv2clc. diff --git a/test/atomics.jl b/test/atomics.jl index 954ea09c..0c5ab11e 100644 --- a/test/atomics.jl +++ b/test/atomics.jl @@ -94,35 +94,4 @@ atomic_operations = [ result_val = OpenCL.@allowscalar a[] @test result_val === T(expected_val) end - - -@testset "atomic_add! ($T)" for T in [Float32, Float64] - # Float64 requires cl_khr_fp64 extension - if T == Float64 && !("cl_khr_fp64" in cl.device().extensions) - continue - end -if "cl_ext_float_atomics" in cl.device().extensions - @eval function atomic_float_add(counter, val::$T) - @builtin_ccall( - "atomic_add", $T, - (LLVMPtr{$T, AS.CrossWorkgroup}, $T), - pointer(counter), val, - ) - return - end - - @testset "SPV_EXT_shader_atomic_float_add extension" begin - a = OpenCL.zeros(T) - @opencl global_size = 1000 extensions = ["SPV_EXT_shader_atomic_float_add"] atomic_float_add(a, one(T)) - @test OpenCL.@allowscalar a[] == T(1000.0) - - spv = sprint() do io - OpenCL.code_native(io, atomic_float_add, Tuple{CLDeviceArray{T, 0, 1}, T}; extensions = ["SPV_EXT_shader_atomic_float_add"]) - end - @test occursin("OpExtension \"SPV_EXT_shader_atomic_float_add\"", spv) - @test occursin("OpAtomicFAddEXT", spv) - end -end - -end end diff --git a/test/runtests.jl b/test/runtests.jl index 04d52120..b0181630 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -4,7 +4,7 @@ import OpenCL import Test ## custom arguments -args = parse_args(ARGS; custom=["platform"]) +args = parse_args(ARGS; custom=["platform", "llvm_to_spirv_backend"]) # `--platform` selects which OpenCL platform to run on, substring-matched against the # platform name/vendor (e.g. `pocl`, `cuda`, `intel`). The special value `pocl_next` @@ -27,6 +27,9 @@ platform_selected(p) = platform_filter === nothing || ispocl(p) = occursin("portable", lowercase(p.name)) +const llvm_to_spirv_backend = args.custom["llvm_to_spirv_backend"] === nothing ? :llvm : + Symbol(args.custom["llvm_to_spirv_backend"].value) + # short, stable label for a platform, used to prefix its tests function target_label(p) str = lowercase(p.name * " " * p.vendor) @@ -83,7 +86,7 @@ for name in keys(GPUArraysTestSuite.tests) testsuite[test] = :(GPUArraysTestSuite.tests[$name](CLArray)) end # wrap a test body on a specific device of the named platform, with a fixed backend -function generate_test(expr, platform_name, device_index, backend) +function generate_test(expr, platform_name, device_index, backend, llvm_to_spirv_backend) return quote platform = first(p for p in cl.platforms() if p.name == $platform_name) device = cl.devices(platform)[$device_index] @@ -91,6 +94,7 @@ function generate_test(expr, platform_name, device_index, backend) cl.platform!(platform) cl.device!(device) OpenCL.program_backend!($(QuoteNode(backend))) + OpenCL.llvm_to_spirv_backend!($(QuoteNode(llvm_to_spirv_backend))) $(expr) end end @@ -102,7 +106,7 @@ for name in collect(keys(testsuite)) body = testsuite[name] delete!(testsuite, name) for t in targets - testsuite["$(t.label)/$name"] = generate_test(body, t.platform, t.index, t.backend) + testsuite["$(t.label)/$name"] = generate_test(body, t.platform, t.index, t.backend, llvm_to_spirv_backend) end end @@ -188,6 +192,7 @@ end const init_code = quote using OpenCL, $pocl_pkg + $(llvm_to_spirv_backend === :khronos && :(using SPIRV_LLVM_Translator_jll)) # bring used symbols into the temporary module import ..GPUArraysTestSuite, ..testf