From ed5ebda38492abd8fb4d640082fc673119882644 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Sat, 17 Oct 2026 12:14:55 +0200 Subject: [PATCH] Don't allocate when building signature types on Julia 1.14 On Julia 1.14, signature_type_by_tt maps dispatch_key over the parameters of the tuple type. That builds a Vector, which keeps the signature from being folded to a constant, so every methodinstance call (i.e., every launch of a cached kernel) allocated proportionally to the number of arguments, regressing KernelAbstractions' POCL launch-allocation test. Splat the parameters directly when none of them is a Type{...}, which is also what's cheapest for a non-constant tuple type, and use a generator otherwise, which folds just like splatting does. Fixes #1024 --- src/jlgen.jl | 10 +++++++++- test/native.jl | 16 ++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/jlgen.jl b/src/jlgen.jl index 0f4cc575..a9278c07 100644 --- a/src/jlgen.jl +++ b/src/jlgen.jl @@ -28,7 +28,15 @@ export methodinstance, generic_methodinstance @inline function signature_type_by_tt(ft::Type, tt::Type) u = Base.unwrap_unionall(tt)::DataType - return Base.rewrap_unionall(Tuple{dispatch_key(ft), map(dispatch_key, u.parameters)...}, tt) + # `methodinstance` calls this on every kernel launch, so for constant `ft` and `tt` + # this needs to fold to a constant, which `map` over the parameters prevents by + # allocating a `Vector`. Without `Type{...}` elements, plain splatting is cheapest. + sig = if Base.isType(ft) || any(Base.isType, u.parameters) + Tuple{dispatch_key(ft), (dispatch_key(p) for p in u.parameters)...} + else + Tuple{ft, u.parameters...} + end + return Base.rewrap_unionall(sig, tt) end else @inline function signature_type_by_tt(ft::Type, tt::Type) diff --git a/test/native.jl b/test/native.jl index 155c2be4..9276f2e3 100644 --- a/test/native.jl +++ b/test/native.jl @@ -344,6 +344,22 @@ end @test Base.isdispatchtuple(mi.specTypes) end +# on 1.10, `methodinstance` is the generic, allocating lookup +VERSION >= v"1.11.0-DEV.1552" && @testset "method instance lookups don't allocate" begin + # back-ends look up the method instance on every kernel launch + mod = @eval module $(gensym()) + import GPUCompiler + kernel(args...) = nothing + lookup() = GPUCompiler.methodinstance(typeof(kernel), + $(Tuple{Vector{Int}, ntuple(_ -> Int, 40)...})) + lookup_type() = GPUCompiler.methodinstance(typeof(kernel), Tuple{Type{Int}, Int}) + end + mod.lookup() + @test (@allocated mod.lookup()) == 0 + mod.lookup_type() + @test (@allocated mod.lookup_type()) == 0 +end + @testset "compilation" begin @testset "callable structs" begin mod = @eval module $(gensym())