Skip to content

Commit

Permalink
effects: replaces usages of ALWAYS_FALSE with TRISTATE_UNKNOWN (#…
Browse files Browse the repository at this point in the history
…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.
  • Loading branch information
aviatesk authored Apr 1, 2022
1 parent 3888176 commit cf649a7
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 13 deletions.
18 changes: 10 additions & 8 deletions base/compiler/abstractinterpretation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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{}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
8 changes: 4 additions & 4 deletions base/compiler/tfuncs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
30 changes: 29 additions & 1 deletion base/compiler/types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down

0 comments on commit cf649a7

Please sign in to comment.