Skip to content

Commit 7a1e404

Browse files
authored
Merge pull request JuliaLang#45701 from JuliaLang/avi/rtconsistency
effects: improve the `:consistent`-cy analysis accuracy
2 parents 1f36c06 + 96a70e3 commit 7a1e404

File tree

12 files changed

+171
-85
lines changed

12 files changed

+171
-85
lines changed

base/compiler/abstractinterpretation.jl

Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f),
221221
elseif isa(matches, MethodMatches) ? (!matches.fullmatch || any_ambig(matches)) :
222222
(!all(matches.fullmatches) || any_ambig(matches))
223223
# Account for the fact that we may encounter a MethodError with a non-covered or ambiguous signature.
224-
all_effects = Effects(all_effects; nothrow=TRISTATE_UNKNOWN)
224+
all_effects = Effects(all_effects; nothrow=ALWAYS_FALSE)
225225
end
226226

227227
rettype = from_interprocedural!(rettype, sv, arginfo, conditionals)
@@ -619,7 +619,7 @@ function abstract_call_method(interp::AbstractInterpreter, method::Method, @nosp
619619
elseif edgecycle
620620
# Some sort of recursion was detected. Even if we did not limit types,
621621
# we cannot guarantee that the call will terminate
622-
effects = Effects(effects; terminates=TRISTATE_UNKNOWN)
622+
effects = Effects(effects; terminates=ALWAYS_FALSE)
623623
end
624624

625625
return MethodCallResult(rt, edgecycle, edgelimited, edge, effects)
@@ -1769,7 +1769,7 @@ function abstract_call_opaque_closure(interp::AbstractInterpreter,
17691769
(aty, rty) = (unwrap_unionall(ftt)::DataType).parameters
17701770
rty = rewrap_unionall(rty isa TypeVar ? rty.lb : rty, ftt)
17711771
if !(rt rty && tuple_tfunc(arginfo.argtypes[2:end]) rewrap_unionall(aty, ftt))
1772-
effects = Effects(effects; nothrow=TRISTATE_UNKNOWN)
1772+
effects = Effects(effects; nothrow=ALWAYS_FALSE)
17731773
end
17741774
end
17751775
rt = from_interprocedural!(rt, sv, arginfo, match.spec_types)
@@ -1953,9 +1953,7 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e),
19531953
at = tmeet(at, ft)
19541954
if at === Bottom
19551955
t = Bottom
1956-
tristate_merge!(sv, Effects(EFFECTS_TOTAL;
1957-
# consistent = ALWAYS_TRUE, # N.B depends on !ismutabletype(t) above
1958-
nothrow = TRISTATE_UNKNOWN))
1956+
tristate_merge!(sv, EFFECTS_THROWS)
19591957
@goto t_computed
19601958
elseif !isa(at, Const)
19611959
allconst = false
@@ -1985,7 +1983,7 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e),
19851983
end
19861984
tristate_merge!(sv, Effects(EFFECTS_TOTAL;
19871985
consistent = !ismutabletype(t) ? ALWAYS_TRUE : TRISTATE_UNKNOWN,
1988-
nothrow = is_nothrow ? ALWAYS_TRUE : TRISTATE_UNKNOWN))
1986+
nothrow = is_nothrow ? ALWAYS_TRUE : ALWAYS_FALSE))
19891987
elseif ehead === :splatnew
19901988
t, isexact = instanceof_tfunc(abstract_eval_value(interp, e.args[1], vtypes, sv))
19911989
is_nothrow = false # TODO: More precision
@@ -2003,8 +2001,8 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e),
20032001
end
20042002
end
20052003
tristate_merge!(sv, Effects(EFFECTS_TOTAL;
2006-
consistent = ismutabletype(t) ? TRISTATE_UNKNOWN : ALWAYS_TRUE,
2007-
nothrow = is_nothrow ? ALWAYS_TRUE : TRISTATE_UNKNOWN))
2004+
consistent = !ismutabletype(t) ? ALWAYS_TRUE : TRISTATE_UNKNOWN,
2005+
nothrow = is_nothrow ? ALWAYS_TRUE : ALWAYS_FALSE))
20082006
elseif ehead === :new_opaque_closure
20092007
tristate_merge!(sv, Effects()) # TODO
20102008
t = Union{}
@@ -2040,12 +2038,12 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e),
20402038
effects = v[2]
20412039
effects = decode_effects_override(effects)
20422040
tristate_merge!(sv, Effects(
2043-
effects.consistent ? ALWAYS_TRUE : TRISTATE_UNKNOWN,
2044-
effects.effect_free ? ALWAYS_TRUE : TRISTATE_UNKNOWN,
2045-
effects.nothrow ? ALWAYS_TRUE : TRISTATE_UNKNOWN,
2046-
effects.terminates_globally ? ALWAYS_TRUE : TRISTATE_UNKNOWN,
2041+
effects.consistent ? ALWAYS_TRUE : ALWAYS_FALSE,
2042+
effects.effect_free ? ALWAYS_TRUE : ALWAYS_FALSE,
2043+
effects.nothrow ? ALWAYS_TRUE : ALWAYS_FALSE,
2044+
effects.terminates_globally ? ALWAYS_TRUE : ALWAYS_FALSE,
20472045
#=nonoverlayed=#true,
2048-
effects.notaskstate ? ALWAYS_TRUE : TRISTATE_UNKNOWN
2046+
effects.notaskstate ? ALWAYS_TRUE : ALWAYS_FALSE
20492047
))
20502048
else
20512049
tristate_merge!(sv, EFFECTS_UNKNOWN)
@@ -2127,20 +2125,20 @@ function abstract_eval_global(M::Module, s::Symbol, frame::InferenceState)
21272125
ty = abstract_eval_global(M, s)
21282126
isa(ty, Const) && return ty
21292127
if isdefined(M,s)
2130-
tristate_merge!(frame, Effects(EFFECTS_TOTAL; consistent=TRISTATE_UNKNOWN))
2128+
tristate_merge!(frame, Effects(EFFECTS_TOTAL; consistent=ALWAYS_FALSE))
21312129
else
21322130
tristate_merge!(frame, Effects(EFFECTS_TOTAL;
2133-
consistent=TRISTATE_UNKNOWN,
2134-
nothrow=TRISTATE_UNKNOWN))
2131+
consistent=ALWAYS_FALSE,
2132+
nothrow=ALWAYS_FALSE))
21352133
end
21362134
return ty
21372135
end
21382136

