From c36672a87d90b9f1efacab30bab584e6a3192084 Mon Sep 17 00:00:00 2001 From: Valentin Churavy Date: Sun, 4 Oct 2026 13:18:01 +0200 Subject: [PATCH] Don't interpolate KernelInterface into macro expansions `@localmem` and `@spawn` expanded to `$KI.localmemory(...)` etc., with the module object in the expression. Expressions containing a module can't be copied with `deepcopy`, which `@kernel` does with its definition, so tools that expand `@localmem` before `@kernel` sees it (e.g. ParallelStencil.jl) failed with "deepcopy of Modules not supported". Interpolate the functions instead, like the other macros do. Assisted-by: Claude Code (Opus 5.5) --- src/KernelAbstractions.jl | 2 +- src/spawn.jl | 10 +++++----- test/localmem.jl | 21 +++++++++++++++++++++ test/spawn.jl | 6 ++++++ 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/KernelAbstractions.jl b/src/KernelAbstractions.jl index 8e5b4f4fe..dca29ffb5 100644 --- a/src/KernelAbstractions.jl +++ b/src/KernelAbstractions.jl @@ -217,7 +217,7 @@ Like [`@uniform`](@ref), the allocation is also executed by padding work-items t outside of the `ndrange`. """ macro localmem(T, dims) - return :($KI.localmemory($(esc(T)), Val($(esc(dims))))) + return :($(KI.localmemory)($(esc(T)), Val($(esc(dims))))) end """ diff --git a/src/spawn.jl b/src/spawn.jl index dc319a692..12a5b1904 100644 --- a/src/spawn.jl +++ b/src/spawn.jl @@ -105,10 +105,10 @@ macro spawn(args...) # `device!` comes first because `wait_event` acts on the queue of the device that is # active when it is called: selecting the device afterwards would leave it unordered. body = quote - $KI.device!($b, $dev) - $KI.wait_event($b, $event) + $(KI.device!)($b, $dev) + $(KI.wait_event)($b, $event) local $result = $expr - $KI.synchronize($b) + $(KI.synchronize)($b) $result end task = if threadpool === nothing @@ -122,8 +122,8 @@ macro spawn(args...) return esc( quote local $b = $backend - local $dev = $(device === nothing ? :($KI.device($b)) : device) - local $event = $KI.record_event($b) + local $dev = $(device === nothing ? :($(KI.device)($b)) : device) + local $event = $(KI.record_event)($b) $task end ) diff --git a/test/localmem.jl b/test/localmem.jl index c1d63c7a0..dc0bde8c8 100644 --- a/test/localmem.jl +++ b/test/localmem.jl @@ -64,7 +64,28 @@ end end end +# A kernel whose `@localmem` was expanded before `@kernel` saw it, as tools that generate +# kernels (e.g. ParallelStencil.jl) do. `@kernel` copies the definition with `deepcopy`, +# which fails for expansions that contain a module. +const pre_expanded_localmem = macroexpand(@__MODULE__, :(KernelAbstractions.@localmem Int (16,))) +@eval @kernel function localmem_pre_expanded(A) + i = @index(Local, Linear) + I = @index(Global, Linear) + lmem = $pre_expanded_localmem + lmem[i] = i + @synchronize + @inbounds A[I] = lmem[16 - i + 1] +end + function localmem_testsuite(backend, ArrayT) + @testset "pre-expanded @localmem" begin + @test deepcopy(pre_expanded_localmem) == pre_expanded_localmem + A = ArrayT{Int}(undef, 32) + localmem_pre_expanded(backend(), 16)(A, ndrange = size(A)) + synchronize(backend()) + @test Array(A) == [17 .- (1:16); 17 .- (1:16)] + end + @testset "kernels" begin @testset for kernel! in (localmem(backend(), 16), localmem2(backend(), 16), localmem_unsafe_indices(backend(), 16), many_localmem(backend(), 16)) A = ArrayT{Int}(undef, 64) diff --git a/test/spawn.jl b/test/spawn.jl index 88a2b1700..2e9995691 100644 --- a/test/spawn.jl +++ b/test/spawn.jl @@ -9,6 +9,12 @@ end end function spawn_testsuite(Backend, AT) + @testset "expansion can be copied" begin + # tools that generate code copy expressions with `deepcopy`, which fails for modules + ex = macroexpand(@__MODULE__, :(KernelAbstractions.@spawn backend nothing)) + @test deepcopy(ex) isa Expr + end + backend = Backend() @testset "ordered after the spawning task" begin