diff --git a/base/abstractarray.jl b/base/abstractarray.jl index 8a2b5dff32c2f..4861ae3c4ac2c 100644 --- a/base/abstractarray.jl +++ b/base/abstractarray.jl @@ -414,14 +414,11 @@ pointer{T}(x::AbstractArray{T}, i::Integer) = (@_inline_meta; unsafe_convert(Ptr # 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 require the type A{T,N} <: AbstractArray{T,N} to define; either: +# getindex(::A, ::Int) # if linearindexing(A) == LinearFast() OR +# getindex{T,N}(::A{T,N}, ::Vararg{Int, N}) # if LinearSlow() +# If the subtype hasn't defined the required method, it falls back to the +# _getindex function again where an error is thrown to prevent stack overflows. function getindex(A::AbstractArray, I...) @_propagate_inbounds_meta @@ -432,42 +429,46 @@ function unsafe_getindex(A::AbstractArray, I...) @inbounds r = getindex(A, I...) r end -## Internal defitions -# 0-dimensional indexing is defined to prevent ambiguities. LinearFast is easy: -_getindex(::LinearFast, A::AbstractArray) = (@_propagate_inbounds_meta; getindex(A, 1)) -# But LinearSlow must take into account the dimensionality of the array: -_getindex{T}(::LinearSlow, A::AbstractArray{T,0}) = error("indexing not defined for ", typeof(A)) -_getindex(::LinearSlow, A::AbstractVector) = (@_propagate_inbounds_meta; getindex(A, 1)) -_getindex(l::LinearSlow, A::AbstractArray) = (@_propagate_inbounds_meta; _getindex(l, A, 1)) - +## Internal definitions _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 +## LinearFast Scalar indexing: canonical method is one Int +_getindex(::LinearFast, A::AbstractArray, ::Int) = error("indexing not defined for ", typeof(A)) +_getindex(::LinearFast, A::AbstractArray, i::Real) = (@_propagate_inbounds_meta; getindex(A, to_index(i))) +function _getindex{T,N}(::LinearFast, A::AbstractArray{T,N}, I::Vararg{Real,N}) # We must check bounds for sub2ind; so we can then use @inbounds + @_inline_meta + J = to_indexes(I...) + @boundscheck checkbounds(A, J...) + @inbounds r = getindex(A, sub2ind(size(A), J...)) + r +end +function _getindex(::LinearFast, A::AbstractArray, I::Real...) # TODO: DEPRECATE FOR #14770 + @_inline_meta J = to_indexes(I...) @boundscheck checkbounds(A, J...) @inbounds r = getindex(A, sub2ind(size(A), J...)) r end -# LinearSlow Scalar indexing -@generated function _getindex{T,AN}(::LinearSlow, A::AbstractArray{T,AN}, I::Real...) + +## LinearSlow Scalar indexing: Canonical method is full dimensionality of Ints +_getindex{T,N}(::LinearSlow, A::AbstractArray{T,N}, ::Vararg{Int, N}) = error("indexing not defined for ", typeof(A)) +_getindex{T,N}(::LinearSlow, A::AbstractArray{T,N}, I::Vararg{Real, N}) = (@_propagate_inbounds_meta; getindex(A, to_indexes(I...)...)) +function _getindex(::LinearSlow, A::AbstractArray, i::Real) + # ind2sub requires all dimensions to be > 0; may as well just check bounds + @_inline_meta + @boundscheck checkbounds(A, i) + @inbounds r = getindex(A, ind2sub(size(A), to_index(i))...) + r +end +@generated function _getindex{T,AN}(::LinearSlow, A::AbstractArray{T,AN}, I::Real...) # TODO: DEPRECATE FOR #14770 N = length(I) - if N == AN - if all(x->x===Int, I) - :(error("indexing not defined for ", typeof(A))) - else - :(@_propagate_inbounds_meta; getindex(A, to_indexes(I...)...)) - end - elseif N > AN + if N > AN # Drop trailing ones Isplat = Expr[:(I[$d]) for d = 1:AN] Osplat = Expr[:(to_index(I[$d]) == 1) for d = AN+1:N] quote - # We only check the trailing ones, so just propagate @inbounds state @_propagate_inbounds_meta @boundscheck (&)($(Osplat...)) || throw_boundserror(A, I) getindex(A, $(Isplat...)) @@ -475,19 +476,15 @@ end else # Expand the last index into the appropriate number of indices Isplat = Expr[:(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] - szcheck = Expr[:(size(A, $d) > 0) for d=N:AN] + sz.args = Expr[:(size(A, $d)) for d=max(N,1):AN] + szcheck = Expr[:(size(A, $d) > 0) for d=max(N,1):AN] + last_idx = N > 0 ? :(to_index(I[$N])) : 1 quote # ind2sub requires all dimensions to be > 0: @_propagate_inbounds_meta @boundscheck (&)($(szcheck...)) || throw_boundserror(A, I) - s = ind2sub($sz, to_index(I[$N])) - getindex(A, $(Isplat...)) + getindex(A, $(Isplat...), ind2sub($sz, $last_idx)...) end end end @@ -504,18 +501,21 @@ function unsafe_setindex!(A::AbstractArray, v, I...) r end ## Internal defitions -_setindex!(::LinearFast, A::AbstractArray, v) = (@_propagate_inbounds_meta; setindex!(A, v, 1)) -_setindex!{T}(::LinearSlow, A::AbstractArray{T,0}, v) = error("indexing not defined for ", typeof(A)) -_setindex!(::LinearSlow, A::AbstractVector, v) = (@_propagate_inbounds_meta; setindex!(A, v, 1)) -_setindex!(l::LinearSlow, A::AbstractArray, v) = (@_propagate_inbounds_meta; _setindex!(l, A, v, 1)) - _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 +_setindex!(::LinearFast, A::AbstractArray, v, ::Int) = error("indexed assignment not defined for ", typeof(A)) +_setindex!(::LinearFast, A::AbstractArray, v, i::Real) = (@_propagate_inbounds_meta; setindex!(A, v, to_index(i))) +function _setindex!{T,N}(::LinearFast, A::AbstractArray{T,N}, v, I::Vararg{Real,N}) # We must check bounds for sub2ind; so we can then use @inbounds + @_inline_meta + J = to_indexes(I...) + @boundscheck checkbounds(A, J...) + @inbounds r = setindex!(A, v, sub2ind(size(A), J...)) + r +end +function _setindex!(::LinearFast, A::AbstractArray, v, I::Real...) # TODO: DEPRECATE FOR #14770 + @_inline_meta J = to_indexes(I...) @boundscheck checkbounds(A, J...) @inbounds r = setindex!(A, v, sub2ind(size(A), J...)) @@ -523,15 +523,18 @@ function _setindex!(::LinearFast, A::AbstractArray, v, I::Real...) end # LinearSlow Scalar indexing -@generated function _setindex!{T,AN}(::LinearSlow, A::AbstractArray{T,AN}, v, I::Real...) +_setindex!{T,N}(::LinearSlow, A::AbstractArray{T,N}, v, ::Vararg{Int, N}) = error("indexed assignment not defined for ", typeof(A)) +_setindex!{T,N}(::LinearSlow, A::AbstractArray{T,N}, v, I::Vararg{Real, N}) = (@_propagate_inbounds_meta; setindex!(A, v, to_indexes(I...)...)) +function _setindex!(::LinearSlow, A::AbstractArray, v, i::Real) + # ind2sub requires all dimensions to be > 0; may as well just check bounds + @_inline_meta + @boundscheck checkbounds(A, i) + @inbounds r = setindex!(A, v, ind2sub(size(A), to_index(i))...) + r +end +@generated function _setindex!{T,AN}(::LinearSlow, A::AbstractArray{T,AN}, v, I::Real...) # TODO: DEPRECATE FOR #14770 N = length(I) - if N == AN - if all(x->x===Int, I) - :(error("indexing not defined for ", typeof(A))) - else - :(@_propagate_inbounds_meta; setindex!(A, v, to_indexes(I...)...)) - end - elseif N > AN + if N > AN # Drop trailing ones Isplat = Expr[:(I[$d]) for d = 1:AN] Osplat = Expr[:(to_index(I[$d]) == 1) for d = AN+1:N] @@ -544,19 +547,15 @@ end else # Expand the last index into the appropriate number of indices Isplat = Expr[:(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] - szcheck = Expr[:(size(A, $d) > 0) for d=N:AN] + sz.args = Expr[:(size(A, $d)) for d=max(N,1):AN] + szcheck = Expr[:(size(A, $d) > 0) for d=max(N,1):AN] + last_idx = N > 0 ? :(to_index(I[$N])) : 1 quote - @_propagate_inbounds_meta # ind2sub requires all dimensions to be > 0: + @_propagate_inbounds_meta @boundscheck (&)($(szcheck...)) || throw_boundserror(A, I) - s = ind2sub($sz, to_index(I[$N])) - setindex!(A, v, $(Isplat...)) + setindex!(A, v, $(Isplat...), ind2sub($sz, $last_idx)...) end end end diff --git a/base/array.jl b/base/array.jl index ace1bb3205923..be4d8be591548 100644 --- a/base/array.jl +++ b/base/array.jl @@ -308,7 +308,7 @@ done(a::Array,i) = i == length(a)+1 # This is more complicated than it needs to be in order to get Win64 through bootstrap getindex(A::Array, i1::Real) = arrayref(A, to_index(i1)) -getindex(A::Array, i1::Real, i2::Real, I::Real...) = arrayref(A, to_index(i1), to_index(i2), to_indexes(I...)...) +getindex(A::Array, i1::Real, i2::Real, I::Real...) = arrayref(A, to_index(i1), to_index(i2), to_indexes(I...)...) # TODO: REMOVE FOR #14770 # Faster contiguous indexing using copy! for UnitRange and Colon function getindex(A::Array, I::UnitRange{Int}) @@ -337,7 +337,7 @@ end ## Indexing: setindex! ## setindex!{T}(A::Array{T}, x, i1::Real) = arrayset(A, convert(T,x)::T, to_index(i1)) -setindex!{T}(A::Array{T}, x, i1::Real, i2::Real, I::Real...) = arrayset(A, convert(T,x)::T, to_index(i1), to_index(i2), to_indexes(I...)...) +setindex!{T}(A::Array{T}, x, i1::Real, i2::Real, I::Real...) = arrayset(A, convert(T,x)::T, to_index(i1), to_index(i2), to_indexes(I...)...) # TODO: REMOVE FOR #14770 # These are redundant with the abstract fallbacks but needed for bootstrap function setindex!(A::Array, x, I::AbstractVector{Int}) diff --git a/base/multidimensional.jl b/base/multidimensional.jl index f442f6a178ee0..f46de4b01d65b 100644 --- a/base/multidimensional.jl +++ b/base/multidimensional.jl @@ -251,10 +251,25 @@ index_shape_dim(A, dim, ::Colon) = (trailingsize(A, dim),) # 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}...) +@inline function _getindex{T,N}(l::LinearIndexing, A::AbstractArray{T,N}, I::Vararg{Union{Real, AbstractArray, Colon},N}) @boundscheck checkbounds(A, I...) _unsafe_getindex(l, A, I...) end +# Explicitly allow linear indexing with one non-scalar index +@inline function _getindex(l::LinearIndexing, A::AbstractArray, i::Union{Real, AbstractArray, Colon}) + @boundscheck checkbounds(A, i) + _unsafe_getindex(l, _maybe_linearize(l, A), i) +end +# But we can speed up LinearSlow arrays by reshaping them to vectors: +_maybe_linearize(::LinearFast, A::AbstractArray) = A +_maybe_linearize(::LinearSlow, A::AbstractVector) = A +_maybe_linearize(::LinearSlow, A::AbstractArray) = reshape(A, length(A)) + +@inline function _getindex{N}(l::LinearIndexing, A::AbstractArray, I::Vararg{Union{Real, AbstractArray, Colon},N}) # TODO: DEPRECATE FOR #14770 + @boundscheck checkbounds(A, I...) + _unsafe_getindex(l, reshape(A, Val{N}), I...) +end + @generated function _unsafe_getindex(::LinearIndexing, A::AbstractArray, I::Union{Real, AbstractArray, Colon}...) N = length(I) quote @@ -268,8 +283,6 @@ end end # 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}) shape = index_shape(src, I) dest = similar(src, shape) @@ -294,7 +307,7 @@ end $(Expr(:meta, :inline)) D = eachindex(dest) Ds = start(D) - idxlens = index_lengths(src, I...) # TODO: unsplat? + idxlens = index_lengths(src, I...) @nloops $N i d->(1:idxlens[d]) d->(@inbounds j_d = getindex(I[d], i_d)) begin d, Ds = next(D, Ds) @inbounds dest[d] = @ncall $N getindex src j @@ -311,10 +324,21 @@ end # 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}...) +@inline function _setindex!{T,N}(l::LinearIndexing, A::AbstractArray{T,N}, x, J::Vararg{Union{Real,AbstractArray,Colon},N}) @boundscheck checkbounds(A, J...) _unsafe_setindex!(l, A, x, J...) end +@inline function _setindex!(l::LinearIndexing, A::AbstractArray, x, j::Union{Real,AbstractArray,Colon}) + @boundscheck checkbounds(A, j) + _unsafe_setindex!(l, _maybe_linearize(l, A), x, j) + A +end +@inline function _setindex!{N}(l::LinearIndexing, A::AbstractArray, x, J::Vararg{Union{Real, AbstractArray, Colon},N}) # TODO: DEPRECATE FOR #14770 + @boundscheck checkbounds(A, J...) + _unsafe_setindex!(l, reshape(A, Val{N}), x, J...) + A +end + @inline function _unsafe_setindex!(::LinearIndexing, A::AbstractArray, x, J::Union{Real,AbstractArray,Colon}...) _unsafe_batchsetindex!(A, _iterable(x), to_indexes(J...)...) end @@ -434,95 +458,6 @@ for (f, fmod, op) = ((:cummin, :_cummin!, :min), (:cummax, :_cummax!, :max)) @eval ($f)(A::AbstractArray) = ($f)(A, 1) end -## SubArray index merging -# A view created like V = A[2:3:8, 5:2:17] can later be indexed as V[2:7], -# creating a new 1d view. -# In such cases we have to collapse the 2d space spanned by the ranges. -# -# API: -# merge_indexes(V, indexes::NTuple, index) -# indexes encodes the view's trailing indexes into the parent array, -# and index encodes the subset of these elements that we'll select. -# -# It returns a CartesianIndex or array of CartesianIndexes. - -# Checking 'in' a range is fast -- so check all possibilities and keep the good ones -@generated function merge_indexes{N}(V, indexes::NTuple{N}, index::Union{Colon, Range}) - # There may be a vector of cartesian indices in the passed indexes... which - # makes the number of indices more than N. Since we pre-allocate the array - # of CartesianIndexes, we need to figure out how big to make it - M = 0 - for T in indexes.parameters - T <: CartesianIndex ? (M += length(T)) : (M += 1) - end - index_length_expr = index <: Colon ? Symbol("Istride_", N+1) : :(length(index)) - quote - Cartesian.@nexprs $N d->(I_d = indexes[d]) - dimlengths = Cartesian.@ncall $N index_lengths_dim V.parent length(V.indexes)-N+1 I - Istride_1 = 1 # strides of the indexes to merge - Cartesian.@nexprs $N d->(Istride_{d+1} = Istride_d*dimlengths[d]) - idx_len = $(index_length_expr) - if idx_len < 0.1*$(Symbol("Istride_", N+1)) # this has not been carefully tuned - return merge_indexes_div(V, indexes, index, dimlengths) - end - Cartesian.@nexprs $N d->(counter_d = 1) # counter_0 is the linear index - k = 0 - merged = Array(CartesianIndex{$M}, idx_len) - Cartesian.@nloops $N i d->(1:dimlengths[d]) d->(counter_{d-1} = counter_d + (i_d-1)*Istride_d; @inbounds idx_d = I_d[i_d]) begin - if counter_0 in index # this branch is elided for ::Colon - @inbounds merged[k+=1] = Cartesian.@ncall $N CartesianIndex{$M} idx - end - end - merged - end -end - -# mapping getindex across the parent and subindices rapidly gets too big to -# automatically inline, but it is crucial that it does so to avoid allocations -# Unlike SubArray's reindex, merge_indexes doesn't drop any indices. -@inline inlinemap(f, t::Tuple, s::Tuple) = (f(t[1], s[1]), inlinemap(f, tail(t), tail(s))...) -inlinemap(f, t::Tuple{}, s::Tuple{}) = () -inlinemap(f, t::Tuple{}, s::Tuple) = () -inlinemap(f, t::Tuple, s::Tuple{}) = () - -# Otherwise, we fall back to the slow div/rem method, using ind2sub. -@inline merge_indexes{N}(V, indexes::NTuple{N}, index) = - merge_indexes_div(V, indexes, index, index_lengths_dim(V.parent, length(V.indexes)-N+1, indexes...)) - -@inline merge_indexes_div{N}(V, indexes::NTuple{N}, index::Real, dimlengths) = - CartesianIndex(inlinemap(getindex, indexes, ind2sub(dimlengths, index))) -merge_indexes_div{N}(V, indexes::NTuple{N}, index::AbstractArray, dimlengths) = - reshape([CartesianIndex(inlinemap(getindex, indexes, ind2sub(dimlengths, i))) for i in index], size(index)) -merge_indexes_div{N}(V, indexes::NTuple{N}, index::Colon, dimlengths) = - [CartesianIndex(inlinemap(getindex, indexes, ind2sub(dimlengths, i))) for i in 1:prod(dimlengths)] - -# Merging indices is particularly difficult in the case where we partially linearly -# index through a multidimensional array. It's easiest if we can simply reduce the -# partial indices to a single linear index into the parent index array. -function merge_indexes{N}(V, indexes::NTuple{N}, index::Tuple{Colon, Vararg{Colon}}) - shape = index_shape(indexes[1], index...) - reshape(merge_indexes(V, indexes, :), (shape[1:end-1]..., shape[end]*prod(index_lengths_dim(V.parent, length(V.indexes)-length(indexes)+2, tail(indexes)...)))) -end -@inline merge_indexes{N}(V, indexes::NTuple{N}, index::Tuple{Real, Vararg{Real}}) = merge_indexes(V, indexes, sub2ind(size(indexes[1]), index...)) -# In general, it's a little trickier, but we can use the product iterator -# if we replace colons with ranges. This can be optimized further. -function merge_indexes{N}(V, indexes::NTuple{N}, index::Tuple) - I = replace_colons(V, indexes, index) - shp = index_shape(indexes[1], I...) # index_shape does no bounds checking - dimlengths = index_lengths_dim(V.parent, length(V.indexes)-N+1, indexes...) - sz = size(indexes[1]) - reshape([CartesianIndex(inlinemap(getindex, indexes, ind2sub(dimlengths, sub2ind(sz, i...)))) for i in product(I...)], shp) -end -@inline replace_colons(V, indexes, I) = replace_colons_dim(V, indexes, 1, I) -@inline replace_colons_dim(V, indexes, dim, I::Tuple{}) = () -@inline replace_colons_dim(V, indexes, dim, I::Tuple{Colon}) = - (1:trailingsize(indexes[1], dim)*prod(index_lengths_dim(V.parent, length(V.indexes)-length(indexes)+2, tail(indexes)...)),) -@inline replace_colons_dim(V, indexes, dim, I::Tuple{Colon, Vararg{Any}}) = - (1:size(indexes[1], dim), replace_colons_dim(V, indexes, dim+1, tail(I))...) -@inline replace_colons_dim(V, indexes, dim, I::Tuple{Any, Vararg{Any}}) = - (I[1], replace_colons_dim(V, indexes, dim+1, tail(I))...) - - cumsum(A::AbstractArray, axis::Integer=1) = cumsum!(similar(A, Base._cumsum_type(A)), A, axis) cumsum!(B, A::AbstractArray) = cumsum!(B, A, 1) cumprod(A::AbstractArray, axis::Integer=1) = cumprod!(similar(A), A, axis) diff --git a/base/reshapedarray.jl b/base/reshapedarray.jl index d085cede6383b..17553c8fa3236 100644 --- a/base/reshapedarray.jl +++ b/base/reshapedarray.jl @@ -38,6 +38,17 @@ reshape(parent::AbstractArray, dims::Dims) = _reshape(parent, dims) reshape(parent::AbstractArray, len::Integer) = reshape(parent, (Int(len),)) reshape(parent::AbstractArray, dims::Int...) = reshape(parent, dims) +reshape{T,N}(parent::AbstractArray{T,N}, ndims::Type{Val{N}}) = parent +function reshape{T,AN,N}(parent::AbstractArray{T,AN}, ndims::Type{Val{N}}) + reshape(parent, rdims((), size(parent), Val{N})) +end +# Move elements from sz to out until out reaches the desired dimensionality N, +# either filling with 1 or collapsing the product of trailing dims into the last element +@pure rdims{N}(out::NTuple{N}, sz::Tuple{}, ::Type{Val{N}}) = out +@pure rdims{N}(out::NTuple{N}, sz::Tuple{Any, Vararg{Any}}, ::Type{Val{N}}) = (front(out)..., last(out) * prod(sz)) +@pure rdims{N}(out::Tuple, sz::Tuple{}, ::Type{Val{N}}) = rdims((out..., 1), (), Val{N}) +@pure rdims{N}(out::Tuple, sz::Tuple{Any, Vararg{Any}}, ::Type{Val{N}}) = rdims((out..., first(sz)), tail(sz), Val{N}) + function _reshape(parent::AbstractArray, dims::Dims) prod(dims) == length(parent) || throw(DimensionMismatch("parent has $(length(parent)) elements, which is incompatible with size $dims")) __reshape((parent, linearindexing(parent)), dims) diff --git a/base/sharedarray.jl b/base/sharedarray.jl index 3e0e5ad50e941..8131424a7b7e3 100644 --- a/base/sharedarray.jl +++ b/base/sharedarray.jl @@ -19,7 +19,7 @@ type SharedArray{T,N} <: DenseArray{T,N} # the local partition into the array when viewed as a single dimensional array. # this can be removed when @parallel or its equivalent supports looping on # a subset of workers. - loc_subarr_1d::SubArray{T,1,Array{T,N},Tuple{UnitRange{Int}},true} + loc_subarr_1d::SubArray{T,1,Array{T,1},Tuple{UnitRange{Int}},true} SharedArray(d,p,r,sn) = new(d,p,r,sn) end diff --git a/base/subarray.jl b/base/subarray.jl index 95f1f7c990422..831ad4a28d992 100644 --- a/base/subarray.jl +++ b/base/subarray.jl @@ -11,6 +11,10 @@ immutable SubArray{T,N,P,I,L} <: AbstractArray{T,N} dims::NTuple{N,Int} first_index::Int # for linear indexing and pointer, only valid when L==true stride1::Int # used only for linear indexing + function SubArray(parent, indexes, dims, first_index, stride1) + check_parent_index_match(parent, indexes) + new(parent, indexes, dims, first_index, stride1) + end end # Compute the linear indexability of the indices, and combine it with the linear indexing of the parent function SubArray(parent::AbstractArray, indexes::Tuple, dims::Tuple) @@ -24,6 +28,9 @@ function SubArray{P, I, N}(::LinearFast, parent::P, indexes::I, dims::NTuple{N, SubArray{eltype(P), N, P, I, true}(parent, indexes, dims, compute_first_index(parent, indexes), compute_stride1(parent, indexes)) end +check_parent_index_match{T,N}(parent::AbstractArray{T,N}, indexes::NTuple{N}) = nothing +check_parent_index_match(parent, indexes) = throw(ArgumentError("number of indices ($(length(indexes))) must match the parent dimensionality ($(ndims(parent)))")) + # The NoSlice one-element vector type keeps dimensions without losing performance immutable NoSlice <: AbstractVector{Int} i::Int @@ -77,9 +84,23 @@ parentindexes(a::AbstractArray) = ntuple(i->1:size(a,i), ndims(a)) ## SubArray creation # Drops singleton dimensions (those indexed with a scalar) -function slice(A::AbstractArray, I::ViewIndex...) +function slice{T,N}(A::AbstractArray{T,N}, I::Vararg{ViewIndex,N}) @_inline_meta @boundscheck checkbounds(A, I...) + unsafe_slice(A, I...) +end +function slice(A::AbstractArray, i::ViewIndex) + @_inline_meta + @boundscheck checkbounds(A, i) + unsafe_slice(reshape(A, Val{1}), i) +end +function slice{N}(A::AbstractArray, I::Vararg{ViewIndex,N}) # TODO: DEPRECATE FOR #14770 + @_inline_meta + @boundscheck checkbounds(A, I...) + unsafe_slice(reshape(A, Val{N}), I...) +end +function unsafe_slice{T,N}(A::AbstractArray{T,N}, I::Vararg{ViewIndex,N}) + @_inline_meta J = to_indexes(I...) SubArray(A, J, index_shape(A, J...)) end @@ -89,9 +110,23 @@ keep_leading_scalars(T::Tuple{Real, Vararg{Real}}) = T keep_leading_scalars(T::Tuple{Real, Vararg{Any}}) = (@_inline_meta; (NoSlice(T[1]), keep_leading_scalars(tail(T))...)) keep_leading_scalars(T::Tuple{Any, Vararg{Any}}) = (@_inline_meta; (T[1], keep_leading_scalars(tail(T))...)) -function sub(A::AbstractArray, I::ViewIndex...) +function sub{T,N}(A::AbstractArray{T,N}, I::Vararg{ViewIndex,N}) @_inline_meta @boundscheck checkbounds(A, I...) + unsafe_sub(A, I...) +end +function sub(A::AbstractArray, i::ViewIndex) + @_inline_meta + @boundscheck checkbounds(A, i) + unsafe_sub(reshape(A, Val{1}), i) +end +function sub{N}(A::AbstractArray, I::Vararg{ViewIndex,N}) # TODO: DEPRECATE FOR #14770 + @_inline_meta + @boundscheck checkbounds(A, I...) + unsafe_sub(reshape(A, Val{N}), I...) +end +function unsafe_sub{T,N}(A::AbstractArray{T,N}, I::Vararg{ViewIndex,N}) + @_inline_meta J = keep_leading_scalars(to_indexes(I...)) SubArray(A, J, index_shape(A, J...)) end @@ -100,63 +135,30 @@ end # # Recursively look through the heads of the parent- and sub-indexes, considering # the following cases: -# * Parent index is empty -> ignore trailing scalars, but preserve added dimensions -# * Parent index is Any -> re-index that with the sub-index -# * Parent index is Scalar -> that dimension was dropped, so skip the sub-index and use the index as is -# -# Furthermore, we must specially consider the case with one final sub-index, -# as it may be a linear index that spans multiple parent indexes. +# * Parent index is array -> re-index that with one or more sub-indexes (one per dimension) +# * Parent index is Colon -> just use the sub-index as provided +# * Parent index is scalar -> that dimension was dropped, so skip the sub-index and use the index as is +typealias AbstractZeroDimArray{T} AbstractArray{T, 0} typealias DroppedScalar Union{Real, AbstractCartesianIndex} -# When indexing beyond the parent indices, drop all trailing scalars (they must be 1 to be inbounds) -reindex(V, idxs::Tuple{}, subidxs::Tuple{Vararg{DroppedScalar}}) = () -# Drop any intervening scalars that are beyond the parent indices but before a nonscalar -reindex(V, idxs::Tuple{}, subidxs::Tuple{DroppedScalar, Vararg{Any}}) = - (@_propagate_inbounds_meta; (reindex(V, idxs, tail(subidxs))...)) -# And keep the nonscalar index to add the dimension -reindex(V, idxs::Tuple{}, subidxs::Tuple{Any, Vararg{Any}}) = - (@_propagate_inbounds_meta; (subidxs[1], reindex(V, idxs, tail(subidxs))...)) + +reindex(V, ::Tuple{}, ::Tuple{}) = () # Skip dropped scalars, so simply peel them off the parent indices and continue reindex(V, idxs::Tuple{DroppedScalar, Vararg{Any}}, subidxs::Tuple{Vararg{Any}}) = (@_propagate_inbounds_meta; (idxs[1], reindex(V, tail(idxs), subidxs)...)) # Colons simply pass their subindexes straight through -reindex(V, idxs::Tuple{Colon}, subidxs::Tuple{Any}) = - (@_propagate_inbounds_meta; (subidxs[1],)) reindex(V, idxs::Tuple{Colon, Vararg{Any}}, subidxs::Tuple{Any, Vararg{Any}}) = (@_propagate_inbounds_meta; (subidxs[1], reindex(V, tail(idxs), tail(subidxs))...)) -reindex(V, idxs::Tuple{Colon, Vararg{Any}}, subidxs::Tuple{Any}) = - (@_propagate_inbounds_meta; (merge_indexes(V, idxs, subidxs[1]),)) -# As an optimization, we don't need to merge indices if all trailing indices are dropped scalars -reindex(V, idxs::Tuple{Colon, Vararg{DroppedScalar}}, subidxs::Tuple{Any}) = - (@_propagate_inbounds_meta; (subidxs[1], tail(idxs)...)) # Re-index into parent vectors with one subindex -reindex(V, idxs::Tuple{AbstractVector}, subidxs::Tuple{Any}) = - (@_propagate_inbounds_meta; (idxs[1][subidxs[1]],)) reindex(V, idxs::Tuple{AbstractVector, Vararg{Any}}, subidxs::Tuple{Any, Vararg{Any}}) = (@_propagate_inbounds_meta; (idxs[1][subidxs[1]], reindex(V, tail(idxs), tail(subidxs))...)) -reindex(V, idxs::Tuple{AbstractVector, Vararg{Any}}, subidxs::Tuple{Any}) = - (@_propagate_inbounds_meta; (merge_indexes(V, idxs, subidxs[1]),)) -reindex(V, idxs::Tuple{AbstractVector, Vararg{DroppedScalar}}, subidxs::Tuple{Any}) = - (@_propagate_inbounds_meta; (idxs[1][subidxs[1]], tail(idxs)...)) # Parent matrices are re-indexed with two sub-indices -reindex(V, idxs::Tuple{AbstractMatrix}, subidxs::Tuple{Any}) = - (@_propagate_inbounds_meta; (idxs[1][subidxs[1]],)) -reindex(V, idxs::Tuple{AbstractMatrix}, subidxs::Tuple{Any, Any}) = - (@_propagate_inbounds_meta; (idxs[1][subidxs[1], subidxs[2]],)) reindex(V, idxs::Tuple{AbstractMatrix, Vararg{Any}}, subidxs::Tuple{Any, Any, Vararg{Any}}) = (@_propagate_inbounds_meta; (idxs[1][subidxs[1], subidxs[2]], reindex(V, tail(idxs), tail(tail(subidxs)))...)) -reindex(V, idxs::Tuple{AbstractMatrix, Vararg{Any}}, subidxs::Tuple{Any}) = - (@_propagate_inbounds_meta; (merge_indexes(V, idxs, subidxs[1]),)) -reindex(V, idxs::Tuple{AbstractMatrix, Vararg{Any}}, subidxs::Tuple{Any, Any}) = - (@_propagate_inbounds_meta; (merge_indexes(V, idxs, subidxs),)) -reindex(V, idxs::Tuple{AbstractMatrix, Vararg{DroppedScalar}}, subidxs::Tuple{Any}) = - (@_propagate_inbounds_meta; (idxs[1][subidxs[1]], tail(idxs)...)) -reindex(V, idxs::Tuple{AbstractMatrix, Vararg{DroppedScalar}}, subidxs::Tuple{Any, Any}) = - (@_propagate_inbounds_meta; (idxs[1][subidxs[1], subidxs[2]], tail(idxs)...)) # In general, we index N-dimensional parent arrays with N indices @generated function reindex{T,N}(V, idxs::Tuple{AbstractArray{T,N}, Vararg{Any}}, subidxs::Tuple{Vararg{Any}}) @@ -164,26 +166,29 @@ reindex(V, idxs::Tuple{AbstractMatrix, Vararg{DroppedScalar}}, subidxs::Tuple{An subs = [:(subidxs[$d]) for d in 1:N] tail = [:(subidxs[$d]) for d in N+1:length(subidxs.parameters)] :(@_propagate_inbounds_meta; (idxs[1][$(subs...)], reindex(V, tail(idxs), ($(tail...),))...)) - elseif length(idxs.parameters) == 1 - :(@_propagate_inbounds_meta; (idxs[1][subidxs...],)) - elseif all(T->T<:DroppedScalar, idxs.parameters[2:end]) - :(@_propagate_inbounds_meta; (idxs[1][subidxs...], tail(idxs)...)) else - :(@_propagate_inbounds_meta; (merge_indexes(V, idxs, subidxs),)) + :(throw(ArgumentError("cannot re-index $(ndims(V)) dimensional SubArray with fewer than $(ndims(V)) indices\nThis should not occur; please submit a bug report."))) end end # In general, we simply re-index the parent indices by the provided ones -getindex(V::SubArray) = (@_propagate_inbounds_meta; getindex(V, 1)) -function getindex(V::SubArray, I::Real...) +typealias SlowSubArray{T,N,P,I} SubArray{T,N,P,I,false} +function getindex{T,N}(V::SlowSubArray{T,N}, I::Vararg{Real,N}) @_inline_meta @boundscheck checkbounds(V, I...) @inbounds r = V.parent[reindex(V, V.indexes, to_indexes(I...))...] r end +# Explicitly define scalar linear indexing -- this is needed so the nonscalar +# indexing methods don't take precedence here +function getindex(V::SlowSubArray, i::Real) + @_inline_meta + @boundscheck checkbounds(V, i) + @inbounds r = V.parent[reindex(V, V.indexes, ind2sub(size(V), to_index(i)))...] + r +end typealias FastSubArray{T,N,P,I} SubArray{T,N,P,I,true} -getindex(V::FastSubArray) = (@_propagate_inbounds_meta; getindex(V, 1)) function getindex(V::FastSubArray, i::Real) @_inline_meta @boundscheck checkbounds(V, i) @@ -198,40 +203,55 @@ function getindex(V::FastContiguousSubArray, i::Real) @inbounds r = V.parent[V.first_index + to_index(i)-1] r end -# We need this because the ::ViewIndex... method would otherwise obscure the Base fallback -function getindex(V::FastSubArray, I::Real...) +# Just like the slow case, explicitly define scalar indexing at N dims, too +function getindex{T,N}(V::FastSubArray{T,N}, I::Vararg{Real,N}) @_inline_meta @boundscheck checkbounds(V, I...) @inbounds r = getindex(V, sub2ind(size(V), to_indexes(I...)...)) r end -getindex{T,N}(V::SubArray{T,N}, I::ViewIndex...) = (@_propagate_inbounds_meta; copy(slice(V, I...))) -setindex!(V::SubArray, x) = (@_propagate_inbounds_meta; setindex!(V, x, 1)) -function setindex!{T,N}(V::SubArray{T,N}, x, I::Real...) +# Nonscalar indexing just copies a view +getindex{T,N}(V::SubArray{T,N}, i::ViewIndex) = (@_propagate_inbounds_meta; copy(slice(V, i))) +getindex{T,N}(V::SubArray{T,N}, I::Vararg{ViewIndex,N}) = (@_propagate_inbounds_meta; copy(slice(V, I...))) + +# Setindex is similar, but since we don't specially define non-scalar methods +# we only need to define the canonical methods. We don't need to worry about +# e.g., linear indexing for SlowSubArray since the fallbacks can do their thing +function setindex!{T,N}(V::SlowSubArray{T,N}, x, I::Vararg{Real,N}) @_inline_meta @boundscheck checkbounds(V, I...) @inbounds V.parent[reindex(V, V.indexes, to_indexes(I...))...] = x V end +function setindex!(V::FastSubArray, x, i::Real) + @_inline_meta + @boundscheck checkbounds(V, i) + @inbounds V.parent[V.first_index + V.stride1*(to_index(i)-1)] = x + V +end +function setindex!(V::FastContiguousSubArray, x, i::Real) + @_inline_meta + @boundscheck checkbounds(V, i) + @inbounds V.parent[V.first_index + to_index(i)-1] = x + V +end # Nonscalar setindex! falls back to the defaults -function slice{T,N}(V::SubArray{T,N}, I::ViewIndex...) +function unsafe_slice{T,N}(V::SubArray{T,N}, I::Vararg{ViewIndex,N}) @_inline_meta - @boundscheck checkbounds(V, I...) idxs = reindex(V, V.indexes, to_indexes(I...)) SubArray(V.parent, idxs, index_shape(V.parent, idxs...)) end -function sub{T,N}(V::SubArray{T,N}, I::ViewIndex...) +function unsafe_sub{T,N}(V::SubArray{T,N}, I::Vararg{ViewIndex,N}) @_inline_meta - @boundscheck checkbounds(V, I...) idxs = reindex(V, V.indexes, keep_leading_scalars(to_indexes(I...))) SubArray(V.parent, idxs, index_shape(V.parent, idxs...)) end -linearindexing(A::FastSubArray) = LinearFast() -linearindexing(A::SubArray) = LinearSlow() +linearindexing{T<:FastSubArray}(::Type{T}) = LinearFast() +linearindexing{T<:SubArray}(::Type{T}) = LinearSlow() getindex(::Colon, i) = to_index(i) unsafe_getindex(::Colon, i) = to_index(i) diff --git a/base/tuple.jl b/base/tuple.jl index 030e8ead2a04d..f48df7b0d0516 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -34,6 +34,10 @@ indexed_next(t::Tuple, i::Int, state) = (t[i], i+1) indexed_next(a::Array, i::Int, state) = (a[i], i+1) indexed_next(I, i, state) = done(I,state) ? throw(BoundsError()) : next(I, state) +# Use dispatch to avoid a branch in first +first(::Tuple{}) = throw(ArgumentError("tuple must be non-empty")) +first(t::Tuple) = t[1] + # eltype eltype(::Type{Tuple{}}) = Bottom diff --git a/doc/manual/interfaces.rst b/doc/manual/interfaces.rst index 58c9f318fdc89..639db39fbb0ce 100644 --- a/doc/manual/interfaces.rst +++ b/doc/manual/interfaces.rst @@ -153,9 +153,9 @@ Methods to implement :func:`size(A) ` Returns a tuple containing the dimensions of A :func:`Base.linearindexing(Type) ` Returns either ``Base.LinearFast()`` or ``Base.LinearSlow()``. See the description below. :func:`getindex(A, i::Int) ` (if ``LinearFast``) Linear scalar indexing -:func:`getindex(A, i1::Int, ..., iN::Int) ` (if ``LinearSlow``, where ``N = ndims(A)``) N-dimensional scalar indexing +:func:`getindex(A, I::Vararg{Int, N}) ` (if ``LinearSlow``, where ``N = ndims(A)``) N-dimensional scalar indexing :func:`setindex!(A, v, i::Int) ` (if ``LinearFast``) Scalar indexed assignment -:func:`setindex!(A, v, i1::Int, ..., iN::Int) ` (if ``LinearSlow``, where ``N = ndims(A)``) N-dimensional scalar indexed assignment +:func:`setindex!(A, v, I::Vararg{Int, N}) ` (if ``LinearSlow``, where ``N = ndims(A)``) N-dimensional scalar indexed assignment **Optional methods** **Default definition** **Brief description** :func:`getindex(A, I...) ` defined in terms of scalar :func:`getindex` :ref:`Multidimensional and nonscalar indexing ` :func:`setindex!(A, I...) ` defined in terms of scalar :func:`setindex!` :ref:`Multidimensional and nonscalar indexed assignment ` @@ -226,15 +226,11 @@ As a more complicated example, let's define our own toy N-dimensional sparse-lik julia> Base.size(A::SparseArray) = A.dims Base.similar{T}(A::SparseArray, ::Type{T}, dims::Dims) = SparseArray(T, dims) - # Define scalar indexing and indexed assignment for up to 3 dimensions - Base.getindex{T}(A::SparseArray{T,1}, i1::Int) = get(A.data, (i1,), zero(T)) - Base.getindex{T}(A::SparseArray{T,2}, i1::Int, i2::Int) = get(A.data, (i1,i2), zero(T)) - Base.getindex{T}(A::SparseArray{T,3}, i1::Int, i2::Int, i3::Int) = get(A.data, (i1,i2,i3), zero(T)) - Base.setindex!{T}(A::SparseArray{T,1}, v, i1::Int) = (A.data[(i1,)] = v) - Base.setindex!{T}(A::SparseArray{T,2}, v, i1::Int, i2::Int) = (A.data[(i1,i2)] = v) - Base.setindex!{T}(A::SparseArray{T,3}, v, i1::Int, i2::Int, i3::Int) = (A.data[(i1,i2,i3)] = v); - -Notice that this is a ``LinearSlow`` array, so we must manually define :func:`getindex` and :func:`setindex!` for each dimensionality we'd like to support. Unlike the ``SquaresVector``, we are able to define :func:`setindex!`, and so we can mutate the array: + # Define scalar indexing and indexed assignment + Base.getindex{T,N}(A::SparseArray{T,N}, I::Vararg{Int,N}) = get(A.data, I, zero(T)) + Base.setindex!{T,N}(A::SparseArray{T,N}, v, I::Vararg{Int,N}) = (A.data[I] = v) + +Notice that this is a ``LinearSlow`` array, so we must manually define :func:`getindex` and :func:`setindex!` at the dimensionality of the array. Unlike the ``SquaresVector``, we are able to define :func:`setindex!`, and so we can mutate the array: .. doctest:: diff --git a/test/abstractarray.jl b/test/abstractarray.jl index 946bc03e9bcc1..7e120ec30de39 100644 --- a/test/abstractarray.jl +++ b/test/abstractarray.jl @@ -302,9 +302,10 @@ end function test_setindex!_internals(::Type{TestAbstractArray}) U = UnimplementedFastArray{Int, 2}() V = UnimplementedSlowArray{Int, 2}() - @test_throws ErrorException setindex!(U, 1) - @test_throws ErrorException Base.unsafe_setindex!(U, 1) - @test_throws ErrorException Base.unsafe_setindex!(U, 1, 1) + @test_throws ErrorException setindex!(U, 0, 1) + @test_throws ErrorException Base.unsafe_setindex!(U, 0, 1) + @test_throws ErrorException setindex!(V, 0, 1, 1) + @test_throws ErrorException Base.unsafe_setindex!(V, 0, 1, 1) end function test_get(::Type{TestAbstractArray})