Skip to content

Strided array traits - #60964

Open
nhz2 wants to merge 42 commits into
JuliaLang:masterfrom
nhz2:nz/try-strided
Open

nhz2 wants to merge 42 commits into
JuliaLang:masterfrom
nhz2:nz/try-strided

Conversation

@nhz2

@nhz2 nhz2 commented Feb 8, 2026 •

Copy link
Copy Markdown
Member

This is an alternative to #60894 and #61807

It adds new trait functions to the strided array interface.

From the NEWS.md additions:

  • New trait functions Base.isstrided, Base.islinearstrided, and Base.isdense describe the
    memory layout of an array type: whether strides and Base.elsize give the location of each element,
    whether the elements are also evenly spaced in column-major order, and whether they are also laid out
    exactly like an Array. They describe only the layout, not how the storage is accessed, so they also
    apply to arrays that cannot be accessed through a Ptr, such as GPU arrays. Base.isdense defaults to
    true for subtypes of DenseArray, and array types with this trait get default strides and
    Base.elsize methods.
  • New trait functions Base.isunsafeloadable and Base.isunsafestorable declare that reading or
    writing an element through a Ptr is equivalent to getindex or setindex!. A strided array type with
    either trait must also provide a pointer to its elements through Base.cconvert and Base.unsafe_convert.

Created with assistance of generative AI

closes #54715 #59435 #10889

@nhz2
nhz2 requested a review from Seelengrab February 8, 2026 21:58
@nhz2 nhz2 added arrays [a, r, r, a, y, s] design Design of APIs or of the language itself feature Indicates new feature / enhancement requests labels Feb 8, 2026
@nhz2
nhz2 marked this pull request as draft March 6, 2026 15:06
@nhz2

nhz2 commented Mar 10, 2026

Copy link
Copy Markdown
Member Author

I've moved the test reorganization to #61250

@nhz2

nhz2 commented Mar 10, 2026

Copy link
Copy Markdown
Member Author

Question: is it a breaking change to replace strides with try_strides? I think that was the decision made in #30432 (comment)

@nhz2 nhz2 added triage This should be discussed on a triage call needs news A NEWS entry is required for this change labels May 18, 2026
@nhz2

nhz2 commented May 18, 2026

Copy link
Copy Markdown
Member Author

JuliaLang/LinearAlgebra.jl#1619 is the companion PR in LinearAlgebra.jl

