Strided array traits - #60964
Strided array traits#60964nhz2 wants to merge 42 commits into
Conversation
|
I've moved the test reorganization to #61250 |
|
Question: is it a breaking change to replace |
|
JuliaLang/LinearAlgebra.jl#1619 is the companion PR in LinearAlgebra.jl |
| """ | ||
| 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`. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
This violates strict aliasing so is not legal
There was a problem hiding this comment.
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) = falseThe 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) = falseThere was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
julia/base/reinterpretarray.jl
Lines 416 to 420 in c07cf8f
This calls check_readable(a) which is:
julia/base/reinterpretarray.jl
Lines 226 to 231 in c07cf8f
Julia cannot arbitrarily change the padding of isbits because they are required to be C compatible.
There was a problem hiding this comment.
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))
endwhy isn't is_ptr_*able(a) = is_ptr_*able(parent(a)) sufficient?
There was a problem hiding this comment.
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)).
There was a problem hiding this comment.
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.
Co-authored-by: Andy Dienes <51664769+adienes@users.noreply.github.com>
try_strides, can_ptr_load, and can_ptr_storetry_strides, is_ptr_loadable, and is_ptr_storeable
try_strides, is_ptr_loadable, and is_ptr_storeabletry_strides, is_ptr_loadable, and is_ptr_storable
|
JuliaIO/InputBuffers.jl#16 is an example use case |
|
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):
Notes:
|
|
Right now there is no fallback definitions for |
| 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 |
|
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 is an alternative to #60894 and #61807
It adds new trait functions to the strided array interface.
From the NEWS.md additions:
Base.isstrided,Base.islinearstrided, andBase.isdensedescribe thememory layout of an array type: whether
stridesandBase.elsizegive 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 alsoapply to arrays that cannot be accessed through a
Ptr, such as GPU arrays.Base.isdensedefaults totruefor subtypes ofDenseArray, and array types with this trait get defaultstridesandBase.elsizemethods.Base.isunsafeloadableandBase.isunsafestorabledeclare that reading orwriting an element through a
Ptris equivalent togetindexorsetindex!. A strided array type witheither trait must also provide a pointer to its elements through
Base.cconvertandBase.unsafe_convert.Created with assistance of generative AI
closes #54715 #59435 #10889