Skip to content

Commit 526b83e

Browse files
authored
Merge pull request #22734 from JuliaLang/yyc/foreigncall
Eliminating allocation of ccall root in simple cases
2 parents 2096248 + 245868c commit 526b83e

File tree

11 files changed

+211
-83
lines changed

11 files changed

+211
-83
lines changed

base/docs/helpdb/Base.jl

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,14 @@ getindex(collection, key...)
115115
"""
116116
cconvert(T,x)
117117
118-
Convert `x` to a value of type `T`, typically by calling `convert(T,x)`
118+
Convert `x` to a value to be passed to C code as type `T`, typically by calling `convert(T, x)`.
119119
120120
In cases where `x` cannot be safely converted to `T`, unlike [`convert`](@ref), `cconvert` may
121121
return an object of a type different from `T`, which however is suitable for
122-
[`unsafe_convert`](@ref) to handle.
122+
[`unsafe_convert`](@ref) to handle. The result of this function should be kept valid (for the GC)
123+
until the result of [`unsafe_convert`](@ref) is not needed anymore.
124+
This can be used to allocate memory that will be accessed by the `ccall`.
125+
If multiple objects need to be allocated, a tuple of the objects can be used as return value.
123126
124127
Neither `convert` nor `cconvert` should take a Julia object and turn it into a `Ptr`.
125128
"""
@@ -881,7 +884,8 @@ trunc
881884
"""
882885
unsafe_convert(T,x)
883886
884-
Convert `x` to a value of type `T`
887+
Convert `x` to a C argument of type `T`
888+
where the input `x` must be the return value of `cconvert(T, ...)`.
885889
886890
In cases where [`convert`](@ref) would need to take a Julia object
887891
and turn it into a `Ptr`, this function should be used to define and perform
@@ -895,6 +899,8 @@ but `x=[a,b,c]` is not.
895899
The `unsafe` prefix on this function indicates that using the result of this function after
896900
the `x` argument to this function is no longer accessible to the program may cause undefined
897901
behavior, including program corruption or segfaults, at any later time.
902+
903+
See also [`cconvert`](@ref)
898904
"""
899905
unsafe_convert
900906