@nhz2
nhz2 marked this pull request as ready for review May 18, 2026 19:05
@nhz2
nhz2 requested a review from adienes May 18, 2026 19:05
Comment thread base/abstractarray.jl Outdated
Comment thread base/abstractarray.jl Outdated
"""
can_ptr_load(A::AbstractArray)::Bool

Return `true` if a pointer to an `isbits` element in `A` can be used to load that element. Otherwise return `false`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

little confused how this interacts with

a pointer to any element of the array can be obtained

so we might have an array where we can obtain a pointer to an element, but we can neither load nor store from that pointer?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, for example, due to padding alignment it is possible to have a write only reinterpret array. If this were wrapped around a readonly array the result would be a both unwriteable and unreadable array.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This violates strict aliasing so is not legal

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By "wrapped" I don't mean unsafe_wrap I mean using a new wrapper array type, this avoids any TBAA issues.
Here is a more explicit example.

# A read only vector wrapper type
struct ReadOnlyWrapper{T, A<:AbstractVector{T}} <: AbstractVector{T}
    parent::A
end
Base.parent(A::ReadOnlyWrapper) = A.parent
Base.size(A::ReadOnlyWrapper) = size(A.parent)
Base.axes(A::ReadOnlyWrapper) = axes(A.parent)
Base.getindex(A::ReadOnlyWrapper, i::Int) = A.parent[i]
Base.cconvert(::Type{Ptr{T}}, A::ReadOnlyWrapper{T}) where {T} = Base.cconvert(Ptr{T}, A.parent)
Base.strides(A::ReadOnlyWrapper) = strides(A.parent)
Base.elsize(::Type{ReadOnlyWrapper{T,A}}) where {T,A} = Base.elsize(A)
Base.try_strides(A::ReadOnlyWrapper) = try_strides(A.parent)
Base.is_ptr_loadable(A::ReadOnlyWrapper) = is_ptr_loadable(A.parent)
# An array with padding
a = [(0x01, 0x0001)]
@show is_ptr_loadable(a)
@show is_ptr_storable(a)
b = reinterpret(UInt8, a);
@show is_ptr_loadable(b)
@show is_ptr_storable(b)
c = ReadOnlyWrapper(b)
@show is_ptr_loadable(c)
@show is_ptr_storable(c)

This results in:

is_ptr_loadable(a) = true
is_ptr_storable(a) = true
is_ptr_loadable(b) = false
is_ptr_storable(b) = true
is_ptr_loadable(c) = false
is_ptr_storable(c) = false

The ordering of the wrapping can be changed:

# An array with padding
a = [(0x01, 0x0001)]
@show is_ptr_loadable(a)
@show is_ptr_storable(a)
b = ReadOnlyWrapper(a)
@show is_ptr_loadable(b)
@show is_ptr_storable(b)
c = reinterpret(UInt8, b);
@show is_ptr_loadable(c)
@show is_ptr_storable(c)

Results in:

is_ptr_loadable(a) = true
is_ptr_storable(a) = true
is_ptr_loadable(b) = true
is_ptr_storable(b) = false
is_ptr_loadable(c) = false
is_ptr_storable(c) = false

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe the example was just intended for the flavor and not as a full motivator, but even here I think some cracks are showing. namely that the is_ptr_*able definitions here are making assumptions about the padding / layout of Tuple{UInt8, UInt16}. but if Julia were to, say, choose to start representing that type without padding then the traits would become wrong.

there's a reason that array_subpadding in reinterpretarray.jl is doing a bunch of nontrivial work, because to query this correctly I think we have to be careful about reading the runtime values of datatype_alignment and sizeof

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The is_ptr_*able methods match the checks in getindex and setindex!, and this is tested. Please compare is_ptr_*able methods to getindex and setindex! methods for ReinterpretArray.

@propagate_inbounds function getindex(a::ReinterpretArray{T,N,S}, inds::Vararg{Int, N}) where {T,N,S}
check_readable(a)
check_ptr_indexable(a) && return _getindex_ptr(a, inds...)
_getindex_ra(a, inds[1], tail(inds))
end

This calls check_readable(a) which is:

function check_readable(a::ReinterpretArray{T, N, S} where N) where {T,S}
# See comment in check_writable
if !a.readable && !array_subpadding(T, S)
throw(PaddingError(T, S))
end
end

Julia cannot arbitrarily change the padding of isbits because they are required to be C compatible.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do they match the checks though? because check_readable and check_writable call array_subpadding which is doing a bunch of work to read the type layout. or put another way:

function is_ptr_loadable(a::ReinterpretArray{T,N,S} where N) where {T,S}
    is_ptr_loadable(parent(a)) && (a.readable || array_subpadding(T, S))
end

function is_ptr_storable(a::ReinterpretArray{T,N,S} where N) where {T,S}
    is_ptr_storable(parent(a)) && (a.writable || array_subpadding(S, T))
end

why isn't is_ptr_*able(a) = is_ptr_*able(parent(a)) sufficient?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only checking the parent isn't enough to ensure there are no padding issues caused by the reinterpret. For that reason (a.readable || array_subpadding(T, S)) is also checked.

The tests in test/reinterpretarray.jl lines 392 and 395 fail with is_ptr_*able(a) = is_ptr_*able(parent(a)).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The practical point is that I want zip_crc32(reinterpret_array_with_strange_padding_issues) to throw some error instead of silently returning something undefined.

Comment thread base/reinterpretarray.jl Outdated
Comment thread base/reinterpretarray.jl Outdated
Comment thread base/reshapedarray.jl Outdated
Comment thread base/exports.jl Outdated
Comment thread base/reshapedarray.jl Outdated
Comment thread base/reshapedarray.jl Outdated
Comment thread base/reinterpretarray.jl Outdated
Comment thread base/abstractarray.jl Outdated
nhz2 and others added 3 commits May 18, 2026 20:33
@nhz2 nhz2 changed the title New functions try_strides, can_ptr_load, and can_ptr_store New functions try_strides, is_ptr_loadable, and is_ptr_storeable May 19, 2026
@nhz2 nhz2 changed the title New functions try_strides, is_ptr_loadable, and is_ptr_storeable New functions try_strides, is_ptr_loadable, and is_ptr_storable May 19, 2026
@nhz2

nhz2 commented May 19, 2026

Copy link
Copy Markdown
Member Author

JuliaIO/InputBuffers.jl#16 is an example use case

@nhz2
nhz2 marked this pull request as ready for review September 24, 2026 16:24
@nhz2 nhz2 added the triage This should be discussed on a triage call label Sep 24, 2026
@StefanKarpinski

Copy link
Copy Markdown
Member

Triage generally likes the API but thinks the names need work and the meanings may need some clarification/crystallization. Some possible name suggestions (from triage discussion):

  • isstrided
  • isstridedlinear or islinearstrided or maybe isstrided( ; linear=true)?
  • iscontiguous could be isdense?

Notes:

  • the is_ptr_{loadable,storable} functions are about readability/writability
  • related to "is memcpyable" — maybe a name that suggests that would be better

@nhz2

nhz2 commented Sep 24, 2026

Copy link
Copy Markdown
Member Author

Right now there is no fallback definitions for DenseArray subtypes. This means that if internal methods move from StridedArray to the trait system, external DenseArray subtypes will no longer use the fast methods. The IO changes here are an example of that. Some current DenseArray subtypes need special cases like AtomicMemory and CodeUnits

@nhz2
nhz2 marked this pull request as draft September 24, 2026 20:11
@nhz2 nhz2 removed needs news A NEWS entry is required for this change triage This should be discussed on a triage call labels Sep 25, 2026
Comment thread base/strings/basic.jl
cconvert(::Type{Ptr{Int8}}, s::CodeUnits{UInt8}) = cconvert(Ptr{Int8}, s.s)

# CodeUnits is currently <: DenseVector but is not in general `isdense`.
isdense(::Type{<:CodeUnits}) = false

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is one way to work around #53996

Comment thread base/abstractarray.jl
@nhz2
nhz2 marked this pull request as ready for review September 25, 2026 18:13
@vtjnash

vtjnash commented Sep 25, 2026

Copy link
Copy Markdown
Member

Seems a good direction. We should eventually update io.jl to use this query as well, since right now the PR has redirected it to still use the older less accurate query.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arrays [a, r, r, a, y, s] design Design of APIs or of the language itself feature Indicates new feature / enhancement requests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make an isdense trait

5 participants