Skip to content

@boundscheck annotations preventing code from being inferred as consistent #58067

Open
@nsajko

Description

@nsajko

This kind of function, where @inbounds affects the value returned, used to be inferred as consistent in v1.10, but is not in v1.11 and above:

julia> function f()
           @boundscheck return true
           false
       end
f (generic function with 1 method)

julia> Base.infer_effects(f, Tuple{})
(!c,+e,+n,+t,+s,+m,+u,+o,+r)

Given that it's not consistent any more, should that pattern be avoided?

More specifically, things like this are relatively common:

@boundscheck checkbounds(Bool, A, index) || return false
return additional_error_check(...)

Should the @boundscheck annotation be removed from code like that, while keeping all error checks, to allow constant folding?

The @boundscheck doc string also needs to be clarified I think.

Examples of the usage of this pattern in Base:

function isvalid(s::SubString, i::Integer)
ib = true
@boundscheck ib = checkbounds(Bool, s, i)
@inbounds return ib && isvalid(s.string, s.offset + i)::Bool
end

julia/base/genericmemory.jl

Lines 106 to 111 in 1117df6

# multi arg case will be overwritten later. This is needed for bootstrapping
function isassigned(a::GenericMemory, i::Int)
@inline
@boundscheck (i - 1)%UInt < length(a)%UInt || return false
return @inbounds memoryref_isassigned(memoryref(a, i), default_access_order(a), false)
end

function Base.isassigned(A::PermutedDimsArray{T,N,perm,iperm}, I::Vararg{Int,N}) where {T,N,perm,iperm}
@boundscheck checkbounds(Bool, A, I...) || return false
@inbounds x = isassigned(A.parent, genperm(I, iperm)...)
x
end

julia/base/subarray.jl

Lines 411 to 428 in 1117df6

function isassigned(V::SubArray{T,N}, I::Vararg{Int,N}) where {T,N}
@inline
@boundscheck checkbounds(Bool, V, I...) || return false
@inbounds r = isassigned(V.parent, reindex(V.indices, I)...)
r
end
function isassigned(V::FastSubArray, i::Int)
@inline
@boundscheck checkbounds(Bool, V, i) || return false
@inbounds r = isassigned(V.parent, _reindexlinear(V, i))
r
end
function isassigned(V::FastSubArray{<:Any, 1}, i::Int)
@inline
@boundscheck checkbounds(Bool, V, i) || return false
@inbounds r = isassigned(V.parent, _reindexlinear(V, i))
r
end

julia/base/array.jl

Lines 237 to 250 in 1117df6

function isassigned(a::Array, i::Int...)
@inline
@_noub_if_noinbounds_meta
@boundscheck checkbounds(Bool, a, i...) || return false
ii = _sub2ind(size(a), i...)
return @inbounds isassigned(memoryrefnew(a.ref, ii, false))
end
function isassigned(a::Vector, i::Int) # slight compiler simplification for the most common case
@inline
@_noub_if_noinbounds_meta
@boundscheck checkbounds(Bool, a, i) || return false
return @inbounds isassigned(memoryrefnew(a.ref, i, false))
end

julia/base/reshapedarray.jl

Lines 280 to 292 in 1117df6

@inline function isassigned(A::ReshapedArrayLF, index::Int)
@boundscheck checkbounds(Bool, A, index) || return false
indexparent = index - firstindex(A) + firstindex(parent(A))
@inbounds ret = isassigned(parent(A), indexparent)
ret
end
@inline function isassigned(A::ReshapedArray{T,N}, indices::Vararg{Int, N}) where {T,N}
@boundscheck checkbounds(Bool, A, indices...) || return false
axp = axes(A.parent)
i = offset_if_vec(_sub2ind(size(A), indices...), axp)
I = ind2sub_rs(axp, A.mi, i)
@inbounds isassigned(A.parent, I...)
end

@propagate_inbounds function isassigned(iter::CartesianIndices{N,R}, I::Vararg{Int, N}) where {N,R}
for i in 1:N
isassigned(iter.indices[i], I[i]) || return false
end
return true
end

julia/base/multidimensional.jl

Lines 1627 to 1641 in 1117df6

@generated function isassigned(B::BitArray, I_0::Int, I::Int...)
N = length(I)
quote
@nexprs $N d->(I_d = I[d])
stride = 1
index = I_0
@nexprs $N d->begin
l = size(B,d)
stride *= l
@boundscheck 1 <= I_{d-1} <= l || return false
index += (I_d - 1) * stride
end
return isassigned(B, index)
end
end

julia/base/multidimensional.jl

Lines 1647 to 1669 in 1117df6

@inline function isassigned(A::AbstractArray, i::Integer...)
# convert to valid indices, checking for Bool
inds = to_indices(A, i)
@boundscheck checkbounds(Bool, A, inds...) || return false
S = IndexStyle(A)
ninds = length(inds)
if (isa(S, IndexLinear) && ninds != 1)
return @inbounds isassigned(A, _to_linear_index(A, inds...))
elseif (!isa(S, IndexLinear) && ninds != ndims(A))
return @inbounds isassigned(A, _to_subscript_indices(A, inds...)...)
else
try
A[inds...]
true
catch e
if isa(e, BoundsError) || isa(e, UndefRefError)
return false
else
rethrow()
end
end
end
end

julia/base/essentials.jl

Lines 1027 to 1030 in 1117df6

function isassigned(v::SimpleVector, i::Int)
@boundscheck 1 <= i <= length(v) || return false
return true
end

Metadata

Metadata

Assignees

No one assigned

    Labels

    arrays[a, r, r, a, y, s]strings"Strings!"

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions