From cf649a78a7fe1d6e080bbfe71faf7b8a4c3c4bdb Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Fri, 1 Apr 2022 13:54:43 +0900 Subject: [PATCH] effects: replaces usages of `ALWAYS_FALSE` with `TRISTATE_UNKNOWN` (#44808) Although they are basically equivalent in the current implementation, it would be more correct conceptually if we propagate `TRISTATE_UNKNOWN` instead of `ALWAYS_TRUE`, as it is hard or impossible to derive a global conclusion from a local analysis on each statement in most places. This commit also adds a docstring that simply explains the design of the effect analysis. --- base/compiler/abstractinterpretation.jl | 18 ++++++++------- base/compiler/tfuncs.jl | 8 +++---- base/compiler/types.jl | 30 ++++++++++++++++++++++++- 3 files changed, 43 insertions(+), 13 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 963b7e9547899..4db0505e4e38c 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -1868,7 +1868,7 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e), t = Bottom tristate_merge!(sv, Effects(EFFECTS_TOTAL; # consistent = ALWAYS_TRUE, # N.B depends on !ismutabletype(t) above - nothrow = ALWAYS_FALSE)) + nothrow = TRISTATE_UNKNOWN)) @goto t_computed elseif !isa(at, Const) allconst = false @@ -1897,8 +1897,8 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e), is_nothrow = false end tristate_merge!(sv, Effects(EFFECTS_TOTAL; - consistent = !ismutabletype(t) ? ALWAYS_TRUE : ALWAYS_FALSE, - nothrow = is_nothrow ? ALWAYS_TRUE : ALWAYS_FALSE)) + consistent = !ismutabletype(t) ? ALWAYS_TRUE : TRISTATE_UNKNOWN, + nothrow = is_nothrow ? ALWAYS_TRUE : TRISTATE_UNKNOWN)) elseif ehead === :splatnew t, isexact = instanceof_tfunc(abstract_eval_value(interp, e.args[1], vtypes, sv)) is_nothrow = false # TODO: More precision @@ -1916,8 +1916,8 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e), end end tristate_merge!(sv, Effects(EFFECTS_TOTAL; - consistent = ismutabletype(t) ? ALWAYS_FALSE : ALWAYS_TRUE, - nothrow = is_nothrow ? ALWAYS_TRUE : ALWAYS_FALSE)) + consistent = ismutabletype(t) ? TRISTATE_UNKNOWN : ALWAYS_TRUE, + nothrow = is_nothrow ? ALWAYS_TRUE : TRISTATE_UNKNOWN)) elseif ehead === :new_opaque_closure tristate_merge!(sv, Effects()) # TODO t = Union{} @@ -2039,9 +2039,11 @@ function abstract_eval_global(M::Module, s::Symbol, frame::InferenceState) ty = abstract_eval_global(M, s) isa(ty, Const) && return ty if isdefined(M,s) - tristate_merge!(frame, Effects(EFFECTS_TOTAL; consistent=ALWAYS_FALSE)) + tristate_merge!(frame, Effects(EFFECTS_TOTAL; consistent=TRISTATE_UNKNOWN)) else - tristate_merge!(frame, Effects(EFFECTS_TOTAL; consistent=ALWAYS_FALSE, nothrow=ALWAYS_FALSE)) + tristate_merge!(frame, Effects(EFFECTS_TOTAL; + consistent=TRISTATE_UNKNOWN, + nothrow=TRISTATE_UNKNOWN)) end return ty end @@ -2281,7 +2283,7 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState) changes = StateUpdate(lhs, VarState(t, false), changes, false) elseif isa(lhs, GlobalRef) tristate_merge!(frame, Effects(EFFECTS_TOTAL, - effect_free=ALWAYS_FALSE, + effect_free=TRISTATE_UNKNOWN, nothrow=TRISTATE_UNKNOWN)) elseif !isa(lhs, SSAValue) tristate_merge!(frame, EFFECTS_UNKNOWN) diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 597519dee67e1..2d614d70d5c90 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -1829,8 +1829,8 @@ function builtin_effects(f::Builtin, argtypes::Vector{Any}, rt) end return Effects(EFFECTS_TOTAL; - consistent = ipo_consistent ? ALWAYS_TRUE : ALWAYS_FALSE, - effect_free = effect_free ? ALWAYS_TRUE : ALWAYS_FALSE, + consistent = ipo_consistent ? ALWAYS_TRUE : TRISTATE_UNKNOWN, + effect_free = effect_free ? ALWAYS_TRUE : TRISTATE_UNKNOWN, nothrow = nothrow ? ALWAYS_TRUE : TRISTATE_UNKNOWN) end @@ -2007,8 +2007,8 @@ function intrinsic_effects(f::IntrinsicFunction, argtypes::Vector{Any}) nothrow = !isvarargtype(argtypes[end]) && intrinsic_nothrow(f, argtypes[2:end]) return Effects(EFFECTS_TOTAL; - consistent = ipo_consistent ? ALWAYS_TRUE : ALWAYS_FALSE, - effect_free = effect_free ? ALWAYS_TRUE : ALWAYS_FALSE, + consistent = ipo_consistent ? ALWAYS_TRUE : TRISTATE_UNKNOWN, + effect_free = effect_free ? ALWAYS_TRUE : TRISTATE_UNKNOWN, nothrow = nothrow ? ALWAYS_TRUE : TRISTATE_UNKNOWN) end diff --git a/base/compiler/types.jl b/base/compiler/types.jl index aa2b997eee70b..96f882ea4a770 100644 --- a/base/compiler/types.jl +++ b/base/compiler/types.jl @@ -33,6 +33,34 @@ function tristate_merge(old::TriState, new::TriState) return new end +""" + effects::Effects + +Represents computational effects of a method call. + +The effects are composed of the following set of different properties: +- `effects.consistent::TriState`: this method is guaranteed to return or terminate consistently +- `effect_free::TriState`: this method is free from externally semantically visible side effects +- `nothrow::TriState`: this method is guaranteed to not throw an exception +- `terminates::TriState`: this method is guaranteed to terminate +- `nonoverlayed::Bool`: indicates that any methods that may be called within this method + are not defined in an [overlayed method table](@ref OverlayMethodTable) +See [`Base.@assume_effects`](@ref) for more detailed explanation on the definitions of these properties. + +Along the abstract interpretation, `Effects` at each statement are analyzed locally and +they are merged into the single global `Effects` that represents the entire effects of +the analyzed method (see `tristate_merge!`). +Each effect property is represented as tri-state and managed separately. +The tri-state consists of `ALWAYS_TRUE`, `TRISTATE_UNKNOWN` and `ALWAYS_FALSE`. +An effect property is initialized with `ALWAYS_TRUE` and then transitioned towards +`TRISTATE_UNKNOWN` or `ALWAYS_FALSE`. When we find a statement that has some effect, +`ALWAYS_TRUE` is propagated if that effect is known to _always_ happen, otherwise +`TRISTATE_UNKNOWN` is propagated. If a property is known to be `ALWAYS_FALSE`, +there is no need to do additional analysis as it can not be refined anyway. +Note that however, within the current data-flow analysis design, it is hard to derive a global +conclusion from a local analysis on each statement, and as a result, the effect analysis +usually propagates `TRISTATE_UNKNOWN` currently. +""" struct Effects consistent::TriState effect_free::TriState @@ -59,7 +87,7 @@ function Effects( end const EFFECTS_TOTAL = Effects(ALWAYS_TRUE, ALWAYS_TRUE, ALWAYS_TRUE, ALWAYS_TRUE, true) -const EFFECTS_THROWS = Effects(ALWAYS_TRUE, ALWAYS_TRUE, ALWAYS_FALSE, ALWAYS_TRUE, true) +const EFFECTS_THROWS = Effects(ALWAYS_TRUE, ALWAYS_TRUE, TRISTATE_UNKNOWN, ALWAYS_TRUE, true) const EFFECTS_UNKNOWN = Effects(TRISTATE_UNKNOWN, TRISTATE_UNKNOWN, TRISTATE_UNKNOWN, TRISTATE_UNKNOWN, true) # mostly unknown, but it's not overlayed at least (e.g. it's not a call) const EFFECTS_UNKNOWN′ = Effects(TRISTATE_UNKNOWN, TRISTATE_UNKNOWN, TRISTATE_UNKNOWN, TRISTATE_UNKNOWN, false) # unknown, really