From c203b6b3b5f307f6ec8d4a9fd819194e5bb36216 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Tue, 13 Oct 2026 12:58:35 +0200 Subject: [PATCH] Preserve `@oneapi` arguments behind a function barrier `@oneapi` wrapped its arguments in `GC.@preserve` in the caller's code, where an argument can be a union of singleton types, e.g. the function in `for op in (max, min)`. Julia 1.12 and 1.13.0-1.13.1 emit a null operand for preserving such a value, crashing LLVM while compiling the caller (JuliaLang/julia#63482). Pass the arguments to a function instead, as CUDA.jl does, so that they have concrete types where they are preserved. Fixes #669 --- src/compiler/execution.jl | 42 ++++++++++++++++++++++----------------- test/execution.jl | 26 ++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 18 deletions(-) diff --git a/src/compiler/execution.jl b/src/compiler/execution.jl index 6dd470934..1f240efd9 100644 --- a/src/compiler/execution.jl +++ b/src/compiler/execution.jl @@ -86,7 +86,7 @@ macro oneapi(ex...) args = call.args[2:end] code = quote end - vars, var_exprs = assign_args!(code, args) + _, var_exprs = assign_args!(code, args) # group keyword argument macro_kwargs, compiler_kwargs, call_kwargs, other_kwargs = @@ -113,24 +113,9 @@ macro oneapi(ex...) # FIXME: macro hygiene wrt. escaping kwarg values (this broke with 1.5) # we esc() the whole thing now, necessitating gensyms... - @gensym f_var kernel_f kernel_args kernel_tt kernel - - # convert the arguments, call the compiler and launch the kernel - # while keeping the original arguments alive push!(code.args, - quote - $f_var = $f - GC.@preserve $(vars...) $f_var begin - $kernel_f = $kernel_convert($f_var) - $kernel_args = map($kernel_convert, ($(var_exprs...),)) - $kernel_tt = Tuple{map(Core.Typeof, $kernel_args)...} - $kernel = $zefunction($kernel_f, $kernel_tt; $(compiler_kwargs...)) - if $launch - $kernel($(var_exprs...); $(call_kwargs...)) - end - $kernel - end - end) + :($compile_and_launch($f, ($(var_exprs...),), $(Val(launch)), + (; $(call_kwargs...)), (; $(compiler_kwargs...))))) return esc(quote let @@ -140,6 +125,27 @@ macro oneapi(ex...) end +# convert the arguments, call the compiler and launch the kernel while keeping the original +# arguments alive. this is a function rather than part of the macro expansion so that the +# arguments have concrete types when they are preserved: `GC.@preserve` of a value that is a +# union of singletons, e.g. `op` in `for op in (max, min)`, crashes codegen on Julia 1.12 and +# 1.13.0-1.13.1 (JuliaLang/julia#63482). +@inline function compile_and_launch(f::F, args::Tuple, ::Val{launch}, + call_kwargs::NamedTuple, + compiler_kwargs::NamedTuple) where {F,launch} + GC.@preserve f args begin + kernel_f = kernel_convert(f) + kernel_args = map(kernel_convert, args) + kernel_tt = Tuple{map(Core.Typeof, kernel_args)...} + kernel = zefunction(kernel_f, kernel_tt; compiler_kwargs...) + if launch + kernel(args...; call_kwargs...) + end + kernel + end +end + + ## argument conversion struct KernelAdaptor end diff --git a/test/execution.jl b/test/execution.jl index 19d71ae65..d1babb855 100644 --- a/test/execution.jl +++ b/test/execution.jl @@ -313,6 +313,32 @@ end end +@testset "arguments of a union of singleton types" begin + # JuliaLang/julia#63482: `GC.@preserve` of such a value crashed codegen + function kernel(a, op) + @inbounds a[1] = op(a[1], Int16(1)) + return + end + + function compile(a) + kernels = [] + for op in (max, min) + push!(kernels, @oneapi launch=false kernel(a, op)) + end + kernels + end + @test length(compile(oneArray(Int16[5]))) == 2 + + function launch(a) + for op in (max, min) + @oneapi kernel(a, op) + end + Array(a) + end + @test launch(oneArray(Int16[5])) == Int16[1] +end + + @testset "immutables" begin # issue #15: immutables not passed by pointer