|
1 | 1 | # utilities for calling foreign functionality more conveniently |
2 | 2 |
|
3 | | -export @checked, with_workspace, with_workspaces, @debug_ccall, @gcsafe_ccall |
| 3 | +export @checked, with_workspace, with_workspaces, |
| 4 | + @debug_ccall, @gcsafe_ccall, @gcunsafe_callback |
4 | 5 |
|
5 | 6 |
|
6 | 7 | ## function wrapper for checking the return value of a function |
@@ -166,10 +167,8 @@ render_arg(io, arg::Union{<:Base.RefValue, AbstractArray}) = summary(io, arg) |
166 | 167 | # TODO: replace with JuliaLang/julia#49933 once merged |
167 | 168 |
|
168 | 169 | # note that this is generally only safe with functions that do not call back into Julia. |
169 | | -# when callbacks occur, the code should ensure the GC is not running: |
170 | | -# - on 1.10 and later, everything is fine because of safepoint_on_entry |
171 | | -# - on 1.9, @cfunction-based callbacks are fine because they transition to gc_unsafe |
172 | | -# - on 1.8 and earlier, the code should explicitly call GC.safepoint()! |
| 170 | +# when callbacks occur, the code should ensure the GC is not running by wrapping the code |
| 171 | +# in the `@gcunsafe` macro |
173 | 172 |
|
174 | 173 | function ccall_macro_lower(func, rettype, types, args, nreq) |
175 | 174 | # instead of re-using ccall or Expr(:foreigncall) to perform argument conversion, |
@@ -213,7 +212,50 @@ function ccall_macro_lower(func, rettype, types, args, nreq) |
213 | 212 | end |
214 | 213 | end |
215 | 214 |
|
| 215 | +""" |
| 216 | + @gcsafe_ccall ... |
| 217 | +
|
| 218 | +Call a foreign function just like `@ccall`, but marking it safe for the GC to run. This is |
| 219 | +useful for functions that may block, so that the GC isn't blocked from running, but may also |
| 220 | +be required to prevent deadlocks (see JuliaGPU/CUDA.jl#2261). |
| 221 | +
|
| 222 | +Note that this is generally only safe with non-Julia C functions that do not call back into |
| 223 | +Julia. When using callbacks, the code should make sure to transition back into GC-unsafe |
| 224 | +mode using the `@gcunsafe` macro. |
| 225 | +""" |
216 | 226 | macro gcsafe_ccall(expr) |
217 | 227 | ccall_macro_lower(Base.ccall_macro_parse(expr)...) |
218 | 228 | end |
219 | 229 |
|
| 230 | +""" |
| 231 | + @gcunsafe_callback function callback(...) |
| 232 | + ... |
| 233 | + end |
| 234 | +
|
| 235 | +Mark a callback function as unsafe for the GC to run. This is normally the default for |
| 236 | +Julia code, and is meant to be used in combination with `@gcsafe_ccall`. |
| 237 | +""" |
| 238 | +macro gcunsafe_callback(ex) |
| 239 | + if VERSION >= v"1.9" |
| 240 | + # on 1.9+, `@cfunction` already transitions to GC-unsafe mode |
| 241 | + return esc(ex) |
| 242 | + end |
| 243 | + |
| 244 | + # parse the function definition |
| 245 | + @assert Meta.isexpr(ex, :function) |
| 246 | + sig = ex.args[1] |
| 247 | + @assert Meta.isexpr(sig, :call) |
| 248 | + body = ex.args[2] |
| 249 | + @assert Meta.isexpr(body, :block) |
| 250 | + |
| 251 | + gcunsafe_body = quote |
| 252 | + gc_state = @ccall(jl_gc_unsafe_enter()::Int8) |
| 253 | + try |
| 254 | + $(ex) |
| 255 | + finally |
| 256 | + @ccall(jl_gc_unsafe_leave(gc_state::Int8)::Cvoid) |
| 257 | + end |
| 258 | + end |
| 259 | + |
| 260 | + return esc(Expr(:function, sig, gcunsafe_body)) |
| 261 | +end |
0 commit comments