From aa220f69031d71a8a377134b2dc993d118dde7a9 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Tue, 14 Jul 2026 13:15:27 +0200 Subject: [PATCH 1/9] Patch CUDA host references after loading --- CUDACore/src/compiler/compilation.jl | 20 +++++++++++++++----- CUDACore/src/compiler/execution.jl | 12 ++++++++---- test/core/codegen.jl | 14 ++++++++++++++ 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/CUDACore/src/compiler/compilation.jl b/CUDACore/src/compiler/compilation.jl index 07a3d5514e..8ab4503773 100644 --- a/CUDACore/src/compiler/compilation.jl +++ b/CUDACore/src/compiler/compilation.jl @@ -21,6 +21,7 @@ const AnyCUDAJob = CompilerJob{PTXCompilerTarget, <:AbstractCUDACompilerParams} const minreq = (; ptx=v"8.0", sm=sm"50") GPUCompiler.runtime_module(@nospecialize(job::AnyCUDAJob)) = CUDACore +GPUCompiler.supports_host_reference_patching(@nospecialize(job::AnyCUDAJob)) = true # filter out functions from libdevice and cudadevrt GPUCompiler.isintrinsic(@nospecialize(job::AnyCUDAJob), fn::String) = @@ -196,11 +197,13 @@ mutable struct CUDACompilerResults # session-portable artifacts (safe to persist across sessions) image::Union{Nothing,Vector{UInt8}} entry::Union{Nothing,String} + host_references::GPUCompiler.HostReferences # session-local kernel handles, linear-scanned by context; usually holds a single entry - kernels::Vector{Tuple{CuContext,CuFunction}} + kernels::Vector{Tuple{CuContext,CuFunction,Vector{Any}}} - CUDACompilerResults() = new(nothing, nothing, Tuple{CuContext,CuFunction}[]) + CUDACompilerResults() = new(nothing, nothing, GPUCompiler.HostReferences(), + Tuple{CuContext,CuFunction,Vector{Any}}[]) end # cache of compiler configurations, per device (but additionally configurable via kwargs) @@ -539,14 +542,20 @@ function compile(@nospecialize(job::CompilerJob)) rm(ptxas_output) end - return (image, entry=LLVM.name(meta.entry)) + return (image, entry=LLVM.name(meta.entry), host_references=meta.host_references) end # link a compiled image into a session-local `CuFunction` on the active context -function link_kernel(@nospecialize(job::CompilerJob), image::Vector{UInt8}, entry::String) +function link_kernel(@nospecialize(job::CompilerJob), image::Vector{UInt8}, entry::String, + refs::GPUCompiler.HostReferences) # load as an executable kernel object on the current context mod = CuModule(image) - CuFunction(mod, entry) + roots = Any[] + for (name, ref) in refs.slots + CuGlobal{UInt}(mod, name)[] = GPUCompiler.resolve_host_reference(ref) + ref isa GPUCompiler.JuliaObjectRef && push!(roots, ref.value) + end + return CuFunction(mod, entry), roots end # look up the cached compilation artifacts for `job`, running the compiler on a miss. @@ -563,6 +572,7 @@ function compile_or_lookup(@nospecialize(job::CompilerJob))::CUDACompilerResults res = @something res GPUCompiler.cached_results(CUDACompilerResults, job) res.image = compiled.image res.entry = compiled.entry + res.host_references = compiled.host_references end return res end diff --git a/CUDACore/src/compiler/execution.jl b/CUDACore/src/compiler/execution.jl index 9f0ee318be..9682ba80f0 100644 --- a/CUDACore/src/compiler/execution.jl +++ b/CUDACore/src/compiler/execution.jl @@ -474,6 +474,7 @@ struct HostKernel{F,TT} <: AbstractKernel{F,TT} f::F fun::CuFunction state::KernelState + roots::Vector{Any} end @doc (@doc AbstractKernel) HostKernel @@ -580,19 +581,22 @@ function cufunction(f::F, tt::TT=Tuple{}; kwargs...) where {F,TT} # the scan is almost always over a single entry (one `===` comparison). ctx = cuda.context fun = nothing - for (cached_ctx, cached_fun) in res.kernels + roots = Any[] + for (cached_ctx, cached_fun, cached_roots) in res.kernels if cached_ctx === ctx fun = cached_fun + roots = cached_roots break end end if fun === nothing - fun = link_kernel(job, res.image::Vector{UInt8}, res.entry::String) + fun, roots = link_kernel(job, res.image::Vector{UInt8}, res.entry::String, + res.host_references) # don't cache session-local handles while generating output: the results struct # is serialized into the package image along with its CodeInstance, and the # handles would come back dangling. if ccall(:jl_generating_output, Cint, ()) != 1 - push!(res.kernels, (ctx, fun)) + push!(res.kernels, (ctx, fun, roots)) end end @@ -604,7 +608,7 @@ function cufunction(f::F, tt::TT=Tuple{}; kwargs...) where {F,TT} # create the kernel state object state = KernelState(create_exceptions!(fun.mod), UInt32(0)) - kernel = HostKernel{F,tt}(f, fun, state) + kernel = HostKernel{F,tt}(f, fun, state, roots) _kernel_instances[key] = kernel end return kernel::HostKernel{F,tt} diff --git a/test/core/codegen.jl b/test/core/codegen.jl index 9ab5b5aa68..63e11fabb1 100644 --- a/test/core/codegen.jl +++ b/test/core/codegen.jl @@ -255,6 +255,20 @@ end ############################################################################################ +@testset "host reference patching" begin + @eval begin + const host_reference_type_tag = CUDACore.GPUCompiler.Runtime.type_tag + host_reference_kernel(out) = (out[1] = host_reference_type_tag(Val(:float32)); return) + end + + out = CUDA.zeros(UInt, 1) + @cuda threads=1 host_reference_kernel(out) + expected = UInt(unsafe_load(cglobal(:jl_float32_type, Ptr{UInt}))) + @test Array(out)[] == expected +end + +############################################################################################ + @testset "SASS" begin @testset "basic reflection" begin From 81242d7e5bc15b57d784dde9746a265feb56a814 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Tue, 14 Jul 2026 15:18:47 +0200 Subject: [PATCH 2/9] Lower host references through CUDA loader slots --- CUDACore/src/compiler/compilation.jl | 10 +++++++--- CUDACore/src/precompile.jl | 3 ++- test/core/codegen.jl | 15 +++++++++++++-- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/CUDACore/src/compiler/compilation.jl b/CUDACore/src/compiler/compilation.jl index 8ab4503773..958471d6c5 100644 --- a/CUDACore/src/compiler/compilation.jl +++ b/CUDACore/src/compiler/compilation.jl @@ -21,7 +21,10 @@ const AnyCUDAJob = CompilerJob{PTXCompilerTarget, <:AbstractCUDACompilerParams} const minreq = (; ptx=v"8.0", sm=sm"50") GPUCompiler.runtime_module(@nospecialize(job::AnyCUDAJob)) = CUDACore -GPUCompiler.supports_host_reference_patching(@nospecialize(job::AnyCUDAJob)) = true +function GPUCompiler.lower_host_references!(@nospecialize(job::AnyCUDAJob), mod::LLVM.Module, + refs::GPUCompiler.HostReferences) + GPUCompiler.emit_host_reference_slots!(mod, refs) +end # filter out functions from libdevice and cudadevrt GPUCompiler.isintrinsic(@nospecialize(job::AnyCUDAJob), fn::String) = @@ -552,8 +555,9 @@ function link_kernel(@nospecialize(job::CompilerJob), image::Vector{UInt8}, entr mod = CuModule(image) roots = Any[] for (name, ref) in refs.slots - CuGlobal{UInt}(mod, name)[] = GPUCompiler.resolve_host_reference(ref) - ref isa GPUCompiler.JuliaObjectRef && push!(roots, ref.value) + slot = CuGlobal{UInt}(mod, name) + slot[] = GPUCompiler.resolve_host_reference(ref) + ref isa GPUCompiler.JuliaValueRef && push!(roots, ref.value) end return CuFunction(mod, entry), roots end diff --git a/CUDACore/src/precompile.jl b/CUDACore/src/precompile.jl index 6297bf7217..701706c67b 100644 --- a/CUDACore/src/precompile.jl +++ b/CUDACore/src/precompile.jl @@ -45,7 +45,8 @@ end # kernel launch infrastructure let CUDACompilerJob = CompilerJob{PTXCompilerTarget, CUDACompilerParams} precompile(Tuple{typeof(cufunction), typeof(identity), Type{Tuple{Nothing}}}) - precompile(Tuple{typeof(link_kernel), CUDACompilerJob, Vector{UInt8}, String}) + precompile(Tuple{typeof(link_kernel), CUDACompilerJob, Vector{UInt8}, String, + GPUCompiler.HostReferences}) # GPUCompiler 2.0 caching pipeline (specialized for CUDACore's results struct) precompile(Tuple{typeof(compile_or_lookup), CUDACompilerJob}) diff --git a/test/core/codegen.jl b/test/core/codegen.jl index 63e11fabb1..1484171af2 100644 --- a/test/core/codegen.jl +++ b/test/core/codegen.jl @@ -256,15 +256,26 @@ end ############################################################################################ @testset "host reference patching" begin - @eval begin + runtime_refs = @eval module $(gensym()) + using ..CUDACore const host_reference_type_tag = CUDACore.GPUCompiler.Runtime.type_tag host_reference_kernel(out) = (out[1] = host_reference_type_tag(Val(:float32)); return) end out = CUDA.zeros(UInt, 1) - @cuda threads=1 host_reference_kernel(out) + @cuda threads=1 runtime_refs.host_reference_kernel(out) expected = UInt(unsafe_load(cglobal(:jl_float32_type, Ptr{UInt}))) @test Array(out)[] == expected + + value_refs = @eval module $(gensym()) + const host_reference_symbol = Symbol("value#global") + host_reference_symbol_kernel(out, name) = + (out[1] = UInt(name === host_reference_symbol); return) + end + + out = CUDA.zeros(UInt, 1) + @cuda threads=1 value_refs.host_reference_symbol_kernel(out, Symbol("value#global")) + @test Array(out)[] == 1 end ############################################################################################ From 28be81292612a83791fe1a4889a36b8a559555aa Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Tue, 14 Jul 2026 15:34:34 +0200 Subject: [PATCH 3/9] Dev GPUCompiler. --- CUDACore/Project.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CUDACore/Project.toml b/CUDACore/Project.toml index ff56e1aded..e07ffff536 100644 --- a/CUDACore/Project.toml +++ b/CUDACore/Project.toml @@ -78,3 +78,6 @@ julia = "1.10" ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" EnzymeCore = "f151be2c-9106-41f4-ab19-57ee4f262869" SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" + +[sources] +GPUCompiler = {url="https://github.com/JuliaGPU/GPUCompiler.jl", rev="tb/relocations"} From 2157168f43fe838ed4b6ad20ef8acb83152d6163 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Thu, 16 Jul 2026 14:57:55 +0200 Subject: [PATCH 4/9] Use host-reference definition lowering --- CUDACore/src/compiler/compilation.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CUDACore/src/compiler/compilation.jl b/CUDACore/src/compiler/compilation.jl index 958471d6c5..fc0067e3c8 100644 --- a/CUDACore/src/compiler/compilation.jl +++ b/CUDACore/src/compiler/compilation.jl @@ -23,7 +23,7 @@ const minreq = (; ptx=v"8.0", sm=sm"50") GPUCompiler.runtime_module(@nospecialize(job::AnyCUDAJob)) = CUDACore function GPUCompiler.lower_host_references!(@nospecialize(job::AnyCUDAJob), mod::LLVM.Module, refs::GPUCompiler.HostReferences) - GPUCompiler.emit_host_reference_slots!(mod, refs) + GPUCompiler.emit_host_reference_definitions!(mod, refs) end # filter out functions from libdevice and cudadevrt From 21946cfb7eb0055be0773d3e764527b31c87c102 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Thu, 16 Jul 2026 20:24:49 +0200 Subject: [PATCH 5/9] Patch interior host references after module load --- CUDACore/src/compiler/compilation.jl | 29 +++++++++++++++++++++------- test/core/codegen.jl | 15 ++++++++++++++ 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/CUDACore/src/compiler/compilation.jl b/CUDACore/src/compiler/compilation.jl index fc0067e3c8..0f6e39f431 100644 --- a/CUDACore/src/compiler/compilation.jl +++ b/CUDACore/src/compiler/compilation.jl @@ -193,7 +193,8 @@ end # GPUCompiler 2.0 caching: back-ends attach a mutable results struct to each cached # `CodeInstance` (on Julia 1.11+ this is Julia's integrated code cache, which also persists # artifacts through package precompilation; on 1.10 it's a session-local store). We keep -# session-portable artifacts (the cubin `image` and entry-point `entry`) separate from the +# conditionally session-portable artifacts (the cubin `image`, entry point, and relocations) +# separate from the # session-local `CuFunction` handles, which are context-specific and must not be serialized # into a package image. mutable struct CUDACompilerResults @@ -553,13 +554,21 @@ function link_kernel(@nospecialize(job::CompilerJob), image::Vector{UInt8}, entr refs::GPUCompiler.HostReferences) # load as an executable kernel object on the current context mod = CuModule(image) - roots = Any[] - for (name, ref) in refs.slots + relocations = GPUCompiler.resolved_relocations(refs) + for (name, value) in relocations.slots slot = CuGlobal{UInt}(mod, name) - slot[] = GPUCompiler.resolve_host_reference(ref) - ref isa GPUCompiler.JuliaValueRef && push!(roots, ref.value) + slot[] = value end - return CuFunction(mod, entry), roots + for ((name, offset), value) in relocations.patches + ptr_ref = Ref{CuPtr{Cvoid}}() + size_ref = Ref{Csize_t}() + cuModuleGetGlobal_v2(ptr_ref, size_ref, mod, name) + offset >= 0 && offset + sizeof(UInt) <= size_ref[] || + error("Host reference patch '$name+$offset' is outside its $(size_ref[])-byte global") + value_ref = Ref(value) + cuMemcpyHtoD_v2(ptr_ref[] + offset, value_ref, sizeof(UInt)) + end + return CuFunction(mod, entry), relocations.roots end # look up the cached compilation artifacts for `job`, running the compiler on a miss. @@ -573,7 +582,13 @@ function compile_or_lookup(@nospecialize(job::CompilerJob))::CUDACompilerResults res = GPUCompiler.cached_results(CUDACompilerResults, job) if res === nothing || res.image === nothing || GPUCompiler.compile_hook[] !== nothing compiled = compile(job) - res = @something res GPUCompiler.cached_results(CUDACompilerResults, job) + if GPUCompiler.supports_relocatable_ir() + res = @something res GPUCompiler.cached_results(CUDACompilerResults, job) + else + # The CodeInstance may be serialized, but generated code from a Julia runtime + # without a reliable GV table is session-bound. + res = CUDACompilerResults() + end res.image = compiled.image res.entry = compiled.entry res.host_references = compiled.host_references diff --git a/test/core/codegen.jl b/test/core/codegen.jl index 1484171af2..0272f2e751 100644 --- a/test/core/codegen.jl +++ b/test/core/codegen.jl @@ -276,6 +276,21 @@ end out = CUDA.zeros(UInt, 1) @cuda threads=1 value_refs.host_reference_symbol_kernel(out, Symbol("value#global")) @test Array(out)[] == 1 + + interior_refs = @eval module $(gensym()) + @noinline produce(cond::Bool, value::Int32) = cond ? value : 1.5 + function interior_reference_kernel(out, cond::Bool, value::Int32) + x = produce(cond, value) + out[1] = UInt(x isa Float64) + return + end + end + + out = CUDA.zeros(UInt, 1) + kernel = @cuda launch=false interior_refs.interior_reference_kernel(out, false, Int32(1)) + kernel(out, false, Int32(1); threads=1) + @test Array(out)[] == 1 + @test any(root -> root === Float64, kernel.roots) end ############################################################################################ From e20fbd4577ae81b9fb3ff062a95e3390b18be505 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Thu, 16 Jul 2026 21:44:29 +0200 Subject: [PATCH 6/9] Adopt GPUCompiler relocation terminology --- CUDACore/src/compiler/compilation.jl | 22 +++++++++++----------- CUDACore/src/compiler/execution.jl | 2 +- CUDACore/src/precompile.jl | 2 +- test/core/codegen.jl | 26 +++++++++++++------------- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/CUDACore/src/compiler/compilation.jl b/CUDACore/src/compiler/compilation.jl index 0f6e39f431..4f03be84af 100644 --- a/CUDACore/src/compiler/compilation.jl +++ b/CUDACore/src/compiler/compilation.jl @@ -21,9 +21,9 @@ const AnyCUDAJob = CompilerJob{PTXCompilerTarget, <:AbstractCUDACompilerParams} const minreq = (; ptx=v"8.0", sm=sm"50") GPUCompiler.runtime_module(@nospecialize(job::AnyCUDAJob)) = CUDACore -function GPUCompiler.lower_host_references!(@nospecialize(job::AnyCUDAJob), mod::LLVM.Module, - refs::GPUCompiler.HostReferences) - GPUCompiler.emit_host_reference_definitions!(mod, refs) +function GPUCompiler.lower_relocations!(@nospecialize(job::AnyCUDAJob), mod::LLVM.Module, + relocs::GPUCompiler.Relocations) + GPUCompiler.emit_patchable_relocations!(mod, relocs) end # filter out functions from libdevice and cudadevrt @@ -201,12 +201,12 @@ mutable struct CUDACompilerResults # session-portable artifacts (safe to persist across sessions) image::Union{Nothing,Vector{UInt8}} entry::Union{Nothing,String} - host_references::GPUCompiler.HostReferences + relocations::GPUCompiler.Relocations # session-local kernel handles, linear-scanned by context; usually holds a single entry kernels::Vector{Tuple{CuContext,CuFunction,Vector{Any}}} - CUDACompilerResults() = new(nothing, nothing, GPUCompiler.HostReferences(), + CUDACompilerResults() = new(nothing, nothing, GPUCompiler.Relocations(), Tuple{CuContext,CuFunction,Vector{Any}}[]) end @@ -546,25 +546,25 @@ function compile(@nospecialize(job::CompilerJob)) rm(ptxas_output) end - return (image, entry=LLVM.name(meta.entry), host_references=meta.host_references) + return (image, entry=LLVM.name(meta.entry), relocations=meta.relocations) end # link a compiled image into a session-local `CuFunction` on the active context function link_kernel(@nospecialize(job::CompilerJob), image::Vector{UInt8}, entry::String, - refs::GPUCompiler.HostReferences) + relocs::GPUCompiler.Relocations) # load as an executable kernel object on the current context mod = CuModule(image) - relocations = GPUCompiler.resolved_relocations(refs) + relocations = GPUCompiler.resolved_relocations(relocs) for (name, value) in relocations.slots slot = CuGlobal{UInt}(mod, name) slot[] = value end - for ((name, offset), value) in relocations.patches + for ((name, offset), value) in relocations.interior ptr_ref = Ref{CuPtr{Cvoid}}() size_ref = Ref{Csize_t}() cuModuleGetGlobal_v2(ptr_ref, size_ref, mod, name) offset >= 0 && offset + sizeof(UInt) <= size_ref[] || - error("Host reference patch '$name+$offset' is outside its $(size_ref[])-byte global") + error("Interior relocation '$name+$offset' is outside its $(size_ref[])-byte global") value_ref = Ref(value) cuMemcpyHtoD_v2(ptr_ref[] + offset, value_ref, sizeof(UInt)) end @@ -591,7 +591,7 @@ function compile_or_lookup(@nospecialize(job::CompilerJob))::CUDACompilerResults end res.image = compiled.image res.entry = compiled.entry - res.host_references = compiled.host_references + res.relocations = compiled.relocations end return res end diff --git a/CUDACore/src/compiler/execution.jl b/CUDACore/src/compiler/execution.jl index 9682ba80f0..a2239545f6 100644 --- a/CUDACore/src/compiler/execution.jl +++ b/CUDACore/src/compiler/execution.jl @@ -591,7 +591,7 @@ function cufunction(f::F, tt::TT=Tuple{}; kwargs...) where {F,TT} end if fun === nothing fun, roots = link_kernel(job, res.image::Vector{UInt8}, res.entry::String, - res.host_references) + res.relocations) # don't cache session-local handles while generating output: the results struct # is serialized into the package image along with its CodeInstance, and the # handles would come back dangling. diff --git a/CUDACore/src/precompile.jl b/CUDACore/src/precompile.jl index 701706c67b..40d80f1de0 100644 --- a/CUDACore/src/precompile.jl +++ b/CUDACore/src/precompile.jl @@ -46,7 +46,7 @@ end let CUDACompilerJob = CompilerJob{PTXCompilerTarget, CUDACompilerParams} precompile(Tuple{typeof(cufunction), typeof(identity), Type{Tuple{Nothing}}}) precompile(Tuple{typeof(link_kernel), CUDACompilerJob, Vector{UInt8}, String, - GPUCompiler.HostReferences}) + GPUCompiler.Relocations}) # GPUCompiler 2.0 caching pipeline (specialized for CUDACore's results struct) precompile(Tuple{typeof(compile_or_lookup), CUDACompilerJob}) diff --git a/test/core/codegen.jl b/test/core/codegen.jl index 0272f2e751..993568b9fe 100644 --- a/test/core/codegen.jl +++ b/test/core/codegen.jl @@ -255,31 +255,31 @@ end ############################################################################################ -@testset "host reference patching" begin - runtime_refs = @eval module $(gensym()) +@testset "relocation patching" begin + runtime_relocs = @eval module $(gensym()) using ..CUDACore - const host_reference_type_tag = CUDACore.GPUCompiler.Runtime.type_tag - host_reference_kernel(out) = (out[1] = host_reference_type_tag(Val(:float32)); return) + const relocated_type_tag = CUDACore.GPUCompiler.Runtime.type_tag + relocation_kernel(out) = (out[1] = relocated_type_tag(Val(:float32)); return) end out = CUDA.zeros(UInt, 1) - @cuda threads=1 runtime_refs.host_reference_kernel(out) + @cuda threads=1 runtime_relocs.relocation_kernel(out) expected = UInt(unsafe_load(cglobal(:jl_float32_type, Ptr{UInt}))) @test Array(out)[] == expected - value_refs = @eval module $(gensym()) - const host_reference_symbol = Symbol("value#global") - host_reference_symbol_kernel(out, name) = - (out[1] = UInt(name === host_reference_symbol); return) + value_relocs = @eval module $(gensym()) + const relocated_symbol = Symbol("value#global") + relocated_symbol_kernel(out, name) = + (out[1] = UInt(name === relocated_symbol); return) end out = CUDA.zeros(UInt, 1) - @cuda threads=1 value_refs.host_reference_symbol_kernel(out, Symbol("value#global")) + @cuda threads=1 value_relocs.relocated_symbol_kernel(out, Symbol("value#global")) @test Array(out)[] == 1 - interior_refs = @eval module $(gensym()) + interior_relocs = @eval module $(gensym()) @noinline produce(cond::Bool, value::Int32) = cond ? value : 1.5 - function interior_reference_kernel(out, cond::Bool, value::Int32) + function interior_relocation_kernel(out, cond::Bool, value::Int32) x = produce(cond, value) out[1] = UInt(x isa Float64) return @@ -287,7 +287,7 @@ end end out = CUDA.zeros(UInt, 1) - kernel = @cuda launch=false interior_refs.interior_reference_kernel(out, false, Int32(1)) + kernel = @cuda launch=false interior_relocs.interior_relocation_kernel(out, false, Int32(1)) kernel(out, false, Int32(1); threads=1) @test Array(out)[] == 1 @test any(root -> root === Float64, kernel.roots) From f0953b53108f4cbe370172aa1f7612187388b1c0 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Fri, 17 Jul 2026 08:51:37 +0200 Subject: [PATCH 7/9] Patch relocations through one loader path --- CUDACore/src/compiler/compilation.jl | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/CUDACore/src/compiler/compilation.jl b/CUDACore/src/compiler/compilation.jl index 4f03be84af..ae6348464b 100644 --- a/CUDACore/src/compiler/compilation.jl +++ b/CUDACore/src/compiler/compilation.jl @@ -555,18 +555,15 @@ function link_kernel(@nospecialize(job::CompilerJob), image::Vector{UInt8}, entr # load as an executable kernel object on the current context mod = CuModule(image) relocations = GPUCompiler.resolved_relocations(relocs) - for (name, value) in relocations.slots - slot = CuGlobal{UInt}(mod, name) - slot[] = value - end - for ((name, offset), value) in relocations.interior + for (site, value) in relocations.sites ptr_ref = Ref{CuPtr{Cvoid}}() size_ref = Ref{Csize_t}() - cuModuleGetGlobal_v2(ptr_ref, size_ref, mod, name) - offset >= 0 && offset + sizeof(UInt) <= size_ref[] || - error("Interior relocation '$name+$offset' is outside its $(size_ref[])-byte global") + cuModuleGetGlobal_v2(ptr_ref, size_ref, mod, site.name) + site.offset >= 0 && site.offset + sizeof(UInt) <= size_ref[] || + error("Relocation '$(site.name)+$(site.offset)' is outside its " * + "$(size_ref[])-byte global") value_ref = Ref(value) - cuMemcpyHtoD_v2(ptr_ref[] + offset, value_ref, sizeof(UInt)) + cuMemcpyHtoD_v2(ptr_ref[] + site.offset, value_ref, sizeof(UInt)) end return CuFunction(mod, entry), relocations.roots end From 569e46b7261c84535f6734d6eccf285f55c356b0 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Fri, 17 Jul 2026 09:44:23 +0200 Subject: [PATCH 8/9] Simplify the relocation loader interface --- CUDACore/src/compiler/compilation.jl | 4 ++-- CUDACore/src/compiler/execution.jl | 2 +- CUDACore/src/precompile.jl | 3 +-- CUDATools/src/reflection.jl | 3 ++- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CUDACore/src/compiler/compilation.jl b/CUDACore/src/compiler/compilation.jl index ae6348464b..15d6c0d3c8 100644 --- a/CUDACore/src/compiler/compilation.jl +++ b/CUDACore/src/compiler/compilation.jl @@ -550,7 +550,7 @@ function compile(@nospecialize(job::CompilerJob)) end # link a compiled image into a session-local `CuFunction` on the active context -function link_kernel(@nospecialize(job::CompilerJob), image::Vector{UInt8}, entry::String, +function link_kernel(image::Vector{UInt8}, entry::String, relocs::GPUCompiler.Relocations) # load as an executable kernel object on the current context mod = CuModule(image) @@ -559,7 +559,7 @@ function link_kernel(@nospecialize(job::CompilerJob), image::Vector{UInt8}, entr ptr_ref = Ref{CuPtr{Cvoid}}() size_ref = Ref{Csize_t}() cuModuleGetGlobal_v2(ptr_ref, size_ref, mod, site.name) - site.offset >= 0 && site.offset + sizeof(UInt) <= size_ref[] || + site.offset + sizeof(UInt) <= size_ref[] || error("Relocation '$(site.name)+$(site.offset)' is outside its " * "$(size_ref[])-byte global") value_ref = Ref(value) diff --git a/CUDACore/src/compiler/execution.jl b/CUDACore/src/compiler/execution.jl index a2239545f6..704e812a9c 100644 --- a/CUDACore/src/compiler/execution.jl +++ b/CUDACore/src/compiler/execution.jl @@ -590,7 +590,7 @@ function cufunction(f::F, tt::TT=Tuple{}; kwargs...) where {F,TT} end end if fun === nothing - fun, roots = link_kernel(job, res.image::Vector{UInt8}, res.entry::String, + fun, roots = link_kernel(res.image::Vector{UInt8}, res.entry::String, res.relocations) # don't cache session-local handles while generating output: the results struct # is serialized into the package image along with its CodeInstance, and the diff --git a/CUDACore/src/precompile.jl b/CUDACore/src/precompile.jl index 40d80f1de0..44c56fe203 100644 --- a/CUDACore/src/precompile.jl +++ b/CUDACore/src/precompile.jl @@ -45,8 +45,7 @@ end # kernel launch infrastructure let CUDACompilerJob = CompilerJob{PTXCompilerTarget, CUDACompilerParams} precompile(Tuple{typeof(cufunction), typeof(identity), Type{Tuple{Nothing}}}) - precompile(Tuple{typeof(link_kernel), CUDACompilerJob, Vector{UInt8}, String, - GPUCompiler.Relocations}) + precompile(Tuple{typeof(link_kernel), Vector{UInt8}, String, GPUCompiler.Relocations}) # GPUCompiler 2.0 caching pipeline (specialized for CUDACore's results struct) precompile(Tuple{typeof(compile_or_lookup), CUDACompilerJob}) diff --git a/CUDATools/src/reflection.jl b/CUDATools/src/reflection.jl index 874603358e..af667b11f0 100644 --- a/CUDATools/src/reflection.jl +++ b/CUDATools/src/reflection.jl @@ -76,7 +76,8 @@ function code_sass(io::IO, job::CompilerJob; raw::Bool=false) end compiled = CUDACore.compile(job) - CUPTI.@enable! cfg CUDACore.link_kernel(job, compiled.image, compiled.entry) + CUPTI.@enable! cfg CUDACore.link_kernel(compiled.image, compiled.entry, + compiled.relocations) return end From 7d26d619176de9fb0dc6bb1cd4fe17b2576f5cb0 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Thu, 23 Jul 2026 13:17:27 +0200 Subject: [PATCH 9/9] Adapt. --- CUDACore/Project.toml | 2 +- CUDACore/src/compiler/compilation.jl | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/CUDACore/Project.toml b/CUDACore/Project.toml index e07ffff536..d7605597d2 100644 --- a/CUDACore/Project.toml +++ b/CUDACore/Project.toml @@ -54,7 +54,7 @@ ChainRulesCore = "1" EnzymeCore = "0.8.2" ExprTools = "0.1" GPUArrays = "11.5.4" -GPUCompiler = "2.1" +GPUCompiler = "2.2" GPUToolbox = "3" KernelAbstractions = "0.9.38" LLVM = "9.6" diff --git a/CUDACore/src/compiler/compilation.jl b/CUDACore/src/compiler/compilation.jl index 15d6c0d3c8..8091aa12bc 100644 --- a/CUDACore/src/compiler/compilation.jl +++ b/CUDACore/src/compiler/compilation.jl @@ -21,10 +21,9 @@ const AnyCUDAJob = CompilerJob{PTXCompilerTarget, <:AbstractCUDACompilerParams} const minreq = (; ptx=v"8.0", sm=sm"50") GPUCompiler.runtime_module(@nospecialize(job::AnyCUDAJob)) = CUDACore -function GPUCompiler.lower_relocations!(@nospecialize(job::AnyCUDAJob), mod::LLVM.Module, - relocs::GPUCompiler.Relocations) - GPUCompiler.emit_patchable_relocations!(mod, relocs) -end +# keep host references symbolic and emit them as patchable globals; `link_kernel` +# patches every site after loading the module +GPUCompiler.relocation_lowering(@nospecialize(job::AnyCUDAJob)) = :patch # filter out functions from libdevice and cudadevrt GPUCompiler.isintrinsic(@nospecialize(job::AnyCUDAJob), fn::String) =