21392137
function handle_global_assignment!(interp::AbstractInterpreter, frame::InferenceState, lhs::GlobalRef, @nospecialize(newty))
2140-
nothrow = global_assignment_nothrow(lhs.mod, lhs.name, newty)
2141-
tristate_merge!(frame, Effects(EFFECTS_TOTAL,
2142-
effect_free=TRISTATE_UNKNOWN,
2143-
nothrow=nothrow ? ALWAYS_TRUE : TRISTATE_UNKNOWN))
2138+
effect_free = ALWAYS_FALSE
2139+
nothrow = global_assignment_nothrow(lhs.mod, lhs.name, newty) ?
2140+
ALWAYS_TRUE : ALWAYS_FALSE
2141+
tristate_merge!(frame, Effects(EFFECTS_TOTAL; effect_free, nothrow))
21442142
end
21452143

21462144
abstract_eval_ssavalue(s::SSAValue, sv::InferenceState) = abstract_eval_ssavalue(s, sv.ssavaluetypes)
@@ -2230,12 +2228,10 @@ end
22302228

22312229
function handle_control_backedge!(frame::InferenceState, from::Int, to::Int)
22322230
if from > to
2233-
if is_effect_overridden(frame, :terminates_globally)
2234-
# this frame is known to terminate
2235-
elseif is_effect_overridden(frame, :terminates_locally)
2231+
if is_effect_overridden(frame, :terminates_locally)
22362232
# this backedge is known to terminate
22372233
else
2238-
tristate_merge!(frame, Effects(EFFECTS_TOTAL; terminates=TRISTATE_UNKNOWN))
2234+
tristate_merge!(frame, Effects(EFFECTS_TOTAL; terminates=ALWAYS_FALSE))
22392235
end
22402236
end
22412237
return nothing

