From a1f827a2cd310835fe2d79b72d9c235a0f274777 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Wed, 30 Sep 2026 17:23:02 +0200 Subject: [PATCH] Support interpolated function pointers in the @gcsafe_ccall fallback On Julia versions without native support for GC-safe calls, @gcsafe_ccall lowers the call itself, but it did not handle function pointers interpolated with `$`, as Base's @ccall does. Calling one failed with a syntax error ("$" expression outside quote). --- src/ccalls.jl | 25 ++++++++++++++++++++++--- test/runtests.jl | 8 ++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/ccalls.jl b/src/ccalls.jl index ab6a3ca..80a91da 100644 --- a/src/ccalls.jl +++ b/src/ccalls.jl @@ -120,10 +120,28 @@ else # we need to do so ourselves in order to insert a jl_gc_safe_enter|leave # just around the inner ccall + # like Base, support calling a function pointer that is interpolated with `$` + statements = [] + if Meta.isexpr(func, :$) + fptr = gensym("fptr") + push!(statements, :($fptr = $(esc(func.args[1])))) + name = QuoteNode(func.args[1]) + push!(statements, quote + if !isa($fptr, Ptr{Cvoid}) + throw(ArgumentError(LazyString("interpolated function `", $name, + "` was not a Ptr{Cvoid}, but ", + typeof($fptr)))) + end + end) + func = fptr + else + func = esc(func) + end + cconvert_exprs = [] cconvert_args = [] for (typ, arg) in zip(types, args) - var = gensym("$(func)_cconvert") + var = gensym("cconvert") push!(cconvert_args, var) push!(cconvert_exprs, :($var = Base.cconvert($(esc(typ)), $(esc(arg))))) end @@ -131,7 +149,7 @@ else unsafe_convert_exprs = [] unsafe_convert_args = [] for (typ, arg) in zip(types, cconvert_args) - var = gensym("$(func)_unsafe_convert") + var = gensym("unsafe_convert") push!(unsafe_convert_args, var) push!(unsafe_convert_exprs, :($var = Base.unsafe_convert($(esc(typ)), $arg))) end @@ -141,7 +159,7 @@ else gc_state = @ccall(jl_gc_safe_enter()::Int8) ret = ccall( - $(esc(func)), $(esc(rettype)), $(Expr(:tuple, map(esc, types)...)), + $func, $(esc(rettype)), $(Expr(:tuple, map(esc, types)...)), $(unsafe_convert_args...) ) @ccall(jl_gc_safe_leave(gc_state::Int8)::Cvoid) @@ -150,6 +168,7 @@ else return quote @inline + $(statements...) $(cconvert_exprs...) GC.@preserve $(cconvert_args...) $(call) end diff --git a/test/runtests.jl b/test/runtests.jl index db0159a..12d85d4 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -62,6 +62,14 @@ using IOCapture @gcsafe_ccall jl_rand()::UInt64 end + # interpolated function pointers + fptr = cglobal(:jl_rand) + @test (@gcsafe_ccall $fptr()::UInt64) isa UInt64 + if !GPUToolbox.HAS_CCALL_GCSAFE + # like `@ccall` on these Julia versions, the fallback checks the pointer + @test_throws ArgumentError @gcsafe_ccall $(nothing)()::UInt64 + end + let llvm = sprint(code_llvm, gc_safe_ccall, ()) # check that the call works @test gc_safe_ccall() isa UInt64