From bf13c3e58872dcc5b31447eef9f7a5d936f7e2ce Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Fri, 25 Sep 2026 12:26:47 +0200 Subject: [PATCH] Throw a KernelError for non-singleton type arguments Before `Core.TypeEgal`, `Type{T}` is only a singleton when `T` has a unique representation, so an argument like `Type{Union{Missing, Bool}}` or `Type{Vector}` fell through to `fieldcount`, which threw an unhelpful `ArgumentError`. Report it as a `KernelError` suggesting `Val(T)` instead. --- src/validation.jl | 10 ++++++++++ test/native.jl | 24 ++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/validation.jl b/src/validation.jl index 0e19d440..474d47a1 100644 --- a/src/validation.jl +++ b/src/validation.jl @@ -94,6 +94,16 @@ function check_invocation(@nospecialize(job::CompilerJob)) This is a CPU-only object not supported by GPUCompiler.""")) end + # Before `Core.TypeEgal`, `Type{T}` is only a singleton when `T` has a unique + # representation, so e.g. `Type{Union{Missing, Int}}` or `Type{Vector}` would be + # passed as a boxed host pointer. + if Base.isType(dt) + throw(KernelError(job, "passing a non-singleton type argument", + """Argument $arg_i to your kernel function is the type $(dt.parameters[1]), which + cannot be passed to a GPU kernel on this version of Julia. + Pass `Val($(dt.parameters[1]))` instead, or a value of that type.""")) + end + # If an object doesn't have fields, it can only be used by identity, so we can allow # them to be passed to the GPU (this also applies to e.g. Symbols). if fieldcount(dt) == 0 diff --git a/test/native.jl b/test/native.jl index b5323661..52eea89e 100644 --- a/test/native.jl +++ b/test/native.jl @@ -1728,6 +1728,30 @@ end end end +@testset "non-singleton type arguments" begin + mod = @eval module $(gensym()) + import ..sink + foo(::Type{T}) where {T} = (sink(Int(Missing <: T)); return) + bar(::Val{T}) where {T} = (sink(Int(Missing <: T)); return) + end + + for T in (Union{Missing, Int}, Vector) + if isdefined(Core, :TypeEgal) + # `Core.TypeEgal` makes every closed type argument a singleton + Native.code_execution(mod.foo, Tuple{Type{T}}) + else + @test_throws_message(KernelError, + Native.code_execution(mod.foo, Tuple{Type{T}})) do msg + occursin("passing a non-singleton type argument", msg) && + occursin(string(T), msg) + end + end + + # the suggested alternative + Native.code_execution(mod.bar, Tuple{Val{T}}) + end +end + @testset "invalid LLVM IR" begin mod = @eval module $(gensym()) foobar(i) = println(i)