Skip to content

Commit 7f0437f

Browse files
committed
Eliminating allocation of ccall root in simple cases
Also fix and test an invalid `unsafe_convert` definition in Base. The result of `pointer_from_objref` on isbits types is never valid to use across safepoint.
1 parent e95dda8 commit 7f0437f

File tree

4 files changed

+97
-6
lines changed

4 files changed

+97
-6
lines changed

base/inference.jl

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5227,6 +5227,27 @@ function occurs_outside_getfield(@nospecialize(e), @nospecialize(sym),
52275227
if head === :(=)
52285228
return occurs_outside_getfield(e.args[2], sym, sv,
52295229
field_count, field_names)
5230+
elseif head === :foreigncall
5231+
args = e.args
5232+
nccallargs = args[5]::Int
5233+
# Only arguments escape the structure/layout of the object,
5234+
# GC root arguments do not.
5235+
# Also note that only being used in the root slot for this ccall itself
5236+
# does **not** mean that the object is not needed during the ccall.
5237+
# However, if its address is never taken
5238+
# and the object is never used in a way that escapes its layout, we can be sure
5239+
# that there's no way the user code can rely on the heap allocation of this object.
5240+
for i in 1:length(args)
5241+
a = args[i]
5242+
if i > 5 + nccallargs && symequal(a, sym)
5243+
# No need to verify indices, uninitialized members can be
5244+
# ignored in root slot.
5245+
continue
5246+
end
5247+
if occurs_outside_getfield(a, sym, sv, field_count, field_names)
5248+
return true
5249+
end
5250+
end
52305251
else
52315252
if (head === :block && isa(sym, Slot) &&
52325253
sv.src.slotflags[slot_id(sym)] & Slot_UsedUndef == 0)
@@ -5810,8 +5831,11 @@ end
58105831
function replace_getfield!(e::Expr, tupname, vals, field_names, sv::InferenceState)
58115832
for i = 1:length(e.args)
58125833
a = e.args[i]
5813-
if isa(a,Expr) && is_known_call(a, getfield, sv.src, sv.mod) &&
5814-
symequal(a.args[2],tupname)
5834+
if !isa(a, Expr)
5835+
continue
5836+
end
5837+
a = a::Expr
5838+
if is_known_call(a, getfield, sv.src, sv.mod) && symequal(a.args[2], tupname)
58155839
idx = if isa(a.args[3], Int)
58165840
a.args[3]
58175841
else
@@ -5840,8 +5864,23 @@ function replace_getfield!(e::Expr, tupname, vals, field_names, sv::InferenceSta
58405864
end
58415865
end
58425866
e.args[i] = val
5843-
elseif isa(a, Expr)
5844-
replace_getfield!(a::Expr, tupname, vals, field_names, sv)
5867+
else
5868+
if a.head === :foreigncall
5869+
args = a.args
5870+
nccallargs = args[5]::Int
5871+
le = length(args)
5872+
next_i = 6 + nccallargs
5873+
while next_i <= le
5874+
i = next_i
5875+
next_i += 1
5876+
5877+
symequal(args[i], tupname) || continue
5878+
# Replace the gc root argument with its fields
5879+
splice!(args, i, vals)
5880+
next_i += length(vals) - 1
5881+
end
5882+
end
5883+
replace_getfield!(a, tupname, vals, field_names, sv)
58455884
end
58465885
end
58475886
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})

test/ccall.jl

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1285,3 +1285,21 @@ evalf_callback_c_19805{FUNC_FT}(ci::callinfos_19805{FUNC_FT}) = cfunction(
12851285

12861286
@test_throws(ErrorException("ccall: the type of argument 1 doesn't correspond to a C type"),
12871287
evalf_callback_c_19805( callinfos_19805(sin) ))
1288+
1289+
# test Ref{abstract_type} calling parameter passes a heap box
1290+
abstract type Abstract22734 end
1291+
struct Bits22734 <: Abstract22734
1292+
x::Int
1293+
y::Float64
1294+
end
1295+
function cb22734(ptr::Ptr{Void})
1296+
gc()
1297+
obj = unsafe_pointer_to_objref(ptr)::Bits22734
1298+
obj.x + obj.y
1299+
end
1300+
ptr22734 = cfunction(cb22734, Float64, Tuple{Ptr{Void}})
1301+
function caller22734(ptr)
1302+
obj = Bits22734(12, 20)
1303+
ccall(ptr, Float64, (Ref{Abstract22734},), obj)
1304+
end
1305+
@test caller22734(ptr22734) === 32.0

test/codegen.jl

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,10 +128,37 @@ function compare_large_struct(a)
128128
end
129129
end
130130

131+
mutable struct MutableStruct
132+
a::Int
133+
MutableStruct() = new()
134+
end
135+
136+
breakpoint_mutable(a::MutableStruct) = ccall(:jl_breakpoint, Void, (Ref{MutableStruct},), a)
137+
138+
# Allocation with uninitialized field as gcroot
139+
mutable struct BadRef
140+
x::MutableStruct
141+
y::MutableStruct
142+
BadRef(x) = new(x)
143+
end
144+
Base.cconvert(::Type{Ptr{BadRef}}, a::MutableStruct) = BadRef(a)
145+
Base.unsafe_convert(::Type{Ptr{BadRef}}, ar::BadRef) = Ptr{BadRef}(pointer_from_objref(ar.x))
146+
147+
breakpoint_badref(a::MutableStruct) = ccall(:jl_breakpoint, Void, (Ptr{BadRef},), a)
148+
131149
if opt_level > 0
132150
@test !contains(get_llvm(isequal, Tuple{Nullable{BigFloat}, Nullable{BigFloat}}), "%gcframe")
133151
@test !contains(get_llvm(pointer_not_safepoint, Tuple{}), "%gcframe")
134152
compare_large_struct_ir = get_llvm(compare_large_struct, Tuple{typeof(create_ref_struct())})
135153
@test contains(compare_large_struct_ir, "call i32 @memcmp")
136154
@test !contains(compare_large_struct_ir, "%gcframe")
155+
156+
@test contains(get_llvm(MutableStruct, Tuple{}), "jl_gc_pool_alloc")
157+
breakpoint_mutable_ir = get_llvm(breakpoint_mutable, Tuple{MutableStruct})
158+
@test !contains(breakpoint_mutable_ir, "%gcframe")
159+
@test !contains(breakpoint_mutable_ir, "jl_gc_pool_alloc")
160+
161+
breakpoint_badref_ir = get_llvm(breakpoint_badref, Tuple{MutableStruct})
162+
@test !contains(breakpoint_badref_ir, "%gcframe")
163+
@test !contains(breakpoint_badref_ir, "jl_gc_pool_alloc")
137164
end

0 commit comments

Comments
 (0)