Skip to content

Commit dd375e1

Browse files
timholyvtjnash
andauthored
invoked calls: record invoke signature in backedges (#46010)
This fixes a long-standing issue with how we've handled `invoke` calls with respect to method invalidation. When we load a package, we need to ask whether a given MethodInstance would be compiled in the same way now (aka, in the user's running session) as when the package was precompiled; in practice, the way we do that is to test whether the dispatches would be to the same methods in the current world-age. `invoke` presents special challenges because it allows the coder to deliberately select a different method than the one that would be chosen by ordinary dispatch; if there is no record of how this choice was made, it can look like it resolves to the wrong method and this can trigger invalidation. This allows a MethodInstance to store dispatch tuples as well as other MethodInstances among their backedges. Additionally: - provide backedge-iterators for both C and Julia that abstracts the specific storage mechanism. - fix a bug in the CodeInstance `relocatability` field, where methods that only return a constant (and hence store `nothing` for `inferred`) were deemed non-relocatable. - fix a bug in which #43990 should have checked that the method had not been deleted. Tests passed formerly simply because we weren't caching external CodeInstances that inferred down to a `Const`; fixing that exposed the bug. This bug has been exposed since merging #43990 for non-`Const` inference, and would affect Revise etc. Co-authored-by: Jameson Nash <vtjnash@gmail.com>
1 parent 3b1c54d commit dd375e1

16 files changed

