Skip to content

Commit 12fbf20

Browse files
Use support for gc_safe=true in Base if available (#6)
Co-authored-by: Christian Guinard <28689358+christiangnrd@users.noreply.github.com>
1 parent 171b80c commit 12fbf20

File tree

3 files changed

+80
-49
lines changed

3 files changed

+80
-49
lines changed

src/ccalls.jl

Lines changed: 55 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -91,64 +91,71 @@ render_arg(io, arg::Base.RefValue{T}) where {T} = print(io, "Ref{", T, "}")
9191

9292

9393
## version of ccall that calls jl_gc_safe_enter|leave around the inner ccall
94-
95-
# TODO: replace with JuliaLang/julia#49933 once merged
96-
9794
# note that this is generally only safe with functions that do not call back into Julia.
9895
# when callbacks occur, the code should ensure the GC is not running by wrapping the code
9996
# in the `@gcunsafe` macro
10097

101-
function ccall_macro_lower(func, rettype, types, args, nreq)
102-
# instead of re-using ccall or Expr(:foreigncall) to perform argument conversion,
103-
# we need to do so ourselves in order to insert a jl_gc_safe_enter|leave
104-
# just around the inner ccall
105-
106-
cconvert_exprs = []
107-
cconvert_args = []
108-
for (typ, arg) in zip(types, args)
109-
var = gensym("$(func)_cconvert")
110-
push!(cconvert_args, var)
111-
push!(cconvert_exprs, :($var = Base.cconvert($(esc(typ)), $(esc(arg)))))
112-
end
113-
114-
unsafe_convert_exprs = []
115-
unsafe_convert_args = []
116-
for (typ, arg) in zip(types, cconvert_args)
117-
var = gensym("$(func)_unsafe_convert")
118-
push!(unsafe_convert_args, var)
119-
push!(unsafe_convert_exprs, :($var = Base.unsafe_convert($(esc(typ)), $arg)))
120-
end
121-
122-
call = quote
123-
$(unsafe_convert_exprs...)
124-
125-
gc_state = @ccall(jl_gc_safe_enter()::Int8)
126-
ret = ccall(
127-
$(esc(func)), $(esc(rettype)), $(Expr(:tuple, map(esc, types)...)),
128-
$(unsafe_convert_args...)
129-
)
130-
@ccall(jl_gc_safe_leave(gc_state::Int8)::Cvoid)
131-
ret
132-
end
133-
134-
return quote
135-
@inline
136-
$(cconvert_exprs...)
137-
GC.@preserve $(cconvert_args...) $(call)
138-
end
139-
end
98+
const HAS_CCALL_GCSAFE = VERSION >= v"1.13.0-DEV.70" || v"1.12-DEV.2029" <= VERSION < v"1.13-"
14099

141100
"""
142-
@gcsafe_ccall ...
101+
@gcsafe_ccall ...
143102
144103
Call a foreign function just like `@ccall`, but marking it safe for the GC to run. This is
145104
useful for functions that may block, so that the GC isn't blocked from running, but may also
146105
be required to prevent deadlocks (see JuliaGPU/CUDA.jl#2261).
147106
148107
Note that this is generally only safe with non-Julia C functions that do not call back into
149-
Julia. When using callbacks, the code should make sure to transition back into GC-unsafe
150-
mode using the `@gcunsafe` macro.
108+
the Julia directly.
151109
"""
152-
macro gcsafe_ccall(expr)
153-
return ccall_macro_lower(Base.ccall_macro_parse(expr)...)
154-
end
110+
macro gcsafe_ccall end
111+
112+
if HAS_CCALL_GCSAFE
113+
macro gcsafe_ccall(expr)
114+
exprs = Any[:(gc_safe = true), expr]
115+
return Base.ccall_macro_lower((:ccall), Base.ccall_macro_parse(exprs)...)
116+
end
117+
else
118+
function ccall_macro_lower(func, rettype, types, args, nreq)
119+
# instead of re-using ccall or Expr(:foreigncall) to perform argument conversion,
120+
# we need to do so ourselves in order to insert a jl_gc_safe_enter|leave
121+
# just around the inner ccall
122+
123+
cconvert_exprs = []
124+
cconvert_args = []
125+
for (typ, arg) in zip(types, args)
126+
var = gensym("$(func)_cconvert")
127+
push!(cconvert_args, var)
128+
push!(cconvert_exprs, :($var = Base.cconvert($(esc(typ)), $(esc(arg)))))
129+
end
130+
131+
unsafe_convert_exprs = []
132+
unsafe_convert_args = []
133+
for (typ, arg) in zip(types, cconvert_args)
134+
var = gensym("$(func)_unsafe_convert")
135+
push!(unsafe_convert_args, var)
136+
push!(unsafe_convert_exprs, :($var = Base.unsafe_convert($(esc(typ)), $arg)))
137+
end
138+
139+
call = quote
140+
$(unsafe_convert_exprs...)
141+
142+
gc_state = @ccall(jl_gc_safe_enter()::Int8)
143+
ret = ccall(
144+
$(esc(func)), $(esc(rettype)), $(Expr(:tuple, map(esc, types)...)),
145+
$(unsafe_convert_args...)
146+
)
147+
@ccall(jl_gc_safe_leave(gc_state::Int8)::Cvoid)
148+
ret
149+
end
150+
151+
return quote
152+
@inline
153+
$(cconvert_exprs...)
154+
GC.@preserve $(cconvert_args...) $(call)
155+
end
156+
end
157+
158+
macro gcsafe_ccall(expr)
159+
return ccall_macro_lower(Base.ccall_macro_parse(expr)...)
160+
end
161+
end # HAS_CCALL_GCSAFE

test/Project.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
[deps]
2+
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
23
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
34

45
[extras]

test/runtests.jl

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Test
22
using GPUToolbox
3+
using InteractiveUtils
34

45
@testset "GPUToolbox.jl" begin
56
@testset "SimpleVersion" begin
@@ -42,5 +43,27 @@ using GPUToolbox
4243
@test !(sv2 > sv2) # Default
4344
end
4445

45-
# TODO: @debug_ccall and @gcsafe_ccall tests
46+
@testset "gcsafe_ccall" begin
47+
function gc_safe_ccall()
48+
# jl_rand is marked as JL_NOTSAFEPOINT
49+
@gcsafe_ccall jl_rand()::UInt64
50+
end
51+
52+
let llvm = sprint(code_llvm, gc_safe_ccall, ())
53+
# check that the call works
54+
@test gc_safe_ccall() isa UInt64
55+
# v1.10 is hard to test since ccall are just raw runtime pointers
56+
if VERSION >= v"1.11"
57+
if !GPUToolbox.HAS_CCALL_GCSAFE
58+
# check for the gc_safe store
59+
@test occursin("jl_gc_safe_enter", llvm)
60+
@test occursin("jl_gc_safe_leave", llvm)
61+
else
62+
@test occursin("store atomic i8 2", llvm)
63+
end
64+
end
65+
end
66+
end
67+
68+
# TODO: @debug_ccall tests
4669
end

0 commit comments

Comments
 (0)