From 2188c382ddfa7b72643117f204fb19931c04cb28 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Fri, 25 Sep 2026 15:37:39 +0200 Subject: [PATCH 1/6] CI: support selecting back-ends with [only ...] and [skip ...] Follow GPUCompiler's commit message tags, and skip the back-end tests on draft pull requests unless selected explicitly, to save CI resources. Co-Authored-By: Claude Opus 5.5 --- .buildkite/pipeline.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 9f2a95828..2211c1bee 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -1,3 +1,7 @@ +# Tests of the KernelAbstractions-related parts of the GPU back-ends. Draft pull requests skip +# them, unless selected by name in the last commit message: `[only cuda]`, `[only metal, oneapi]` +# or `[only tests]` for all of them. `[skip amdgpu]` or `[skip tests]` does the opposite. +# (`opencl` selects the OpenCL.jl tests on CUDA hardware.) steps: - label: "CUDA Julia {{matrix.version}}" matrix: @@ -29,6 +33,10 @@ steps: Pkg.test("CUDA"; coverage=true, test_args=["core/kernelabstractions", "core/kernelinterface"])' agents: queue: "cuda" + if: | + build.message !~ /\[skip [^\]]*(tests|cuda)/ && + (build.message =~ /\[only [^\]]*(tests|cuda)/ || + build.message !~ /\[only / && !build.pull_request.draft) timeout_in_minutes: 120 soft_fail: - exit_status: 3 @@ -92,6 +100,10 @@ steps: Pkg.test("Metal"; coverage=true, test_args=["kernelabstractions", "kernelinterface"])' agents: queue: "metal" + if: | + build.message !~ /\[skip [^\]]*(tests|metal)/ && + (build.message =~ /\[only [^\]]*(tests|metal)/ || + build.message !~ /\[only / && !build.pull_request.draft) timeout_in_minutes: 120 soft_fail: - exit_status: 3 @@ -126,6 +138,10 @@ steps: Pkg.test("oneAPI"; coverage=true, test_args=["kernelabstractions", "kernelinterface"])' agents: queue: "oneapi" + if: | + build.message !~ /\[skip [^\]]*(tests|oneapi)/ && + (build.message =~ /\[only [^\]]*(tests|oneapi)/ || + build.message !~ /\[only / && !build.pull_request.draft) timeout_in_minutes: 120 soft_fail: - exit_status: 3 @@ -161,6 +177,10 @@ steps: Pkg.test("AMDGPU"; coverage=true, test_args=["kernelabstractions", "kernelinterface"])' agents: queue: "rocm" + if: | + build.message !~ /\[skip [^\]]*(tests|amdgpu)/ && + (build.message =~ /\[only [^\]]*(tests|amdgpu)/ || + build.message !~ /\[only / && !build.pull_request.draft) timeout_in_minutes: 120 soft_fail: - exit_status: 3 @@ -196,6 +216,10 @@ steps: Pkg.test("OpenCL"; coverage=true, test_args=`--platform=nvidia nvidia/kernelabstractions nvidia/kernelinterface`)' agents: queue: "cuda" + if: | + build.message !~ /\[skip [^\]]*(tests|opencl)/ && + (build.message =~ /\[only [^\]]*(tests|opencl)/ || + build.message !~ /\[only / && !build.pull_request.draft) timeout_in_minutes: 120 soft_fail: - exit_status: 3 From 8a208bfc8fc9f64062d6ea055981e29cfa0b0d7c Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Fri, 25 Sep 2026 15:17:30 +0200 Subject: [PATCH 2/6] Avoid constructing the RNG fallback signature at run time The Float16/Float32 randn/randexp overlays @invoke Random's AbstractFloat fallback with a Type{<:AbstractFloat} annotation, which builds a UnionAll when the kernel runs. Julia 1.14 no longer folds such freshly constructed types to constants (JuliaLang/julia#62001), so the kernel ended up calling jl_type_unionall and failed to compile. Hoist the signature into a constant. --- src/pocl/device/random.jl | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/pocl/device/random.jl b/src/pocl/device/random.jl index d931232dc..fe7503bf3 100644 --- a/src/pocl/device/random.jl +++ b/src/pocl/device/random.jl @@ -232,11 +232,16 @@ end end end +# Signature of Random's generic `AbstractFloat` fallbacks. Kept as a constant because +# spelling it inline (e.g. with `@invoke`) constructs the `UnionAll` at run time, which +# inference no longer folds away as of Julia 1.14 (JuliaLang/julia#62001). +const AbstractFloatFallback = Tuple{AbstractRNG, Type{<:AbstractFloat}} + # Use the table-free fallback, but compute it in Float32 because its polar transform can # overflow in Float16. Keep this scoped to our RNG: overlay methods take precedence over # regular dispatch and an AbstractRNG method would shadow methods for other device RNGs. @device_override @inline function Random.randn(rng::Philox2x32, ::Type{T}) where {T <: Union{Float16, Float32}} - return T(@invoke Random.randn(rng::AbstractRNG, Float32::Type{<:AbstractFloat})) + return T(invoke(Random.randn, AbstractFloatFallback, rng, Float32)) end ## randexp @@ -262,7 +267,7 @@ end # Compute through Float32 to avoid requiring Float16 `log1p` support. @device_override @inline function Random.randexp(rng::Philox2x32, ::Type{T}) where {T <: Union{Float16, Float32}} - return T(@invoke Random.randexp(rng::AbstractRNG, Float32::Type{<:AbstractFloat})) + return T(invoke(Random.randexp, AbstractFloatFallback, rng, Float32)) end @device_override Random.Sampler( From d6027f9d9df982e846ceb883a0cdc85243e2b536 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Fri, 25 Sep 2026 15:22:00 +0200 Subject: [PATCH 3/6] Override _throw_boundserror_indices on POCL Julia 1.14 reports indexed bounds errors through Base._throw_boundserror_indices instead of throw_boundserror, so the existing override no longer applied and out-of-bounds accesses lost their error message. Mirror the override Metal.jl already has. --- src/pocl/device/quirks.jl | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/pocl/device/quirks.jl b/src/pocl/device/quirks.jl index 035bcffb0..70c4e3397 100644 --- a/src/pocl/device/quirks.jl +++ b/src/pocl/device/quirks.jl @@ -32,6 +32,16 @@ end @device_override @noinline Base.throw_boundserror(A, I) = @gputhrow "BoundsError" "Out-of-bounds array access" +# essentials.jl +# Julia 1.14 routes indexed bounds errors through `_throw_boundserror_indices` +# rather than `throw_boundserror`, bypassing the override above. +@static if isdefined(Base, :_throw_boundserror_indices) + @device_override @noinline Base._throw_boundserror_indices(A) = + @gputhrow "BoundsError" "Out-of-bounds array access" + @device_override @noinline Base._throw_boundserror_indices(A, i1, I...) = + @gputhrow "BoundsError" "Out-of-bounds array access" +end + # trig.jl @device_override @noinline Base.Math.sincos_domain_error(x) = @gputhrow "DomainError" "sincos(x) is only defined for finite x" From 00f9dce0eb21e41c83b60abc2c872828f0068474 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Fri, 25 Sep 2026 16:05:29 +0200 Subject: [PATCH 4/6] Fix broadcasting types on Julia 1.14 Julia 1.14 specializes type-valued arguments on `Core.TypeEgal{T}` (JuliaLang/julia#62001), so a broadcast of a type constructor has `TypeEgal{T}` as its function type parameter. That isn't matched by the `Type{T}` in our adaptor rule, which replaces the non-isbits type with a closure, making such broadcasts fail to compile. Match any subtype of `Type{T}` instead. Co-Authored-By: Claude Opus 5.5 --- src/pocl/compiler/execution.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pocl/compiler/execution.jl b/src/pocl/compiler/execution.jl index 4bee8ba72..c4d0d32cd 100644 --- a/src/pocl/compiler/execution.jl +++ b/src/pocl/compiler/execution.jl @@ -118,9 +118,10 @@ Adapt.adapt_structure(to::KernelAdaptor, r::Base.RefValue{<:Union{DataType, Type CLRefType{r[]}() # case where type is the function being broadcasted +# (on Julia 1.14, the function type parameter is `Core.TypeEgal{T} <: Type{T}`) Adapt.adapt_structure( to::KernelAdaptor, - bc::Broadcast.Broadcasted{Style, <:Any, Type{T}} + bc::Broadcast.Broadcasted{Style, <:Any, <:Type{T}} ) where {Style, T} = Broadcast.Broadcasted{Style}((x...) -> T(x...), adapt(to, bc.args), bc.axes) From 1289de0f5b81dcb1d1ebbddbc9dfb05b95913f8f Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Fri, 25 Sep 2026 16:05:38 +0200 Subject: [PATCH 5/6] Keep the POCL Sampler override a plain overlay It returns a different sampler than the host method, so it must not be defined as a consistent overlay, as SPIRVIntrinsics' `@device_override` will do (JuliaGPU/OpenCL.jl): concrete evaluation would then substitute the host result, which our overlaid `rand` methods cannot handle. Co-Authored-By: Claude Opus 5.5 --- src/pocl/device/random.jl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pocl/device/random.jl b/src/pocl/device/random.jl index fe7503bf3..4360b25f9 100644 --- a/src/pocl/device/random.jl +++ b/src/pocl/device/random.jl @@ -270,7 +270,10 @@ end return T(invoke(Random.randexp, AbstractFloatFallback, rng, Float32)) end -@device_override Random.Sampler( +# NOTE: not a consistent overlay (as SPIRVIntrinsics' `@device_override` may define), as +# this returns a different sampler than the host method: concrete evaluation would +# otherwise substitute the latter, which our overlaid `rand` methods fail to handle. +Base.Experimental.@overlay method_table Random.Sampler( ::Type{<:AbstractRNG}, r::AbstractUnitRange{T}, ::Random.Repetition ) where {T <: Union{Int64, UInt64}} = Random.SamplerRangeFast(r) From d812fbc5acaa5473701b3f1267ed8ea41f8ae0e2 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Fri, 25 Sep 2026 16:39:08 +0200 Subject: [PATCH 6/6] Convert functions that capture a type for use in kernels LinearAlgebra's `det` of triangular matrices now reduces with `Base.Fix1(convert, T)`, which isn't isbits (JuliaLang/LinearAlgebra.jl#1658). Convert `Fix1` and `Fix2` capturing a type into closures that carry the type as a parameter instead, like we already do for broadcasting types. Co-Authored-By: Claude Opus 5.5 --- src/pocl/compiler/execution.jl | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/pocl/compiler/execution.jl b/src/pocl/compiler/execution.jl index c4d0d32cd..7e0e20de2 100644 --- a/src/pocl/compiler/execution.jl +++ b/src/pocl/compiler/execution.jl @@ -125,6 +125,17 @@ Adapt.adapt_structure( ) where {Style, T} = Broadcast.Broadcasted{Style}((x...) -> T(x...), adapt(to, bc.args), bc.axes) +# functions that capture a type, e.g., `Base.Fix1(convert, T)` as used by LinearAlgebra, +# which isn't a valid kernel argument either +function Adapt.adapt_structure(to::KernelAdaptor, f::Base.Fix1{<:Any, <:Type{T}}) where {T} + g = adapt(to, f.f) + return (x...) -> g(T, x...) +end +function Adapt.adapt_structure(to::KernelAdaptor, f::Base.Fix2{<:Any, <:Type{T}}) where {T} + g = adapt(to, f.f) + return (x...) -> g(x..., T) +end + """ clconvert(x, [pointers])