Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 28 additions & 13 deletions base/abstractarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand All @@ -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).

Expand All @@ -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}
Expand Down
4 changes: 3 additions & 1 deletion base/multidimensional.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion base/permuteddimsarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
31 changes: 22 additions & 9 deletions base/reinterpretarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
36 changes: 25 additions & 11 deletions base/reshapedarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -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
Expand Down
37 changes: 27 additions & 10 deletions base/subarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
14 changes: 7 additions & 7 deletions test/abstractarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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) = ()
Expand All @@ -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))
Expand Down
12 changes: 6 additions & 6 deletions test/reinterpretarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
7 changes: 0 additions & 7 deletions test/testhelpers/StridedArrays.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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})

Expand Down
Loading