Skip to content
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

inlining: Use concrete-eval effects if available #47305

Merged
merged 1 commit into from
Oct 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 85 additions & 24 deletions base/compiler/ssair/inlining.jl
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,8 @@ end

function ir_inline_item!(compact::IncrementalCompact, idx::Int, argexprs::Vector{Any},
linetable::Vector{LineInfoNode}, item::InliningTodo,
boundscheck::Symbol, todo_bbs::Vector{Tuple{Int, Int}})
boundscheck::Symbol, todo_bbs::Vector{Tuple{Int, Int}},
extra_flags::UInt8 = inlined_flags_for_effects(item.effects))
# Ok, do the inlining here
sparam_vals = item.mi.sparam_vals
def = item.mi.def::Method
Expand Down Expand Up @@ -411,6 +412,7 @@ function ir_inline_item!(compact::IncrementalCompact, idx::Int, argexprs::Vector
break
end
inline_compact[idx′] = stmt′
inline_compact[SSAValue(idx′)][:flag] |= extra_flags
end
just_fixup!(inline_compact, new_new_offset, late_fixup_offset)
compact.result_idx = inline_compact.result_idx
Expand Down Expand Up @@ -445,6 +447,14 @@ function ir_inline_item!(compact::IncrementalCompact, idx::Int, argexprs::Vector
stmt′ = PhiNode(Int32[edge+bb_offset for edge in stmt′.edges], stmt′.values)
end
inline_compact[idx′] = stmt′
if extra_flags != 0 && !isa(stmt′, Union{GotoNode, GotoIfNot})
if (extra_flags & IR_FLAG_NOTHROW) != 0 && inline_compact[SSAValue(idx′)][:type] === Union{}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we may need this sort of tweak for the other effects, e.g. :consistent-cy can now be proved in a presence of (unreturned) mutable allocations but it sounds quite wrong to mark the mutable allocation as :consistent.
Having said that, this may be enough for the meanwhile since we only look for nothrow and effect_free in our optimization passes, and I'm fine with leaving these handling for the future when the potential problem comes up.

# Shown nothrow, but also guaranteed to throw => unreachable
inline_compact[idx′] = ReturnNode()
else
inline_compact[SSAValue(idx′)][:flag] |= extra_flags
end
end
end
just_fixup!(inline_compact, new_new_offset, late_fixup_offset)
compact.result_idx = inline_compact.result_idx
Expand Down Expand Up @@ -838,8 +848,9 @@ end

# the general resolver for usual and const-prop'ed calls
function resolve_todo(mi::MethodInstance, result::Union{MethodMatch,InferenceResult},
argtypes::Vector{Any}, @nospecialize(info::CallInfo), flag::UInt8,
state::InliningState; invokesig::Union{Nothing,Vector{Any}}=nothing)
argtypes::Vector{Any}, @nospecialize(info::CallInfo), flag::UInt8,
state::InliningState; invokesig::Union{Nothing,Vector{Any}}=nothing,
override_effects::Effects = EFFECTS_UNKNOWN′)
et = InliningEdgeTracker(state.et, invokesig)

#XXX: update_valid_age!(min_valid[1], max_valid[1], sv)
Expand All @@ -860,6 +871,10 @@ function resolve_todo(mi::MethodInstance, result::Union{MethodMatch,InferenceRes
(; src, effects) = cached_result
end

if override_effects !== EFFECTS_UNKNOWN′
effects = override_effects
end

# 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)
Expand Down Expand Up @@ -937,7 +952,8 @@ can_inline_typevars(m::MethodMatch, argtypes::Vector{Any}) = can_inline_typevars

function analyze_method!(match::MethodMatch, argtypes::Vector{Any},
@nospecialize(info::CallInfo), flag::UInt8, state::InliningState;
allow_typevars::Bool, invokesig::Union{Nothing,Vector{Any}}=nothing)
allow_typevars::Bool, invokesig::Union{Nothing,Vector{Any}}=nothing,
override_effects::Effects=EFFECTS_UNKNOWN′)
method = match.method
spec_types = match.spec_types

Expand Down Expand Up @@ -967,11 +983,13 @@ function analyze_method!(match::MethodMatch, argtypes::Vector{Any},
mi = specialize_method(match; preexisting=true) # Union{Nothing, MethodInstance}
if mi === nothing
et = InliningEdgeTracker(state.et, invokesig)
return compileable_specialization(match, Effects(), et, info;
effects = override_effects
effects === EFFECTS_UNKNOWN′ && (effects = info_effects(nothing, match, state))
return compileable_specialization(match, effects, et, info;
compilesig_invokes=state.params.compilesig_invokes)
end

return resolve_todo(mi, match, argtypes, info, flag, state; invokesig)
return resolve_todo(mi, match, argtypes, info, flag, state; invokesig, override_effects)
end

function retrieve_ir_for_inlining(mi::MethodInstance, src::Array{UInt8, 1})
Expand All @@ -994,6 +1012,37 @@ function flags_for_effects(effects::Effects)
return flags
end

"""
inlined_flags_for_effects(effects::Effects)

This function answers the query:

Given a call site annotated as `effects`, what can we say about each inlined
statement after the inlining?

Note that this is different from `flags_for_effects`, which just talks about
the call site itself. Consider for example:

````
function foo()
V = Any[]
push!(V, 1)
tuple(V...)
end
```

This function is properly inferred effect_free, because it has no global effects.
However, we may not inline each statement with an :effect_free flag, because
that would incorrectly lose the `push!`.
"""
function inlined_flags_for_effects(effects::Effects)
flags::UInt8 = 0
if is_nothrow(effects)
flags |= IR_FLAG_NOTHROW
end
return flags
end

function handle_single_case!(todo::Vector{Pair{Int,Any}},
ir::IRCode, idx::Int, stmt::Expr, @nospecialize(case), params::OptimizationParams,
isinvoke::Bool = false)
Expand Down Expand Up @@ -1170,21 +1219,26 @@ function handle_invoke_call!(todo::Vector{Pair{Int,Any}},
end
result = info.result
invokesig = sig.argtypes
if isa(result, ConcreteResult) && may_inline_concrete_result(result)
item = concrete_result_item(result, state; invokesig)
else
argtypes = invoke_rewrite(sig.argtypes)
if isa(result, ConstPropResult)
mi = result.result.linfo
validate_sparams(mi.sparam_vals) || return nothing
if argtypes_to_type(argtypes) <: mi.def.sig
item = resolve_todo(mi, result.result, argtypes, info, flag, state; invokesig)
handle_single_case!(todo, ir, idx, stmt, item, state.params, true)
return nothing
end
override_effects = EFFECTS_UNKNOWN′
if isa(result, ConcreteResult)
if may_inline_concrete_result(result)
item = concrete_result_item(result, state; invokesig)
handle_single_case!(todo, ir, idx, stmt, item, state.params, true)
return nothing
end
item = analyze_method!(match, argtypes, info, flag, state; allow_typevars=false, invokesig)
override_effects = result.effects
end
argtypes = invoke_rewrite(sig.argtypes)
if isa(result, ConstPropResult)
mi = result.result.linfo
validate_sparams(mi.sparam_vals) || return nothing
if argtypes_to_type(argtypes) <: mi.def.sig
item = resolve_todo(mi, result.result, argtypes, info, flag, state; invokesig, override_effects)
handle_single_case!(todo, ir, idx, stmt, item, state.params, true)
return nothing
end
end
item = analyze_method!(match, argtypes, info, flag, state; allow_typevars=false, invokesig, override_effects)
handle_single_case!(todo, ir, idx, stmt, item, state.params, true)
return nothing
end
Expand Down Expand Up @@ -1296,10 +1350,12 @@ function handle_any_const_result!(cases::Vector{InliningCase},
@nospecialize(result), match::MethodMatch, argtypes::Vector{Any},
@nospecialize(info::CallInfo), flag::UInt8, state::InliningState;
allow_abstract::Bool, allow_typevars::Bool)
override_effects = EFFECTS_UNKNOWN′
if isa(result, ConcreteResult)
if may_inline_concrete_result(result)
return handle_concrete_result!(cases, result, state)
else
override_effects = result.effects
result = nothing
end
end
Expand All @@ -1313,7 +1369,7 @@ function handle_any_const_result!(cases::Vector{InliningCase},
return handle_const_prop_result!(cases, result, argtypes, info, flag, state; allow_abstract, allow_typevars)
else
@assert result === nothing
return handle_match!(cases, match, argtypes, info, flag, state; allow_abstract, allow_typevars)
return handle_match!(cases, match, argtypes, info, flag, state; allow_abstract, allow_typevars, override_effects)
end
end

Expand Down Expand Up @@ -1444,14 +1500,14 @@ end
function handle_match!(cases::Vector{InliningCase},
match::MethodMatch, argtypes::Vector{Any}, @nospecialize(info::CallInfo), flag::UInt8,
state::InliningState;
allow_abstract::Bool, allow_typevars::Bool)
allow_abstract::Bool, allow_typevars::Bool, override_effects::Effects)
spec_types = match.spec_types
allow_abstract || isdispatchtuple(spec_types) || return false
# We may see duplicated dispatch signatures here when a signature gets widened
# during abstract interpretation: for the purpose of inlining, we can just skip
# processing this dispatch candidate (unless unmatched type parameters are present)
!allow_typevars && _any(case->case.sig === spec_types, cases) && return true
item = analyze_method!(match, argtypes, info, flag, state; allow_typevars)
item = analyze_method!(match, argtypes, info, flag, state; allow_typevars, override_effects)
item === nothing && return false
push!(cases, InliningCase(spec_types, item))
return true
Expand Down Expand Up @@ -1526,8 +1582,13 @@ function handle_opaque_closure_call!(todo::Vector{Pair{Int,Any}},
mi = result.result.linfo
validate_sparams(mi.sparam_vals) || return nothing
item = resolve_todo(mi, result.result, sig.argtypes, info, flag, state)
elseif isa(result, ConcreteResult) && may_inline_concrete_result(result)
item = concrete_result_item(result, state)
elseif isa(result, ConcreteResult)
if may_inline_concrete_result(result)
item = concrete_result_item(result, state)
else
override_effects = result.effects
item = analyze_method!(info.match, sig.argtypes, info, flag, state; allow_typevars=false, override_effects)
end
else
item = analyze_method!(info.match, sig.argtypes, info, flag, state; allow_typevars=false)
end
Expand Down
25 changes: 25 additions & 0 deletions test/compiler/inline.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1811,3 +1811,28 @@ let src = code_typed1((NewInstruction,Any,Any,CallInfo)) do newinst, stmt, type,
@test count(iscall((src,NamedTuple)), src.code) == 0
@test count(isnew, src.code) == 1
end

# Test that inlining can still use nothrow information from concrete-eval
# even if the result itself is too big to be inlined, and nothrow is not
# known without concrete-eval
const THE_BIG_TUPLE = ntuple(identity, 1024)
function return_the_big_tuple(err::Bool)
err && error("BAD")
return THE_BIG_TUPLE
end
@noinline function return_the_big_tuple_noinline(err::Bool)
err && error("BAD")
return THE_BIG_TUPLE
end
big_tuple_test1() = return_the_big_tuple(false)[1]
big_tuple_test2() = return_the_big_tuple_noinline(false)[1]

@test fully_eliminated(big_tuple_test2, Tuple{})
# Currently we don't run these cleanup passes, but let's make sure that
# if we did, inlining would be able to remove this
let ir = Base.code_ircode(big_tuple_test1, Tuple{})[1][1]
ir = Core.Compiler.compact!(ir, true)
ir = Core.Compiler.cfg_simplify!(ir)
ir = Core.Compiler.compact!(ir, true)
@test length(ir.stmts) == 1
end