base/inference.jl

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3687,6 +3687,10 @@ function substitute!(@nospecialize(e), na::Int, argexprs::Vector{Any}, @nospecia
36873687
for argt
36883688
in e.args[3] ]
36893689
e.args[3] = svec(argtuple...)
3690+
elseif i == 4
3691+
@assert isa((e.args[4]::QuoteNode).value, Symbol)
3692+
elseif i == 5
3693+
@assert isa(e.args[5], Int)
36903694
else
36913695
e.args[i] = substitute!(e.args[i], na, argexprs, spsig, spvals, offset)
36923696
end
@@ -4766,16 +4770,16 @@ function inlining_pass(e::Expr, sv::InferenceState, stmts, ins)
47664770
# by the interpreter and inlining might put in something it can't handle,
47674771
# like another ccall (or try to move the variables out into the function)
47684772
if e.head === :foreigncall
4769-
# 3 is rewritten to 1 below to handle the callee.
4770-
i0 = 3
4773+
# 5 is rewritten to 1 below to handle the callee.
4774+
i0 = 5
47714775
isccall = true
47724776
elseif is_known_call(e, Core.Intrinsics.llvmcall, sv.src, sv.mod)
47734777
i0 = 5
47744778
end
47754779
has_stmts = false # needed to preserve order-of-execution
47764780
prev_stmts_length = length(stmts)
47774781
for _i = length(eargs):-1:i0
4778-
if isccall && _i == 3
4782+
if isccall && _i == 5
47794783
i = 1
47804784
isccallee = true
47814785
else
@@ -4830,10 +4834,21 @@ function inlining_pass(e::Expr, sv::InferenceState, stmts, ins)
48304834
end
48314835
if isccall
48324836
le = length(eargs)
4833-
for i = 4:2:(le - 1)
4834-
if eargs[i] === eargs[i + 1]
4835-
eargs[i + 1] = 0
4837+
nccallargs = eargs[5]::Int
4838+
ccallargs = ObjectIdDict()
4839+
for i in 6:(5 + nccallargs)
4840+
ccallargs[eargs[i]] = nothing
4841+
end
4842+
i = 6 + nccallargs
4843+
while i <= le
4844+
rootarg = eargs[i]
4845+
if haskey(ccallargs, rootarg)
4846+
deleteat!(eargs, i)
4847+
le -= 1
4848+
elseif i < le
4849+
ccallargs[rootarg] = nothing
48364850
end
4851+
i += 1
48374852
end
48384853
end
48394854
if e.head !== :call
@@ -5216,6 +5231,27 @@ function occurs_outside_getfield(@nospecialize(e), @nospecialize(sym),
52165231
if head === :(=)
52175232
return occurs_outside_getfield(e.args[2], sym, sv,
52185233
field_count, field_names)
5234+
elseif head === :foreigncall
5235+
args = e.args
5236+
nccallargs = args[5]::Int
5237+
# Only arguments escape the structure/layout of the object,
5238+
# GC root arguments do not.
5239+
# Also note that only being used in the root slot for this ccall itself
5240+
# does **not** mean that the object is not needed during the ccall.
5241+
# However, if its address is never taken
5242+
# and the object is never used in a way that escapes its layout, we can be sure
5243+
# that there's no way the user code can rely on the heap allocation of this object.
5244+
for i in 1:length(args)
5245+
a = args[i]
5246+
if i > 5 + nccallargs && symequal(a, sym)
5247+
# No need to verify indices, uninitialized members can be
5248+
# ignored in root slot.
5249+
continue
5250+
end
5251+
if occurs_outside_getfield(a, sym, sv, field_count, field_names)
5252+
return true
5253+
end
5254+
end
52195255
else
52205256
if (head === :block && isa(sym, Slot) &&
52215257
sv.src.slotflags[slot_id(sym)] & Slot_UsedUndef == 0)
@@ -5799,8 +5835,11 @@ end
57995835
function replace_getfield!(e::Expr, tupname, vals, field_names, sv::InferenceState)
58005836
for i = 1:length(e.args)
58015837
a = e.args[i]
5802-
if isa(a,Expr) && is_known_call(a, getfield, sv.src, sv.mod) &&
5803-
symequal(a.args[2],tupname)
5838+
if !isa(a, Expr)
5839+
continue
5840+
end
5841+
a = a::Expr
5842+
if is_known_call(a, getfield, sv.src, sv.mod) && symequal(a.args[2], tupname)
58045843
idx = if isa(a.args[3], Int)
58055844
a.args[3]
58065845
else
@@ -5829,8 +5868,23 @@ function replace_getfield!(e::Expr, tupname, vals, field_names, sv::InferenceSta
58295868
end
58305869
end
58315870
e.args[i] = val
5832-
elseif isa(a, Expr)
5833-
replace_getfield!(a::Expr, tupname, vals, field_names, sv)
5871+
else
5872+
if a.head === :foreigncall
5873+
args = a.args
5874+
nccallargs = args[5]::Int
5875+
le = length(args)
5876+
next_i = 6 + nccallargs
5877+
while next_i <= le
5878+
i = next_i
5879+
next_i += 1
5880+
5881+
symequal(args[i], tupname) || continue
5882+
# Replace the gc root argument with its fields
5883+
splice!(args, i, vals)
5884+
next_i += length(vals) - 1
5885+
end
5886+
end
5887+
replace_getfield!(a, tupname, vals, field_names, sv)
58345888
end
58355889
end
58365890
end

base/refpointer.jl

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,16 @@ convert(::Type{Ref{T}}, x) where {T} = RefValue{T}(x)
5454

5555
function unsafe_convert(P::Type{Ptr{T}}, b::RefValue{T}) where T
5656
if isbits(T)
57-
return convert(P, data_pointer_from_objref(b))
57+
return convert(P, pointer_from_objref(b))
58+
elseif isleaftype(T)
59+
return convert(P, pointer_from_objref(b.x))
5860
else
59-
return convert(P, data_pointer_from_objref(b.x))
61+
# If the slot is not leaf type, it could be either isbits or not.
62+
# If it is actually an isbits type and the type inference can infer that
63+
# it can rebox the `b.x` if we simply call `pointer_from_objref(b.x)` on it.
64+
# Instead, explicitly load the pointer from the `RefValue` so that the pointer
65+
# is the same as the one rooted in the `RefValue` object.
66+
return convert(P, pointerref(Ptr{Ptr{Void}}(pointer_from_objref(b)), 1, 0))
6067
end
6168
end
6269
function unsafe_convert(P::Type{Ptr{Any}}, b::RefValue{Any})

doc/src/devdocs/llvm.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ pointer which drops the reference to the array value. However, we of course
260260
need to make sure that the array does stay alive while we're doing the `ccall`.
261261
To understand how this is done, first recall the lowering of the above code:
262262
```julia
263-
return $(Expr(:foreigncall, :(:foo), Void, svec(Ptr{Float64}), :($(Expr(:foreigncall, :(:jl_array_ptr), Ptr{Float64}, svec(Any), :(A), 0))), :(A)))
263+
return $(Expr(:foreigncall, :(:foo), Void, svec(Ptr{Float64}), :(:ccall), 1, :($(Expr(:foreigncall, :(:jl_array_ptr), Ptr{Float64}, svec(Any), :(:ccall), 1, :(A)))), :(A)))
264264
```
265265
The last `:(A)`, is an extra argument list inserted during lowering that informs
266266
the code generator which Julia level values need to be kept alive for the

doc/src/manual/calling-c-and-fortran-code.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,8 +255,9 @@ ccall((:foo, "libfoo"), Void, (Int32, Float64),
255255
```
256256

257257
[`Base.cconvert()`](@ref) normally just calls [`convert()`](@ref), but can be defined to return an
258-
arbitrary new object more appropriate for passing to C. For example, this is used to convert an
259-
`Array` of objects (e.g. strings) to an array of pointers.
258+
arbitrary new object more appropriate for passing to C.
259+
This should be used to perform all allocations of memory that will be accessed by the C code.
260+
For example, this is used to convert an `Array` of objects (e.g. strings) to an array of pointers.
260261

261262
[`Base.unsafe_convert()`](@ref) handles conversion to `Ptr` types. It is considered unsafe because
262263
converting an object to a native pointer can hide the object from the garbage collector, causing

0 commit comments

Comments
 (0)