+585
-150
lines changed

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ Compiler/Runtime improvements
3535
`@nospecialize`-d call sites and avoiding excessive compilation. ([#44512])
3636
* All the previous usages of `@pure`-macro in `Base` has been replaced with the preferred
3737
`Base.@assume_effects`-based annotations. ([#44776])
38+
* `invoke(f, invokesig, args...)` calls to a less-specific method than would normally be chosen
39+
for `f(args...)` are no longer spuriously invalidated when loading package precompile files. ([#46010])
3840

3941
Command-line option changes
4042
---------------------------

base/compiler/abstractinterpretation.jl

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,11 @@ function collect_const_args(argtypes::Vector{Any})
801801
end for i = 2:length(argtypes) ]
802802
end
803803

804+
function invoke_signature(invokesig::Vector{Any})
805+
ft, argtyps = widenconst(invokesig[2]), instanceof_tfunc(widenconst(invokesig[3]))[1]
806+
return rewrap_unionall(Tuple{ft, unwrap_unionall(argtyps).parameters...}, argtyps)
807+
end
808+
804809
function concrete_eval_call(interp::AbstractInterpreter,
805810
@nospecialize(f), result::MethodCallResult, arginfo::ArgInfo, sv::InferenceState)
806811
concrete_eval_eligible(interp, f, result, arginfo, sv) || return nothing
@@ -1631,7 +1636,7 @@ function abstract_invoke(interp::AbstractInterpreter, (; fargs, argtypes)::ArgIn
16311636
ti = tienv[1]; env = tienv[2]::SimpleVector
16321637
result = abstract_call_method(interp, method, ti, env, false, sv)
16331638
(; rt, edge, effects) = result
1634-
edge !== nothing && add_backedge!(edge::MethodInstance, sv)
1639+
edge !== nothing && add_backedge!(edge::MethodInstance, sv, types)
16351640
match = MethodMatch(ti, env, method, argtype <: method.sig)
16361641
res = nothing
16371642
sig = match.spec_types

base/compiler/inferencestate.jl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -479,12 +479,15 @@ function add_cycle_backedge!(frame::InferenceState, caller::InferenceState, curr
479479
end
480480

481481
# temporarily accumulate our edges to later add as backedges in the callee
482-
function add_backedge!(li::MethodInstance, caller::InferenceState)
482+
function add_backedge!(li::MethodInstance, caller::InferenceState, invokesig::Union{Nothing,Type}=nothing)
483483
isa(caller.linfo.def, Method) || return # don't add backedges to toplevel exprs
484484
edges = caller.stmt_edges[caller.currpc]
485485
if edges === nothing
486486
edges = caller.stmt_edges[caller.currpc] = []
487487
end
488+
if invokesig !== nothing
489+
push!(edges, invokesig)
490+
end
488491
push!(edges, li)
489492
return nothing
490493
end

base/compiler/optimize.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ intersect!(et::EdgeTracker, range::WorldRange) =
6262
et.valid_worlds[] = intersect(et.valid_worlds[], range)
6363

6464
push!(et::EdgeTracker, mi::MethodInstance) = push!(et.edges, mi)
65+
function add_edge!(et::EdgeTracker, @nospecialize(invokesig), mi::MethodInstance)
66+
invokesig === nothing && return push!(et.edges, mi)
67+
push!(et.edges, invokesig, mi)
68+
end
6569
function push!(et::EdgeTracker, ci::CodeInstance)
6670
intersect!(et, WorldRange(min_world(li), max_world(li)))
6771
push!(et, ci.def)

base/compiler/ssair/inlining.jl

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,21 @@ pass to apply its own inlining policy decisions.
2929
struct DelayedInliningSpec
3030
match::Union{MethodMatch, InferenceResult}
3131
argtypes::Vector{Any}
32+
invokesig # either nothing or a signature (signature is for an `invoke` call)
3233
end
34+
DelayedInliningSpec(match, argtypes) = DelayedInliningSpec(match, argtypes, nothing)
3335

3436
struct InliningTodo
3537
# The MethodInstance to be inlined
3638
mi::MethodInstance
3739
spec::Union{ResolvedInliningSpec, DelayedInliningSpec}
3840
end
3941

40-
InliningTodo(mi::MethodInstance, match::MethodMatch, argtypes::Vector{Any}) =
41-
InliningTodo(mi, DelayedInliningSpec(match, argtypes))
42+
InliningTodo(mi::MethodInstance, match::MethodMatch, argtypes::Vector{Any}, invokesig=nothing) =
43+
InliningTodo(mi, DelayedInliningSpec(match, argtypes, invokesig))
4244

43-
InliningTodo(result::InferenceResult, argtypes::Vector{Any}) =
44-
InliningTodo(result.linfo, DelayedInliningSpec(result, argtypes))
45+
InliningTodo(result::InferenceResult, argtypes::Vector{Any}, invokesig=nothing) =
46+
InliningTodo(result.linfo, DelayedInliningSpec(result, argtypes, invokesig))
4547

4648
struct ConstantCase
4749
val::Any
@@ -810,15 +812,15 @@ end
810812

811813
function resolve_todo(todo::InliningTodo, state::InliningState, flag::UInt8)
812814
mi = todo.mi
813-
(; match, argtypes) = todo.spec::DelayedInliningSpec
815+
(; match, argtypes, invokesig) = todo.spec::DelayedInliningSpec
814816
et = state.et
815817

816818
#XXX: update_valid_age!(min_valid[1], max_valid[1], sv)
817819
if isa(match, InferenceResult)
818820
inferred_src = match.src
819821
if isa(inferred_src, ConstAPI)
820822
# use constant calling convention
821-
et !== nothing && push!(et, mi)
823+
et !== nothing && add_edge!(et, invokesig, mi)
822824
return ConstantCase(quoted(inferred_src.val))
823825
else
824826
src = inferred_src # ::Union{Nothing,CodeInfo} for NativeInterpreter
@@ -829,7 +831,7 @@ function resolve_todo(todo::InliningTodo, state::InliningState, flag::UInt8)
829831
if code isa CodeInstance
830832
if use_const_api(code)
831833
# in this case function can be inlined to a constant
832-
et !== nothing && push!(et, mi)
834+
et !== nothing && add_edge!(et, invokesig, mi)
833835
return ConstantCase(quoted(code.rettype_const))
834836
else
835837
src = @atomic :monotonic code.inferred
@@ -851,7 +853,7 @@ function resolve_todo(todo::InliningTodo, state::InliningState, flag::UInt8)
851853

852854
src === nothing && return compileable_specialization(et, match, effects)
853855

854-
et !== nothing && push!(et, mi)
856+
et !== nothing && add_edge!(et, invokesig, mi)
855857
return InliningTodo(mi, retrieve_ir_for_inlining(mi, src), effects)
856858
end
857859

@@ -873,7 +875,7 @@ function validate_sparams(sparams::SimpleVector)
873875
return true
874876
end
875877

876-
function analyze_method!(match::MethodMatch, argtypes::Vector{Any},
878+
function analyze_method!(match::MethodMatch, argtypes::Vector{Any}, invokesig,
877879
flag::UInt8, state::InliningState)
878880
method = match.method
879881
spec_types = match.spec_types
@@ -905,7 +907,7 @@ function analyze_method!(match::MethodMatch, argtypes::Vector{Any},
905907
mi = specialize_method(match; preexisting=true) # Union{Nothing, MethodInstance}
906908
isa(mi, MethodInstance) || return compileable_specialization(et, match, Effects())
907909

908-
todo = InliningTodo(mi, match, argtypes)
910+
todo = InliningTodo(mi, match, argtypes, invokesig)
909911
# If we don't have caches here, delay resolving this MethodInstance
910912
# until the batch inlining step (or an external post-processing pass)
911913
state.mi_cache === nothing && return todo
@@ -1100,17 +1102,18 @@ function inline_invoke!(
11001102
if isa(result, ConcreteResult)
11011103
item = concrete_result_item(result, state)
11021104
else
1105+
invokesig = invoke_signature(sig.argtypes)
11031106
argtypes = invoke_rewrite(sig.argtypes)
11041107
if isa(result, ConstPropResult)
1105-
(; mi) = item = InliningTodo(result.result, argtypes)
1108+
(; mi) = item = InliningTodo(result.result, argtypes, invokesig)
11061109
validate_sparams(mi.sparam_vals) || return nothing
11071110
if argtypes_to_type(argtypes) <: mi.def.sig
11081111
state.mi_cache !== nothing && (item = resolve_todo(item, state, flag))
11091112
handle_single_case!(ir, idx, stmt, item, todo, state.params, true)
11101113
return nothing
11111114
end
11121115
end
1113-
item = analyze_method!(match, argtypes, flag, state)
1116+
item = analyze_method!(match, argtypes, invokesig, flag, state)
11141117
end
11151118
handle_single_case!(ir, idx, stmt, item, todo, state.params, true)
11161119
return nothing
@@ -1328,7 +1331,7 @@ function handle_match!(
13281331
# during abstract interpretation: for the purpose of inlining, we can just skip
13291332
# processing this dispatch candidate
13301333
_any(case->case.sig === spec_types, cases) && return true
1331-
item = analyze_method!(match, argtypes, flag, state)
1334+
item = analyze_method!(match, argtypes, nothing, flag, state)
13321335
item === nothing && return false
13331336
push!(cases, InliningCase(spec_types, item))
13341337
return true
@@ -1475,7 +1478,7 @@ function assemble_inline_todo!(ir::IRCode, state::InliningState)
14751478
if isa(result, ConcreteResult)
14761479
item = concrete_result_item(result, state)
14771480
else
1478-
item = analyze_method!(info.match, sig.argtypes, flag, state)
1481+
item = analyze_method!(info.match, sig.argtypes, nothing, flag, state)
14791482
end
14801483
handle_single_case!(ir, idx, stmt, item, todo, state.params)
14811484
end

base/compiler/typeinfer.jl

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,9 @@ function CodeInstance(
312312
const_flags = 0x00
313313
end
314314
end
315-
relocatability = isa(inferred_result, Vector{UInt8}) ? inferred_result[end] : UInt8(0)
315+
relocatability = isa(inferred_result, Vector{UInt8}) ? inferred_result[end] :
316+
inferred_result === nothing ? UInt8(1) : UInt8(0)
317+
# relocatability = isa(inferred_result, Vector{UInt8}) ? inferred_result[end] : UInt8(0)
316318
return CodeInstance(result.linfo,
317319
widenconst(result_type), rettype_const, inferred_result,
318320
const_flags, first(valid_worlds), last(valid_worlds),
@@ -561,17 +563,12 @@ function store_backedges(frame::InferenceResult, edges::Vector{Any})
561563
end
562564

563565
function store_backedges(caller::MethodInstance, edges::Vector{Any})
564-
i = 1
565-
while i <= length(edges)
566-
to = edges[i]
566+
for (typ, to) in BackedgeIterator(edges)
567567
if isa(to, MethodInstance)
568-
ccall(:jl_method_instance_add_backedge, Cvoid, (Any, Any), to, caller)
569-
i += 1
568+
ccall(:jl_method_instance_add_backedge, Cvoid, (Any, Any, Any), to, typ, caller)
570569
else
571570
typeassert(to, Core.MethodTable)
572-
typ = edges[i + 1]
573571
ccall(:jl_method_table_add_backedge, Cvoid, (Any, Any, Any), to, typ, caller)
574-
i += 2
575572
end
576573
end
577574
end

base/compiler/utilities.jl

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,59 @@ Check if `method` is declared as `Base.@constprop :none`.
223223
"""
224224
is_no_constprop(method::Union{Method,CodeInfo}) = method.constprop == 0x02
225225

226+
#############
227+
# backedges #
228+
#############
229+
230+
"""
231+
BackedgeIterator(backedges::Vector{Any})
232+
233+
Return an iterator over a list of backedges. Iteration returns `(sig, caller)` elements,
234+
which will be one of the following:
235+
236+
- `(nothing, caller::MethodInstance)`: a call made by ordinary inferrable dispatch
237+
- `(invokesig, caller::MethodInstance)`: a call made by `invoke(f, invokesig, args...)`
238+
- `(specsig, mt::MethodTable)`: an abstract call
239+
240+
# Examples
241+
242+
```julia
243+
julia> callme(x) = x+1
244+
callme (generic function with 1 method)
245+
246+
julia> callyou(x) = callme(x)
247+
callyou (generic function with 1 method)
248+
249+
julia> callyou(2.0)
250+
3.0
251+
252+
julia> mi = first(which(callme, (Any,)).specializations)
253+
MethodInstance for callme(::Float64)
254+
255+
julia> @eval Core.Compiler for (sig, caller) in BackedgeIterator(Main.mi.backedges)
256+
println(sig)
257+
println(caller)
258+
end
259+
nothing
260+
callyou(Float64) from callyou(Any)
261+
```
262+
"""
263+
struct BackedgeIterator
264+
backedges::Vector{Any}
265+
end
266+
267+
const empty_backedge_iter = BackedgeIterator(Any[])
268+
269+
270+
function iterate(iter::BackedgeIterator, i::Int=1)
271+
backedges = iter.backedges
272+
i > length(backedges) && return nothing
273+
item = backedges[i]
274+
isa(item, MethodInstance) && return (nothing, item), i+1 # regular dispatch
275+
isa(item, Core.MethodTable) && return (backedges[i+1], item), i+2 # abstract dispatch
276+
return (item, backedges[i+1]::MethodInstance), i+2 # `invoke` calls
277+
end
278+
226279
#########
227280
# types #
228281
#########

base/loading.jl

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2262,12 +2262,12 @@ macro __DIR__()
22622262
end
22632263

22642264
"""
2265-
precompile(f, args::Tuple{Vararg{Any}})
2265+
precompile(f, argtypes::Tuple{Vararg{Any}})
22662266
2267-
Compile the given function `f` for the argument tuple (of types) `args`, but do not execute it.
2267+
Compile the given function `f` for the argument tuple (of types) `argtypes`, but do not execute it.
22682268
"""
2269-
function precompile(@nospecialize(f), @nospecialize(args::Tuple))
2270-
precompile(Tuple{Core.Typeof(f), args...})
2269+
function precompile(@nospecialize(f), @nospecialize(argtypes::Tuple))
2270+
precompile(Tuple{Core.Typeof(f), argtypes...})
22712271
end
22722272

22732273
const ENABLE_PRECOMPILE_WARNINGS = Ref(false)
@@ -2279,6 +2279,27 @@ function precompile(@nospecialize(argt::Type))
22792279
return ret
22802280
end
22812281

2282+
# Variants that work for `invoke`d calls for which the signature may not be sufficient
2283+
precompile(mi::Core.MethodInstance, world::UInt=get_world_counter()) =
2284+
(ccall(:jl_compile_method_instance, Cvoid, (Any, Any, UInt), mi, C_NULL, world); return true)
2285+
2286+
"""
2287+
precompile(f, argtypes::Tuple{Vararg{Any}}, m::Method)
2288+
2289+
Precompile a specific method for the given argument types. This may be used to precompile
2290+
a different method than the one that would ordinarily be chosen by dispatch, thus
2291+
mimicking `invoke`.
2292+
"""
2293+
function precompile(@nospecialize(f), @nospecialize(argtypes::Tuple), m::Method)
2294+
precompile(Tuple{Core.Typeof(f), argtypes...}, m)
2295+
end
2296+
2297+
function precompile(@nospecialize(argt::Type), m::Method)
2298+
atype, sparams = ccall(:jl_type_intersection_with_env, Any, (Any, Any), argt, m.sig)::SimpleVector
2299+
mi = Core.Compiler.specialize_method(m, atype, sparams)
2300+
return precompile(mi)
2301+
end
2302+
22822303
precompile(include_package_for_output, (PkgId, String, Vector{String}, Vector{String}, Vector{String}, typeof(_concrete_dependencies), Nothing))
22832304
precompile(include_package_for_output, (PkgId, String, Vector{String}, Vector{String}, Vector{String}, typeof(_concrete_dependencies), String))
22842305
precompile(create_expr_cache, (PkgId, String, String, typeof(_concrete_dependencies), IO, IO))

0 commit comments

Comments
 (0)