Skip to content

Commit 8b86d91

Browse files
committed
effects: analyze bounds checking of getfield properly
This allows us to prove `:nothrow`-ness of `getfield` when bounds checking is turned off manually. It still taints `:nothrow` when a name of invalid type is given.
1 parent e022ab2 commit 8b86d91

File tree

2 files changed

+40
-24
lines changed

2 files changed

+40
-24
lines changed

base/compiler/tfuncs.jl

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -937,7 +937,7 @@ function getfield_boundscheck((; fargs, argtypes)::ArgInfo) # Symbol
937937
return :unknown
938938
end
939939

940-
function getfield_nothrow(arginfo::ArgInfo, boundscheck::Symbol=getfield_boundscheck(arginfo))
940+
function getfield_nothrow(𝕃::AbstractLattice, arginfo::ArgInfo, boundscheck::Symbol=getfield_boundscheck(arginfo))
941941
(;argtypes) = arginfo
942942
boundscheck === :unknown && return false
943943
ordering = Const(:not_atomic)
@@ -958,17 +958,19 @@ function getfield_nothrow(arginfo::ArgInfo, boundscheck::Symbol=getfield_boundsc
958958
if ordering !== :not_atomic # TODO: this is assuming not atomic
959959
return false
960960
end
961-
return getfield_nothrow(argtypes[2], argtypes[3], !(boundscheck === :off))
961+
return getfield_nothrow(𝕃, argtypes[2], argtypes[3], !(boundscheck === :off))
962962
else
963963
return false
964964
end
965965
end
966-
@nospecs function getfield_nothrow(s00, name, boundscheck::Bool)
966+
@nospecs function getfield_nothrow(𝕃::AbstractLattice, s00, name, boundscheck::Bool)
967967
# If we don't have boundscheck off and don't know the field, don't even bother
968968
if boundscheck
969969
isa(name, Const) || return false
970970
end
971971

972+
= Core.Compiler.:(𝕃)
973+
972974
# If we have s00 being a const, we can potentially refine our type-based analysis above
973975
if isa(s00, Const) || isconstType(s00)
974976
if !isa(s00, Const)
@@ -984,31 +986,32 @@ end
984986
end
985987
return isdefined(sv, nval)
986988
end
987-
if !boundscheck && !isa(sv, Module)
988-
# If bounds checking is disabled and all fields are assigned,
989-
# we may assume that we don't throw
990-
for i = 1:fieldcount(typeof(sv))
991-
isdefined(sv, i) || return false
992-
end
993-
return true
989+
boundscheck && return false
990+
# If bounds checking is disabled and all fields are assigned,
991+
# we may assume that we don't throw
992+
isa(sv, Module) && return false
993+
name Int || name Symbol || return false
994+
for i = 1:fieldcount(typeof(sv))
995+
isdefined(sv, i) || return false
994996
end
995-
return false
997+
return true
996998
end
997999

9981000
s0 = widenconst(s00)
9991001
s = unwrap_unionall(s0)
10001002
if isa(s, Union)
1001-
return getfield_nothrow(rewrap_unionall(s.a, s00), name, boundscheck) &&
1002-
getfield_nothrow(rewrap_unionall(s.b, s00), name, boundscheck)
1003+
return getfield_nothrow(𝕃, rewrap_unionall(s.a, s00), name, boundscheck) &&
1004+
getfield_nothrow(𝕃, rewrap_unionall(s.b, s00), name, boundscheck)
10031005
elseif isType(s) && isTypeDataType(s.parameters[1])
10041006
s = s0 = DataType
10051007
end
10061008
if isa(s, DataType)
10071009
# Can't say anything about abstract types
10081010
isabstracttype(s) && return false
1009-
# If all fields are always initialized, and bounds check is disabled, we can assume
1010-
# we don't throw
1011+
# If all fields are always initialized, and bounds check is disabled,
1012+
# we can assume we don't throw
10111013
if !boundscheck && s.name.n_uninitialized == 0
1014+
name Int || name Symbol || return false
10121015
return true
10131016
end
10141017
# Else we need to know what the field is
@@ -2012,7 +2015,7 @@ function array_builtin_common_nothrow(argtypes::Vector{Any}, first_idx_idx::Int,
20122015
# If we have @inbounds (first argument is false), we're allowed to assume
20132016
# we don't throw bounds errors.
20142017
if isa(boundscheck, Const)
2015-
!(boundscheck.val::Bool) && return true
2018+
boundscheck.val::Bool || return true
20162019
end
20172020
# Else we can't really say anything here
20182021
# TODO: In the future we may be able to track the shapes of arrays though
@@ -2067,7 +2070,7 @@ end
20672070
elseif f === invoke
20682071
return false
20692072
elseif f === getfield
2070-
return getfield_nothrow(ArgInfo(nothing, Any[Const(f), argtypes...]))
2073+
return getfield_nothrow(𝕃, ArgInfo(nothing, Any[Const(f), argtypes...]))
20712074
elseif f === setfield!
20722075
if na == 3
20732076
return setfield!_nothrow(𝕃, argtypes[1], argtypes[2], argtypes[3])
@@ -2224,7 +2227,7 @@ function isdefined_effects(𝕃::AbstractLattice, argtypes::Vector{Any})
22242227
return Effects(EFFECTS_TOTAL; consistent, nothrow)
22252228
end
22262229

2227-
function getfield_effects(arginfo::ArgInfo, @nospecialize(rt))
2230+
function getfield_effects(𝕃::AbstractLattice, arginfo::ArgInfo, @nospecialize(rt))
22282231
(;argtypes) = arginfo
22292232
# consistent if the argtype is immutable
22302233
length(argtypes) < 3 && return EFFECTS_THROWS
@@ -2240,9 +2243,9 @@ function getfield_effects(arginfo::ArgInfo, @nospecialize(rt))
22402243
if !(length(argtypes) 3 && getfield_notundefined(obj, argtypes[3]))
22412244
consistent = ALWAYS_FALSE
22422245
end
2243-
nothrow = getfield_nothrow(arginfo, :on)
2246+
bcheck = getfield_boundscheck(arginfo)
2247+
nothrow = getfield_nothrow(𝕃, arginfo, bcheck)
22442248
if !nothrow
2245-
bcheck = getfield_boundscheck(arginfo)
22462249
if !(bcheck === :on || bcheck === :boundscheck)
22472250
# If we cannot independently prove inboundsness, taint consistency.
22482251
# The inbounds-ness assertion requires dynamic reachability, while
@@ -2293,7 +2296,7 @@ function builtin_effects(𝕃::AbstractLattice, @nospecialize(f::Builtin), argin
22932296
@assert !contains_is(_SPECIAL_BUILTINS, f)
22942297

22952298
if f === getfield
2296-
return getfield_effects(arginfo, rt)
2299+
return getfield_effects(𝕃, arginfo, rt)
22972300
end
22982301
argtypes = arginfo.argtypes[2:end]
22992302

test/compiler/effects.jl

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -487,14 +487,27 @@ end |> Core.Compiler.is_consistent
487487
end |> Core.Compiler.is_effect_free
488488

489489
# `getfield_effects` handles access to union object nicely
490-
@test Core.Compiler.is_consistent(Core.Compiler.getfield_effects(Core.Compiler.ArgInfo(nothing, Any[Core.Const(getfield), Some{String}, Core.Const(:value)]), String))
491-
@test Core.Compiler.is_consistent(Core.Compiler.getfield_effects(Core.Compiler.ArgInfo(nothing, Any[Core.Const(getfield), Some{Symbol}, Core.Const(:value)]), Symbol))
492-
@test Core.Compiler.is_consistent(Core.Compiler.getfield_effects(Core.Compiler.ArgInfo(nothing, Any[Core.Const(getfield), Union{Some{Symbol},Some{String}}, Core.Const(:value)]), Union{Symbol,String}))
490+
let 𝕃 = Core.Compiler.fallback_lattice
491+
@test Core.Compiler.is_consistent(Core.Compiler.getfield_effects(𝕃, Core.Compiler.ArgInfo(nothing, Any[Core.Const(getfield), Some{String}, Core.Const(:value)]), String))
492+
@test Core.Compiler.is_consistent(Core.Compiler.getfield_effects(𝕃, Core.Compiler.ArgInfo(nothing, Any[Core.Const(getfield), Some{Symbol}, Core.Const(:value)]), Symbol))
493+
@test Core.Compiler.is_consistent(Core.Compiler.getfield_effects(𝕃, Core.Compiler.ArgInfo(nothing, Any[Core.Const(getfield), Union{Some{Symbol},Some{String}}, Core.Const(:value)]), Union{Symbol,String}))
494+
end
493495
@test Base.infer_effects((Bool,)) do c
494496
obj = c ? Some{String}("foo") : Some{Symbol}(:bar)
495497
return getfield(obj, :value)
496498
end |> Core.Compiler.is_consistent
497499

500+
# getfield is nothrow when bounds checking is turned off
501+
@test Base.infer_effects((Tuple{Int,Int},Int)) do t, i
502+
getfield(t, i, false)
503+
end |> Core.Compiler.is_nothrow
504+
@test Base.infer_effects((Tuple{Int,Int},Symbol)) do t, i
505+
getfield(t, i, false)
506+
end |> Core.Compiler.is_nothrow
507+
@test Base.infer_effects((Tuple{Int,Int},String)) do t, i
508+
getfield(t, i, false) # invalid name type
509+
end |> !Core.Compiler.is_nothrow
510+
498511
@test Core.Compiler.is_consistent(Base.infer_effects(setindex!, (Base.RefValue{Int}, Int)))
499512

500513
# :inaccessiblememonly effect

0 commit comments

Comments
 (0)