Skip to content

Commit 7b84093

Browse files
aviateskpcjentsch
authored andcommitted
effects: refactor builtin_effects (JuliaLang#46097)
Factor special builtin handlings into separated functions (e.g. `getfield_effects`) so that we can refactor them more easily. This also improves analysis accuracy a bit, e.g. ```julia julia> Base.infer_effects((Bool,)) do c obj = c ? Some{String}("foo") : Some{Symbol}(:bar) return getfield(obj, :value) end (!c,+e,!n,+t,!s) # master (+c,+e,!n,+t,+s) # this PR ```
1 parent f48231e commit 7b84093

File tree

5 files changed

+104
-66
lines changed

5 files changed

+104
-66
lines changed

base/compiler/abstractinterpretation.jl

+9-10
Original file line numberDiff line numberDiff line change
@@ -2161,16 +2161,15 @@ function abstract_eval_global(M::Module, s::Symbol)
21612161
end
21622162

21632163
function abstract_eval_global(M::Module, s::Symbol, frame::InferenceState)
2164-
ty = abstract_eval_global(M, s)
2165-
isa(ty, Const) && return ty
2166-
if isdefined(M,s)
2167-
tristate_merge!(frame, Effects(EFFECTS_TOTAL; consistent=ALWAYS_FALSE))
2168-
else
2169-
tristate_merge!(frame, Effects(EFFECTS_TOTAL;
2170-
consistent=ALWAYS_FALSE,
2171-
nothrow=ALWAYS_FALSE))
2172-
end
2173-
return ty
2164+
rt = abstract_eval_global(M, s)
2165+
consistent = nothrow = ALWAYS_FALSE
2166+
if isa(rt, Const)
2167+
consistent = nothrow = ALWAYS_TRUE
2168+
elseif isdefined(M,s)
2169+
nothrow = ALWAYS_TRUE
2170+
end
2171+
tristate_merge!(frame, Effects(EFFECTS_TOTAL; consistent, nothrow))
2172+
return rt
21742173
end
21752174

21762175
function handle_global_assignment!(interp::AbstractInterpreter, frame::InferenceState, lhs::GlobalRef, @nospecialize(newty))

base/compiler/tfuncs.jl

+62-46
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,7 @@ function getfield_boundscheck(argtypes::Vector{Any}) # ::Union{Bool, Nothing, Ty
749749
else
750750
return nothing
751751
end
752+
isvarargtype(boundscheck) && return nothing
752753
widenconst(boundscheck) !== Bool && return nothing
753754
boundscheck = widenconditional(boundscheck)
754755
if isa(boundscheck, Const)
@@ -1850,67 +1851,82 @@ const _SPECIAL_BUILTINS = Any[
18501851
Core._apply_iterate
18511852
]
18521853

1854+
function isdefined_effects(argtypes::Vector{Any})
1855+
# consistent if the first arg is immutable
1856+
isempty(argtypes) && return EFFECTS_THROWS
1857+
obj = argtypes[1]
1858+
isvarargtype(obj) && return Effects(EFFECTS_THROWS; consistent=ALWAYS_FALSE)
1859+
consistent = is_immutable_argtype(obj) ? ALWAYS_TRUE : ALWAYS_FALSE
1860+
nothrow = isdefined_nothrow(argtypes) ? ALWAYS_TRUE : ALWAYS_FALSE
1861+
return Effects(EFFECTS_TOTAL; consistent, nothrow)
1862+
end
1863+
1864+
function getfield_effects(argtypes::Vector{Any}, @nospecialize(rt))
1865+
# consistent if the argtype is immutable
1866+
isempty(argtypes) && return EFFECTS_THROWS
1867+
obj = argtypes[1]
1868+
isvarargtype(obj) && return Effects(EFFECTS_THROWS; consistent=ALWAYS_FALSE)
1869+
consistent = is_immutable_argtype(obj) ? ALWAYS_TRUE : ALWAYS_FALSE
1870+
# access to `isbitstype`-field initialized with undefined value leads to undefined behavior
1871+
# so should taint `:consistent`-cy while access to uninitialized non-`isbitstype` field
1872+
# throws `UndefRefError` so doesn't need to taint it
1873+
# NOTE `getfield_notundefined` conservatively checks if this field is never initialized
1874+
# with undefined value so that we don't taint `:consistent`-cy too aggressively here
1875+
if !(length(argtypes) 2 && getfield_notundefined(widenconst(obj), argtypes[2]))
1876+
consistent = ALWAYS_FALSE
1877+
end
1878+
if getfield_boundscheck(argtypes) !== true
1879+
# If we cannot independently prove inboundsness, taint consistency.
1880+
# The inbounds-ness assertion requires dynamic reachability, while
1881+
# :consistent needs to be true for all input values.
1882+
# N.B. We do not taint for `--check-bounds=no` here -that happens in
1883+
# InferenceState.
1884+
if length(argtypes) 2 && getfield_nothrow(argtypes[1], argtypes[2], true)
1885+
nothrow = ALWAYS_TRUE
1886+
else
1887+
consistent = nothrow = ALWAYS_FALSE
1888+
end
1889+
else
1890+
nothrow = getfield_nothrow(argtypes) ? ALWAYS_TRUE : ALWAYS_FALSE
1891+
end
1892+
return Effects(EFFECTS_TOTAL; consistent, nothrow)
1893+
end
1894+
1895+
function getglobal_effects(argtypes::Vector{Any}, @nospecialize(rt))
1896+
consistent = nothrow = ALWAYS_FALSE
1897+
if getglobal_nothrow(argtypes)
1898+
# typeasserts below are already checked in `getglobal_nothrow`
1899+
M, s = (argtypes[1]::Const).val::Module, (argtypes[2]::Const).val::Symbol
1900+
if isconst(M, s)
1901+
consistent = nothrow = ALWAYS_TRUE
1902+
else
1903+
nothrow = ALWAYS_TRUE
1904+
end
1905+
end
1906+
return Effects(EFFECTS_TOTAL; consistent, nothrow)
1907+
end
1908+
18531909
function builtin_effects(f::Builtin, argtypes::Vector{Any}, @nospecialize(rt))
18541910
if isa(f, IntrinsicFunction)
18551911
return intrinsic_effects(f, argtypes)
18561912
end
18571913

18581914
@assert !contains_is(_SPECIAL_BUILTINS, f)
18591915

1860-
if (f === Core.getfield || f === Core.isdefined) && length(argtypes) >= 2
1861-
# consistent if the argtype is immutable
1862-
if isvarargtype(argtypes[1])
1863-
return Effects(; effect_free=ALWAYS_TRUE, terminates=ALWAYS_TRUE, nonoverlayed=true)
1864-
end
1865-
s = widenconst(argtypes[1])
1866-
if isType(s) || !isa(s, DataType) || isabstracttype(s)
1867-
return Effects(; effect_free=ALWAYS_TRUE, terminates=ALWAYS_TRUE, nonoverlayed=true)
1868-
end
1869-
s = s::DataType
1870-
consistent = !ismutabletype(s) ? ALWAYS_TRUE : ALWAYS_FALSE
1871-
# access to `isbitstype`-field initialized with undefined value leads to undefined behavior
1872-
# so should taint `:consistent`-cy while access to uninitialized non-`isbitstype` field
1873-
# throws `UndefRefError` so doesn't need to taint it
1874-
# NOTE `getfield_notundefined` conservatively checks if this field is never initialized
1875-
# with undefined value so that we don't taint `:consistent`-cy too aggressively here
1876-
if f === Core.getfield && !getfield_notundefined(s, argtypes[2])
1877-
consistent = ALWAYS_FALSE
1878-
end
1879-
if f === Core.getfield && !isvarargtype(argtypes[end]) && getfield_boundscheck(argtypes) !== true
1880-
# If we cannot independently prove inboundsness, taint consistency.
1881-
# The inbounds-ness assertion requires dynamic reachability, while
1882-
# :consistent needs to be true for all input values.
1883-
# N.B. We do not taint for `--check-bounds=no` here -that happens in
1884-
# InferenceState.
1885-
if getfield_nothrow(argtypes[1], argtypes[2], true)
1886-
nothrow = ALWAYS_TRUE
1887-
else
1888-
consistent = nothrow = ALWAYS_FALSE
1889-
end
1890-
else
1891-
nothrow = (!isvarargtype(argtypes[end]) && builtin_nothrow(f, argtypes, rt)) ?
1892-
ALWAYS_TRUE : ALWAYS_FALSE
1893-
end
1894-
effect_free = ALWAYS_TRUE
1916+
if f === isdefined
1917+
return isdefined_effects(argtypes)
1918+
elseif f === getfield
1919+
return getfield_effects(argtypes, rt)
18951920
elseif f === getglobal
1896-
if getglobal_nothrow(argtypes)
1897-
consistent = isconst( # types are already checked in `getglobal_nothrow`
1898-
(argtypes[1]::Const).val::Module, (argtypes[2]::Const).val::Symbol) ?
1899-
ALWAYS_TRUE : ALWAYS_FALSE
1900-
nothrow = ALWAYS_TRUE
1901-
else
1902-
consistent = nothrow = ALWAYS_FALSE
1903-
end
1904-
effect_free = ALWAYS_TRUE
1921+
return getglobal_effects(argtypes, rt)
19051922
else
19061923
consistent = contains_is(_CONSISTENT_BUILTINS, f) ? ALWAYS_TRUE : ALWAYS_FALSE
19071924
effect_free = (contains_is(_EFFECT_FREE_BUILTINS, f) || contains_is(_PURE_BUILTINS, f)) ?
19081925
ALWAYS_TRUE : ALWAYS_FALSE
19091926
nothrow = (!(!isempty(argtypes) && isvarargtype(argtypes[end])) && builtin_nothrow(f, argtypes, rt)) ?
19101927
ALWAYS_TRUE : ALWAYS_FALSE
1928+
return Effects(EFFECTS_TOTAL; consistent, effect_free, nothrow)
19111929
end
1912-
1913-
return Effects(EFFECTS_TOTAL; consistent, effect_free, nothrow)
19141930
end
19151931

19161932
function builtin_nothrow(@nospecialize(f), argtypes::Vector{Any}, @nospecialize(rt))

base/compiler/typeinfer.jl

+2-9
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,8 @@ function adjust_effects(sv::InferenceState)
429429
if !ipo_effects.inbounds_taints_consistency && rt === Bottom
430430
# always throwing an error counts or never returning both count as consistent
431431
ipo_effects = Effects(ipo_effects; consistent=ALWAYS_TRUE)
432-
elseif ipo_effects.consistent === TRISTATE_UNKNOWN && is_consistent_rt(rt)
432+
end
433+
if ipo_effects.consistent === TRISTATE_UNKNOWN && is_consistent_argtype(rt)
433434
# in a case when the :consistent-cy here is only tainted by mutable allocations
434435
# (indicated by `TRISTATE_UNKNOWN`), we may be able to refine it if the return
435436
# type guarantees that the allocations are never returned
@@ -460,14 +461,6 @@ function adjust_effects(sv::InferenceState)
460461
return ipo_effects
461462
end
462463

463-
is_consistent_rt(@nospecialize rt) = _is_consistent_rt(widenconst(ignorelimited(rt)))
464-
function _is_consistent_rt(@nospecialize ty)
465-
if isa(ty, Union)
466-
return _is_consistent_rt(ty.a) && _is_consistent_rt(ty.b)
467-
end
468-
return ty === Symbol || isbitstype(ty)
469-
end
470-
471464
# inference completed on `me`
472465
# update the MethodInstance
473466
function finish(me::InferenceState, interp::AbstractInterpreter)

base/compiler/typeutils.jl

+21
Original file line numberDiff line numberDiff line change
@@ -300,3 +300,24 @@ function unwraptv(@nospecialize t)
300300
end
301301
return t
302302
end
303+
304+
# this query is specially written for `adjust_effects` and returns true if a value of this type
305+
# never involves inconsistency of mutable objects that are allocated somewhere within a call graph
306+
is_consistent_argtype(@nospecialize ty) = is_consistent_type(widenconst(ignorelimited(ty)))
307+
is_consistent_type(@nospecialize ty) = _is_consistent_type(unwrap_unionall(ty))
308+
function _is_consistent_type(@nospecialize ty)
309+
if isa(ty, Union)
310+
return is_consistent_type(ty.a) && is_consistent_type(ty.b)
311+
end
312+
# N.B. String and Symbol are mutable, but also egal always, and so they never be inconsistent
313+
return ty === String || ty === Symbol || isbitstype(ty)
314+
end
315+
316+
is_immutable_argtype(@nospecialize ty) = is_immutable_type(widenconst(ignorelimited(ty)))
317+
is_immutable_type(@nospecialize ty) = _is_immutable_type(unwrap_unionall(ty))
318+
function _is_immutable_type(@nospecialize ty)
319+
if isa(ty, Union)
320+
return is_immutable_type(ty.a) && is_immutable_type(ty.b)
321+
end
322+
return !isabstracttype(ty) && !ismutabletype(ty)
323+
end

test/compiler/effects.jl

+10-1
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ end |> !Core.Compiler.is_nothrow
295295
# even if 2-arg `getfield` may throw, it should be still `:consistent`
296296
@test Core.Compiler.is_consistent(Base.infer_effects(getfield, (NTuple{5, Float64}, Int)))
297297

298-
# SimpleVector allocation can be consistent
298+
# SimpleVector allocation is consistent
299299
@test Core.Compiler.is_consistent(Base.infer_effects(Core.svec))
300300
@test Base.infer_effects() do
301301
Core.svec(nothing, 1, "foo")
@@ -305,3 +305,12 @@ end |> Core.Compiler.is_consistent
305305
@test Base.infer_effects((Vector{Int},)) do a
306306
Base.@assume_effects :effect_free @ccall jl_array_ptr(a::Any)::Ptr{Int}
307307
end |> Core.Compiler.is_effect_free
308+
309+
# `getfield_effects` handles union object nicely
310+
@test Core.Compiler.is_consistent(Core.Compiler.getfield_effects(Any[Some{String}, Core.Const(:value)], String))
311+
@test Core.Compiler.is_consistent(Core.Compiler.getfield_effects(Any[Some{Symbol}, Core.Const(:value)], Symbol))
312+
@test Core.Compiler.is_consistent(Core.Compiler.getfield_effects(Any[Union{Some{Symbol},Some{String}}, Core.Const(:value)], Union{Symbol,String}))
313+
@test Base.infer_effects((Bool,)) do c
314+
obj = c ? Some{String}("foo") : Some{Symbol}(:bar)
315+
return getfield(obj, :value)
316+
end |> Core.Compiler.is_consistent

0 commit comments

Comments
 (0)