From 3e070f463b1fef94c03e8e10ab2af50501598858 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Tue, 13 Sep 2022 23:33:55 +0900 Subject: [PATCH 1/5] inference: setup separate functions for each backedge kind Also changes the argument list so that they are ordered as `(caller, [backedge information])`. --- base/compiler/abstractinterpretation.jl | 18 ++++++----- base/compiler/inferencestate.jl | 41 ++++++++++++++++--------- base/compiler/optimize.jl | 8 +++-- base/compiler/ssair/inlining.jl | 24 +++++++++++++-- base/compiler/typeinfer.jl | 2 +- 5 files changed, 65 insertions(+), 28 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 098adc349c95d..c58453b6e3733 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -483,15 +483,15 @@ function add_call_backedges!(interp::AbstractInterpreter, end end for edge in edges - add_backedge!(edge, sv) + add_backedge!(sv, edge) end # also need an edge to the method table in case something gets # added that did not intersect with any existing method if isa(matches, MethodMatches) - matches.fullmatch || add_mt_backedge!(matches.mt, atype, sv) + matches.fullmatch || add_mt_backedge!(sv, matches.mt, atype) else for (thisfullmatch, mt) in zip(matches.fullmatches, matches.mts) - thisfullmatch || add_mt_backedge!(mt, atype, sv) + thisfullmatch || add_mt_backedge!(sv, mt, atype) end end end @@ -889,7 +889,11 @@ function abstract_call_method_with_const_args(interp::AbstractInterpreter, end res = concrete_eval_call(interp, f, result, arginfo, sv, invokecall) if isa(res, ConstCallResults) - add_backedge!(res.const_result.mi, sv, invokecall === nothing ? nothing : invokecall.lookupsig) + if invokecall === nothing + add_backedge!(sv, res.const_result.mi) + else + add_invoke_backedge!(sv, invokecall.lookupsig, res.const_result.mi) + end return res end mi = maybe_get_const_prop_profitable(interp, result, f, arginfo, match, sv) @@ -936,7 +940,7 @@ function abstract_call_method_with_const_args(interp::AbstractInterpreter, result = inf_result.result # if constant inference hits a cycle, just bail out isa(result, InferenceState) && return nothing - add_backedge!(mi, sv) + add_backedge!(sv, mi) return ConstCallResults(result, ConstPropResult(inf_result), inf_result.ipo_effects) end @@ -1692,7 +1696,7 @@ function abstract_invoke(interp::AbstractInterpreter, (; fargs, argtypes)::ArgIn ti = tienv[1]; env = tienv[2]::SimpleVector result = abstract_call_method(interp, method, ti, env, false, sv) (; rt, edge, effects) = result - edge !== nothing && add_backedge!(edge::MethodInstance, sv, lookupsig) + edge !== nothing && add_invoke_backedge!(sv, lookupsig, edge::MethodInstance) match = MethodMatch(ti, env, method, argtype <: method.sig) res = nothing sig = match.spec_types @@ -1848,7 +1852,7 @@ function abstract_call_opaque_closure(interp::AbstractInterpreter, sig = argtypes_to_type(arginfo.argtypes) result = abstract_call_method(interp, closure.source, sig, Core.svec(), false, sv) (; rt, edge, effects) = result - edge !== nothing && add_backedge!(edge, sv) + edge !== nothing && add_backedge!(sv, edge) tt = closure.typ sigT = (unwrap_unionall(tt)::DataType).parameters[1] match = MethodMatch(sig, Core.svec(), closure.source, sig <: rewrap_unionall(sigT, tt)) diff --git a/base/compiler/inferencestate.jl b/base/compiler/inferencestate.jl index e9bd7474d265d..e1d20f01042c4 100644 --- a/base/compiler/inferencestate.jl +++ b/base/compiler/inferencestate.jl @@ -478,38 +478,49 @@ function record_ssa_assign!(ssa_id::Int, @nospecialize(new), frame::InferenceSta return nothing end -function add_cycle_backedge!(frame::InferenceState, caller::InferenceState, currpc::Int) +function add_cycle_backedge!(caller::InferenceState, frame::InferenceState, currpc::Int) update_valid_age!(frame, caller) backedge = (caller, currpc) contains_is(frame.cycle_backedges, backedge) || push!(frame.cycle_backedges, backedge) - add_backedge!(frame.linfo, caller) + add_backedge!(caller, frame.linfo) return frame end # temporarily accumulate our edges to later add as backedges in the callee -function add_backedge!(li::MethodInstance, caller::InferenceState, @nospecialize(invokesig=nothing)) - isa(caller.linfo.def, Method) || return # don't add backedges to toplevel exprs - edges = caller.stmt_edges[caller.currpc] - if edges === nothing - edges = caller.stmt_edges[caller.currpc] = [] +function add_backedge!(caller::InferenceState, li::MethodInstance) + edges = get_stmt_edges!(caller) + if edges !== nothing + push!(edges, li) end - if invokesig !== nothing - push!(edges, invokesig) + return nothing +end + +function add_invoke_backedge!(caller::InferenceState, @nospecialize(invokesig::Type), li::MethodInstance) + edges = get_stmt_edges!(caller) + if edges !== nothing + push!(edges, invokesig, li) end - push!(edges, li) return nothing end # used to temporarily accumulate our no method errors to later add as backedges in the callee method table -function add_mt_backedge!(mt::Core.MethodTable, @nospecialize(typ), caller::InferenceState) - isa(caller.linfo.def, Method) || return # don't add backedges to toplevel exprs +function add_mt_backedge!(caller::InferenceState, mt::Core.MethodTable, @nospecialize(typ)) + edges = get_stmt_edges!(caller) + if edges !== nothing + push!(edges, mt, typ) + end + return nothing +end + +function get_stmt_edges!(caller::InferenceState) + if !isa(caller.linfo.def, Method) + return nothing # don't add backedges to toplevel exprs + end edges = caller.stmt_edges[caller.currpc] if edges === nothing edges = caller.stmt_edges[caller.currpc] = [] end - push!(edges, mt) - push!(edges, typ) - return nothing + return edges end function empty_backedges!(frame::InferenceState, currpc::Int = frame.currpc) diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index 1b6d19bc152b6..51cc95a728ff0 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -65,9 +65,13 @@ intersect!(et::EdgeTracker, range::WorldRange) = et.valid_worlds[] = intersect(et.valid_worlds[], range) push!(et::EdgeTracker, mi::MethodInstance) = push!(et.edges, mi) -function add_edge!(et::EdgeTracker, @nospecialize(invokesig), mi::MethodInstance) - invokesig === nothing && return push!(et.edges, mi) +function add_backedge!(et::EdgeTracker, mi::MethodInstance) + push!(et.edges, mi) + return nothing +end +function add_invoke_backedge!(et::EdgeTracker, @nospecialize(invokesig), mi::MethodInstance) push!(et.edges, invokesig, mi) + return nothing end function push!(et::EdgeTracker, ci::CodeInstance) intersect!(et, WorldRange(min_world(li), max_world(li))) diff --git a/base/compiler/ssair/inlining.jl b/base/compiler/ssair/inlining.jl index 5b6473b35b086..728ee06aee2ab 100644 --- a/base/compiler/ssair/inlining.jl +++ b/base/compiler/ssair/inlining.jl @@ -832,7 +832,13 @@ function resolve_todo(todo::InliningTodo, state::InliningState, flag::UInt8) inferred_src = match.src if isa(inferred_src, ConstAPI) # use constant calling convention - et !== nothing && add_edge!(et, invokesig, mi) + if et !== nothing + if invokesig === nothing + add_backedge!(et, mi) + else + add_invoke_backedge!(et, invokesig, mi) + end + end return ConstantCase(quoted(inferred_src.val)) else src = inferred_src # ::Union{Nothing,CodeInfo} for NativeInterpreter @@ -843,7 +849,13 @@ function resolve_todo(todo::InliningTodo, state::InliningState, flag::UInt8) if code isa CodeInstance if use_const_api(code) # in this case function can be inlined to a constant - et !== nothing && add_edge!(et, invokesig, mi) + if et !== nothing + if invokesig === nothing + add_backedge!(et, mi) + else + add_invoke_backedge!(et, invokesig, mi) + end + end return ConstantCase(quoted(code.rettype_const)) else src = @atomic :monotonic code.inferred @@ -867,7 +879,13 @@ function resolve_todo(todo::InliningTodo, state::InliningState, flag::UInt8) src === nothing && return compileable_specialization(et, match, effects; compilesig_invokes=state.params.compilesig_invokes) - et !== nothing && add_edge!(et, invokesig, mi) + if et !== nothing + if invokesig === nothing + add_backedge!(et, mi) + else + add_invoke_backedge!(et, invokesig, mi) + end + end return InliningTodo(mi, retrieve_ir_for_inlining(mi, src), effects) end diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index dad2ba0a7e3b1..c5d19166ad7d0 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -806,7 +806,7 @@ function merge_call_chain!(interp::AbstractInterpreter, parent::InferenceState, # of recursion. merge_effects!(interp, parent, Effects(EFFECTS_TOTAL; terminates=false)) while true - add_cycle_backedge!(child, parent, parent.currpc) + add_cycle_backedge!(parent, child, parent.currpc) union_caller_cycle!(ancestor, child) merge_effects!(interp, child, Effects(EFFECTS_TOTAL; terminates=false)) child = parent From 5cef616a41b80a667497fbea31bfeef634007b87 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Wed, 14 Sep 2022 00:18:27 +0900 Subject: [PATCH 2/5] inference: fix backedge computation for const-prop'ed callsite With this commit `abstract_call_method_with_const_args` doesn't add backedge but rather returns the backedge to the caller, letting the callers like `abstract_call_gf_by_type` and `abstract_invoke` take the responsibility to add backedge to current context appropriately. As a result, this fixes the backedge calculation for const-prop'ed `invoke` callsite. For example, for the following call graph, ```julia foo(a::Int) = a > 0 ? :int : println(a) foo(a::Integer) = a > 0 ? "integer" : println(a) bar(a::Int) = @invoke foo(a::Integer) ``` Previously we added the wrong backedge `nothing, bar(Int64) from bar(Int64)`: ```julia julia> last(only(code_typed(()->bar(42)))) String julia> let m = only(methods(foo, (UInt,))) @eval Core.Compiler for (sig, caller) in BackedgeIterator($m.specializations[1].backedges) println(sig, ", ", caller) end end Tuple{typeof(Main.foo), Integer}, bar(Int64) from bar(Int64) nothing, bar(Int64) from bar(Int64) ``` but now we only add `invoke`-backedge: ```julia julia> last(only(code_typed(()->bar(42)))) String julia> let m = only(methods(foo, (UInt,))) @eval Core.Compiler for (sig, caller) in BackedgeIterator($m.specializations[1].backedges) println(sig, ", ", caller) end end Tuple{typeof(Main.foo), Integer}, bar(Int64) from bar(Int64) ``` --- base/compiler/abstractinterpretation.jl | 41 +++++++++++-------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index c58453b6e3733..53c6b2157d05d 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -126,7 +126,6 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), for sig_n in splitsigs result = abstract_call_method(interp, method, sig_n, svec(), multiple_matches, sv) (; rt, edge, effects) = result - edge === nothing || push!(edges, edge) this_argtypes = isa(matches, MethodMatches) ? argtypes : matches.applicable_argtypes[i] this_arginfo = ArgInfo(fargs, this_argtypes) const_call_result = abstract_call_method_with_const_args(interp, result, @@ -135,12 +134,13 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), if const_call_result !== nothing if const_call_result.rt ⊑ᵢ rt rt = const_call_result.rt - (; effects, const_result) = const_call_result + (; effects, const_result, edge) = const_call_result end end all_effects = merge_effects(all_effects, effects) push!(const_results, const_result) any_const_result |= const_result !== nothing + edge === nothing || push!(edges, edge) this_rt = tmerge(this_rt, rt) if bail_out_call(interp, this_rt, sv) break @@ -153,7 +153,6 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), (; rt, edge, effects) = result this_conditional = ignorelimited(rt) this_rt = widenwrappedconditional(rt) - edge === nothing || push!(edges, edge) # try constant propagation with argtypes for this match # this is in preparation for inlining, or improving the return result this_argtypes = isa(matches, MethodMatches) ? argtypes : matches.applicable_argtypes[i] @@ -169,12 +168,13 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), if this_const_rt ⊑ᵢ this_rt this_conditional = this_const_conditional this_rt = this_const_rt - (; effects, const_result) = const_call_result + (; effects, const_result, edge) = const_call_result end end all_effects = merge_effects(all_effects, effects) push!(const_results, const_result) any_const_result |= const_result !== nothing + edge === nothing || push!(edges, edge) end @assert !(this_conditional isa Conditional) "invalid lattice element returned from inter-procedural context" seen += 1 @@ -838,17 +838,18 @@ function concrete_eval_call(interp::AbstractInterpreter, f = invoke end world = get_world_counter(interp) + edge = result.edge::MethodInstance value = try Core._call_in_world_total(world, f, args...) catch # The evaluation threw. By :consistent-cy, we're guaranteed this would have happened at runtime - return ConstCallResults(Union{}, ConcreteResult(result.edge::MethodInstance, result.effects), result.effects) + return ConstCallResults(Union{}, ConcreteResult(edge, result.effects), result.effects, edge) end if is_inlineable_constant(value) || call_result_unused(sv) # If the constant is not inlineable, still do the const-prop, since the # code that led to the creation of the Const may be inlineable in the same # circumstance and may be optimizable. - return ConstCallResults(Const(value), ConcreteResult(result.edge::MethodInstance, EFFECTS_TOTAL, value), EFFECTS_TOTAL) + return ConstCallResults(Const(value), ConcreteResult(edge, EFFECTS_TOTAL, value), EFFECTS_TOTAL, edge) end return false else # eligible for semi-concrete evaluation @@ -875,10 +876,12 @@ struct ConstCallResults rt::Any const_result::ConstResult effects::Effects + edge::MethodInstance ConstCallResults(@nospecialize(rt), const_result::ConstResult, - effects::Effects) = - new(rt, const_result, effects) + effects::Effects, + edge::MethodInstance) = + new(rt, const_result, effects, edge) end function abstract_call_method_with_const_args(interp::AbstractInterpreter, @@ -888,14 +891,7 @@ function abstract_call_method_with_const_args(interp::AbstractInterpreter, return nothing end res = concrete_eval_call(interp, f, result, arginfo, sv, invokecall) - if isa(res, ConstCallResults) - if invokecall === nothing - add_backedge!(sv, res.const_result.mi) - else - add_invoke_backedge!(sv, invokecall.lookupsig, res.const_result.mi) - end - return res - end + isa(res, ConstCallResults) && return res mi = maybe_get_const_prop_profitable(interp, result, f, arginfo, match, sv) mi === nothing && return nothing # try semi-concrete evaluation @@ -907,7 +903,7 @@ function abstract_call_method_with_const_args(interp::AbstractInterpreter, if isa(ir, IRCode) T = ir_abstract_constant_propagation(interp, mi_cache, sv, mi, ir, arginfo.argtypes) if !isa(T, Type) || typeintersect(T, Bool) === Union{} - return ConstCallResults(T, SemiConcreteResult(mi, ir, result.effects), result.effects) + return ConstCallResults(T, SemiConcreteResult(mi, ir, result.effects), result.effects, mi) end end end @@ -940,8 +936,7 @@ function abstract_call_method_with_const_args(interp::AbstractInterpreter, result = inf_result.result # if constant inference hits a cycle, just bail out isa(result, InferenceState) && return nothing - add_backedge!(sv, mi) - return ConstCallResults(result, ConstPropResult(inf_result), inf_result.ipo_effects) + return ConstCallResults(result, ConstPropResult(inf_result), inf_result.ipo_effects, mi) end # if there's a possibility we could get a better result with these constant arguments @@ -1696,7 +1691,6 @@ function abstract_invoke(interp::AbstractInterpreter, (; fargs, argtypes)::ArgIn ti = tienv[1]; env = tienv[2]::SimpleVector result = abstract_call_method(interp, method, ti, env, false, sv) (; rt, edge, effects) = result - edge !== nothing && add_invoke_backedge!(sv, lookupsig, edge::MethodInstance) match = MethodMatch(ti, env, method, argtype <: method.sig) res = nothing sig = match.spec_types @@ -1715,10 +1709,11 @@ function abstract_invoke(interp::AbstractInterpreter, (; fargs, argtypes)::ArgIn const_result = nothing if const_call_result !== nothing if ⊑(typeinf_lattice(interp), const_call_result.rt, rt) - (; rt, effects, const_result) = const_call_result + (; rt, effects, const_result, edge) = const_call_result end end effects = Effects(effects; nonoverlayed=!overlayed) + edge !== nothing && add_invoke_backedge!(sv, lookupsig, edge) return CallMeta(from_interprocedural!(ipo_lattice(interp), rt, sv, arginfo, sig), effects, InvokeCallInfo(match, const_result)) end @@ -1852,7 +1847,6 @@ function abstract_call_opaque_closure(interp::AbstractInterpreter, sig = argtypes_to_type(arginfo.argtypes) result = abstract_call_method(interp, closure.source, sig, Core.svec(), false, sv) (; rt, edge, effects) = result - edge !== nothing && add_backedge!(sv, edge) tt = closure.typ sigT = (unwrap_unionall(tt)::DataType).parameters[1] match = MethodMatch(sig, Core.svec(), closure.source, sig <: rewrap_unionall(sigT, tt)) @@ -1862,7 +1856,7 @@ function abstract_call_opaque_closure(interp::AbstractInterpreter, nothing, arginfo, match, sv) if const_call_result !== nothing if const_call_result.rt ⊑ rt - (; rt, effects, const_result) = const_call_result + (; rt, effects, const_result, edge) = const_call_result end end end @@ -1878,6 +1872,7 @@ function abstract_call_opaque_closure(interp::AbstractInterpreter, end end rt = from_interprocedural!(ipo, rt, sv, arginfo, match.spec_types) + edge !== nothing && add_backedge!(sv, edge) return CallMeta(rt, effects, info) end From c28b5c21130a1df81085b2215370ce5e96cf32f3 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Wed, 14 Sep 2022 00:43:36 +0900 Subject: [PATCH 3/5] inference: make `BackedgePair` struct --- base/compiler/typeinfer.jl | 12 ++++++------ base/compiler/utilities.jl | 15 +++++++++------ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index c5d19166ad7d0..6db3c42a6ca54 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -567,13 +567,13 @@ function store_backedges(frame::InferenceResult, edges::Vector{Any}) nothing end -function store_backedges(caller::MethodInstance, edges::Vector{Any}) - for (typ, to) in BackedgeIterator(edges) - if isa(to, MethodInstance) - ccall(:jl_method_instance_add_backedge, Cvoid, (Any, Any, Any), to, typ, caller) +function store_backedges(frame::MethodInstance, edges::Vector{Any}) + for (; sig, caller) in BackedgeIterator(edges) + if isa(caller, MethodInstance) + ccall(:jl_method_instance_add_backedge, Cvoid, (Any, Any, Any), caller, sig, frame) else - typeassert(to, Core.MethodTable) - ccall(:jl_method_table_add_backedge, Cvoid, (Any, Any, Any), to, typ, caller) + typeassert(caller, Core.MethodTable) + ccall(:jl_method_table_add_backedge, Cvoid, (Any, Any, Any), caller, sig, frame) end end end diff --git a/base/compiler/utilities.jl b/base/compiler/utilities.jl index 071b0b6089b98..88e002a469575 100644 --- a/base/compiler/utilities.jl +++ b/base/compiler/utilities.jl @@ -235,9 +235,9 @@ is_no_constprop(method::Union{Method,CodeInfo}) = method.constprop == 0x02 Return an iterator over a list of backedges. Iteration returns `(sig, caller)` elements, which will be one of the following: -- `(nothing, caller::MethodInstance)`: a call made by ordinary inferrable dispatch -- `(invokesig, caller::MethodInstance)`: a call made by `invoke(f, invokesig, args...)` -- `(specsig, mt::MethodTable)`: an abstract call +- `BackedgePair(nothing, caller::MethodInstance)`: a call made by ordinary inferrable dispatch +- `BackedgePair(invokesig, caller::MethodInstance)`: a call made by `invoke(f, invokesig, args...)` +- `BackedgePair(specsig, mt::MethodTable)`: an abstract call # Examples @@ -254,7 +254,7 @@ julia> callyou(2.0) julia> mi = first(which(callme, (Any,)).specializations) MethodInstance for callme(::Float64) -julia> @eval Core.Compiler for (sig, caller) in BackedgeIterator(Main.mi.backedges) +julia> @eval Core.Compiler for (; sig, caller) in BackedgeIterator(Main.mi.backedges) println(sig) println(caller) end @@ -268,8 +268,11 @@ end const empty_backedge_iter = BackedgeIterator(Any[]) -const MethodInstanceOrTable = Union{MethodInstance, Core.MethodTable} -const BackedgePair = Pair{Union{Type, Nothing, MethodInstanceOrTable}, MethodInstanceOrTable} +struct BackedgePair + sig # ::Union{Nothing,Type} + caller::Union{MethodInstance,Core.MethodTable} + BackedgePair(@nospecialize(sig), caller::Union{MethodInstance,Core.MethodTable}) = new(sig, caller) +end function iterate(iter::BackedgeIterator, i::Int=1) backedges = iter.backedges From 7c8e7caa6bb2c00f272221095968eac2219cb7f3 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Wed, 14 Sep 2022 20:16:08 +0900 Subject: [PATCH 4/5] add invalidation test for `invoke` call --- test/worlds.jl | 71 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 53 insertions(+), 18 deletions(-) diff --git a/test/worlds.jl b/test/worlds.jl index 93445e07699c0..3c60f006faef2 100644 --- a/test/worlds.jl +++ b/test/worlds.jl @@ -226,13 +226,13 @@ g38435(x) = f38435(x, x) f38435(::Int, ::Int) = 3.0 @test g38435(1) === 3.0 +# Invalidation +# ============ -## Invalidation tests - -function instance(f, types) +function method_instance(f, types=Base.default_tt(f)) m = which(f, types) inst = nothing - tt = Tuple{typeof(f), types...} + tt = Base.signature_type(f, types) specs = m.specializations if isa(specs, Nothing) elseif isa(specs, Core.SimpleVector) @@ -290,30 +290,30 @@ f35855(::Float64) = 2 applyf35855([1]) applyf35855([1.0]) applyf35855(Any[1]) -wint = worlds(instance(applyf35855, (Vector{Int},))) -wfloat = worlds(instance(applyf35855, (Vector{Float64},))) -wany2 = worlds(instance(applyf35855, (Vector{Any},))) +wint = worlds(method_instance(applyf35855, (Vector{Int},))) +wfloat = worlds(method_instance(applyf35855, (Vector{Float64},))) +wany2 = worlds(method_instance(applyf35855, (Vector{Any},))) src2 = code_typed(applyf35855, (Vector{Any},))[1] f35855(::String) = 3 applyf35855(Any[1]) -@test worlds(instance(applyf35855, (Vector{Int},))) == wint -@test worlds(instance(applyf35855, (Vector{Float64},))) == wfloat -wany3 = worlds(instance(applyf35855, (Vector{Any},))) +@test worlds(method_instance(applyf35855, (Vector{Int},))) == wint +@test worlds(method_instance(applyf35855, (Vector{Float64},))) == wfloat +wany3 = worlds(method_instance(applyf35855, (Vector{Any},))) src3 = code_typed(applyf35855, (Vector{Any},))[1] @test !(wany3 == wany2) || equal(src3, src2) # code doesn't change unless you invalidate f35855(::AbstractVector) = 4 applyf35855(Any[1]) -wany4 = worlds(instance(applyf35855, (Vector{Any},))) +wany4 = worlds(method_instance(applyf35855, (Vector{Any},))) src4 = code_typed(applyf35855, (Vector{Any},))[1] @test !(wany4 == wany3) || equal(src4, src3) # code doesn't change unless you invalidate f35855(::Dict) = 5 applyf35855(Any[1]) -wany5 = worlds(instance(applyf35855, (Vector{Any},))) +wany5 = worlds(method_instance(applyf35855, (Vector{Any},))) src5 = code_typed(applyf35855, (Vector{Any},))[1] @test (wany5 == wany4) == equal(src5, src4) f35855(::Set) = 6 # with current settings, this shouldn't invalidate applyf35855(Any[1]) -wany6 = worlds(instance(applyf35855, (Vector{Any},))) +wany6 = worlds(method_instance(applyf35855, (Vector{Any},))) src6 = code_typed(applyf35855, (Vector{Any},))[1] @test wany6 == wany5 @test equal(src6, src5) @@ -322,11 +322,11 @@ applyf35855_2(c) = f35855_2(c[1]) f35855_2(::Int) = 1 f35855_2(::Float64) = 2 applyf35855_2(Any[1]) -wany3 = worlds(instance(applyf35855_2, (Vector{Any},))) +wany3 = worlds(method_instance(applyf35855_2, (Vector{Any},))) src3 = code_typed(applyf35855_2, (Vector{Any},))[1] f35855_2(::AbstractVector) = 4 applyf35855_2(Any[1]) -wany4 = worlds(instance(applyf35855_2, (Vector{Any},))) +wany4 = worlds(method_instance(applyf35855_2, (Vector{Any},))) src4 = code_typed(applyf35855_2, (Vector{Any},))[1] @test !(wany4 == wany3) || equal(src4, src3) # code doesn't change unless you invalidate @@ -343,25 +343,60 @@ end (::Type{X})(x::Real) where {T, X<:FixedPoint35855{T}} = X(round(T, typemax(T)*x), 0) @test worlds(mi) == w -mi = instance(convert, (Type{Nothing}, String)) +mi = method_instance(convert, (Type{Nothing}, String)) w = worlds(mi) abstract type Colorant35855 end Base.convert(::Type{C}, c) where {C<:Colorant35855} = false @test worlds(mi) == w -# NamedTuple and extensions of eltype +## NamedTuple and extensions of eltype outer(anyc) = inner(anyc[]) inner(s::Union{Vector,Dict}; kw=false) = inneri(s, kwi=maximum(s), kwb=kw) inneri(s, args...; kwargs...) = inneri(IOBuffer(), s, args...; kwargs...) inneri(io::IO, s::Union{Vector,Dict}; kwi=0, kwb=false) = (print(io, first(s), " "^kwi, kwb); String(take!(io))) @test outer(Ref{Any}([1,2,3])) == "1 false" -mi = instance(Core.kwfunc(inneri), (NamedTuple{(:kwi,:kwb),TT} where TT<:Tuple{Any,Bool}, typeof(inneri), Vector{T} where T)) +mi = method_instance(Core.kwfunc(inneri), (NamedTuple{(:kwi,:kwb),TT} where TT<:Tuple{Any,Bool}, typeof(inneri), Vector{T} where T)) w = worlds(mi) abstract type Container{T} end Base.eltype(::Type{C}) where {T,C<:Container{T}} = T @test worlds(mi) == w +## invoke call + +_invoke46741(a::Int) = a > 0 ? :int : println(a) +_invoke46741(a::Integer) = a > 0 ? :integer : println(a) +invoke46741(a) = @invoke _invoke46741(a::Integer) +@test invoke46741(42) === :integer +invoke46741_world = worlds(method_instance(invoke46741, (Int,))) +_invoke46741(a::Int) = a > 0 ? :int2 : println(a) +@test invoke46741(42) === :integer +@test worlds(method_instance(invoke46741, (Int,))) == invoke46741_world +_invoke46741(a::UInt) = a > 0 ? :uint2 : println(a) +@test invoke46741(42) === :integer +@test worlds(method_instance(invoke46741, (Int,))) == invoke46741_world +_invoke46741(a::Integer) = a > 0 ? :integer2 : println(a) +@test invoke46741(42) === :integer2 +@test worlds(method_instance(invoke46741, (Int,))) ≠ invoke46741_world + +# const-prop'ed call +_invoke46741(a::Int) = a > 0 ? :int : println(a) +_invoke46741(a::Integer) = a > 0 ? :integer : println(a) +invoke46741() = @invoke _invoke46741(42::Integer) +@test invoke46741() === :integer +invoke46741_world = worlds(method_instance(invoke46741, ())) +_invoke46741(a::Int) = a > 0 ? :int2 : println(a) +@test invoke46741() === :integer +@test worlds(method_instance(invoke46741, ())) == invoke46741_world +_invoke46741(a::UInt) = a > 0 ? :uint2 : println(a) +@test invoke46741() === :integer +@test worlds(method_instance(invoke46741, ())) == invoke46741_world +_invoke46741(a::Integer) = a > 0 ? :integer2 : println(a) +@test invoke46741() === :integer2 +@test worlds(method_instance(invoke46741, ())) ≠ invoke46741_world + # invoke_in_world +# =============== + f_inworld(x) = "world one; x=$x" g_inworld(x; y) = "world one; x=$x, y=$y" wc_aiw1 = get_world_counter() From 59daacb53ea347a9aa30aaffffe61d99b254cbd2 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Thu, 15 Sep 2022 11:56:28 +0900 Subject: [PATCH 5/5] optimizer: fixup inlining backedge calculation --- base/compiler/optimize.jl | 5 -- base/compiler/ssair/inlining.jl | 92 ++++++++++++++++----------------- base/compiler/ssair/passes.jl | 6 +-- 3 files changed, 49 insertions(+), 54 deletions(-) diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index 51cc95a728ff0..8eacd8013656e 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -64,7 +64,6 @@ EdgeTracker() = EdgeTracker(Any[], 0:typemax(UInt)) intersect!(et::EdgeTracker, range::WorldRange) = et.valid_worlds[] = intersect(et.valid_worlds[], range) -push!(et::EdgeTracker, mi::MethodInstance) = push!(et.edges, mi) function add_backedge!(et::EdgeTracker, mi::MethodInstance) push!(et.edges, mi) return nothing @@ -73,10 +72,6 @@ function add_invoke_backedge!(et::EdgeTracker, @nospecialize(invokesig), mi::Met push!(et.edges, invokesig, mi) return nothing end -function push!(et::EdgeTracker, ci::CodeInstance) - intersect!(et, WorldRange(min_world(li), max_world(li))) - push!(et, ci.def) -end struct InliningState{S <: Union{EdgeTracker, Nothing}, MICache, I<:AbstractInterpreter} params::OptimizationParams diff --git a/base/compiler/ssair/inlining.jl b/base/compiler/ssair/inlining.jl index 728ee06aee2ab..aa7f76da59587 100644 --- a/base/compiler/ssair/inlining.jl +++ b/base/compiler/ssair/inlining.jl @@ -78,8 +78,25 @@ struct UnionSplit new(fully_covered, atype, cases, Int[]) end +struct InliningEdgeTracker + et::Union{Nothing,EdgeTracker} + invokesig # ::Union{Nothing,Type} + InliningEdgeTracker(et::Union{Nothing,EdgeTracker}, @nospecialize(invokesig=nothing)) = new(et, invokesig) +end + @specialize +function add_inlining_backedge!((; et, invokesig)::InliningEdgeTracker, mi::MethodInstance) + if et !== nothing + if invokesig === nothing + add_backedge!(et, mi) + else + add_invoke_backedge!(et, invokesig, mi) + end + end + return nothing +end + function ssa_inlining_pass!(ir::IRCode, linetable::Vector{LineInfoNode}, state::InliningState, propagate_inbounds::Bool) # Go through the function, performing simple inlining (e.g. replacing call by constants # and analyzing legality of inlining). @@ -802,43 +819,36 @@ function rewrite_apply_exprargs!( return new_argtypes end -function compileable_specialization(et::Union{EdgeTracker, Nothing}, - match::MethodMatch, effects::Effects; compilesig_invokes = true) +function compileable_specialization(match::MethodMatch, effects::Effects, + et::InliningEdgeTracker; compilesig_invokes::Bool=true) mi = specialize_method(match; compilesig=compilesig_invokes) mi === nothing && return nothing - et !== nothing && push!(et, mi) + add_inlining_backedge!(et, mi) return InvokeCase(mi, effects) end -function compileable_specialization(et::Union{EdgeTracker, Nothing}, - linfo::MethodInstance, effects::Effects; compilesig_invokes = true) +function compileable_specialization(linfo::MethodInstance, effects::Effects, + et::InliningEdgeTracker; compilesig_invokes::Bool=true) mi = specialize_method(linfo.def::Method, linfo.specTypes, linfo.sparam_vals; compilesig=compilesig_invokes) mi === nothing && return nothing - et !== nothing && push!(et, mi) + add_inlining_backedge!(et, mi) return InvokeCase(mi, effects) end -function compileable_specialization(et::Union{EdgeTracker, Nothing}, result::InferenceResult, effects::Effects; kwargs...) - return compileable_specialization(et, result.linfo, effects; kwargs...) -end +compileable_specialization(result::InferenceResult, args...; kwargs...) = (@nospecialize; + compileable_specialization(result.linfo, args...; kwargs...)) function resolve_todo(todo::InliningTodo, state::InliningState, flag::UInt8) mi = todo.mi (; match, argtypes, invokesig) = todo.spec::DelayedInliningSpec - et = state.et + et = InliningEdgeTracker(state.et, invokesig) #XXX: update_valid_age!(min_valid[1], max_valid[1], sv) if isa(match, InferenceResult) inferred_src = match.src if isa(inferred_src, ConstAPI) # use constant calling convention - if et !== nothing - if invokesig === nothing - add_backedge!(et, mi) - else - add_invoke_backedge!(et, invokesig, mi) - end - end + add_inlining_backedge!(et, mi) return ConstantCase(quoted(inferred_src.val)) else src = inferred_src # ::Union{Nothing,CodeInfo} for NativeInterpreter @@ -849,13 +859,7 @@ function resolve_todo(todo::InliningTodo, state::InliningState, flag::UInt8) if code isa CodeInstance if use_const_api(code) # in this case function can be inlined to a constant - if et !== nothing - if invokesig === nothing - add_backedge!(et, mi) - else - add_invoke_backedge!(et, invokesig, mi) - end - end + add_inlining_backedge!(et, mi) return ConstantCase(quoted(code.rettype_const)) else src = @atomic :monotonic code.inferred @@ -870,22 +874,16 @@ function resolve_todo(todo::InliningTodo, state::InliningState, flag::UInt8) # the duplicated check might have been done already within `analyze_method!`, but still # we need it here too since we may come here directly using a constant-prop' result if !state.params.inlining || is_stmt_noinline(flag) - return compileable_specialization(et, match, effects; + return compileable_specialization(match, effects, et; compilesig_invokes=state.params.compilesig_invokes) end src = inlining_policy(state.interp, src, flag, mi, argtypes) - src === nothing && return compileable_specialization(et, match, effects; + src === nothing && return compileable_specialization(match, effects, et; compilesig_invokes=state.params.compilesig_invokes) - if et !== nothing - if invokesig === nothing - add_backedge!(et, mi) - else - add_invoke_backedge!(et, invokesig, mi) - end - end + add_inlining_backedge!(et, mi) return InliningTodo(mi, retrieve_ir_for_inlining(mi, src), effects) end @@ -925,7 +923,7 @@ function can_inline_typevars(method::Method, argtypes::Vector{Any}) end can_inline_typevars(m::MethodMatch, argtypes::Vector{Any}) = can_inline_typevars(m.method, argtypes) -function analyze_method!(match::MethodMatch, argtypes::Vector{Any}, invokesig, +function analyze_method!(match::MethodMatch, argtypes::Vector{Any}, @nospecialize(invokesig), flag::UInt8, state::InliningState, allow_typevars::Bool = false) method = match.method spec_types = match.spec_types @@ -952,12 +950,13 @@ function analyze_method!(match::MethodMatch, argtypes::Vector{Any}, invokesig, (allow_typevars && can_inline_typevars(match, argtypes)) || return nothing end - et = state.et - # See if there exists a specialization for this method signature mi = specialize_method(match; preexisting=true) # Union{Nothing, MethodInstance} - isa(mi, MethodInstance) || return compileable_specialization(et, - match, Effects(); compilesig_invokes=state.params.compilesig_invokes) + if mi === nothing + et = InliningEdgeTracker(state.et, invokesig) + return compileable_specialization(match, Effects(), et; + compilesig_invokes=state.params.compilesig_invokes) + end todo = InliningTodo(mi, match, argtypes, invokesig) # If we don't have caches here, delay resolving this MethodInstance @@ -1160,10 +1159,10 @@ function inline_invoke!( return nothing end result = info.result + invokesig = invoke_signature(sig.argtypes) if isa(result, ConcreteResult) - item = concrete_result_item(result, state) + item = concrete_result_item(result, state, invokesig) else - invokesig = invoke_signature(sig.argtypes) argtypes = invoke_rewrite(sig.argtypes) if isa(result, ConstPropResult) (; mi) = item = InliningTodo(result.result, argtypes, invokesig) @@ -1180,8 +1179,8 @@ function inline_invoke!( return nothing end -function invoke_signature(invokesig::Vector{Any}) - ft, argtyps = widenconst(invokesig[2]), instanceof_tfunc(widenconst(invokesig[3]))[1] +function invoke_signature(argtypes::Vector{Any}) + ft, argtyps = widenconst(argtypes[2]), instanceof_tfunc(widenconst(argtypes[3]))[1] return rewrap_unionall(Tuple{ft, unwrap_unionall(argtyps).parameters...}, argtyps) end @@ -1262,7 +1261,7 @@ function process_simple!(ir::IRCode, idx::Int, state::InliningState, todo::Vecto length(info.results) == 1 || return nothing match = info.results[1]::MethodMatch match.fully_covers || return nothing - case = compileable_specialization(state.et, match, Effects(); + case = compileable_specialization(match, Effects(), InliningEdgeTracker(state.et); compilesig_invokes=state.params.compilesig_invokes) case === nothing && return nothing stmt.head = :invoke_modify @@ -1462,10 +1461,11 @@ function handle_semi_concrete_result!(result::SemiConcreteResult, cases::Vector{ return true end -function concrete_result_item(result::ConcreteResult, state::InliningState) +function concrete_result_item(result::ConcreteResult, state::InliningState, @nospecialize(invokesig=nothing)) if !isdefined(result, :result) || !is_inlineable_constant(result.result) - case = compileable_specialization(state.et, result.mi, result.effects; - compilesig_invokes = state.params.compilesig_invokes) + et = InliningEdgeTracker(state.et, invokesig) + case = compileable_specialization(result.mi, result.effects, et; + compilesig_invokes=state.params.compilesig_invokes) @assert case !== nothing "concrete evaluation should never happen for uncompileable callsite" return case end diff --git a/base/compiler/ssair/passes.jl b/base/compiler/ssair/passes.jl index 490a47ca017ee..d00c556340518 100644 --- a/base/compiler/ssair/passes.jl +++ b/base/compiler/ssair/passes.jl @@ -1053,11 +1053,11 @@ end # data structure into the global cache (see the comment in `handle_finalizer_call!`) function try_inline_finalizer!(ir::IRCode, argexprs::Vector{Any}, idx::Int, mi::MethodInstance, inlining::InliningState) code = get(inlining.mi_cache, mi, nothing) - et = inlining.et + et = InliningEdgeTracker(inlining.et) if code isa CodeInstance if use_const_api(code) # No code in the function - Nothing to do - et !== nothing && push!(et, mi) + add_inlining_backedge!(et, mi) return true end src = @atomic :monotonic code.inferred @@ -1073,7 +1073,7 @@ function try_inline_finalizer!(ir::IRCode, argexprs::Vector{Any}, idx::Int, mi:: length(src.cfg.blocks) == 1 || return false # Ok, we're committed to inlining the finalizer - et !== nothing && push!(et, mi) + add_inlining_backedge!(et, mi) linetable_offset, extra_coverage_line = ir_inline_linetable!(ir.linetable, src, mi.def, ir[SSAValue(idx)][:line]) if extra_coverage_line != 0