From 5ff15cbe319c58c476a6004dc074a61e236f3dce 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 aea731c43d1dc..5252e41244ccc 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -1858,7 +1858,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 @@ -1887,8 +1887,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 @@ -1906,8 +1906,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{} @@ -2031,9 +2031,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 @@ -2284,7 +2286,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 a92a2bc977262..23a18ae8d8aeb 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -1786,8 +1786,8 @@ function builtin_effects(f::Builtin, argtypes::Vector{Any}, rt) effect_free = contains_is(_EFFECT_FREE_BUILTINS, f) || contains_is(_PURE_BUILTINS, f) 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 @@ -1964,8 +1964,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 b5ae060dd5394..94b6a1c4c8743 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