Skip to content

Commit

Permalink
make CallInfo propagate effects
Browse files Browse the repository at this point in the history
Add `call_effects(::CallInfo) -> Effects` feature to the `CallInfo`
interface. This change will make it easier for the inlining algorithm to
see the effects of a call in question.

This commit also setups new `ModifyFieldInfo` callinfo type so that it
can propagate `modifyfield!`-specific information to the inliner, which
implements a special handling for the call.
  • Loading branch information
aviatesk committed Dec 1, 2022
1 parent 04a4edb commit a531f9c
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 130 deletions.
119 changes: 67 additions & 52 deletions base/compiler/abstractinterpretation.jl

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions base/compiler/ssair/EscapeAnalysis/interprocedural.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import Core.Compiler:
MethodInstance, InferenceResult, Signature, ConstPropResult, ConcreteResult,
SemiConcreteResult, CallInfo, NoCallInfo, MethodResultPure, MethodMatchInfo,
UnionSplitInfo, ConstCallInfo, InvokeCallInfo,
UnionSplitInfo, ConstCallInfo, InvokeCallInfo, MethodLookupResult,
call_sig, argtypes_to_type, is_builtin, is_return_type, istopfunction,
validate_sparams, specialize_method, invoke_rewrite

Expand Down Expand Up @@ -47,13 +47,13 @@ function resolve_call(ir::IRCode, stmt::Expr, @nospecialize(info::CallInfo))
elseif isa(info, ConstCallInfo)
return analyze_const_call(sig, info)
elseif isa(info, MethodMatchInfo)
infos = MethodMatchInfo[info]
results = MethodLookupResult[info.results]
elseif isa(info, UnionSplitInfo)
infos = info.matches
results = info.matches
else # isa(info, ReturnTypeCallInfo), etc.
return missing
end
return analyze_call(sig, infos)
return analyze_call(sig, results)
end

function analyze_invoke_call(sig::Signature, info::InvokeCallInfo)
Expand Down Expand Up @@ -114,11 +114,11 @@ function analyze_const_call(sig::Signature, cinfo::ConstCallInfo)
return EACallInfo(linfos, nothrow)
end

