From 48f517379a6e63b9cd3161daa3f4d117172d3eb7 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Wed, 28 Oct 2026 07:37:11 +0100 Subject: [PATCH] Keep a kernel's program alive while the kernel exists Compilation creates a `Program`, creates the kernel from it and drops the `Program`. Once that object is finalized, Intel's CPU runtime treats the program handle as invalid even though the kernel still uses it, so `kernel.program` (clGetKernelInfo + clRetainProgram) throws CL_INVALID_PROGRAM. This made the "program backend switch" test fail whenever a collection ran between compiling and querying the kernel. --- lib/cl/kernel.jl | 11 ++++++++--- test/kernel.jl | 7 +++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/cl/kernel.jl b/lib/cl/kernel.jl index 7f0eaa21..92ec3843 100644 --- a/lib/cl/kernel.jl +++ b/lib/cl/kernel.jl @@ -9,14 +9,19 @@ mutable struct Kernel <: CLObject const id::cl_kernel const lock::ReentrantLock + # Intel's CPU runtime invalidates a program handle once its reference count drops to + # zero, even while kernels still use it, so keep the program the kernel was created from. + const program::Union{Program,Nothing} + # Cache settings registered by `set_indirect_memory!` with `clSetKernelExecInfo`. # They persist across launches, so unchanged settings need no driver call. # Guarded by `lock`; direct calls to `clSetKernelExecInfo` bypass this cache. const indirect_pointers::Vector{Vector{CLPtr{Cvoid}}} # SVM, buffer and USM pointers indirect_access::NTuple{3,Bool} # USM device, host and shared - function Kernel(k::cl_kernel, retain::Bool=false) - kernel = new(k, ReentrantLock(), [CLPtr{Cvoid}[] for _ in 1:3], (false, false, false)) + function Kernel(k::cl_kernel, retain::Bool=false; program::Union{Program,Nothing}=nothing) + kernel = new(k, ReentrantLock(), program, [CLPtr{Cvoid}[] for _ in 1:3], + (false, false, false)) retain && clRetainKernel(kernel) finalizer(clReleaseKernel, kernel) return kernel @@ -45,7 +50,7 @@ function Kernel(p::Program, kernel_name::String) if err_code[] != CL_SUCCESS throw(CLError(err_code[])) end - return Kernel(kernel_id) + return Kernel(kernel_id; program=p) end function Base.getproperty(k::Kernel, s::Symbol) diff --git a/test/kernel.jl b/test/kernel.jl index a938305b..76f4301f 100644 --- a/test/kernel.jl +++ b/test/kernel.jl @@ -29,6 +29,13 @@ end @test k.reference_count > 0 @test k.program == prg @test typeof(k.attributes) == String + + # the kernel's program stays queryable after the `Program` that built it is collected + # (a function, so that the `Program` isn't kept alive by the testset's scope) + kernel_of(src) = cl.Kernel(cl.build!(cl.Program(source=src)), "sum") + k = kernel_of(test_source) + GC.gc(true) + @test k.program isa cl.Program end @testset "mem/workgroup size" begin