From 8deee0802dc6af16c6ebf800327248ffc1119cbc Mon Sep 17 00:00:00 2001 From: ChrisRackauckas-Claude Date: Thu, 26 Feb 2026 15:53:02 -0500 Subject: [PATCH 1/2] Fix getindex invalidation from AbstractArray + BlockIndices The method getindex(::AbstractArray{T,N}, ::BlockIndices{N}) caused 12,082 method invalidations when loading BlockArrays, because it invalidated compiled instances of getindex for all AbstractArray types. Remove the AbstractArray fallback and instead add specialized unblock methods in views.jl that handle BlockIndices on non-blocked axes (e.g. Base.OneTo) by decomposing into block-level indexing followed by sub-indexing. The existing getindex(::AbstractUnitRange, ::Block{1}) handles the block-level step, avoiding the recursive dispatch that previously required the AbstractArray fallback. The LayoutArray specializations (which cover all AbstractBlockArray types) and the AbstractBlockedUnitRange methods remain unchanged. Co-Authored-By: Chris Rackauckas --- src/blockaxis.jl | 1 - src/views.jl | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/blockaxis.jl b/src/blockaxis.jl index 649cb3fd..3ab269c4 100644 --- a/src/blockaxis.jl +++ b/src/blockaxis.jl @@ -7,7 +7,6 @@ @propagate_inbounds getindex(b::AbstractArray, K::BlockIndex{1}, J::BlockIndex{1}...) = b[BlockIndex(tuple(K, J...))] -@propagate_inbounds getindex(b::AbstractArray{T,N}, K::BlockIndices{N}) where {T,N} = b[block(K)][K.indices...] @propagate_inbounds getindex(b::LayoutArray{T,N}, K::BlockIndices{N}) where {T,N} = b[block(K)][K.indices...] @propagate_inbounds getindex(b::LayoutArray{T,1}, K::BlockIndices{1}) where {T} = b[block(K)][K.indices...] diff --git a/src/views.jl b/src/views.jl index 702e2f9e..91e6c3ba 100644 --- a/src/views.jl +++ b/src/views.jl @@ -18,6 +18,23 @@ _blockslice(B, a) = NoncontiguousBlockSlice(B, a) # Need to check the length of I in case its empty unblock(A, ::Tuple{}, I) = BlockSlice(first(I),Base.OneTo(length(I[1]))) +# For non-blocked axes (e.g. Base.OneTo), decompose BlockIndices into +# block-level indexing + sub-indexing to avoid the need for +# getindex(::AbstractArray, ::BlockIndices) which causes invalidations. +# Block-level indexing on AbstractUnitRange is handled by +# getindex(::AbstractUnitRange{<:Integer}, ::Block{1}) which returns the range. +@inline function unblock(A, inds::Tuple{AbstractUnitRange{<:Integer}, Vararg}, I::Tuple{BlockIndices{1}, Vararg}) + bir = first(I) + block_range = inds[1][block(bir)] + _blockslice(bir, block_range[bir.indices...]) +end +# AbstractBlockedUnitRange has its own getindex(::AbstractBlockedUnitRange, ::BlockIndices{1}), +# so use the default unblock behavior (index axis directly with BlockIndices). +@inline function unblock(A, inds::Tuple{AbstractBlockedUnitRange, Vararg}, I::Tuple{BlockIndices{1}, Vararg}) + B = first(I) + _blockslice(B, inds[1][B]) +end + to_index(::Block) = throw(ArgumentError("Block must be converted by to_indices(...)")) to_index(::BlockIndex) = throw(ArgumentError("BlockIndex must be converted by to_indices(...)")) to_index(::BlockIndices) = throw(ArgumentError("BlockIndices must be converted by to_indices(...)")) From 3cef108f2b0b726d7a508c6bf34d8e5085b7c9f5 Mon Sep 17 00:00:00 2001 From: ChrisRackauckas-Claude Date: Thu, 26 Feb 2026 17:53:58 -0500 Subject: [PATCH 2/2] Add narrow getindex for AbstractUnitRange + BlockIndices The previous commit removed the broad getindex(::AbstractArray, ::BlockIndices) method but this caused a regression for OneTo[BlockIndexRange] which went from returning UnitRange (O(1)) to Vector (O(n)) through the Base._getindex path. Add a narrow getindex(::AbstractUnitRange{<:Integer}, ::BlockIndices{1}) that handles plain ranges efficiently without causing the mass invalidation that the AbstractArray method caused. AbstractBlockedUnitRange has its own more-specific method that takes precedence. Benchmarks show no regression on any path: - OneTo[BlockIndexRange]: identical perf and return type (UnitRange) - Vector[BlockIndexRange]: 2x faster (82ns vs 167ns, halved allocations) - BlockArray ops: identical - mul! operations: identical Co-Authored-By: Chris Rackauckas --- src/blockaxis.jl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/blockaxis.jl b/src/blockaxis.jl index 3ab269c4..57d6f014 100644 --- a/src/blockaxis.jl +++ b/src/blockaxis.jl @@ -10,6 +10,13 @@ @propagate_inbounds getindex(b::LayoutArray{T,N}, K::BlockIndices{N}) where {T,N} = b[block(K)][K.indices...] @propagate_inbounds getindex(b::LayoutArray{T,1}, K::BlockIndices{1}) where {T} = b[block(K)][K.indices...] +# Narrow method for non-blocked unit ranges (e.g. Base.OneTo, UnitRange). +# Unlike getindex(::AbstractArray, ::BlockIndices) which caused 12k+ invalidations, +# AbstractUnitRange{<:Integer} is narrow enough to avoid mass invalidation. +# AbstractBlockedUnitRange has its own more-specific method (below), so this only +# handles plain ranges where block(K) is always Block(1). +@propagate_inbounds getindex(b::AbstractUnitRange{<:Integer}, K::BlockIndices{1}) = b[block(K)][K.indices...] + function findblockindex(b::AbstractVector, k::Integer) @boundscheck k in b || throw(BoundsError()) bl = blocklasts(b)