Skip to content

Commit ce64b80

Browse files
authored
Merge pull request #48853 from JuliaLang/avi/boundscheck
effects: assume `:nothrow`-ness more when bounds checking is manually turned off
2 parents b8057f3 + 8b86d91 commit ce64b80

File tree

2 files changed

+60
-32
lines changed

2 files changed

+60
-32
lines changed

base/compiler/tfuncs.jl

Lines changed: 32 additions & 27 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
@@ -1999,18 +2002,20 @@ function array_type_undefable(@nospecialize(arytype))
19992002
end
20002003
end
20012004

2002-
function array_builtin_common_nothrow(argtypes::Vector{Any}, first_idx_idx::Int)
2005+
function array_builtin_common_nothrow(argtypes::Vector{Any}, first_idx_idx::Int, isarrayref::Bool)
20032006
length(argtypes) >= 4 || return false
20042007
boundscheck = argtypes[1]
20052008
arytype = argtypes[2]
20062009
array_builtin_common_typecheck(boundscheck, arytype, argtypes, first_idx_idx) || return false
2007-
# If we could potentially throw undef ref errors, bail out now.
2008-
arytype = widenconst(arytype)
2009-
array_type_undefable(arytype) && return false
2010+
if isarrayref
2011+
# If we could potentially throw undef ref errors, bail out now.
2012+
arytype = widenconst(arytype)
2013+
array_type_undefable(arytype) && return false
2014+
end
20102015
# If we have @inbounds (first argument is false), we're allowed to assume
20112016
# we don't throw bounds errors.
20122017
if isa(boundscheck, Const)
2013-
!(boundscheck.val::Bool) && return true
2018+
boundscheck.val::Bool || return true
20142019
end
20152020
# Else we can't really say anything here
20162021
# TODO: In the future we may be able to track the shapes of arrays though
@@ -2042,11 +2047,11 @@ end
20422047
@nospecs function _builtin_nothrow(𝕃::AbstractLattice, f, argtypes::Vector{Any}, rt)
20432048
= Core.Compiler.:(𝕃)
20442049
if f === arrayset
2045-
array_builtin_common_nothrow(argtypes, 4) || return false
2050+
array_builtin_common_nothrow(argtypes, 4, #=isarrayref=#false) || return false
20462051
# Additionally check element type compatibility
20472052
return arrayset_typecheck(argtypes[2], argtypes[3])
20482053
elseif f === arrayref || f === const_arrayref
2049-
return array_builtin_common_nothrow(argtypes, 3)
2054+
return array_builtin_common_nothrow(argtypes, 3, #=isarrayref=#true)
20502055
elseif f === Core._expr
20512056
length(argtypes) >= 1 || return false
20522057
return argtypes[1] Symbol
@@ -2065,7 +2070,7 @@ end
20652070
elseif f === invoke
20662071
return false
20672072
elseif f === getfield
2068-
return getfield_nothrow(ArgInfo(nothing, Any[Const(f), argtypes...]))
2073+
return getfield_nothrow(𝕃, ArgInfo(nothing, Any[Const(f), argtypes...]))
20692074
elseif f === setfield!
20702075
if na == 3
20712076
return setfield!_nothrow(𝕃, argtypes[1], argtypes[2], argtypes[3])
@@ -2222,7 +2227,7 @@ function isdefined_effects(𝕃::AbstractLattice, argtypes::Vector{Any})
22222227
return Effects(EFFECTS_TOTAL; consistent, nothrow)
22232228
end
22242229

2225-
function getfield_effects(arginfo::ArgInfo, @nospecialize(rt))
2230+
function getfield_effects(𝕃::AbstractLattice, arginfo::ArgInfo, @nospecialize(rt))
22262231
(;argtypes) = arginfo
22272232
# consistent if the argtype is immutable
22282233
length(argtypes) < 3 && return EFFECTS_THROWS
@@ -2238,9 +2243,9 @@ function getfield_effects(arginfo::ArgInfo, @nospecialize(rt))
22382243
if !(length(argtypes) 3 && getfield_notundefined(obj, argtypes[3]))
22392244
consistent = ALWAYS_FALSE
22402245
end
2241-
nothrow = getfield_nothrow(arginfo, :on)
2246+
bcheck = getfield_boundscheck(arginfo)
2247+
nothrow = getfield_nothrow(𝕃, arginfo, bcheck)
22422248
if !nothrow
2243-
bcheck = getfield_boundscheck(arginfo)
22442249
if !(bcheck === :on || bcheck === :boundscheck)
22452250
# If we cannot independently prove inboundsness, taint consistency.
22462251
# The inbounds-ness assertion requires dynamic reachability, while
@@ -2291,7 +2296,7 @@ function builtin_effects(𝕃::AbstractLattice, @nospecialize(f::Builtin), argin
22912296
@assert !contains_is(_SPECIAL_BUILTINS, f)
22922297

22932298
if f === getfield
2294-
return getfield_effects(arginfo, rt)
2299+
return getfield_effects(𝕃, arginfo, rt)
22952300
end
22962301
argtypes = arginfo.argtypes[2:end]
22972302

test/compiler/effects.jl

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -455,9 +455,19 @@ let effects = Base.infer_effects(f_setfield_nothrow, ())
455455
end
456456

457457
# nothrow for arrayset
458-
@test Base.infer_effects((Vector{Int},Int)) do a, i
459-
a[i] = 0 # may throw
458+
@test Base.infer_effects((Vector{Int},Int,Int)) do a, v, i
459+
Base.arrayset(true, a, v, i)
460460
end |> !Core.Compiler.is_nothrow
461+
@test Base.infer_effects((Vector{Int},Int,Int)) do a, v, i
462+
a[i] = v # may throw
463+
end |> !Core.Compiler.is_nothrow
464+
# when bounds checking is turned off, it should be safe
465+
@test Base.infer_effects((Vector{Int},Int,Int)) do a, v, i
466+
Base.arrayset(false, a, v, i)
467+
end |> Core.Compiler.is_nothrow
468+
@test Base.infer_effects((Vector{Number},Number,Int)) do a, v, i
469+
Base.arrayset(false, a, v, i)
470+
end |> Core.Compiler.is_nothrow
461471

462472
# even if 2-arg `getfield` may throw, it should be still `:consistent`
463473
@test Core.Compiler.is_consistent(Base.infer_effects(getfield, (NTuple{5, Float64}, Int)))
@@ -477,14 +487,27 @@ end |> Core.Compiler.is_consistent
477487
end |> Core.Compiler.is_effect_free
478488

479489
# `getfield_effects` handles access to union object nicely
480-
@test Core.Compiler.is_consistent(Core.Compiler.getfield_effects(Core.Compiler.ArgInfo(nothing, Any[Core.Const(getfield), Some{String}, Core.Const(:value)]), String))
481-
@test Core.Compiler.is_consistent(Core.Compiler.getfield_effects(Core.Compiler.ArgInfo(nothing, Any[Core.Const(getfield), Some{Symbol}, Core.Const(:value)]), Symbol))
482-
@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
483495
@test Base.infer_effects((Bool,)) do c
484496
obj = c ? Some{String}("foo") : Some{Symbol}(:bar)
485497
return getfield(obj, :value)
486498
end |> Core.Compiler.is_consistent
487499

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+
488511
@test Core.Compiler.is_consistent(Base.infer_effects(setindex!, (Base.RefValue{Int}, Int)))
489512

490513
# :inaccessiblememonly effect

0 commit comments

Comments
 (0)