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 Sep 29, 2022
1 parent fb34f00 commit 0626272
Show file tree
Hide file tree
Showing 7 changed files with 274 additions and 236 deletions.
142 changes: 80 additions & 62 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
251 changes: 120 additions & 131 deletions base/compiler/ssair/inlining.jl

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions base/compiler/ssair/passes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -872,7 +872,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 @@ -1212,7 +1212,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
66 changes: 46 additions & 20 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,7 +30,9 @@ not a call to a generic function.
"""
struct MethodMatchInfo <: CallInfo
results::MethodLookupResult
effects::Effects
end
call_effects_impl(info::MethodMatchInfo) = info.effects

"""
info::UnionSplitInfo <: CallInfo
Expand All @@ -38,17 +44,10 @@ 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
call_effects_impl(info::UnionSplitInfo) = info.effects

struct ConstPropResult
result::InferenceResult
Expand Down Expand Up @@ -78,9 +77,10 @@ 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
call_effects_impl(info::ConstCallInfo) = call_effects(info.call)

"""
info::MethodResultPure <: CallInfo
Expand All @@ -92,10 +92,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 @@ -106,7 +107,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 @@ -121,10 +121,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 @@ -135,6 +136,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 @@ -146,10 +154,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 @@ -158,7 +168,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 @@ -175,6 +187,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 @@ -184,12 +197,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 @@ -199,7 +213,19 @@ object type.
"""
struct FinalizerInfo <: CallInfo
info::CallInfo
effects::Effects
finalizer_effects::Effects # the effects for the finalizer call
end
call_effects_impl(::FinalizerInfo) = Effects()

"""
info::ModifyFieldInfo <: CallInfo
Represents a resolved all of `modifyfield!(obj, name, op, x, [order])`.
`info.info` wraps the call information of `op(getfield(obj, name), x)`.
"""
struct ModifyFieldInfo <: CallInfo
info::CallInfo
end
call_effects_impl(::ModifyFieldInfo) = Effects()

@specialize
32 changes: 18 additions & 14 deletions base/compiler/tfuncs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1148,15 +1148,15 @@ end
function abstract_modifyfield!(interp::AbstractInterpreter, argtypes::Vector{Any}, sv::InferenceState)
nargs = length(argtypes)
if !isempty(argtypes) && isvarargtype(argtypes[nargs])
nargs - 1 <= 6 || return CallMeta(Bottom, EFFECTS_THROWS, NoCallInfo())
nargs - 1 <= 6 || return CallMeta(Bottom, NoCallInfo(EFFECTS_THROWS))
nargs > 3 || return CallMeta(Any, EFFECTS_UNKNOWN, NoCallInfo())
else
5 <= nargs <= 6 || return CallMeta(Bottom, EFFECTS_THROWS, NoCallInfo())
5 <= nargs <= 6 || return CallMeta(Bottom, NoCallInfo(EFFECTS_THROWS))
end
o = unwrapva(argtypes[2])
f = unwrapva(argtypes[3])
RT = modifyfield!_tfunc(o, f, Any, Any)
info = false
info = NoCallInfo()
if nargs >= 5 && RT !== Bottom
# we may be able to refine this to a PartialStruct by analyzing `op(o.f, v)::T`
# as well as compute the info for the method matches
Expand All @@ -1172,9 +1172,9 @@ function abstract_modifyfield!(interp::AbstractInterpreter, argtypes::Vector{Any
elseif isconcretetype(RT) && has_nontrivial_const_info(typeinf_lattice(interp), TF2) # isconcrete condition required to form a PartialStruct
RT = PartialStruct(RT, Any[TF, TF2])
end
info = callinfo.info
info = ModifyFieldInfo(callinfo.info)
end
return CallMeta(RT, Effects(), info)
return CallMeta(RT, info)
end
replacefield!_tfunc(o, f, x, v, success_order, failure_order) = (@nospecialize; replacefield!_tfunc(o, f, x, v))
replacefield!_tfunc(o, f, x, v, success_order) = (@nospecialize; replacefield!_tfunc(o, f, x, v))
Expand Down Expand Up @@ -2237,7 +2237,7 @@ function return_type_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, s
if isa(af_argtype, DataType) && af_argtype <: Tuple
argtypes_vec = Any[aft, af_argtype.parameters...]
if contains_is(argtypes_vec, Union{})
return CallMeta(Const(Union{}), EFFECTS_TOTAL, NoCallInfo())
return CallMeta(Const(Union{}), NoCallInfo(EFFECTS_TOTAL))
end
# Run the abstract_call without restricting abstract call
# sites. Otherwise, our behavior model of abstract_call
Expand All @@ -2250,36 +2250,40 @@ function return_type_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, s
else
call = abstract_call(interp, ArgInfo(nothing, argtypes_vec), sv, -1)
end
info = verbose_stmt_info(interp) ? MethodResultPure(ReturnTypeCallInfo(call.info)) : MethodResultPure()
if verbose_stmt_info(interp)
info = MethodResultPure(ReturnTypeCallInfo(call.info))
else
info = MethodResultPure(NoCallInfo(EFFECTS_TOTAL))
end
rt = widenconditional(call.rt)
if isa(rt, Const)
# output was computed to be constant
return CallMeta(Const(typeof(rt.val)), EFFECTS_TOTAL, info)
return CallMeta(Const(typeof(rt.val)), info)
end
rt = widenconst(rt)
if rt === Bottom || (isconcretetype(rt) && !iskindtype(rt))
# output cannot be improved so it is known for certain
return CallMeta(Const(rt), EFFECTS_TOTAL, info)
return CallMeta(Const(rt), info)
elseif isa(sv, InferenceState) && !isempty(sv.pclimitations)
# conservatively express uncertainty of this result
# in two ways: both as being a subtype of this, and
# because of LimitedAccuracy causes
return CallMeta(Type{<:rt}, EFFECTS_TOTAL, info)
return CallMeta(Type{<:rt}, info)
elseif (isa(tt, Const) || isconstType(tt)) &&
(isa(aft, Const) || isconstType(aft))
# input arguments were known for certain
# XXX: this doesn't imply we know anything about rt
return CallMeta(Const(rt), EFFECTS_TOTAL, info)
return CallMeta(Const(rt), info)
elseif isType(rt)
return CallMeta(Type{rt}, EFFECTS_TOTAL, info)
return CallMeta(Type{rt}, info)
else
return CallMeta(Type{<:rt}, EFFECTS_TOTAL, info)
return CallMeta(Type{<:rt}, info)
end
end
end
end
end
return CallMeta(Type, EFFECTS_THROWS, NoCallInfo())
return CallMeta(Type, NoCallInfo(EFFECTS_THROWS))
end

# N.B.: typename maps type equivalence classes to a single value
Expand Down
1 change: 1 addition & 0 deletions base/compiler/types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -286,3 +286,4 @@ ipo_lattice(::AbstractInterpreter) = InferenceLattice(IPOResultLattice.instance)
optimizer_lattice(::AbstractInterpreter) = OptimizerLattice()

abstract type CallInfo end
call_effects(@nospecialize info::CallInfo) = call_effects_impl(info)::Effects

0 comments on commit 0626272

Please sign in to comment.