From b77b0269cde31ce772df018dd796df2d6dd87a2e Mon Sep 17 00:00:00 2001 From: Matt Bauman Date: Tue, 5 May 2015 18:43:26 -0400 Subject: [PATCH] AbstractArray: fancy getindex and setindex! fallbacks This implements "fancy" indexing behaviors for all AbstractArray subtypes. * Converts CartesianIndices to their full dimensionality, regardless of the types of any other indices * For linear fast arrays, computes the linear index from multidimensional scalars * For linear slow arrays, computes the full dimensionality indexing subscript from a linear index * Implements non-scalar indexing: * Logical indexing * Vectors or ranges of indices * Indexing with one multidimensional array of indices The non-scalar indexing relies heavily on `similar`, so a default implementation for AbstractArrays was created to return an Array. Additionally, this expands the usage of `Base.unsafe_getindex` for indexing without bounds checks. --- base/abstractarray.jl | 291 +++++++++++++++++++++++++-------- base/multidimensional.jl | 337 +++++++++++++++++++++++---------------- base/operators.jl | 25 +-- base/range.jl | 3 + base/subarray.jl | 5 +- base/subarray2.jl | 1 - test/sparsedir/sparse.jl | 5 - 7 files changed, 432 insertions(+), 235 deletions(-) diff --git a/base/abstractarray.jl b/base/abstractarray.jl index 27a9fdc02cc79..9792108e155dc 100644 --- a/base/abstractarray.jl +++ b/base/abstractarray.jl @@ -118,41 +118,53 @@ linearindexing{T<:Range}(::Type{T}) = LinearFast() *(::LinearFast, ::LinearSlow) = LinearSlow() *(::LinearSlow, ::LinearSlow) = LinearSlow() +# The real @inline macro is not available this early in the bootstrap, so this +# internal macro splices the meta Expr directly into the function body. +macro _inline_meta() + Expr(:meta, :inline) +end +macro _noinline_meta() + Expr(:meta, :noinline) +end + ## Bounds checking ## -checkbounds(sz::Int, i::Int) = 1 <= i <= sz || throw(BoundsError()) -checkbounds(sz::Int, i::Real) = checkbounds(sz, to_index(i)) -checkbounds(sz::Int, I::AbstractVector{Bool}) = length(I) == sz || throw(BoundsError()) -checkbounds(sz::Int, r::Range{Int}) = isempty(r) || (minimum(r) >= 1 && maximum(r) <= sz) || throw(BoundsError()) -checkbounds{T<:Real}(sz::Int, r::Range{T}) = checkbounds(sz, to_index(r)) -checkbounds(sc::Int, ::Colon) = true - -function checkbounds{T <: Real}(sz::Int, I::AbstractArray{T}) +_checkbounds(sz::Int, i::Int) = 1 <= i <= sz +_checkbounds(sz::Int, i::Real) = (@_inline_meta; _checkbounds(sz, to_index(i))) +_checkbounds(sz::Int, I::AbstractVector{Bool}) = length(I) == sz +_checkbounds(sz::Int, I::AbstractArray{Bool}) = length(I) == sz # setindex! allows this +_checkbounds(sz::Int, r::Range{Int}) = (@_inline_meta; isempty(r) || (minimum(r) >= 1 && maximum(r) <= sz)) +_checkbounds{T<:Real}(sz::Int, r::Range{T}) = (@_inline_meta; _checkbounds(sz, to_index(r))) +_checkbounds(sz::Int, ::Colon) = true +function _checkbounds{T <: Real}(sz::Int, I::AbstractArray{T}) + @_inline_meta + b = true for i in I - checkbounds(sz, i) + b &= _checkbounds(sz, i) end + b end +# Prevent allocation of a GC frame by hiding the BoundsError in a noinline function +throw_boundserror(A, I) = (@_noinline_meta; throw(BoundsError(A, I))) -checkbounds(A::AbstractArray, I::AbstractArray{Bool}) = size(A) == size(I) || throw(BoundsError()) - -checkbounds(A::AbstractArray, I) = checkbounds(length(A), I) - +checkbounds(A::AbstractArray, I::AbstractArray{Bool}) = size(A) == size(I) || throw_boundserror(A, I) +checkbounds(A::AbstractArray, I::AbstractVector{Bool}) = length(A) == length(I) || throw_boundserror(A, I) +checkbounds(A::AbstractArray, I) = (@_inline_meta; _checkbounds(length(A), I) || throw_boundserror(A, I)) function checkbounds(A::AbstractMatrix, I::Union(Real,AbstractArray,Colon), J::Union(Real,AbstractArray,Colon)) - checkbounds(size(A,1), I) - checkbounds(size(A,2), J) + @_inline_meta + (_checkbounds(size(A,1), I) && _checkbounds(size(A,2), J)) || throw_boundserror(A, (I, J)) end - function checkbounds(A::AbstractArray, I::Union(Real,AbstractArray,Colon), J::Union(Real,AbstractArray,Colon)) - checkbounds(size(A,1), I) - checkbounds(trailingsize(A,2), J) + @_inline_meta + (_checkbounds(size(A,1), I) && _checkbounds(trailingsize(A,2), J)) || throw_boundserror(A, (I, J)) end - function checkbounds(A::AbstractArray, I::Union(Real,AbstractArray,Colon)...) + @_inline_meta n = length(I) if n > 0 for dim = 1:(n-1) - checkbounds(size(A,dim), I[dim]) + _checkbounds(size(A,dim), I[dim]) || throw_boundserror(A, I) end - checkbounds(trailingsize(A,n), I[n]) + _checkbounds(trailingsize(A,n), I[n]) || throw_boundserror(A, I) end end @@ -178,6 +190,8 @@ similar (a::AbstractArray, T) = similar(a, T, size(a)) similar{T}(a::AbstractArray{T}, dims::Dims) = similar(a, T, dims) similar{T}(a::AbstractArray{T}, dims::Int...) = similar(a, T, dims) similar (a::AbstractArray, T, dims::Int...) = similar(a, T, dims) +# similar creates an Array by default +similar (a::AbstractArray, T, dims::Dims) = Array(T, dims) function reshape(a::AbstractArray, dims::Dims) if prod(dims) != length(a) @@ -361,11 +375,7 @@ zero{T}(x::AbstractArray{T}) = fill!(similar(x), zero(T)) # While the definitions for LinearFast are all simple enough to inline on their # own, LinearSlow's CartesianRange is more complicated and requires explicit -# inlining. The real @inline macro is not available this early in the bootstrap, -# so this internal macro splices the meta Expr directly into the function body. -macro _inline_meta() - Expr(:meta, :inline) -end +# inlining. start(A::AbstractArray) = (@_inline_meta(); itr = eachindex(A); (itr, start(itr))) next(A::AbstractArray,i) = (@_inline_meta(); (idx, s) = next(i[1], i[2]); (A[idx], (i[1], s))) done(A::AbstractArray,i) = done(i[1], i[2]) @@ -430,19 +440,6 @@ imag{T<:Real}(x::AbstractArray{T}) = zero(x) \(A::Number, B::AbstractArray) = B ./ A -## Indexing: getindex ## - -getindex(t::AbstractArray, i::Real) = error("indexing not defined for ", typeof(t)) - -# linear indexing with a single multi-dimensional index -function getindex(A::AbstractArray, I::AbstractArray) - x = similar(A, size(I)) - for i in eachindex(I) - x[i] = A[I[i]] - end - return x -end - # index A[:,:,...,i,:,:,...] where "i" is in dimension "d" # TODO: more optimized special cases slicedim(A::AbstractArray, d::Integer, i) = @@ -490,42 +487,202 @@ function circshift{T,N}(a::AbstractArray{T,N}, shiftamts) a[(I::NTuple{N,Vector{Int}})...] end -## Indexing: setindex! ## - -# 1-d indexing is assumed defined on subtypes -setindex!(t::AbstractArray, x, i::Real) = - error("setindex! not defined for ",typeof(t)) -setindex!(t::AbstractArray, x) = throw(MethodError(setindex!, (t, x))) - -## Indexing: handle more indices than dimensions if "extra" indices are 1 - -# Don't require vector/matrix subclasses to implement more than 1/2 indices, -# respectively, by handling the extra dimensions in AbstractMatrix. - -function getindex(A::AbstractVector, i1,i2,i3...) - if i2*prod(i3) != 1 - throw(BoundsError()) +## Approach: +# We only define one fallback method on getindex for all argument types. +# That dispatches to an (inlined) internal _getindex function, where the goal is +# to transform the indices such that we can call the only getindex method that +# we require AbstractArray subtypes must define, either: +# getindex(::T, ::Int) # if linearindexing(T) == LinearFast() +# getindex(::T, ::Int, ::Int, #=...ndims(A) indices...=#) if LinearSlow() +# Unfortunately, it is currently impossible to express the latter method for +# arbitrary dimensionalities. We could get around that with ::CartesianIndex{N}, +# but that isn't as obvious and would require that the function be inlined to +# avoid allocations. If the subtype hasn't defined those methods, it goes back +# to the _getindex function where an error is thrown to prevent stack overflows. +# +# We use the same scheme for unsafe_getindex, with the exception that we can +# fallback to the safe version if the subtype hasn't defined the required +# unsafe method. + +function getindex(A::AbstractArray, I...) + @_inline_meta + _getindex(linearindexing(A), A, I...) +end +function unsafe_getindex(A::AbstractArray, I...) + @_inline_meta + _unsafe_getindex(linearindexing(A), A, I...) +end +## Internal defitions +_getindex(::LinearFast, A::AbstractArray) = (@_inline_meta; getindex(A, 1)) +_getindex(::LinearSlow, A::AbstractArray) = (@_inline_meta; _getindex(A, 1)) +_unsafe_getindex(::LinearFast, A::AbstractArray) = (@_inline_meta; unsafe_getindex(A, 1)) +_unsafe_getindex(::LinearSlow, A::AbstractArray) = (@_inline_meta; _unsafe_getindex(A, 1)) +_getindex(::LinearIndexing, A::AbstractArray, I...) = error("indexing $(typeof(A)) with types $(typeof(I)) is not supported") +_unsafe_getindex(::LinearIndexing, A::AbstractArray, I...) = error("indexing $(typeof(A)) with types $(typeof(I)) is not supported") + +## LinearFast Scalar indexing +_getindex(::LinearFast, A::AbstractArray, I::Int) = error("indexing not defined for ", typeof(A)) +function _getindex(::LinearFast, A::AbstractArray, I::Real...) + @_inline_meta + # We must check bounds for sub2ind; so we can then call unsafe_getindex + checkbounds(A, I...) + unsafe_getindex(A, sub2ind(size(A), to_index(I)...)) +end +_unsafe_getindex(::LinearFast, A::AbstractArray, I::Int) = (@_inline_meta; getindex(A, I)) +function _unsafe_getindex(::LinearFast, A::AbstractArray, I::Real...) + @_inline_meta + unsafe_getindex(A, sub2ind(size(A), to_index(I)...)) +end + +# LinearSlow Scalar indexing +@generated function _getindex{T,AN}(::LinearSlow, A::AbstractArray{T,AN}, I::Real...) + N = length(I) + if N == AN + :(error("indexing not defined for ", typeof(A))) + elseif N > AN + # Drop trailing ones + Isplat = Expr[:(to_index(I[$d])) for d = 1:AN] + Osplat = Expr[:(to_index(I[$d]) == 1) for d = AN+1:N] + quote + $(Expr(:meta, :inline)) + (&)($(Osplat...)) || throw_boundserror(A, I) + getindex(A, $(Isplat...)) + end + else + # Expand the last index into the appropriate number of indices + Isplat = Expr[:(to_index(I[$d])) for d = 1:N-1] + i = 0 + for d=N:AN + push!(Isplat, :(s[$(i+=1)])) + end + sz = Expr(:tuple) + sz.args = Expr[:(size(A, $d)) for d=N:AN] + quote + $(Expr(:meta, :inline)) + # ind2sub requires all dimensions to be nonzero, so checkbounds first + checkbounds(A, I...) + s = ind2sub($sz, to_index(I[$N])) + unsafe_getindex(A, $(Isplat...)) + end end - A[i1] end -function getindex(A::AbstractMatrix, i1,i2,i3,i4...) - if i3*prod(i4) != 1 - throw(BoundsError()) +@generated function _unsafe_getindex{T,AN}(::LinearSlow, A::AbstractArray{T,AN}, I::Real...) + N = length(I) + if N == AN + Isplat = Expr[:(to_index(I[$d])) for d = 1:N] + :($(Expr(:meta, :inline)); getindex(A, $(Isplat...))) + elseif N > AN + # Drop trailing dimensions (unchecked) + Isplat = Expr[:(to_index(I[$d])) for d = 1:AN] + quote + $(Expr(:meta, :inline)) + unsafe_getindex(A, $(Isplat...)) + end + else + # Expand the last index into the appropriate number of indices + Isplat = Expr[:(to_index(I[$d])) for d = 1:N-1] + for d=N:AN + push!(Isplat, :(s[$(d-N+1)])) + end + sz = Expr(:tuple) + sz.args = Expr[:(size(A, $d)) for d=N:AN] + quote + $(Expr(:meta, :inline)) + s = ind2sub($sz, to_index(I[$N])) + unsafe_getindex(A, $(Isplat...)) + end end - A[i1,i2] end -function setindex!(A::AbstractVector, x, i1,i2,i3...) - if i2*prod(i3) != 1 - throw(BoundsError()) +## Setindex! is defined similarly. We first dispatch to an internal _setindex! +# function that allows dispatch on array storage +function setindex!(A::AbstractArray, v, I...) + @_inline_meta + _setindex!(linearindexing(A), A, v, I...) +end +function unsafe_setindex!(A::AbstractArray, v, I...) + @_inline_meta + _unsafe_setindex!(linearindexing(A), A, v, I...) +end +## Internal defitions +_setindex!(::LinearFast, A::AbstractArray, v) = (@_inline_meta; setindex!(A, v, 1)) +_setindex!(::LinearSlow, A::AbstractArray, v) = (@_inline_meta; _setindex!(A, v, 1)) +_unsafe_setindex!(::LinearFast, A::AbstractArray, v) = (@_inline_meta; unsafe_setindex!(A, v, 1)) +_unsafe_setindex!(::LinearSlow, A::AbstractArray, v) = (@_inline_meta; _unsafe_setindex!(A, v, 1)) +_setindex!(::LinearIndexing, A::AbstractArray, v, I...) = error("indexing $(typeof(A)) with types $(typeof(I)) is not supported") +_unsafe_setindex!(::LinearIndexing, A::AbstractArray, v, I...) = error("indexing $(typeof(A)) with types $(typeof(I)) is not supported") + +## LinearFast Scalar indexing +_setindex!(::LinearFast, A::AbstractArray, v, I::Int) = error("indexed assignment not defined for ", typeof(A)) +function _setindex!(::LinearFast, A::AbstractArray, v, I::Real...) + @_inline_meta + # We must check bounds for sub2ind; so we can then call unsafe_setindex! + checkbounds(A, I...) + unsafe_setindex!(A, v, sub2ind(size(A), to_index(I)...)) +end +_unsafe_setindex!(::LinearFast, A::AbstractArray, v, I::Int) = (@_inline_meta; setindex!(A, v, I)) +function _unsafe_setindex!(::LinearFast, A::AbstractArray, v, I::Real...) + @_inline_meta + unsafe_setindex!(A, v, sub2ind(size(A), to_index(I)...)) +end + +# LinearSlow Scalar indexing +@generated function _setindex!{T,AN}(::LinearSlow, A::AbstractArray{T,AN}, v, I::Real...) + N = length(I) + if N == AN + :(error("indexed assignment not defined for ", typeof(A))) + elseif N > AN + # Drop trailing ones + Isplat = Expr[:(to_index(I[$d])) for d = 1:AN] + Osplat = Expr[:(to_index(I[$d]) == 1) for d = AN+1:N] + quote + $(Expr(:meta, :inline)) + (&)($(Osplat...)) || throw_boundserror(A, I) + setindex!(A, v, $(Isplat...)) + end + else + # Expand the last index into the appropriate number of indices + Isplat = Expr[:(to_index(I[$d])) for d = 1:N-1] + i = 0 + for d=N:AN + push!(Isplat, :(s[$(i+=1)])) + end + sz = Expr(:tuple) + sz.args = Expr[:(size(A, $d)) for d=N:AN] + quote + $(Expr(:meta, :inline)) + checkbounds(A, I...) + s = ind2sub($sz, to_index(I[$N])) + unsafe_setindex!(A, v, $(Isplat...)) + end end - A[i1] = x end -function setindex!(A::AbstractMatrix, x, i1,i2,i3,i4...) - if i3*prod(i4) != 1 - throw(BoundsError()) +@generated function _unsafe_setindex!{T,AN}(::LinearSlow, A::AbstractArray{T,AN}, v, I::Real...) + N = length(I) + if N == AN + Isplat = Expr[:(to_index(I[$d])) for d = 1:N] + :(setindex!(A, v, $(Isplat...))) + elseif N > AN + # Drop trailing dimensions (unchecked) + Isplat = Expr[:(to_index(I[$d])) for d = 1:AN] + quote + $(Expr(:meta, :inline)) + unsafe_setindex!(A, v, $(Isplat...)) + end + else + # Expand the last index into the appropriate number of indices + Isplat = Expr[:(to_index(I[$d])) for d = 1:N-1] + for d=N:AN + push!(Isplat, :(s[$(d-N+1)])) + end + sz = Expr(:tuple) + sz.args = Expr[:(size(A, $d)) for d=N:AN] + quote + $(Expr(:meta, :inline)) + s = ind2sub($sz, to_index(I[$N])) + unsafe_setindex!(A, v, $(Isplat...)) + end end - A[i1,i2] = x end ## get (getindex with a default value) ## diff --git a/base/multidimensional.jl b/base/multidimensional.jl index 29deefb4dfd0d..d1afc368d740a 100644 --- a/base/multidimensional.jl +++ b/base/multidimensional.jl @@ -55,65 +55,6 @@ length{I<:CartesianIndex}(::Type{I})=length(super(I)) # indexing getindex(index::CartesianIndex, i::Integer) = getfield(index, i)::Int -@generated function getindex{N}(A::Array, index::CartesianIndex{N}) - :(Base.arrayref(A, $(cartindex_exprs((index,), (:index,))...))) -end -@generated function getindex{N}(A::Array, i::Integer, index::CartesianIndex{N}) - :(Base.arrayref(A, $(cartindex_exprs((i, index), (:i, :index))...))) -end -@generated function getindex{M,N}(A::Array, index1::CartesianIndex{M}, i::Integer, index2::CartesianIndex{N}) - :(Base.arrayref(A, $(cartindex_exprs((index1, i, index2), (:index1, :i, :index2))...))) -end -@generated function setindex!{T,N}(A::Array{T}, v, index::CartesianIndex{N}) - :(Base.arrayset(A, convert($T,v), $(cartindex_exprs((index,), (:index,))...))) -end -@generated function setindex!{T,N}(A::Array{T}, v, i::Integer, index::CartesianIndex{N}) - :(Base.arrayset(A, convert($T,v), $(cartindex_exprs((i, index), (:i, :index))...))) -end -@generated function setindex!{T,M,N}(A::Array{T}, v, index1::CartesianIndex{M}, i::Integer, index2::CartesianIndex{N}) - :(Base.arrayset(A, convert($T,v), $(cartindex_exprs((index1, i, index2), (:index1, :i, :index2))...))) -end - -@generated function getindex{N}(A::AbstractArray, index::CartesianIndex{N}) - :(getindex(A, $(cartindex_exprs((index,), (:index,))...))) -end -@generated function getindex{N}(A::AbstractArray, i::Integer, index::CartesianIndex{N}) - :(getindex(A, $(cartindex_exprs((i, index), (:i, :index))...))) -end -@generated function setindex!{T,N}(A::AbstractArray{T}, v, index::CartesianIndex{N}) - :(setindex!(A, v, $(cartindex_exprs((index,), (:index,))...))) -end -@generated function setindex!{T,N}(A::AbstractArray{T}, v, i::Integer, index::CartesianIndex{N}) - :(setindex!(A, v, $(cartindex_exprs((i, index), (:i, :index))...))) -end -for AT in (AbstractVector, AbstractMatrix, AbstractArray) # nix ambiguity warning - @eval begin - @generated function getindex{M,N}(A::$AT, index1::CartesianIndex{M}, i::Integer, index2::CartesianIndex{N}) - :(getindex(A, $(cartindex_exprs((index1, i, index2), (:index1, :i, :index2))...))) - end - @generated function setindex!{M,N}(A::$AT, v, index1::CartesianIndex{M}, i::Integer, index2::CartesianIndex{N}) - :(setindex!(A, v, $(cartindex_exprs((index1, i, index2), (:index1, :i, :index2))...))) - end - end -end - -function cartindex_exprs(indexes, syms) - exprs = Any[] - for (i,ind) in enumerate(indexes) - if ind <: Number - push!(exprs, :($(syms[i]))) - else - for j = 1:length(ind) - push!(exprs, :($(syms[i])[$j])) - end - end - end - if isempty(exprs) - push!(exprs, 1) # Handle the zero-dimensional case - end - exprs -end - # arithmetic, min/max for op in (:+, :-, :min, :max) @eval begin @@ -226,122 +167,238 @@ end # IteratorsMD using .IteratorsMD - -### From array.jl - -@generated function checksize(A::AbstractArray, I...) +# Recursively compute the lengths of a list of indices, without dropping scalars +# These need to be inlined for more than 3 indexes +index_lengths(A::AbstractArray, I::Colon) = (length(A),) +index_lengths(A::AbstractArray, I::AbstractArray{Bool}) = (sum(I),) +index_lengths(A::AbstractArray, I::AbstractArray) = (length(I),) +@inline index_lengths(A::AbstractArray, I...) = index_lengths_dim(A, 1, I...) +index_lengths_dim(A, dim) = () +index_lengths_dim(A, dim, ::Colon) = (trailingsize(A, dim),) +@inline index_lengths_dim(A, dim, ::Colon, i, I...) = (size(A, dim), index_lengths_dim(A, dim+1, i, I...)...) +@inline index_lengths_dim(A, dim, ::Real, I...) = (1, index_lengths_dim(A, dim+1, I...)...) +@inline index_lengths_dim(A, dim, i::AbstractVector{Bool}, I...) = (sum(i), index_lengths_dim(A, dim+1, I...)...) +@inline index_lengths_dim(A, dim, i::AbstractVector, I...) = (length(i), index_lengths_dim(A, dim+1, I...)...) + +# shape of array to create for getindex() with indexes I, dropping trailing scalars +index_shape(A::AbstractArray, I::AbstractArray) = size(I) # Linear index reshape +index_shape(A::AbstractArray, I::AbstractArray{Bool}) = (sum(I),) # Logical index +index_shape(A::AbstractArray, I::Colon) = (length(A),) +@inline index_shape(A::AbstractArray, I...) = index_shape_dim(A, 1, I...) +index_shape_dim(A, dim, I::Real...) = () +index_shape_dim(A, dim, ::Colon) = (trailingsize(A, dim),) +@inline index_shape_dim(A, dim, ::Colon, i, I...) = (size(A, dim), index_shape_dim(A, dim+1, i, I...)...) +@inline index_shape_dim(A, dim, ::Real, I...) = (1, index_shape_dim(A, dim+1, I...)...) +@inline index_shape_dim(A, dim, i::AbstractVector{Bool}, I...) = (sum(i), index_shape_dim(A, dim+1, I...)...) +@inline index_shape_dim(A, dim, i::AbstractVector, I...) = (length(i), index_shape_dim(A, dim+1, I...)...) + +### From abstractarray.jl: Internal multidimensional indexing definitions ### +# These are not defined on directly ongetindex and unsafe_getindex to avoid +# ambiguities for AbstractArray subtypes. See the note in abstractarray.jl + +# Note that it's most efficient to call checkbounds first, and then to_index +@inline function _getindex(l::LinearIndexing, A::AbstractArray, I::Union(Real, AbstractArray, Colon)...) + checkbounds(A, I...) + _unsafe_getindex(l, A, I...) +end +@generated function _unsafe_getindex(l::LinearIndexing, A::AbstractArray, I::Union(Real, AbstractArray, Colon)...) N = length(I) - ex = Expr(:block) - push!(ex.args, :(idxlens = index_lengths(A, I...))) - for d=1:N - push!(ex.args, :(size(A, $d) == idxlens[$d] || throw(DimensionMismatch("index ", $d, " has length ", idxlens[$d], ", but size(A, ", $d, ") = ", size(A,$d))))) + quote + # This is specifically *not* inlined. + @nexprs $N d->(I_d = to_index(I[d])) + dest = similar(A, @ncall $N index_shape A I) + @ncall $N checksize dest I + @ncall $N _unsafe_getindex! dest l A I end - push!(ex.args, :(nothing)) - ex end -@inline unsafe_getindex(v::BitArray, ind::Int) = Base.unsafe_bitgetindex(v.chunks, ind) +# logical indexing optimization - don't use find (within to_index) +# This is inherently a linear operation in the source, but we could potentially +# use fast dividing integers to speed it up. +function _unsafe_getindex(::LinearIndexing, src::AbstractArray, I::AbstractArray{Bool}) + # Both index_shape and checksize compute sum(I); manually hoist it out + N = sum(I) + dest = similar(src, (N,)) + size(dest) == (N,) || throw(DimensionMismatch()) + D = eachindex(dest) + Ds = start(D) + s = 0 + for b in eachindex(I) + s+=1 + if unsafe_getindex(I, b) + d, Ds = next(D, Ds) + unsafe_setindex!(dest, unsafe_getindex(src, s), d) + end + end + dest +end -@inline unsafe_setindex!{T}(v::Array{T}, x::T, ind::Int) = (@inbounds v[ind] = x; v) -@inline unsafe_setindex!{T}(v::AbstractArray{T}, x::T, ind::Int) = (v[ind] = x; v) -@inline unsafe_setindex!(v::BitArray, x::Bool, ind::Int) = (Base.unsafe_bitsetindex!(v.chunks, x, ind); v) -@inline unsafe_setindex!{T}(v::AbstractArray{T}, x::T, ind::Real) = unsafe_setindex!(v, x, to_index(ind)) +# Indexing with an array of indices is inherently linear in the source, but +# might be able to be optimized with fast dividing integers +@inline function _unsafe_getindex!(dest::AbstractArray, ::LinearIndexing, src::AbstractArray, I::AbstractArray) + D = eachindex(dest) + Ds = start(D) + for idx in I + d, Ds = next(D, Ds) + unsafe_setindex!(dest, unsafe_getindex(src, idx), d) + end + dest +end -# Version that uses cartesian indexing for src -@generated function _getindex!(dest::Array, src::AbstractArray, I::Union(Int,AbstractVector,Colon)...) +# Fast source - compute the linear index +@generated function _unsafe_getindex!(dest::AbstractArray, ::LinearFast, src::AbstractArray, I::Union(Real, AbstractVector, Colon)...) N = length(I) - Isplat = Expr[:(I[$d]) for d = 1:N] quote - checksize(dest, $(Isplat...)) - k = 1 - @nloops $N i dest d->(@inbounds j_d = unsafe_getindex(I[d], i_d)) begin - @inbounds dest[k] = (@nref $N src j) - k += 1 + $(Expr(:meta, :inline)) + stride_1 = 1 + @nexprs $N d->(stride_{d+1} = stride_d*size(src, d)) + $(symbol(:offset_, N)) = 1 + D = eachindex(dest) + Ds = start(D) + @nloops $N i dest d->(offset_{d-1} = offset_d + (unsafe_getindex(I[d], i_d)-1)*stride_d) begin + d, Ds = next(D, Ds) + unsafe_setindex!(dest, unsafe_getindex(src, offset_0), d) end dest end end - -# Version that uses linear indexing for src -@generated function _getindex!(dest::Array, src::Array, I::Union(Int,AbstractVector,Colon)...) +# Slow source - index with the indices provided. +# TODO: this may not be the full dimensionality; that case could be optimized +@generated function _unsafe_getindex!(dest::AbstractArray, ::LinearSlow, src::AbstractArray, I::Union(Real, AbstractVector, Colon)...) N = length(I) - Isplat = Expr[:(I[$d]) for d = 1:N] quote - checksize(dest, $(Isplat...)) - stride_1 = 1 - @nexprs $N d->(stride_{d+1} = stride_d*size(src,d)) - @nexprs $N d->(offset_d = 1) # only really need offset_$N = 1 - k = 1 - @nloops $N i dest d->(@inbounds offset_{d-1} = offset_d + (unsafe_getindex(I[d], i_d)-1)*stride_d) begin - @inbounds dest[k] = src[offset_0] - k += 1 + $(Expr(:meta, :inline)) + D = eachindex(dest) + Ds = start(D) + @nloops $N i dest d->(j_d = unsafe_getindex(I[d], i_d)) begin + d, Ds = next(D, Ds) + v = @ncall $N unsafe_getindex src j + unsafe_setindex!(dest, v, d) end dest end end -# It's most efficient to call checkbounds first, then to_index, and finally -# allocate the output. Hence the different variants. -_getindex(A, I::Tuple{Vararg{Union(Int,AbstractVector,Colon),}}) = - _getindex!(similar(A, index_shape(A, I...)), A, I...) - -# The @generated function here is just to work around the performance hit -# of splatting -@generated function getindex(A::Array, I::Union(Real,AbstractVector,Colon)...) +# checksize ensures the output array A is the correct size for the given indices +checksize(A::AbstractArray, I::AbstractArray) = size(A) == size(I) || throw(DimensionMismatch("index 1 has size $(size(I)), but size(A) = $(size(A))")) +checksize(A::AbstractArray, I::AbstractArray{Bool}) = length(A) == sum(I) || throw(DimensionMismatch("index 1 selects $(sum(I)) elements, but length(A) = $(length(A))")) +@generated function checksize(A::AbstractArray, I...) N = length(I) - Isplat = Expr[:(I[$d]) for d = 1:N] quote - checkbounds(A, $(Isplat...)) - _getindex(A, to_index($(Isplat...))) + @nexprs $N d->(_checksize(A, d, I[d]) || throw(DimensionMismatch("index $d selects $(length(I[d])) elements, but size(A, $d) = $(size(A,d))"))) end end +_checksize(A::AbstractArray, dim, I) = size(A, dim) == length(I) +_checksize(A::AbstractArray, dim, I::AbstractVector{Bool}) = size(A, dim) == sum(I) +_checksize(A::AbstractArray, dim, ::Colon) = true +_checksize(A::AbstractArray, dim, ::Real) = size(A, dim) == 1 -# Also a safe version of getindex! -@generated function getindex!(dest, src, I::Union(Real,AbstractVector,Colon)...) +@inline unsafe_setindex!{T}(v::Array{T}, x::T, ind::Int) = (@inbounds v[ind] = x; v) +@inline unsafe_setindex!(v::BitArray, x::Bool, ind::Int) = (Base.unsafe_bitsetindex!(v.chunks, x, ind); v) +@inline unsafe_setindex!(v::BitArray, x, ind::Real) = (Base.unsafe_bitsetindex!(v.chunks, convert(Bool, x), to_index(ind)); v) + +## setindex! ## +# For multi-element setindex!, we check bounds, convert the indices (to_index), +# and ensure the value to set is either an AbstractArray or a Repeated scalar +# before redispatching to the _unsafe_batchsetindex! +_iterable(v::AbstractArray) = v +_iterable(v) = repeated(v) +@inline function _setindex!(l::LinearIndexing, A::AbstractArray, x, J::Union(Real,AbstractArray,Colon)...) + checkbounds(A, J...) + _unsafe_setindex!(l, A, x, J...) +end +@inline function _unsafe_setindex!(l::LinearIndexing, A::AbstractArray, x, J::Union(Real,AbstractVector,Colon)...) + _unsafe_batchsetindex!(l, A, _iterable(x), to_index(J)...) +end + +# While setindex! with one array argument doesn't mean anything special, it is +# still supported for symmetry with getindex. +_unsafe_setindex!(l::LinearIndexing, A::AbstractArray, x, I::AbstractArray) = _unsafe_setindex!(l, A, x, vec(I)) +# 1-d logical indexing: override the above to avoid calling find (in to_index) +function _unsafe_setindex!(::LinearIndexing, A::AbstractArray, x, I::AbstractVector{Bool}) + X = _iterable(x) + Xs = start(X) + i = 0 + c = 0 + for b in eachindex(I) + i+=1 + if unsafe_getindex(I, b) + done(X, Xs) && throw_setindex_mismatch(x, c+1) + (v, Xs) = next(X, Xs) + unsafe_setindex!(A, v, i) + c += 1 + end + end + setindex_shape_check(X, c) + A +end + +# Use iteration over X so we don't need to worry about its storage +@generated function _unsafe_batchsetindex!(::LinearFast, A::AbstractArray, X, I::Union(Real,AbstractVector,Colon)...) N = length(I) - Isplat = Expr[:(I[$d]) for d = 1:N] - Jsplat = Expr[:(to_index(I[$d])) for d = 1:N] quote - checkbounds(src, $(Isplat...)) - _getindex!(dest, src, $(Jsplat...)) + @nexprs $N d->(I_d = I[d]) + idxlens = @ncall $N index_lengths A I + @ncall $N setindex_shape_check X (d->idxlens[d]) + Xs = start(X) + stride_1 = 1 + @nexprs $N d->(stride_{d+1} = stride_d*size(A,d)) + $(symbol(:offset_, N)) = 1 + @nloops $N i d->(1:idxlens[d]) d->(offset_{d-1} = offset_d + (unsafe_getindex(I_d, i_d)-1)*stride_d) begin + v, Xs = next(X, Xs) + unsafe_setindex!(A, v, offset_0) + end + A end end - - -@generated function setindex!(A::Array, x, J::Union(Real,AbstractArray,Colon)...) - N = length(J) - if x<:AbstractArray - ex=quote - X = x - idxlens = @ncall $N index_lengths A I - setindex_shape_check(X, idxlens...) - Xs = start(X) - @nloops $N i d->(1:idxlens[d]) d->(@inbounds offset_{d-1} = offset_d + (unsafe_getindex(I_d, i_d)-1)*stride_d) begin - v, Xs = next(X, Xs) - @inbounds A[offset_0] = v - end +@generated function _unsafe_batchsetindex!(::LinearSlow, A::AbstractArray, X, I::Union(Real,AbstractVector,Colon)...) + N = length(I) + quote + @nexprs $N d->(I_d = I[d]) + idxlens = @ncall $N index_lengths A I + @ncall $N setindex_shape_check X (d->idxlens[d]) + Xs = start(X) + @nloops $N i d->(1:idxlens[d]) d->(j_d = unsafe_getindex(I_d, i_d)) begin + v, Xs = next(X, Xs) + @ncall $N unsafe_setindex! A v j end - else - ex=quote - idxlens = @ncall $N index_lengths A I - @nloops $N i d->(1:idxlens[d]) d->(@inbounds offset_{d-1} = offset_d + (unsafe_getindex(I_d, i_d)-1)*stride_d) begin - @inbounds A[offset_0] = x + A + end +end + +# Cartesian indexing +function cartindex_exprs(indexes, syms) + exprs = Any[] + for (i,ind) in enumerate(indexes) + if ind <: CartesianIndex + for j = 1:length(ind) + push!(exprs, :($syms[$i][$j])) end + else + push!(exprs, :($syms[$i])) end end - quote - @nexprs $N d->(J_d = J[d]) - @ncall $N checkbounds A J - @nexprs $N d->(I_d = to_index(J_d)) - stride_1 = 1 - @nexprs $N d->(stride_{d+1} = stride_d*size(A,d)) - @nexprs $N d->(offset_d = 1) # really only need offset_$N = 1 - $ex - A + if isempty(exprs) + push!(exprs, 1) # Handle the zero-dimensional case end + exprs +end +@generated function _getindex{T,N}(l::LinearIndexing, A::AbstractArray{T,N}, I::Union(Real,AbstractArray,Colon,CartesianIndex)...) + :($(Expr(:meta, :inline)); getindex(A, $(cartindex_exprs(I, :I)...))) +end +@generated function _unsafe_getindex{T,N}(l::LinearIndexing, A::AbstractArray{T,N}, I::Union(Real,AbstractArray,Colon,CartesianIndex)...) + :($(Expr(:meta, :inline)); unsafe_getindex(A, $(cartindex_exprs(I, :I)...))) +end +@generated function _setindex!{T,N}(l::LinearIndexing, A::AbstractArray{T,N}, v, I::Union(Real,AbstractArray,Colon,CartesianIndex)...) + :($(Expr(:meta, :inline)); setindex!(A, v, $(cartindex_exprs(I, :I)...))) +end +@generated function _unsafe_setindex!{T,N}(l::LinearIndexing, A::AbstractArray{T,N}, v, I::Union(Real,AbstractArray,Colon,CartesianIndex)...) + :($(Expr(:meta, :inline)); unsafe_setindex!(A, v, $(cartindex_exprs(I, :I)...))) end +## + @generated function findn{T,N}(A::AbstractArray{T,N}) quote nnzA = countnz(A) @@ -580,6 +637,8 @@ end ## getindex +@inline unsafe_getindex(v::BitArray, ind::Int) = Base.unsafe_bitgetindex(v.chunks, ind) + # general scalar indexing with two or more indices # (uses linear indexing, which is defined in bitarray.jl) # (code is duplicated for safe and unsafe versions for performance reasons) diff --git a/base/operators.jl b/base/operators.jl index 7bcfafa97134d..3db8f3f244864 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -221,22 +221,6 @@ function promote_shape(a::Dims, b::Dims) return a end -# The lengths of the given indices, lowering : to the appropriate size -index_lengths(A::AbstractArray, I...) = index_lengths_dim(A, 1, I...) -index_lengths_dim(A, dim) = () -index_lengths_dim(A, dim, ::Colon) = dim == 1 ? (length(A),) : (trailingsize(A, dim),) -index_lengths_dim(A, dim, ::Colon, I...) = tuple(size(A, dim), index_lengths_dim(A, dim+1, I...)...) -index_lengths_dim(A, dim, ::Real, I...) = tuple(1, index_lengths_dim(A, dim+1, I...)...) -index_lengths_dim(A, dim, i, I...) = tuple(length(i), index_lengths_dim(A, dim+1, I...)...) - -# shape of array to create for getindex() with indexes I -# drop dimensions indexed with trailing scalars -index_shape(A::AbstractArray, I...) = index_shape_dim(A, 1, I...) -index_shape_dim(A, dim, I::Real...) = () -index_shape_dim(A, dim, ::Colon) = dim == 1 ? (length(A),) : (trailingsize(A, dim),) -index_shape_dim(A, dim, ::Colon, I...) = tuple(size(A, dim), index_shape_dim(A, dim+1, I...)...) -index_shape_dim(A, dim, i, I...) = tuple(length(i), index_shape_dim(A, dim+1, I...)...) - function throw_setindex_mismatch(X, I) if length(I) == 1 throw(DimensionMismatch("tried to assign $(length(X)) elements to $(I[1]) destinations")) @@ -287,16 +271,16 @@ end setindex_shape_check(X::AbstractArray) = (length(X)==1 || throw_setindex_mismatch(X,())) -setindex_shape_check(X::AbstractArray, i) = +setindex_shape_check(X::AbstractArray, i::Int) = (length(X)==i || throw_setindex_mismatch(X, (i,))) -setindex_shape_check{T}(X::AbstractArray{T,1}, i) = +setindex_shape_check{T}(X::AbstractArray{T,1}, i::Int) = (length(X)==i || throw_setindex_mismatch(X, (i,))) -setindex_shape_check{T}(X::AbstractArray{T,1}, i, j) = +setindex_shape_check{T}(X::AbstractArray{T,1}, i::Int, j::Int) = (length(X)==i*j || throw_setindex_mismatch(X, (i,j))) -function setindex_shape_check{T}(X::AbstractArray{T,2}, i, j) +function setindex_shape_check{T}(X::AbstractArray{T,2}, i::Int, j::Int) if length(X) != i*j throw_setindex_mismatch(X, (i,j)) end @@ -305,6 +289,7 @@ function setindex_shape_check{T}(X::AbstractArray{T,2}, i, j) throw_setindex_mismatch(X, (i,j)) end end +setindex_shape_check(X, I::Int...) = nothing # Non-arrays broadcast to all idxs # convert to integer index to_index(i::Int) = i diff --git a/base/range.jl b/base/range.jl index 8f40abf8d5876..d3503d94fd547 100644 --- a/base/range.jl +++ b/base/range.jl @@ -362,6 +362,9 @@ function getindex{T}(r::LinSpace{T}, i::Integer) convert(T, ((r.len-i)*r.start + (i-1)*r.stop)/r.divisor) end +getindex(r::Range, ::Colon) = copy(r) +unsafe_getindex(r::Range, ::Colon) = copy(r) + function check_indexingrange(s, r) sl = length(s) rl = length(r) diff --git a/base/subarray.jl b/base/subarray.jl index d8bb2870d3296..03221e36151c9 100644 --- a/base/subarray.jl +++ b/base/subarray.jl @@ -342,9 +342,8 @@ end length(I.parameters) == LD ? (:(LinearFast())) : (:(LinearSlow())) end -getindex(::Colon, ::Colon) = Colon() -getindex{T}(v::AbstractArray{T,1}, ::Colon) = v -getindex(::Colon, i) = i +getindex(::Colon, i) = to_index(i) +unsafe_getindex(v::Colon, i) = to_index(i) step(::Colon) = 1 first(::Colon) = 1 diff --git a/base/subarray2.jl b/base/subarray2.jl index 7c2183c83ef6d..a8266da72f0f4 100644 --- a/base/subarray2.jl +++ b/base/subarray2.jl @@ -176,4 +176,3 @@ unsafe_getindex(v::Range, ind::Int) = first(v) + (ind-1)*step(v) @inline unsafe_getindex(v::Array, ind::Int) = (@inbounds x = v[ind]; x) unsafe_getindex(v::AbstractArray, ind::Int) = v[ind] unsafe_getindex(v::Colon, ind::Int) = ind -unsafe_getindex(v, ind::Real) = unsafe_getindex(v, to_index(ind)) diff --git a/test/sparsedir/sparse.jl b/test/sparsedir/sparse.jl index 4a23c3df99ba7..aa0fab1cd4ca0 100644 --- a/test/sparsedir/sparse.jl +++ b/test/sparsedir/sparse.jl @@ -742,11 +742,6 @@ a = SparseMatrixCSC(2, 2, [1, 3, 5], [1, 2, 1, 2], [1.0, 0.0, 0.0, 1.0]) @test_approx_eq cholfact(a)\[2.0, 3.0] [2.0, 3.0] end -# issue #10113 -let S = spzeros(5,1), I = [false,true,false,true,false] - @test_throws BoundsError S[I] -end - # issue #9917 @test sparse([]') == reshape(sparse([]), 1, 0) @test full(sparse([])) == zeros(0, 1)