base/compiler/inferencestate.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ mutable struct InferenceState
179179
# requires dynamic reachability, while the former is global).
180180
inbounds = inbounds_option()
181181
inbounds_taints_consistency = !(inbounds === :on || (inbounds === :default && !any_inbounds(code)))
182-
consistent = inbounds_taints_consistency ? TRISTATE_UNKNOWN : ALWAYS_TRUE
182+
consistent = inbounds_taints_consistency ? ALWAYS_FALSE : ALWAYS_TRUE
183183
ipo_effects = Effects(EFFECTS_TOTAL; consistent, inbounds_taints_consistency)
184184

185185
params = InferenceParams(interp)

base/compiler/ssair/show.jl

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -791,7 +791,11 @@ function show_ir(io::IO, code::Union{IRCode, CodeInfo}, config::IRShowConfig=def
791791
end
792792

793793
tristate_letter(t::TriState) = t === ALWAYS_TRUE ? '+' : t === ALWAYS_FALSE ? '!' : '?'
794-
tristate_color(t::TriState) = t === ALWAYS_TRUE ? :green : t === ALWAYS_FALSE ? :red : :orange
794+
tristate_color(t::TriState) = t === ALWAYS_TRUE ? :green : t === ALWAYS_FALSE ? :red : :yellow
795+
tristate_repr(t::TriState) =
796+
t === ALWAYS_TRUE ? "ALWAYS_TRUE" :
797+
t === ALWAYS_FALSE ? "ALWAYS_FALSE" :
798+
t === TRISTATE_UNKNOWN ? "TRISTATE_UNKNOWN" : nothing
795799

796800
function Base.show(io::IO, e::Core.Compiler.Effects)
797801
print(io, "(")
@@ -808,4 +812,13 @@ function Base.show(io::IO, e::Core.Compiler.Effects)
808812
e.nonoverlayed || printstyled(io, ''; color=:red)
809813
end
810814

815+
function Base.show(io::IO, t::TriState)
816+
s = tristate_repr(t)
817+
if s !== nothing
818+
printstyled(io, s; color = tristate_color(t))
819+
else # unknown state, redirect to the fallback printing
820+
Base.@invoke show(io::IO, t::Any)
821+
end
822+
end
823+
811824
@specialize

base/compiler/tfuncs.jl

Lines changed: 35 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1794,14 +1794,14 @@ const _SPECIAL_BUILTINS = Any[
17941794
Core._apply_iterate
17951795
]
17961796

1797-
function builtin_effects(f::Builtin, argtypes::Vector{Any}, rt)
1797+
function builtin_effects(f::Builtin, argtypes::Vector{Any}, @nospecialize rt)
17981798
if isa(f, IntrinsicFunction)
17991799
return intrinsic_effects(f, argtypes)
18001800
end
18011801

18021802
@assert !contains_is(_SPECIAL_BUILTINS, f)
18031803

1804-
nothrow = false
1804+
argtypes′ = argtypes[2:end]
18051805
if (f === Core.getfield || f === Core.isdefined) && length(argtypes) >= 3
18061806
# consistent if the argtype is immutable
18071807
if isvarargtype(argtypes[2])
@@ -1812,37 +1812,42 @@ function builtin_effects(f::Builtin, argtypes::Vector{Any}, rt)
18121812
return Effects(; effect_free=ALWAYS_TRUE, terminates=ALWAYS_TRUE, nonoverlayed=true)
18131813
end
18141814
s = s::DataType
1815-
ipo_consistent = !ismutabletype(s)
1816-
nothrow = false
1817-
if f === Core.getfield && !isvarargtype(argtypes[end]) &&
1818-
getfield_boundscheck(argtypes[2:end]) !== true
1815+
consistent = !ismutabletype(s) ? ALWAYS_TRUE : ALWAYS_FALSE
1816+
if f === Core.getfield && !isvarargtype(argtypes[end]) && getfield_boundscheck(argtypes′) !== true
18191817
# If we cannot independently prove inboundsness, taint consistency.
18201818
# The inbounds-ness assertion requires dynamic reachability, while
18211819
# :consistent needs to be true for all input values.
18221820
# N.B. We do not taint for `--check-bounds=no` here -that happens in
18231821
# InferenceState.
1824-
nothrow = getfield_nothrow(argtypes[2], argtypes[3], true)
1825-
ipo_consistent &= nothrow
1822+
if getfield_nothrow(argtypes[2], argtypes[3], true)
1823+
nothrow = ALWAYS_TRUE
1824+
else
1825+
consistent = nothrow = ALWAYS_FALSE
1826+
end
18261827
else
1827-
nothrow = isvarargtype(argtypes[end]) ? false :
1828-
builtin_nothrow(f, argtypes[2:end], rt)
1828+
nothrow = (!isvarargtype(argtypes[end]) && builtin_nothrow(f, argtypes′, rt)) ?
1829+
ALWAYS_TRUE : ALWAYS_FALSE
18291830
end
1830-
effect_free = true
1831+
effect_free = ALWAYS_TRUE
18311832
elseif f === getglobal && length(argtypes) >= 3
1832-
nothrow = getglobal_nothrow(argtypes[2:end])
1833-
ipo_consistent = nothrow && isconst( # types are already checked in `getglobal_nothrow`
1834-
(argtypes[2]::Const).val::Module, (argtypes[3]::Const).val::Symbol)
1835-
effect_free = true
1833+
if getglobal_nothrow(argtypes′)
1834+
consistent = isconst( # types are already checked in `getglobal_nothrow`
1835+
(argtypes[2]::Const).val::Module, (argtypes[3]::Const).val::Symbol) ?
1836+
ALWAYS_TRUE : ALWAYS_FALSE
1837+
nothrow = ALWAYS_TRUE
1838+
else
1839+
consistent = nothrow = ALWAYS_FALSE
1840+
end
1841+
effect_free = ALWAYS_TRUE
18361842
else
1837-
ipo_consistent = contains_is(_CONSISTENT_BUILTINS, f)
1838-
effect_free = contains_is(_EFFECT_FREE_BUILTINS, f) || contains_is(_PURE_BUILTINS, f)
1839-
nothrow = isvarargtype(argtypes[end]) ? false : builtin_nothrow(f, argtypes[2:end], rt)
1843+
consistent = contains_is(_CONSISTENT_BUILTINS, f) ? ALWAYS_TRUE : ALWAYS_FALSE
1844+
effect_free = (contains_is(_EFFECT_FREE_BUILTINS, f) || contains_is(_PURE_BUILTINS, f)) ?
1845+
ALWAYS_TRUE : ALWAYS_FALSE
1846+
nothrow = (!isvarargtype(argtypes[end]) && builtin_nothrow(f, argtypes′, rt)) ?
1847+
ALWAYS_TRUE : ALWAYS_FALSE
18401848
end
18411849

1842-
return Effects(EFFECTS_TOTAL;
1843-
consistent = ipo_consistent ? ALWAYS_TRUE : TRISTATE_UNKNOWN,
1844-
effect_free = effect_free ? ALWAYS_TRUE : TRISTATE_UNKNOWN,
1845-
nothrow = nothrow ? ALWAYS_TRUE : TRISTATE_UNKNOWN)
1850+
return Effects(EFFECTS_TOTAL; consistent, effect_free, nothrow)
18461851
end
18471852

18481853
function builtin_nothrow(@nospecialize(f), argtypes::Array{Any, 1}, @nospecialize(rt))
@@ -2008,19 +2013,18 @@ function intrinsic_effects(f::IntrinsicFunction, argtypes::Vector{Any})
20082013
return Effects()
20092014
end
20102015

2011-
ipo_consistent = !(
2016+
consistent = !(
20122017
f === Intrinsics.pointerref || # this one is volatile
20132018
f === Intrinsics.arraylen || # this one is volatile
20142019
f === Intrinsics.sqrt_llvm_fast || # this one may differ at runtime (by a few ulps)
20152020
f === Intrinsics.have_fma || # this one depends on the runtime environment
2016-
f === Intrinsics.cglobal) # cglobal lookup answer changes at runtime
2017-
effect_free = !(f === Intrinsics.pointerset)
2018-
nothrow = !isvarargtype(argtypes[end]) && intrinsic_nothrow(f, argtypes[2:end])
2019-
2020-
return Effects(EFFECTS_TOTAL;
2021-
consistent = ipo_consistent ? ALWAYS_TRUE : TRISTATE_UNKNOWN,
2022-
effect_free = effect_free ? ALWAYS_TRUE : TRISTATE_UNKNOWN,
2023-
nothrow = nothrow ? ALWAYS_TRUE : TRISTATE_UNKNOWN)
2021+
f === Intrinsics.cglobal # cglobal lookup answer changes at runtime
2022+
) ? ALWAYS_TRUE : ALWAYS_FALSE
2023+
effect_free = !(f === Intrinsics.pointerset) ? ALWAYS_TRUE : ALWAYS_FALSE
2024+
nothrow = (!isvarargtype(argtypes[end]) && intrinsic_nothrow(f, argtypes[2:end])) ?
2025+
ALWAYS_TRUE : ALWAYS_FALSE
2026+
2027+
return Effects(EFFECTS_TOTAL; consistent, effect_free, nothrow)
20242028
end
20252029

20262030
# TODO: this function is a very buggy and poor model of the return_type function

base/compiler/typeinfer.jl

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -430,10 +430,18 @@ end
430430
function adjust_effects(sv::InferenceState)
431431
ipo_effects = Effects(sv)
432432

433-
# Always throwing an error counts or never returning both count as consistent,
434-
# but we don't currently model idempontency using dataflow, so we don't notice.
435-
# Fix that up here to improve precision.
436-
if !ipo_effects.inbounds_taints_consistency && sv.bestguess === Union{}
433+
# refine :consistent-cy effect using the return type information
434+
# TODO this adjustment tries to compromise imprecise :consistent-cy information,
435+
# that is currently modeled in a flow-insensitive way: ideally we want to model it
436+
# with a proper dataflow analysis instead
437+
rt = sv.bestguess
438+
if !ipo_effects.inbounds_taints_consistency && rt === Bottom
439+
# always throwing an error counts or never returning both count as consistent
440+
ipo_effects = Effects(ipo_effects; consistent=ALWAYS_TRUE)
441+
elseif ipo_effects.consistent === TRISTATE_UNKNOWN && is_consistent_rt(rt)
442+
# in a case when the :consistent-cy here is only tainted by mutable allocations
443+
# (indicated by `TRISTATE_UNKNOWN`), we may be able to refine it if the return
444+
# type guarantees that the allocations are never returned
437445
ipo_effects = Effects(ipo_effects; consistent=ALWAYS_TRUE)
438446
end
439447

@@ -461,6 +469,14 @@ function adjust_effects(sv::InferenceState)
461469
return ipo_effects
462470
end
463471

472+
is_consistent_rt(@nospecialize rt) = _is_consistent_rt(widenconst(ignorelimited(rt)))
473+
function _is_consistent_rt(@nospecialize ty)
474+
if isa(ty, Union)
475+
return _is_consistent_rt(ty.a) && _is_consistent_rt(ty.b)
476+
end
477+
return ty === Symbol || isbitstype(ty)
478+
end
479+
464480
# inference completed on `me`
465481
# update the MethodInstance
466482
function finish(me::InferenceState, interp::AbstractInterpreter)
@@ -775,11 +791,11 @@ function merge_call_chain!(parent::InferenceState, ancestor::InferenceState, chi
775791
# and ensure that walking the parent list will get the same result (DAG) from everywhere
776792
# Also taint the termination effect, because we can no longer guarantee the absence
777793
# of recursion.
778-
tristate_merge!(parent, Effects(EFFECTS_TOTAL; terminates=TRISTATE_UNKNOWN))
794+
tristate_merge!(parent, Effects(EFFECTS_TOTAL; terminates=ALWAYS_FALSE))
779795
while true
780796
add_cycle_backedge!(child, parent, parent.currpc)
781797
union_caller_cycle!(ancestor, child)
782-
tristate_merge!(child, Effects(EFFECTS_TOTAL; terminates=TRISTATE_UNKNOWN))
798+
tristate_merge!(child, Effects(EFFECTS_TOTAL; terminates=ALWAYS_FALSE))
783799
child = parent
784800
child === ancestor && break
785801
parent = child.parent::InferenceState

base/compiler/types.jl

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,20 @@ Along the abstract interpretation, `Effects` at each statement are analyzed loca
5656
they are merged into the single global `Effects` that represents the entire effects of
5757
the analyzed method (see `tristate_merge!`).
5858
Each effect property is represented as tri-state and managed separately.
59-
The tri-state consists of `ALWAYS_TRUE`, `TRISTATE_UNKNOWN` and `ALWAYS_FALSE`.
59+
The tri-state consists of `ALWAYS_TRUE`, `TRISTATE_UNKNOWN` and `ALWAYS_FALSE`, where they
60+
have the following meanings:
61+
- `ALWAYS_TRUE`: this method is guaranteed to not have this effect.
62+
- `ALWAYS_FALSE`: this method may have this effect, and there is no need to do any further
63+
analysis w.r.t. this effect property as this conclusion will not be refined anyway.
64+
- `TRISTATE_UNKNOWN`: this effect property may still be refined to `ALWAYS_TRUE` or
65+
`ALWAYS_FALSE`, e.g. using return type information.
66+
6067
An effect property is initialized with `ALWAYS_TRUE` and then transitioned towards
61-
`TRISTATE_UNKNOWN` or `ALWAYS_FALSE`. When we find a statement that has some effect,
62-
`ALWAYS_TRUE` is propagated if that effect is known to _always_ happen, otherwise
63-
`TRISTATE_UNKNOWN` is propagated. If a property is known to be `ALWAYS_FALSE`,
64-
there is no need to do additional analysis as it can not be refined anyway.
65-
Note that however, within the current data-flow analysis design, it is hard to derive a global
66-
conclusion from a local analysis on each statement, and as a result, the effect analysis
67-
usually propagates `TRISTATE_UNKNOWN` currently.
68+
`ALWAYS_FALSE`. When we find a statement that has some effect, either of `TRISTATE_UNKNOWN`
69+
or `ALWAYS_FALSE` is propagated. Note that however, within the current flow-insensitive
70+
analysis design, it is usually difficult to derive a global conclusion accurately from local
71+
analysis on each statement, and therefore, the effect analysis usually propagates the
72+
`ALWAYS_FALSE` state conservatively.
6873
"""
6974
struct Effects
7075
consistent::TriState
@@ -94,10 +99,10 @@ function Effects(
9499
false)
95100
end
96101

97-
const EFFECTS_TOTAL = Effects(ALWAYS_TRUE, ALWAYS_TRUE, ALWAYS_TRUE, ALWAYS_TRUE, true, ALWAYS_TRUE)
98-
const EFFECTS_THROWS = Effects(ALWAYS_TRUE, ALWAYS_TRUE, TRISTATE_UNKNOWN, ALWAYS_TRUE, true, ALWAYS_TRUE)
99-
const EFFECTS_UNKNOWN = Effects(TRISTATE_UNKNOWN, TRISTATE_UNKNOWN, TRISTATE_UNKNOWN, TRISTATE_UNKNOWN, true, TRISTATE_UNKNOWN) # mostly unknown, but it's not overlayed at least (e.g. it's not a call)
100-
const EFFECTS_UNKNOWN′ = Effects(TRISTATE_UNKNOWN, TRISTATE_UNKNOWN, TRISTATE_UNKNOWN, TRISTATE_UNKNOWN, false, TRISTATE_UNKNOWN) # unknown, really
102+
const EFFECTS_TOTAL = Effects(ALWAYS_TRUE, ALWAYS_TRUE, ALWAYS_TRUE, ALWAYS_TRUE, true, ALWAYS_TRUE)
103+
const EFFECTS_THROWS = Effects(ALWAYS_TRUE, ALWAYS_TRUE, ALWAYS_FALSE, ALWAYS_TRUE, true, ALWAYS_TRUE)
104+
const EFFECTS_UNKNOWN = Effects(ALWAYS_FALSE, ALWAYS_FALSE, ALWAYS_FALSE, ALWAYS_FALSE, true, ALWAYS_FALSE) # mostly unknown, but it's not overlayed at least (e.g. it's not a call)
105+
const EFFECTS_UNKNOWN′ = Effects(ALWAYS_FALSE, ALWAYS_FALSE, ALWAYS_FALSE, ALWAYS_FALSE, false, ALWAYS_FALSE) # unknown, really
101106

102107
function Effects(e::Effects = EFFECTS_UNKNOWN′;
103108
consistent::TriState = e.consistent,

0 commit comments

Comments
 (0)