function analyze_call(sig::Signature, infos::Vector{MethodMatchInfo})
function analyze_call(sig::Signature, results::Vector{MethodLookupResult})
linfos = Linfo[]
local nothrow = true # required to account for potential escape via MethodError
for i in 1:length(infos)
meth = infos[i].results
for i in 1:length(results)
meth = results[i]
nothrow &= !meth.ambig
nmatch = Core.Compiler.length(meth)
if nmatch == 0 # No applicable methods
Expand Down
46 changes: 10 additions & 36 deletions base/compiler/ssair/inlining.jl
Original file line number Diff line number Diff line change
Expand Up @@ -973,9 +973,7 @@ 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)
effects = override_effects
effects === EFFECTS_UNKNOWN′ && (effects = info_effects(nothing, match, state))
return compileable_specialization(match, effects, et, info;
return compileable_specialization(match, call_effects(info), et, info;
compilesig_invokes=state.params.compilesig_invokes)
end

Expand Down Expand Up @@ -1107,8 +1105,8 @@ function inline_apply!(todo::Vector{Pair{Int,Any}},
new_info = info.call
end
else
@assert info === NoCallInfo()
new_info = info = NoCallInfo()
@assert info isa NoCallInfo
new_info = info
end
arg_start = 3
argtypes = sig.argtypes
Expand Down Expand Up @@ -1286,7 +1284,7 @@ function process_simple!(todo::Vector{Pair{Int,Any}}, ir::IRCode, idx::Int, stat
end

if (sig.f !== Core.invoke && sig.f !== Core.finalizer && sig.f !== modifyfield!) && is_builtin(sig)
# No inlining for builtins (other invoke/apply/typeassert/finalizer)
# No inlining for builtins
return nothing
end

Expand Down Expand Up @@ -1323,25 +1321,6 @@ function handle_any_const_result!(cases::Vector{InliningCase},
end
end

function info_effects(@nospecialize(result), match::MethodMatch, state::InliningState)
if isa(result, ConcreteResult)
return result.effects
elseif isa(result, SemiConcreteResult)
return result.effects
elseif isa(result, ConstPropResult)
return result.result.ipo_effects
else
mi = specialize_method(match; preexisting=true)
if isa(mi, MethodInstance)
code = get(code_cache(state), mi, nothing)
if code isa CodeInstance
return decode_effects(code.ipo_purity_bits)
end
end
return Effects()
end
end

function compute_inlining_cases(@nospecialize(info::CallInfo), flag::UInt8, sig::Signature,
state::InliningState)
nunion = nsplit(info)
Expand All @@ -1354,7 +1333,6 @@ function compute_inlining_cases(@nospecialize(info::CallInfo), flag::UInt8, sig:
local only_method = nothing
local meth::MethodLookupResult
local all_result_count = 0
local joint_effects::Effects = EFFECTS_TOTAL
local nothrow::Bool = true
for i = 1:nunion
meth = getsplit(info, i)
Expand All @@ -1380,7 +1358,6 @@ function compute_inlining_cases(@nospecialize(info::CallInfo), flag::UInt8, sig:
for (j, match) in enumerate(meth)
all_result_count += 1
result = getresult(info, all_result_count)
joint_effects = merge_effects(joint_effects, info_effects(result, match, state))
nothrow &= match.fully_covers
any_fully_covered |= match.fully_covers
if !validate_sparams(match.sparams)
Expand All @@ -1401,8 +1378,6 @@ function compute_inlining_cases(@nospecialize(info::CallInfo), flag::UInt8, sig:
end
end

joint_effects = Effects(joint_effects; nothrow)

if handled_all_cases && revisit_idx !== nothing
# we handled everything except one match with unmatched sparams,
# so try to handle it by bypassing validate_sparams
Expand Down Expand Up @@ -1434,17 +1409,17 @@ function compute_inlining_cases(@nospecialize(info::CallInfo), flag::UInt8, sig:
filter!(case::InliningCase->isdispatchtuple(case.sig), cases)
end

return cases, (handled_all_cases & any_fully_covered), joint_effects
return cases, (handled_all_cases & any_fully_covered)
end

function handle_call!(todo::Vector{Pair{Int,Any}},
ir::IRCode, idx::Int, stmt::Expr, @nospecialize(info::CallInfo), flag::UInt8, sig::Signature,
state::InliningState)
cases = compute_inlining_cases(info, flag, sig, state)
cases === nothing && return nothing
cases, all_covered, joint_effects = cases
cases, all_covered = cases
handle_cases!(todo, ir, idx, stmt, argtypes_to_type(sig.argtypes), cases,
all_covered, joint_effects, state.params)
all_covered, call_effects(info), state.params)
end

function handle_match!(cases::Vector{InliningCase},
Expand Down Expand Up @@ -1566,17 +1541,16 @@ end

function handle_finalizer_call!(ir::IRCode, idx::Int, stmt::Expr, info::FinalizerInfo,
state::InliningState)

# Finalizers don't return values, so if their execution is not observable,
# we can just not register them
if is_removable_if_unused(info.effects)
if is_removable_if_unused(info.finalizer_effects)
ir[SSAValue(idx)] = nothing
return nothing
end

# Only inline finalizers that are known nothrow and notls.
# This avoids having to set up state for finalizer isolation
is_finalizer_inlineable(info.effects) || return nothing
is_finalizer_inlineable(info.finalizer_effects) || return nothing

ft = argextype(stmt.args[2], ir)
has_free_typevars(ft) && return nothing
Expand All @@ -1588,7 +1562,7 @@ function handle_finalizer_call!(ir::IRCode, idx::Int, stmt::Expr, info::Finalize

cases = compute_inlining_cases(info.info, #=flag=#UInt8(0), sig, state)
cases === nothing && return nothing
cases, all_covered, _ = cases
cases, all_covered = cases
if all_covered && length(cases) == 1
# NOTE we don't append `item1` to `stmt` here so that we don't serialize
# `Core.Compiler` data structure into the global cache
Expand Down
4 changes: 2 additions & 2 deletions base/compiler/ssair/passes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -889,7 +889,7 @@ function sroa_pass!(ir::IRCode, inlining::Union{Nothing, InliningState} = nothin
3 <= length(stmt.args) <= 5 || continue
info = compact[SSAValue(idx)][:info]
if isa(info, FinalizerInfo)
is_finalizer_inlineable(info.effects) || continue
is_finalizer_inlineable(info.finalizer_effects) || continue
else
# Inlining performs legality checks on the finalizer to determine
# whether or not we may inline it. If so, it appends extra arguments
Expand Down Expand Up @@ -1235,7 +1235,7 @@ function try_resolve_finalizer!(ir::IRCode, idx::Int, finalizer_idx::Int, defuse

finalizer_stmt = ir[SSAValue(finalizer_idx)][:inst]
argexprs = Any[finalizer_stmt.args[2], finalizer_stmt.args[3]]
flags = info isa FinalizerInfo ? flags_for_effects(info.effects) : IR_FLAG_NULL
flags = info isa FinalizerInfo ? flags_for_effects(info.finalizer_effects) : IR_FLAG_NULL
if length(finalizer_stmt.args) >= 4
inline = finalizer_stmt.args[4]
if inline === nothing
Expand Down
58 changes: 37 additions & 21 deletions base/compiler/stmtinfo.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,15 @@ and any additional information (`call.info`) for a given generic call.
"""
struct CallMeta
rt::Any
effects::Effects
info::CallInfo
CallMeta(@nospecialize(rt), @nospecialize(info::CallInfo)) = new(rt, info)
end

struct NoCallInfo <: CallInfo end
struct NoCallInfo <: CallInfo
effects::Effects
end
NoCallInfo() = NoCallInfo(Effects())
call_effects_impl(info::NoCallInfo) = info.effects

"""
info::MethodMatchInfo <: CallInfo
Expand All @@ -26,10 +30,12 @@ not a call to a generic function.
"""
struct MethodMatchInfo <: CallInfo
results::MethodLookupResult
effects::Effects
end
nsplit_impl(info::MethodMatchInfo) = 1
getsplit_impl(info::MethodMatchInfo, idx::Int) = (@assert idx == 1; info.results)
getresult_impl(::MethodMatchInfo, ::Int) = nothing
call_effects_impl(info::MethodMatchInfo) = info.effects

"""
info::UnionSplitInfo <: CallInfo
Expand All @@ -41,20 +47,13 @@ each partition (`info.matches::Vector{MethodMatchInfo}`).
This info is illegal on any statement that is not a call to a generic function.
"""
struct UnionSplitInfo <: CallInfo
matches::Vector{MethodMatchInfo}
end

nmatches(info::MethodMatchInfo) = length(info.results)
function nmatches(info::UnionSplitInfo)
n = 0
for mminfo in info.matches
n += nmatches(mminfo)
end
return n
matches::Vector{MethodLookupResult}
effects::Effects
end
nsplit_impl(info::UnionSplitInfo) = length(info.matches)
getsplit_impl(info::UnionSplitInfo, idx::Int) = getsplit_impl(info.matches[idx], 1)
getresult_impl(::UnionSplitInfo, ::Int) = nothing
call_effects_impl(info::UnionSplitInfo) = info.effects

struct ConstPropResult
result::InferenceResult
Expand Down Expand Up @@ -84,12 +83,13 @@ In addition to the original call information `info.call`, this info also keeps t
of constant inference `info.results::Vector{Union{Nothing,ConstResult}}`.
"""
struct ConstCallInfo <: CallInfo
call::Union{MethodMatchInfo,UnionSplitInfo}
call::CallInfo
results::Vector{Union{Nothing,ConstResult}}
end
nsplit_impl(info::ConstCallInfo) = nsplit(info.call)
getsplit_impl(info::ConstCallInfo, idx::Int) = getsplit(info.call, idx)
getresult_impl(info::ConstCallInfo, idx::Int) = info.results[idx]
call_effects_impl(info::ConstCallInfo) = call_effects(info.call)

"""
info::MethodResultPure <: CallInfo
Expand All @@ -101,10 +101,11 @@ by calling an `@pure` function).
struct MethodResultPure <: CallInfo
info::CallInfo
end
let instance = MethodResultPure(NoCallInfo())
let instance = MethodResultPure(NoCallInfo(EFFECTS_TOTAL))
global MethodResultPure
MethodResultPure() = instance
end
call_effects_impl(info::MethodResultPure) = call_effects(info.info)

"""
ainfo::AbstractIterationInfo
Expand All @@ -115,7 +116,6 @@ Each (abstract) call to `iterate`, corresponds to one entry in `ainfo.each::Vect
struct AbstractIterationInfo
each::Vector{CallMeta}
end

const MaybeAbstractIterationInfo = Union{Nothing, AbstractIterationInfo}

"""
Expand All @@ -130,10 +130,11 @@ not an `_apply_iterate` call.
"""
struct ApplyCallInfo <: CallInfo
# The info for the call itself
call::Any
call::CallInfo
# AbstractIterationInfo for each argument, if applicable
arginfo::Vector{MaybeAbstractIterationInfo}
end
call_effects_impl(info::ApplyCallInfo) = call_effects(info.call)

"""
info::UnionSplitApplyCallInfo <: CallInfo
Expand All @@ -144,6 +145,13 @@ This info is illegal on any statement that is not an `_apply_iterate` call.
struct UnionSplitApplyCallInfo <: CallInfo
infos::Vector{ApplyCallInfo}
end
function call_effects_impl(info::UnionSplitApplyCallInfo)
all_effects = EFFECTS_TOTAL
for ainfo in info.infos
all_effects = merge_effects(all_effects, call_effects(ainfo))
end
return all_effects
end

"""
info::InvokeCallInfo
Expand All @@ -155,10 +163,12 @@ Optionally keeps `info.result::InferenceResult` that keeps constant information.
struct InvokeCallInfo <: CallInfo
match::MethodMatch
result::Union{Nothing,ConstResult}
effects::Effects
end
call_effects_impl(info::InvokeCallInfo) = info.effects

"""
info::OpaqueClosureCallInfo
info::OpaqueClosureCallInfo <: CallInfo
Represents a resolved call of opaque closure, carrying the `info.match::MethodMatch` of
the method that has been processed.
Expand All @@ -167,7 +177,9 @@ Optionally keeps `info.result::InferenceResult` that keeps constant information.
struct OpaqueClosureCallInfo <: CallInfo
match::MethodMatch
result::Union{Nothing,ConstResult}
effects::Effects
end
call_effects_impl(info::OpaqueClosureCallInfo) = info.effects

"""
info::OpaqueClosureCreateInfo <: CallInfo
Expand All @@ -184,6 +196,7 @@ struct OpaqueClosureCreateInfo <: CallInfo
return new(unspec)
end
end
call_effects_impl(::OpaqueClosureCreateInfo) = Effects()

# Stmt infos that are used by external consumers, but not by optimization.
# These are not produced by default and must be explicitly opted into by
Expand All @@ -193,12 +206,13 @@ end
info::ReturnTypeCallInfo <: CallInfo
Represents a resolved call of `Core.Compiler.return_type`.
`info.call` wraps the info corresponding to the call that `Core.Compiler.return_type` call
was supposed to analyze.
`info.call` wraps the call information corresponding to the call that
the `Core.Compiler.return_type` call was supposed to analyze.
"""
struct ReturnTypeCallInfo <: CallInfo
info::CallInfo
end
call_effects_impl(::ReturnTypeCallInfo) = EFFECTS_TOTAL

"""
info::FinalizerInfo <: CallInfo
Expand All @@ -207,9 +221,10 @@ Represents the information of a potential (later) call to the finalizer on the g
object type.
"""
struct FinalizerInfo <: CallInfo
info::CallInfo # the callinfo for the finalizer call
effects::Effects # the effects for the finalizer call
info::CallInfo # the callinfo for the finalizer call
finalizer_effects::Effects # the effects for the finalizer call
end
call_effects_impl(::FinalizerInfo) = Effects()

"""
info::ModifyFieldInfo <: CallInfo
Expand All @@ -220,5 +235,6 @@ Represents a resolved all of `modifyfield!(obj, name, op, x, [order])`.
struct ModifyFieldInfo <: CallInfo
info::CallInfo # the callinfo for the `op(getfield(obj, name), x)` call
end
call_effects_impl(::ModifyFieldInfo) = Effects()

@specialize
Loading

0 comments on commit a531f9c

Please sign in to comment.