From e2225ac04d79ac982d6b40cc33367748a0662ff3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mos=C3=A8=20Giordano?= Date: Fri, 20 Jun 2025 20:31:22 +0100 Subject: [PATCH 1/5] Use `_checkbounds_array` in more places Co-authored-by: Matt Bauman --- base/array.jl | 2 +- base/genericmemory.jl | 4 ++-- base/strings/basic.jl | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/base/array.jl b/base/array.jl index 5c9b37f887ea8..5e1ffd7fb1b01 100644 --- a/base/array.jl +++ b/base/array.jl @@ -993,7 +993,7 @@ function setindex!(A::Array{T}, x, i::Int) where {T} end function _setindex!(A::Array{T}, x::T, i::Int) where {T} @_noub_if_noinbounds_meta - @boundscheck (i - 1)%UInt < length(A)%UInt || throw_boundserror(A, (i,)) + @boundscheck _checkbounds_array(Bool, A, i) || throw_boundserror(A, (i,)) memoryrefset!(memoryrefnew(A.ref, i, false), x, :not_atomic, false) return A end diff --git a/base/genericmemory.jl b/base/genericmemory.jl index 1b45ba23bcded..10d23d23c319b 100644 --- a/base/genericmemory.jl +++ b/base/genericmemory.jl @@ -106,7 +106,7 @@ sizeof(a::GenericMemory) = Core.sizeof(a) # 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 + @boundscheck _checkbounds_array(Bool, a, i) || return false return @inbounds memoryref_isassigned(memoryref(a, i), default_access_order(a), false) end @@ -227,7 +227,7 @@ Memory{T}(x::AbstractArray{S,1}) where {T,S} = copyto_axcheck!(Memory{T}(undef, function _iterate_array(A::Union{Memory, Array}, i::Int) @inline - (i - 1)%UInt < length(A)%UInt ? (A[i], i + 1) : nothing + _checkbounds_array(Bool, A, i) ? (A[i], i + 1) : nothing end iterate(A::Memory, i=1) = (@inline; _iterate_array(A, i)) diff --git a/base/strings/basic.jl b/base/strings/basic.jl index 314903898b92a..73932439bedde 100644 --- a/base/strings/basic.jl +++ b/base/strings/basic.jl @@ -797,7 +797,7 @@ size(s::CodeUnits) = (length(s),) elsize(s::Type{<:CodeUnits{T}}) where {T} = sizeof(T) @propagate_inbounds getindex(s::CodeUnits, i::Int) = codeunit(s.s, i) IndexStyle(::Type{<:CodeUnits}) = IndexLinear() -@inline iterate(s::CodeUnits, i=1) = (i % UInt) - 1 < length(s) ? (@inbounds s[i], i + 1) : nothing +@inline iterate(s::CodeUnits, i=1) = _checkbounds_array(Bool, s, i) ? (@inbounds s[i], i + 1) : nothing write(io::IO, s::CodeUnits) = write(io, s.s) From 59821a123fda20f7f2ab10c9ae0d8b3f8ec96b17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mos=C3=A8=20Giordano?= Date: Sat, 21 Jun 2025 01:20:33 +0100 Subject: [PATCH 2/5] Use `checkbounds` in more places --- base/abstractarray.jl | 7 ++++--- base/array.jl | 6 +----- base/essentials.jl | 21 ++++++++++++++------- base/genericmemory.jl | 11 +---------- base/strings/basic.jl | 2 +- 5 files changed, 21 insertions(+), 26 deletions(-) diff --git a/base/abstractarray.jl b/base/abstractarray.jl index 8f55e6a56eba8..6b3a8b979bea9 100644 --- a/base/abstractarray.jl +++ b/base/abstractarray.jl @@ -387,6 +387,7 @@ function eachindex(A::AbstractArray, B::AbstractArray...) @inline eachindex(IndexStyle(A,B...), A, B...) end +eachindex(::IndexLinear, A::Union{Array, Memory}) = unchecked_oneto(length(A)) eachindex(::IndexLinear, A::AbstractArray) = (@inline; oneto(length(A))) eachindex(::IndexLinear, A::AbstractVector) = (@inline; axes1(A)) function eachindex(::IndexLinear, A::AbstractArray, B::AbstractArray...) @@ -749,7 +750,7 @@ false checkindex(::Type{Bool}, inds, i) = throw(ArgumentError(LazyString("unable to check bounds for indices of type ", typeof(i)))) checkindex(::Type{Bool}, inds::AbstractUnitRange, i::Real) = (first(inds) <= i) & (i <= last(inds)) checkindex(::Type{Bool}, inds::IdentityUnitRange, i::Real) = checkindex(Bool, inds.indices, i) -checkindex(::Type{Bool}, inds::OneTo{T}, i::T) where {T<:BitInteger} = unsigned(i - one(i)) < unsigned(last(inds)) +checkindex(::Type{Bool}, inds::OneTo{T}, i::T) where {T<:BitInteger} = _checkbounds_array_onebased(Bool, last(inds), i) checkindex(::Type{Bool}, inds::AbstractUnitRange, ::Colon) = true checkindex(::Type{Bool}, inds::AbstractUnitRange, ::Slice) = true checkindex(::Type{Bool}, inds::AbstractUnitRange, i::AbstractRange) = @@ -1237,7 +1238,7 @@ oneunit(x::AbstractMatrix{T}) where {T} = _one(oneunit(T), x) iterate_starting_state(A) = iterate_starting_state(A, IndexStyle(A)) iterate_starting_state(A, ::IndexLinear) = firstindex(A) iterate_starting_state(A, ::IndexStyle) = (eachindex(A),) -iterate(A::AbstractArray, state = iterate_starting_state(A)) = _iterate(A, state) +@inline iterate(A::AbstractArray, state = iterate_starting_state(A)) = _iterate(A, state) function _iterate(A::AbstractArray, state::Tuple) y = iterate(state...) y === nothing && return nothing @@ -1245,7 +1246,7 @@ function _iterate(A::AbstractArray, state::Tuple) end function _iterate(A::AbstractArray, state::Integer) checkbounds(Bool, A, state) || return nothing - @inbounds(A[state]), state + one(state) + A[state], state + one(state) end isempty(a::AbstractArray) = (length(a) == 0) diff --git a/base/array.jl b/base/array.jl index 5e1ffd7fb1b01..cacef7c4acd5d 100644 --- a/base/array.jl +++ b/base/array.jl @@ -902,10 +902,6 @@ function grow_to!(dest, itr, st) return dest end -## Iteration ## - -iterate(A::Array, i=1) = (@inline; _iterate_array(A, i)) - ## Indexing: getindex ## """ @@ -993,7 +989,7 @@ function setindex!(A::Array{T}, x, i::Int) where {T} end function _setindex!(A::Array{T}, x::T, i::Int) where {T} @_noub_if_noinbounds_meta - @boundscheck _checkbounds_array(Bool, A, i) || throw_boundserror(A, (i,)) + @boundscheck checkbounds(Bool, A, i) || throw_boundserror(A, (i,)) memoryrefset!(memoryrefnew(A.ref, i, false), x, :not_atomic, false) return A end diff --git a/base/essentials.jl b/base/essentials.jl index 820cce6839f13..8313dc114657b 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -377,13 +377,20 @@ macro _nospecializeinfer_meta() return Expr(:meta, :nospecializeinfer) end -function _checkbounds_array(::Type{Bool}, A::Union{Array, GenericMemory}, i::Int) +# NOTE: this function expects one-based indexing. We can't use `require_one_based_indexing` +# here because it's defined later in the bootstrap process. Use only where you are really +# sure about indexing. +function _checkbounds_array_onebased(::Type{Bool}, len::Integer, i::Int) @inline - ult_int(bitcast(UInt, sub_int(i, 1)), bitcast(UInt, length(A))) + ult_int(bitcast(UInt, sub_int(i, 1)), bitcast(UInt, len)) end -function _checkbounds_array(A::Union{Array, GenericMemory}, i::Int) +function _checkbounds_array_onebased(::Type{Bool}, A::AbstractArray, i::Int) @inline - _checkbounds_array(Bool, A, i) || throw_boundserror(A, (i,)) + _checkbounds_array_onebased(Bool, length(A), i) +end +function _checkbounds_array_onebased(A::Union{Array, GenericMemory}, i::Int) + @inline + _checkbounds_array_onebased(Bool, A, i) || throw_boundserror(A, (i,)) end default_access_order(a::GenericMemory{:not_atomic}) = :not_atomic @@ -393,7 +400,7 @@ default_access_order(a::GenericMemoryRef{:atomic}) = :monotonic function getindex(A::GenericMemory, i::Int) @_noub_if_noinbounds_meta - (@_boundscheck) && _checkbounds_array(A, i) + (@_boundscheck) && _checkbounds_array_onebased(A, i) memoryrefget(memoryrefnew(memoryrefnew(A), i, false), default_access_order(A), false) end @@ -962,13 +969,13 @@ end # linear indexing function getindex(A::Array, i::Int) @_noub_if_noinbounds_meta - @boundscheck _checkbounds_array(A, i) + @boundscheck _checkbounds_array_onebased(A, i) memoryrefget(memoryrefnew(getfield(A, :ref), i, false), :not_atomic, false) end # simple Array{Any} operations needed for bootstrap function setindex!(A::Array{Any}, @nospecialize(x), i::Int) @_noub_if_noinbounds_meta - @boundscheck _checkbounds_array(A, i) + @boundscheck _checkbounds_array_onebased(A, i) memoryrefset!(memoryrefnew(getfield(A, :ref), i, false), x, :not_atomic, false) return A end diff --git a/base/genericmemory.jl b/base/genericmemory.jl index 10d23d23c319b..b180462115f41 100644 --- a/base/genericmemory.jl +++ b/base/genericmemory.jl @@ -106,7 +106,7 @@ sizeof(a::GenericMemory) = Core.sizeof(a) # multi arg case will be overwritten later. This is needed for bootstrapping function isassigned(a::GenericMemory, i::Int) @inline - @boundscheck _checkbounds_array(Bool, a, i) || return false + @boundscheck checkbounds(Bool, a, i) || return false return @inbounds memoryref_isassigned(memoryref(a, i), default_access_order(a), false) end @@ -223,15 +223,6 @@ Memory{T}(x::AbstractArray{S,1}) where {T,S} = copyto_axcheck!(Memory{T}(undef, ## copying iterators to containers -## Iteration ## - -function _iterate_array(A::Union{Memory, Array}, i::Int) - @inline - _checkbounds_array(Bool, A, i) ? (A[i], i + 1) : nothing -end - -iterate(A::Memory, i=1) = (@inline; _iterate_array(A, i)) - ## Indexing: getindex ## # Faster contiguous indexing using copyto! for AbstractUnitRange and Colon diff --git a/base/strings/basic.jl b/base/strings/basic.jl index 73932439bedde..6619a2b25574e 100644 --- a/base/strings/basic.jl +++ b/base/strings/basic.jl @@ -797,7 +797,7 @@ size(s::CodeUnits) = (length(s),) elsize(s::Type{<:CodeUnits{T}}) where {T} = sizeof(T) @propagate_inbounds getindex(s::CodeUnits, i::Int) = codeunit(s.s, i) IndexStyle(::Type{<:CodeUnits}) = IndexLinear() -@inline iterate(s::CodeUnits, i=1) = _checkbounds_array(Bool, s, i) ? (@inbounds s[i], i + 1) : nothing +@inline iterate(s::CodeUnits, i=1) = checkbounds(Bool, s, i) ? (@inbounds s[i], i + 1) : nothing write(io::IO, s::CodeUnits) = write(io, s.s) From 574d8e1291ad1a4b4a2417448616d0953c1d338d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mos=C3=A8=20Giordano?= <765740+giordano@users.noreply.github.com> Date: Mon, 23 Jun 2025 15:42:47 +0200 Subject: [PATCH 3/5] Relax signature of `_checkbounds_array_onebased` --- base/essentials.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/essentials.jl b/base/essentials.jl index 8313dc114657b..983e1505c50c7 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -380,7 +380,7 @@ end # NOTE: this function expects one-based indexing. We can't use `require_one_based_indexing` # here because it's defined later in the bootstrap process. Use only where you are really # sure about indexing. -function _checkbounds_array_onebased(::Type{Bool}, len::Integer, i::Int) +function _checkbounds_array_onebased(::Type{Bool}, len::Integer, i::Integer) @inline ult_int(bitcast(UInt, sub_int(i, 1)), bitcast(UInt, len)) end From fe0393cb0f2b687db77aeb8fddc72783057af7a7 Mon Sep 17 00:00:00 2001 From: Matt Bauman Date: Mon, 23 Jun 2025 13:39:55 -0400 Subject: [PATCH 4/5] _checkbounds_array is really just a method of checkbounds --- base/abstractarray.jl | 2 +- base/essentials.jl | 22 ++++++++-------------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/base/abstractarray.jl b/base/abstractarray.jl index 6b3a8b979bea9..665d70c525d81 100644 --- a/base/abstractarray.jl +++ b/base/abstractarray.jl @@ -750,7 +750,7 @@ false checkindex(::Type{Bool}, inds, i) = throw(ArgumentError(LazyString("unable to check bounds for indices of type ", typeof(i)))) checkindex(::Type{Bool}, inds::AbstractUnitRange, i::Real) = (first(inds) <= i) & (i <= last(inds)) checkindex(::Type{Bool}, inds::IdentityUnitRange, i::Real) = checkindex(Bool, inds.indices, i) -checkindex(::Type{Bool}, inds::OneTo{T}, i::T) where {T<:BitInteger} = _checkbounds_array_onebased(Bool, last(inds), i) +checkindex(::Type{Bool}, inds::OneTo{T}, i::T) where {T<:BitInteger} = unsigned(i - one(i)) < unsigned(last(inds)) checkindex(::Type{Bool}, inds::AbstractUnitRange, ::Colon) = true checkindex(::Type{Bool}, inds::AbstractUnitRange, ::Slice) = true checkindex(::Type{Bool}, inds::AbstractUnitRange, i::AbstractRange) = diff --git a/base/essentials.jl b/base/essentials.jl index 983e1505c50c7..e1bc648e7dbd0 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -377,20 +377,14 @@ macro _nospecializeinfer_meta() return Expr(:meta, :nospecializeinfer) end -# NOTE: this function expects one-based indexing. We can't use `require_one_based_indexing` -# here because it's defined later in the bootstrap process. Use only where you are really -# sure about indexing. -function _checkbounds_array_onebased(::Type{Bool}, len::Integer, i::Integer) +# These special checkbounds methods are defined early for bootstrapping +function checkbounds(::Type{Bool}, A::Union{Array, Memory}, i::Int) @inline - ult_int(bitcast(UInt, sub_int(i, 1)), bitcast(UInt, len)) + ult_int(bitcast(UInt, sub_int(i, 1)), bitcast(UInt, length(A))) end -function _checkbounds_array_onebased(::Type{Bool}, A::AbstractArray, i::Int) +function checkbounds(A::Union{Array, GenericMemory}, i::Int) @inline - _checkbounds_array_onebased(Bool, length(A), i) -end -function _checkbounds_array_onebased(A::Union{Array, GenericMemory}, i::Int) - @inline - _checkbounds_array_onebased(Bool, A, i) || throw_boundserror(A, (i,)) + checkbounds(Bool, A, i) || throw_boundserror(A, (i,)) end default_access_order(a::GenericMemory{:not_atomic}) = :not_atomic @@ -400,7 +394,7 @@ default_access_order(a::GenericMemoryRef{:atomic}) = :monotonic function getindex(A::GenericMemory, i::Int) @_noub_if_noinbounds_meta - (@_boundscheck) && _checkbounds_array_onebased(A, i) + (@_boundscheck) && checkbounds(A, i) memoryrefget(memoryrefnew(memoryrefnew(A), i, false), default_access_order(A), false) end @@ -969,13 +963,13 @@ end # linear indexing function getindex(A::Array, i::Int) @_noub_if_noinbounds_meta - @boundscheck _checkbounds_array_onebased(A, i) + @boundscheck checkbounds(A, i) memoryrefget(memoryrefnew(getfield(A, :ref), i, false), :not_atomic, false) end # simple Array{Any} operations needed for bootstrap function setindex!(A::Array{Any}, @nospecialize(x), i::Int) @_noub_if_noinbounds_meta - @boundscheck _checkbounds_array_onebased(A, i) + @boundscheck checkbounds(A, i) memoryrefset!(memoryrefnew(getfield(A, :ref), i, false), x, :not_atomic, false) return A end From e19d72e0927ef7c9b452c856c094460ccfd01883 Mon Sep 17 00:00:00 2001 From: Matt Bauman Date: Mon, 23 Jun 2025 14:08:55 -0400 Subject: [PATCH 5/5] defer changes to iteration to a separate PR --- base/abstractarray.jl | 5 ++--- base/array.jl | 4 ++++ base/genericmemory.jl | 9 +++++++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/base/abstractarray.jl b/base/abstractarray.jl index 665d70c525d81..8f55e6a56eba8 100644 --- a/base/abstractarray.jl +++ b/base/abstractarray.jl @@ -387,7 +387,6 @@ function eachindex(A::AbstractArray, B::AbstractArray...) @inline eachindex(IndexStyle(A,B...), A, B...) end -eachindex(::IndexLinear, A::Union{Array, Memory}) = unchecked_oneto(length(A)) eachindex(::IndexLinear, A::AbstractArray) = (@inline; oneto(length(A))) eachindex(::IndexLinear, A::AbstractVector) = (@inline; axes1(A)) function eachindex(::IndexLinear, A::AbstractArray, B::AbstractArray...) @@ -1238,7 +1237,7 @@ oneunit(x::AbstractMatrix{T}) where {T} = _one(oneunit(T), x) iterate_starting_state(A) = iterate_starting_state(A, IndexStyle(A)) iterate_starting_state(A, ::IndexLinear) = firstindex(A) iterate_starting_state(A, ::IndexStyle) = (eachindex(A),) -@inline iterate(A::AbstractArray, state = iterate_starting_state(A)) = _iterate(A, state) +iterate(A::AbstractArray, state = iterate_starting_state(A)) = _iterate(A, state) function _iterate(A::AbstractArray, state::Tuple) y = iterate(state...) y === nothing && return nothing @@ -1246,7 +1245,7 @@ function _iterate(A::AbstractArray, state::Tuple) end function _iterate(A::AbstractArray, state::Integer) checkbounds(Bool, A, state) || return nothing - A[state], state + one(state) + @inbounds(A[state]), state + one(state) end isempty(a::AbstractArray) = (length(a) == 0) diff --git a/base/array.jl b/base/array.jl index cacef7c4acd5d..61a3c39ca2bb7 100644 --- a/base/array.jl +++ b/base/array.jl @@ -902,6 +902,10 @@ function grow_to!(dest, itr, st) return dest end +## Iteration ## + +iterate(A::Array, i=1) = (@inline; _iterate_array(A, i)) + ## Indexing: getindex ## """ diff --git a/base/genericmemory.jl b/base/genericmemory.jl index b180462115f41..d25d213a3546e 100644 --- a/base/genericmemory.jl +++ b/base/genericmemory.jl @@ -223,6 +223,15 @@ Memory{T}(x::AbstractArray{S,1}) where {T,S} = copyto_axcheck!(Memory{T}(undef, ## copying iterators to containers +## Iteration ## + +function _iterate_array(A::Union{Memory, Array}, i::Int) + @inline + checkbounds(Bool, A, i) ? (A[i], i + 1) : nothing +end + +iterate(A::Memory, i=1) = (@inline; _iterate_array(A, i)) + ## Indexing: getindex ## # Faster contiguous indexing using copyto! for AbstractUnitRange and Colon