diff --git a/src/ccalls.jl b/src/ccalls.jl index 3fe88e0..94d9182 100644 --- a/src/ccalls.jl +++ b/src/ccalls.jl @@ -91,64 +91,71 @@ render_arg(io, arg::Base.RefValue{T}) where {T} = print(io, "Ref{", T, "}") ## version of ccall that calls jl_gc_safe_enter|leave around the inner ccall - -# TODO: replace with JuliaLang/julia#49933 once merged - # note that this is generally only safe with functions that do not call back into Julia. # when callbacks occur, the code should ensure the GC is not running by wrapping the code # in the `@gcunsafe` macro -function ccall_macro_lower(func, rettype, types, args, nreq) - # instead of re-using ccall or Expr(:foreigncall) to perform argument conversion, - # we need to do so ourselves in order to insert a jl_gc_safe_enter|leave - # just around the inner ccall - - cconvert_exprs = [] - cconvert_args = [] - for (typ, arg) in zip(types, args) - var = gensym("$(func)_cconvert") - push!(cconvert_args, var) - push!(cconvert_exprs, :($var = Base.cconvert($(esc(typ)), $(esc(arg))))) - end - - unsafe_convert_exprs = [] - unsafe_convert_args = [] - for (typ, arg) in zip(types, cconvert_args) - var = gensym("$(func)_unsafe_convert") - push!(unsafe_convert_args, var) - push!(unsafe_convert_exprs, :($var = Base.unsafe_convert($(esc(typ)), $arg))) - end - - call = quote - $(unsafe_convert_exprs...) - - gc_state = @ccall(jl_gc_safe_enter()::Int8) - ret = ccall( - $(esc(func)), $(esc(rettype)), $(Expr(:tuple, map(esc, types)...)), - $(unsafe_convert_args...) - ) - @ccall(jl_gc_safe_leave(gc_state::Int8)::Cvoid) - ret - end - - return quote - @inline - $(cconvert_exprs...) - GC.@preserve $(cconvert_args...) $(call) - end -end +const HAS_CCALL_GCSAFE = VERSION >= v"1.13.0-DEV.70" || v"1.12-DEV.2029" <= VERSION < v"1.13-" """ - @gcsafe_ccall ... +@gcsafe_ccall ... Call a foreign function just like `@ccall`, but marking it safe for the GC to run. This is useful for functions that may block, so that the GC isn't blocked from running, but may also be required to prevent deadlocks (see JuliaGPU/CUDA.jl#2261). Note that this is generally only safe with non-Julia C functions that do not call back into -Julia. When using callbacks, the code should make sure to transition back into GC-unsafe -mode using the `@gcunsafe` macro. +the Julia directly. """ -macro gcsafe_ccall(expr) - return ccall_macro_lower(Base.ccall_macro_parse(expr)...) -end +macro gcsafe_ccall end + +if HAS_CCALL_GCSAFE + macro gcsafe_ccall(expr) + exprs = Any[:(gc_safe = true), expr] + return Base.ccall_macro_lower((:ccall), Base.ccall_macro_parse(exprs)...) + end +else + function ccall_macro_lower(func, rettype, types, args, nreq) + # instead of re-using ccall or Expr(:foreigncall) to perform argument conversion, + # we need to do so ourselves in order to insert a jl_gc_safe_enter|leave + # just around the inner ccall + + cconvert_exprs = [] + cconvert_args = [] + for (typ, arg) in zip(types, args) + var = gensym("$(func)_cconvert") + push!(cconvert_args, var) + push!(cconvert_exprs, :($var = Base.cconvert($(esc(typ)), $(esc(arg))))) + end + + unsafe_convert_exprs = [] + unsafe_convert_args = [] + for (typ, arg) in zip(types, cconvert_args) + var = gensym("$(func)_unsafe_convert") + push!(unsafe_convert_args, var) + push!(unsafe_convert_exprs, :($var = Base.unsafe_convert($(esc(typ)), $arg))) + end + + call = quote + $(unsafe_convert_exprs...) + + gc_state = @ccall(jl_gc_safe_enter()::Int8) + ret = ccall( + $(esc(func)), $(esc(rettype)), $(Expr(:tuple, map(esc, types)...)), + $(unsafe_convert_args...) + ) + @ccall(jl_gc_safe_leave(gc_state::Int8)::Cvoid) + ret + end + + return quote + @inline + $(cconvert_exprs...) + GC.@preserve $(cconvert_args...) $(call) + end + end + + macro gcsafe_ccall(expr) + return ccall_macro_lower(Base.ccall_macro_parse(expr)...) + end +end # HAS_CCALL_GCSAFE diff --git a/test/Project.toml b/test/Project.toml index 7fb0db5..b91e6e3 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,4 +1,5 @@ [deps] +InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [extras] diff --git a/test/runtests.jl b/test/runtests.jl index 2a93553..d26ca7f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,5 +1,6 @@ using Test using GPUToolbox +using InteractiveUtils @testset "GPUToolbox.jl" begin @testset "SimpleVersion" begin @@ -42,5 +43,27 @@ using GPUToolbox @test !(sv2 > sv2) # Default end - # TODO: @debug_ccall and @gcsafe_ccall tests + @testset "gcsafe_ccall" begin + function gc_safe_ccall() + # jl_rand is marked as JL_NOTSAFEPOINT + @gcsafe_ccall jl_rand()::UInt64 + end + + let llvm = sprint(code_llvm, gc_safe_ccall, ()) + # check that the call works + @test gc_safe_ccall() isa UInt64 + # v1.10 is hard to test since ccall are just raw runtime pointers + if VERSION >= v"1.11" + if !GPUToolbox.HAS_CCALL_GCSAFE + # check for the gc_safe store + @test occursin("jl_gc_safe_enter", llvm) + @test occursin("jl_gc_safe_leave", llvm) + else + @test occursin("store atomic i8 2", llvm) + end + end + end + end + + # TODO: @debug_ccall tests end