-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
inference: fixes and improvements for backedge computation #46741
Conversation
0bc5ab8
to
9dd281d
Compare
9dd281d
to
684d2ac
Compare
@nanosoldier |
Your benchmark job has completed - no performance regressions were detected. A full report can be found here. |
684d2ac
to
0f9eb79
Compare
@nanosoldier |
0f9eb79
to
b9bf19c
Compare
Your benchmark job has completed - no performance regressions were detected. A full report can be found here. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is pretty subtle. Can we make sure to have a test case that actually triggers this invalidation to make sure that the invoke backedge exists and works properly?
ed14602
to
609e0ea
Compare
Really appreciate you doing this! I think you're doing a better job of implementing a transition I was wondering about, whether we need to start considering julia/base/multidimensional.jl Line 1642 in ffaaa30
that unfortunately still fails on this branch: julia> m = which(unique, Tuple{Any})
unique(itr)
@ Base set.jl:170
julia> specs = collect(Iterators.filter(m.specializations) do mi
mi === nothing && return false
return mi.specTypes.parameters[end] === Vector{Int} # find specialization of `unique(::Any)` for `::Vector{Int}`
end)
Any[]
julia> Base._unique_dims([1,2,3],:) # no existing callers with specialization `Vector{Int}`, let's make one
3-element Vector{Int64}:
1
2
3
julia> mi = only(Iterators.filter(m.specializations) do mi
mi === nothing && return false
return mi.specTypes.parameters[end] === Vector{Int} # find specialization of `unique(::Any)` for `::Vector{Int}`
end)
MethodInstance for unique(::Vector{Int64})
julia> mi.def
unique(itr)
@ Base set.jl:170
julia> mi.backedges
3-element Vector{Any}:
Tuple{typeof(unique), Any}
MethodInstance for Base._unique_dims(::Vector{Int64}, ::Colon)
MethodInstance for Base._unique_dims(::Vector{Int64}, ::Colon) The MethodInstance should not be duplicated. I/we could fix it like #46715 but I wonder if more will crop up. |
Also changes the argument list so that they are ordered as `(caller, [backedge information])`.
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) ```
609e0ea
to
59daacb
Compare
The last commit should fix this case. It turns out that we also need to fixup backedge calculation during inlining. In general, I think the current implementation is maintainable. At least now we distinguish usual/abstract/invoke backedges, that hopefully has an essentially same effects as making |
@@ -843,7 +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 | |||
et !== nothing && add_edge!(et, invokesig, mi) | |||
add_inlining_backedge!(et, mi) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Keno why do we need to account for mi
-backedge for this constant result case? The mi
is retrieved here
julia/base/compiler/ssair/inlining.jl
Line 954 in 59daacb
mi = specialize_method(match; preexisting=true) # Union{Nothing, MethodInstance} |
and so the abstract interpretation should account for it already.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We were probably just being conservative here. We didn't used to be particularly careful about whether information was being computed during abstract interpretation or in the optimizer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. It seems a bit inconsistent that we doesn't account for mi
-backedge for [semi-]concrete evaluated case, e.g.:
julia/base/compiler/ssair/inlining.jl
Lines 1464 to 1474 in 59daacb
function concrete_result_item(result::ConcreteResult, state::InliningState, @nospecialize(invokesig=nothing)) | |
if !isdefined(result, :result) || !is_inlineable_constant(result.result) | |
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 | |
@assert result.effects === EFFECTS_TOTAL | |
return ConstantCase(quoted(result.result)) | |
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, come up with something that has a constant return that gets inlined here, add a test for it and then delete the backedge here. If things pass (and go through PkgEval), I'm comfortable to remove it.
This PR consists of the following commits:
inference: setup separate functions for each backedge kind
Hopefully make it clear what kind of backedge is being added at each place.
This commit also changes the argument list so that they are ordered as
(caller, [backedge information])
.inference: fix backedge computation for const-prop'ed callsite
With this commit
abstract_call_method_with_const_args
doesn't addbackedge but rather returns the backedge to the caller, letting the
callers like
abstract_call_gf_by_type
andabstract_invoke
take theresponsibility 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,
Previously we added the wrong backedge
nothing, bar(Int64) from bar(Int64)
:but now we only add
invoke
-backedge: