diff --git a/base/abstractarray.jl b/base/abstractarray.jl index 4029f5a173f0d..9eaab95c4c60a 100644 --- a/base/abstractarray.jl +++ b/base/abstractarray.jl @@ -560,9 +560,11 @@ function last(v::AbstractVector, n::Integer) end """ - strides(A) + strides(A)::Union{Nothing, Dims} -Return a tuple of the memory strides in each dimension. +Return a tuple of the memory strides in each dimension, for an `AbstractArray` with a +strided memory layout. For arrays with a non-strided layout (such as sparse arrays), return +`nothing`. See also [`stride`](@ref). @@ -573,13 +575,19 @@ julia> A = fill(1, (3,4,5)); julia> strides(A) (1, 3, 12) ``` + +!!! compat "Julia 1.14" + The fallback returning `nothing` for non-strided arrays was added in Julia 1.14. """ -function strides end +function strides(::AbstractArray) + nothing +end """ stride(A, k::Integer) -Return the distance in memory (in number of elements) between adjacent elements in dimension `k`. +Return the distance in memory (in number of elements) between adjacent elements in dimension `k`. For arrays with a non-strided layout (such as sparse arrays), return +`nothing`. See also [`strides`](@ref). @@ -593,21 +601,28 @@ julia> stride(A,2) julia> stride(A,3) 12 ``` + +!!! compat "Julia 1.14" + The fallback returning `nothing` for non-strided arrays was added in Julia 1.14. """ function stride(A::AbstractArray, k::Integer) st = strides(A) - k ≤ ndims(A) && return st[k] - ndims(A) == 0 && return 1 - sz = size(A) - s = st[1] * sz[1] - for i in 2:ndims(A) - s += st[i] * sz[i] + if st === nothing + return nothing + else + k ≤ ndims(A) && return st[k] + ndims(A) == 0 && return 1 + sz = size(A) + s = st[1] * Int(sz[1]) + for i in 2:ndims(A) + s += st[i] * Int(sz[i]) + end + return s end - return s end -@inline size_to_strides(s, d, sz...) = (s, size_to_strides(s * d, sz...)...) -size_to_strides(s, d) = (s,) +@inline size_to_strides(s, d, sz...)::Dims = (Int(s), size_to_strides(Int(s) * Int(d), sz...)...) +size_to_strides(s, d)::Dims = (Int(s),) size_to_strides(s) = () function isstored(A::AbstractArray{<:Any,N}, I::Vararg{Integer,N}) where {N} diff --git a/base/multidimensional.jl b/base/multidimensional.jl index b43425879ae8a..12ccc0ce443fd 100644 --- a/base/multidimensional.jl +++ b/base/multidimensional.jl @@ -1140,7 +1140,9 @@ end # Test if two arrays are backed by exactly the same memory in exactly the same order _parentsmatch(A::AbstractArray, B::AbstractArray) = A === B _parentsmatch(A::DenseArray, B::DenseArray) = elsize(A) == elsize(B) && pointer(A) == pointer(B) && size(A) == size(B) -_parentsmatch(A::StridedArray, B::StridedArray) = elsize(A) == elsize(B) && pointer(A) == pointer(B) && strides(A) == strides(B) +function _parentsmatch(A::StridedArray, B::StridedArray) + elsize(A) == elsize(B) && pointer(A) == pointer(B) && strides(A) == strides(B) != nothing +end # Given two SubArrays with the same parent, check if the indices might overlap (returning true if unsure) _indicesmightoverlap(A::Tuple{}, B::Tuple{}) = true diff --git a/base/permuteddimsarray.jl b/base/permuteddimsarray.jl index 890a32e2dfdd2..4c0ac7d04bb89 100644 --- a/base/permuteddimsarray.jl +++ b/base/permuteddimsarray.jl @@ -62,7 +62,7 @@ Base.cconvert(::Type{Ptr{T}}, A::PermutedDimsArray{T}) where {T} = Base.cconvert Base.pointer(A::PermutedDimsArray, i::Integer) = throw(ArgumentError("pointer(A, i) is deliberately unsupported for PermutedDimsArray")) function Base.strides(A::PermutedDimsArray{T,N,perm}) where {T,N,perm} - s = strides(parent(A)) + s = @something strides(parent(A)) return nothing ntuple(d->s[perm[d]], Val(N)) end Base.elsize(::Type{<:PermutedDimsArray{<:Any, <:Any, <:Any, <:Any, P}}) where {P} = Base.elsize(P) diff --git a/base/reinterpretarray.jl b/base/reinterpretarray.jl index 6ca3f4273f8d1..d9ada5592290f 100644 --- a/base/reinterpretarray.jl +++ b/base/reinterpretarray.jl @@ -199,23 +199,36 @@ stride(A::Union{DenseArray,StridedReshapedArray,StridedReinterpretArray}, k::Int function strides(a::ReinterpretArray{T,<:Any,S,<:AbstractArray{S},IsReshaped}) where {T,S,IsReshaped} _checkcontiguous(Bool, a) && return size_to_strides(1, size(a)...) stp = strides(parent(a)) - els, elp = sizeof(T), sizeof(S) + isnothing(stp) && return nothing + els::Int, elp::Int = sizeof(T), sizeof(S) els == elp && return stp # 0dim parent is also handled here. - IsReshaped && els < elp && return (1, _checked_strides(stp, els, elp)...) - stp[1] == 1 || throw(ArgumentError("Parent must be contiguous in the 1st dimension!")) - st′ = _checked_strides(tail(stp), els, elp) - return IsReshaped ? st′ : (1, st′...) + if IsReshaped && els < elp + x = _try_checked_strides(stp, els, elp) + if isnothing(x) + return nothing + else + return (1, x...) + end + end + stp[1] == 1 || return nothing # Parent must be contiguous in the 1st dimension! + st′ = _try_checked_strides(tail(stp), els, elp) + if isnothing(st′) + return nothing + else + return IsReshaped ? st′ : (1, st′...) + end end -@inline function _checked_strides(stp::Tuple, els::Integer, elp::Integer) +@inline function _try_checked_strides(stp::Tuple, els::Integer, elp::Integer) if elp > els && rem(elp, els) == 0 N = div(elp, els) return map(i -> N * i, stp) end drs = map(i -> divrem(elp * i, els), stp) - all(i->iszero(i[2]), drs) || - throw(ArgumentError("Parent's strides could not be exactly divided!")) - map(first, drs) + if !all(i->iszero(i[2]), drs) + return nothing # Parent's strides could not be exactly divided! + end + return map(first, drs) end _checkcontiguous(::Type{Bool}, A::ReinterpretArray) = _checkcontiguous(Bool, parent(A)) diff --git a/base/reshapedarray.jl b/base/reshapedarray.jl index bc469682dede7..99995c541110e 100644 --- a/base/reshapedarray.jl +++ b/base/reshapedarray.jl @@ -357,8 +357,11 @@ viewindexing(I::Tuple{ReshapedRange, Vararg{ScalarIndex}}) = IndexLinear() compute_stride1(s, inds, I::Tuple{ReshapedRange, Vararg{Any}}) = s*step(I[1].parent) compute_offset1(parent::AbstractVector, stride1::Integer, I::Tuple{ReshapedRange}) = (@inline; first(I[1]) - first(axes1(I[1]))*stride1) -substrides(strds::NTuple{N,Int}, I::Tuple{ReshapedUnitRange, Vararg{Any}}) where N = - (size_to_strides(strds[1], size(I[1])...)..., substrides(tail(strds), tail(I))...) +function try_substrides(strds::Tuple{Int, Vararg{Int}}, I::Tuple{ReshapedUnitRange, Vararg{Any}}) + rest = try_substrides(tail(strds), tail(I)) + isnothing(rest) && return nothing + (size_to_strides(first(strds), size(first(I))...)..., rest...) +end # This exists for backwards compatibility, normally the cconvert method below will be used function unsafe_convert(::Type{Ptr{S}}, V::SubArray{T,N,P,<:Tuple{Vararg{Union{RangeIndex,ReshapedUnitRange}}}}) where {S,T,N,P} @@ -419,26 +422,37 @@ _checkcontiguous(::Type{Bool}, A::FastContiguousSubArray) = _checkcontiguous(Boo function strides(a::ReshapedArray) _checkcontiguous(Bool, a) && return size_to_strides(1, size(a)...) + apst = strides(a.parent) + isnothing(apst) && return nothing apsz::Dims = size(a.parent) - apst::Dims = strides(a.parent) msz, mst, n = merge_adjacent_dim(apsz, apst) # Try to perform "lazy" reshape - n == ndims(a.parent) && return size_to_strides(mst, size(a)...) # Parent is stridevector like - return _reshaped_strides(size(a), 1, msz, mst, n, apsz, apst) + if n == ndims(a.parent) + size_to_strides(mst, size(a)...) # Parent is stridevector like + else + _try_reshaped_strides(size(a), 1, msz, mst, n, apsz, apst) + end end -function _reshaped_strides(::Dims{0}, reshaped::Int, msz::Int, ::Int, ::Int, ::Dims, ::Dims) - reshaped == msz && return () - throw(ArgumentError("Input is not strided.")) +function _try_reshaped_strides(::Dims{0}, reshaped::Int, msz::Int, ::Int, ::Int, ::Dims, ::Dims) + if reshaped == msz + () + else + nothing + end end -function _reshaped_strides(sz::Dims, reshaped::Int, msz::Int, mst::Int, n::Int, apsz::Dims, apst::Dims) +function _try_reshaped_strides(sz::Dims, reshaped::Int, msz::Int, mst::Int, n::Int, apsz::Dims, apst::Dims) st = reshaped * mst reshaped = reshaped * sz[1] if length(sz) > 1 && reshaped == msz && sz[2] != 1 msz, mst, n = merge_adjacent_dim(apsz, apst, n + 1) reshaped = 1 end - sts = _reshaped_strides(tail(sz), reshaped, msz, mst, n, apsz, apst) - return (st, sts...) + sts = _try_reshaped_strides(tail(sz), reshaped, msz, mst, n, apsz, apst) + if isnothing(sts) + nothing + else + (st, sts...) + end end merge_adjacent_dim(::Dims{0}, ::Dims{0}) = 1, 1, 0 diff --git a/base/subarray.jl b/base/subarray.jl index 65e0026bf90e2..bd7e0afdcf9b0 100644 --- a/base/subarray.jl +++ b/base/subarray.jl @@ -426,16 +426,33 @@ IndexStyle(::Type{<:FastSubArray}) = IndexLinear() # Strides are the distance in memory between adjacent elements in a given dimension # which we determine from the strides of the parent -strides(V::SubArray) = substrides(strides(V.parent), V.indices) - -substrides(strds::Tuple{}, ::Tuple{}) = () -substrides(strds::NTuple{N,Int}, I::Tuple{ScalarIndex, Vararg{Any}}) where N = (substrides(tail(strds), tail(I))...,) -substrides(strds::NTuple{N,Int}, I::Tuple{Slice, Vararg{Any}}) where N = (first(strds), substrides(tail(strds), tail(I))...) -substrides(strds::NTuple{N,Int}, I::Tuple{AbstractRange, Vararg{Any}}) where N = (first(strds)*step(I[1]), substrides(tail(strds), tail(I))...) -substrides(strds, I::Tuple{Any, Vararg{Any}}) = throw(ArgumentError( - LazyString("strides is invalid for SubArrays with indices of type ", typeof(I[1])))) - -stride(V::SubArray, d::Integer) = d <= ndims(V) ? strides(V)[d] : strides(V)[end] * size(V)[end] +strides(V::SubArray) = try_substrides(strides(V.parent), V.indices) + +# Recursively try to calculate the strides +try_substrides(::Any, ::Any) = nothing +try_substrides(strds::Tuple{}, ::Tuple{}) = () +function try_substrides(strds::Tuple{Int, Vararg{Int}}, I::Tuple{ScalarIndex, Vararg{Any}}) + try_substrides(tail(strds), tail(I)) +end +function try_substrides(strds::Tuple{Int, Vararg{Int}}, I::Tuple{Slice, Vararg{Any}}) + rest = try_substrides(tail(strds), tail(I)) + isnothing(rest) && return nothing + (first(strds), rest...) +end +function try_substrides(strds::Tuple{Int, Vararg{Int}}, I::Tuple{AbstractRange, Vararg{Any}}) + rest = try_substrides(tail(strds), tail(I)) + isnothing(rest) && return nothing + (first(strds)*Int(step(first(I))), rest...) +end + +function stride(V::SubArray, d::Integer) + st = strides(V) + if st === nothing + nothing + else + d <= ndims(V) ? st[d] : st[end] * Int(size(V)[end]) + end +end compute_stride1(parent::AbstractArray, I::NTuple{N,Any}) where {N} = (@inline; compute_stride1(1, fill_to_length(axes(parent), OneTo(1), Val(N)), I)) diff --git a/test/abstractarray.jl b/test/abstractarray.jl index 8b6ee30f8d418..1db5e153e0799 100644 --- a/test/abstractarray.jl +++ b/test/abstractarray.jl @@ -1945,7 +1945,7 @@ end a = view(rand(10,10), 1:10, 1:10) check_strided_get(vec(a)) b = view(parent(a), 1:9, 1:10) - check_strides_throws("Input is not strided.", vec(b)) + @test isnothing(strides(vec(b))) # StridedVector parent for n in 1:3 a = view(collect(1:60n), 1:n:60n) @@ -1967,10 +1967,10 @@ end check_strided_get(reshape(a,3,3,10)) check_strided_get(reshape(a,1,3,1,3,1,5,1,2)) check_strided_get(reshape(a,3,3,5,1,1,2,1,1)) - check_strides_throws("Input is not strided.", reshape(a,3,6,5)) - check_strides_throws("Input is not strided.", reshape(a,3,2,3,5)) - check_strides_throws("Input is not strided.", reshape(a,3,5,3,2)) - check_strides_throws("Input is not strided.", reshape(a,5,3,3,2)) + @test isnothing(strides(reshape(a,3,6,5))) + @test isnothing(strides(reshape(a,3,2,3,5))) + @test isnothing(strides(reshape(a,3,5,3,2))) + @test isnothing(strides(reshape(a,5,3,3,2))) # Zero dimensional parent struct FakeZeroDimArray <: AbstractArray{Int, 0} end Base.strides(::FakeZeroDimArray) = () @@ -1993,8 +1993,8 @@ end struct Fill44087 <: AbstractArray{Int,0} a::Int end - # `stride` shouldn't work if `strides` is not defined. - @test_throws MethodError stride(Fill44087(1), 1) + # `stride` should return nothing if `strides` is not defined. + @test isnothing(stride(Fill44087(1), 1)) # It is intentionally to only check the return type. (The value is somehow arbitrary) @test stride(fill(1), 1) isa Int check_strided_get(fill(1)) diff --git a/test/reinterpretarray.jl b/test/reinterpretarray.jl index 4ebb212eb2851..1bdbe626ec42e 100644 --- a/test/reinterpretarray.jl +++ b/test/reinterpretarray.jl @@ -251,22 +251,22 @@ end if mod(step(viewax2), 2) == 0 check_strided_get(reinterpret(Int64, view(A, 1:8, viewax2))) else - check_strides_throws("Parent's strides", reinterpret(Int64, view(A, 1:8, viewax2))) + @test isnothing(strides(reinterpret(Int64, view(A, 1:8, viewax2)))) end # non-integer-multiplied classified if mod(step(viewax2), 3) == 0 check_strided_get(reinterpret(NTuple{3,Int16}, view(A, 2:7, viewax2))) else - check_strides_throws("Parent's strides", reinterpret(NTuple{3,Int16}, view(A, 2:7, viewax2))) + @test isnothing(strides(reinterpret(NTuple{3,Int16}, view(A, 2:7, viewax2)))) end if mod(step(viewax2), 5) == 0 check_strided_get(reinterpret(NTuple{5,Int16}, view(A, 2:11, viewax2))) else - check_strides_throws("Parent's strides", reinterpret(NTuple{5,Int16}, view(A, 2:11, viewax2))) + @test isnothing(strides(reinterpret(NTuple{5,Int16}, view(A, 2:11, viewax2)))) end # dim1 is not contiguous for T in (Int16, Int64) - check_strides_throws("Parent must", reinterpret(T, view(A, 8:-1:1, viewax2))) + @test isnothing(strides(reinterpret(T, view(A, 8:-1:1, viewax2)))) end check_strided_get(reinterpret(Float32, view(A, 8:-1:1, viewax2))) end @@ -285,9 +285,9 @@ end if mod(step(viewax1), 2) == 0 check_strided_get(reinterpret(reshape, Int64, view(A, 1:2, viewax1, viewax2))) else - @test_throws "Parent's strides" strides(reinterpret(reshape, Int64, view(A, 1:2, viewax1, viewax2))) + @test isnothing(strides(reinterpret(reshape, Int64, view(A, 1:2, viewax1, viewax2)))) end - @test_throws "Parent must" strides(reinterpret(reshape, Int64, view(A, 1:2:3, viewax1, viewax2))) + @test isnothing(strides(reinterpret(reshape, Int64, view(A, 1:2:3, viewax1, viewax2)))) end end diff --git a/test/testhelpers/StridedArrays.jl b/test/testhelpers/StridedArrays.jl index 0f6ee8329164f..3430b6b5fa1a6 100644 --- a/test/testhelpers/StridedArrays.jl +++ b/test/testhelpers/StridedArrays.jl @@ -7,12 +7,9 @@ module StridedArrays -using Test: @test_throws - export strided_ptr export check_strided_get export check_strided_set -export check_strides_throws export Strider export NonMemStridedArray @@ -24,10 +21,6 @@ function strided_ptr(f, a::AbstractArray{T}) where {T} end end -function check_strides_throws(err, a) - @test_throws err strides(a) -end - """ check_strided_get(a::AbstractArray{T,N})