Skip to content

Commit

Permalink
[release-1.11] Avoid dropping call edges in presence of identical `…
Browse files Browse the repository at this point in the history
…invoke` edges (#57077)

The intermediate data structure here (used for edge de-duplication) was
accidentally recording `invoke` edges as if they were `call` edges.

That bug is _very_ frequently benign, but if there are identical call
and invoke edges in the edge list and the invoke edge is scanned first,
the call edge will be unsoundly dropped, leading to invalidation (#265)
bugs.

Already fixed on master (by happy accident) as part of
#54894
  • Loading branch information
topolarity authored Jan 21, 2025
1 parent 0a8ad1f commit 6cdafa9
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 3 deletions.
5 changes: 3 additions & 2 deletions src/staticdata_utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,8 @@ static void jl_collect_edges(jl_array_t *edges, jl_array_t *ext_targets, jl_arra
// (invokeTypes, c) => invoke
// (nullptr, invokeTypes) => missing call
// (invokeTypes, nullptr) => missing invoke (unused--inferred as Any)
void *target = ptrhash_get(&edges_map2, invokeTypes ? (void*)invokeTypes : (void*)callee);
void *key = invokeTypes ? (void*)invokeTypes : (void*)callee;
void *target = ptrhash_get(&edges_map2, key);
if (target == HT_NOTFOUND) {
size_t min_valid = 0;
size_t max_valid = ~(size_t)0;
Expand Down Expand Up @@ -551,7 +552,7 @@ static void jl_collect_edges(jl_array_t *edges, jl_array_t *ext_targets, jl_arra
jl_array_ptr_1d_push(ext_targets, callee);
jl_array_ptr_1d_push(ext_targets, matches);
target = (void*)((char*)HT_NOTFOUND + jl_array_nrows(ext_targets) / 3);
ptrhash_put(&edges_map2, (void*)callee, target);
ptrhash_put(&edges_map2, key, target);
}
idxs[++nt] = (char*)target - (char*)HT_NOTFOUND - 1;
}
Expand Down
39 changes: 38 additions & 1 deletion test/precompile.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1107,6 +1107,17 @@ precompile_test_harness("invoke") do dir
f44320(::Any) = 2
g44320() = invoke(f44320, Tuple{Any}, 0)
g44320()
# Issue #57115
f57115(@nospecialize(::Any)) = error("unimplemented")
function g57115(@nospecialize(x))
if @noinline rand(Bool)
# Add an 'invoke' edge from 'foo' to 'bar'
Core.invoke(f57115, Tuple{Any}, x)
else
# ... and also an identical 'call' edge
@noinline f57115(x)
end
end
# Adding new specializations should not invalidate `invoke`s
function getlast(itr)
Expand All @@ -1123,6 +1134,8 @@ precompile_test_harness("invoke") do dir
"""
module $CallerModule
using $InvokeModule
import $InvokeModule: f57115, g57115
# involving external modules
callf(x) = f(x)
callg(x) = x < 5 ? g(x) : invoke(g, Tuple{Real}, x)
Expand All @@ -1143,6 +1156,8 @@ precompile_test_harness("invoke") do dir
# Issue #44320
f44320(::Real) = 3
# Issue #57115
f57115(::Int) = 1
call_getlast(x) = getlast(x)
Expand All @@ -1163,6 +1178,7 @@ precompile_test_harness("invoke") do dir
internalnc(3)
call_getlast([1,2,3])
end
precompile(g57115, (Any,))
# Now that we've precompiled, invalidate with a new method that overrides the `invoke` dispatch
$InvokeModule.h(x::Integer) = -1
Expand All @@ -1183,7 +1199,7 @@ precompile_test_harness("invoke") do dir
for m in methods(func)
m.sig.parameters[end] === T && return m
end
error("no ::Real method found for $func")
error("no ::$T method found for $func")
end
function nvalid(mi::Core.MethodInstance)
isdefined(mi, :cache) || return 0
Expand Down Expand Up @@ -1229,6 +1245,27 @@ precompile_test_harness("invoke") do dir
m = only(methods(M.g44320))
@test (m.specializations::Core.MethodInstance).cache.max_world == typemax(UInt)

m = only(methods(M.g57115))
mi = m.specializations::Core.MethodInstance

f_m = get_method_for_type(M.f57115, Any)
f_mi = f_m.specializations::Core.MethodInstance

# Make sure that f57115(::Any) has a 'call' backedge to 'g57115'
has_f_call_backedge = false
it = Core.Compiler.BackedgeIterator(f_mi.backedges)
# Manually-written iterate(...) protocol, since this is in Core.Compiler
item = Core.Compiler.iterate(it)
while item !== nothing
(; sig, caller) = item[1]
if sig === nothing && caller === mi
has_f_call_backedge = true
break
end
item = Core.Compiler.iterate(it, item[2])
end
@test has_f_call_backedge

m = which(MI.getlast, (Any,))
@test (m.specializations::Core.MethodInstance).cache.max_world == typemax(UInt)

Expand Down

0 comments on commit 6cdafa9

Please sign in to comment.