Skip to content
Merged
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
16 changes: 15 additions & 1 deletion src/array.jl
Original file line number Diff line number Diff line change
Expand Up @@ -419,10 +419,24 @@ end
## interop with GPU arrays

function Base.unsafe_convert(::Type{oneDeviceArray{T,N,AS.CrossWorkgroup}}, a::oneArray{T,N}) where {T,N}
oneDeviceArray{T,N,AS.CrossWorkgroup}(size(a), reinterpret(LLVMPtr{T,AS.CrossWorkgroup}, pointer(a)),
ptr = pointer(a)
check_alignment(a, ptr)
oneDeviceArray{T,N,AS.CrossWorkgroup}(size(a), reinterpret(LLVMPtr{T,AS.CrossWorkgroup}, ptr),
a.maxsize - a.offset)
end

# a `oneArray` can point at memory that is not aligned for its element type, e.g., after
# `reinterpret` of a view. that is fine for copies, but element accesses in kernels assume
# the alignment of the element type (see `alignment`).
function check_alignment(a::oneArray{T}, ptr) where {T}
align = alignment(T)
if UInt(ptr) % align != 0 && !isempty(a)
alignment_error(T, ptr, align)
end
end
@noinline alignment_error(T, ptr, align) =
throw(ArgumentError("cannot use a $T array at $ptr in a kernel, which requires $align-byte alignment. Such arrays result from `reinterpret`ing a view that doesn't start at a multiple of the alignment; use `copy` to get an aligned array."))


## memory copying

Expand Down
1 change: 1 addition & 0 deletions src/compiler/execution.jl
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ Adapt.adapt_storage(to::KernelAdaptor, p::ZePtr{T}) where {T} =
# convert oneAPI host arrays to device arrays
function Adapt.adapt_storage(to::KernelAdaptor, xs::oneArray{T,N}) where {T,N}
to.refs === nothing || push!(to.refs, xs.data)
check_alignment(xs, device_pointer(xs))
ptr = reinterpret(LLVMPtr{T,AS.CrossWorkgroup}, device_pointer(xs))
oneDeviceArray{T,N,AS.CrossWorkgroup}(size(xs), ptr, xs.maxsize - xs.offset)
end
Expand Down
18 changes: 12 additions & 6 deletions src/device/array.jl
Original file line number Diff line number Diff line change
Expand Up @@ -105,18 +105,18 @@ Base.unsafe_convert(::Type{LLVMPtr{T,A}}, x::oneDeviceArray{T,<:Any,A}) where {T

## indexing intrinsics

# TODO: how are allocations aligned by the level zero API? keep track of this
# because it enables optimizations like Load Store Vectorization
# (cfr. shared memory and its wider-than-datatype alignment)

@generated function alignment(::oneDeviceArray{T}) where {T}
# the alignment that accesses to elements of type `T` assume. pointers that do not come from
# an allocation (`reinterpret` of a view) are checked when converting to a device array, see
# `check_alignment`.
@generated function alignment(::Type{T}) where {T}
if Base.isbitsunion(T)
_, sz, al = Base.uniontype_layout(T)
al
else
Base.datatype_alignment(T)
end
end
alignment(::oneDeviceArray{T}) where {T} = alignment(T)

@device_function @inline function arrayref(A::oneDeviceArray{T}, index::Integer) where {T}
# simplified bounds check to avoid the OneTo construction, which calls `max`
Expand Down Expand Up @@ -287,9 +287,15 @@ Base.show(io::IO, mime::MIME"text/plain", a::oneDeviceArray) = show(io, a)
end

function Base.reinterpret(::Type{T}, a::oneDeviceArray{S,N,A}) where {T,S,N,A}
err = _reinterpret_exception(T, a)
err = GPUArrays._reinterpret_exception(T, a)
err === nothing || throw(err)

# `T` may require a stronger alignment than `a` was checked for. this is not a bounds
# check: the alignment that element accesses assume depends on it.
if alignment(T) > alignment(S) && !isempty(a) && UInt(a.ptr) % alignment(T) != 0
@gputhrow "ArgumentError" "cannot reinterpret an array whose pointer is not aligned for the new element type"
end

if sizeof(T) == sizeof(S) # fast case
return oneDeviceArray{T,N,A}(size(a), reinterpret(LLVMPtr{T,A}, a.ptr), a.maxsize)
end
Expand Down
9 changes: 9 additions & 0 deletions test/array.jl
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,15 @@ end
v = view(a, 2:7) # offset of 1 Int32 = 4 bytes
r = reinterpret(Int64, v) # Int64 = 8 bytes; 4 is not a multiple of 8
@test Array(r) == reinterpret(Int64, @view Array(a)[2:7])

# kernels require aligned elements
@test_throws ArgumentError r .+ 1
@test Array(copy(r) .+ 1) == reinterpret(Int64, @view Array(a)[2:7]) .+ 1
@test_throws ArgumentError Base.unsafe_convert(oneDeviceVector{Int64,oneAPI.AS.CrossWorkgroup}, r)
# empty arrays have no elements to access
e = reinterpret(Int64, view(a, 2:1))
@test isempty(oneAPI.kernel_convert(e))
@test isempty(Base.unsafe_convert(oneDeviceVector{Int64,oneAPI.AS.CrossWorkgroup}, e))
end

@testset "aliasing" begin
Expand Down
23 changes: 23 additions & 0 deletions test/execution.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1648,3 +1648,26 @@ end
@test Array(out) == fill(a[1], n)
end
end

@testset "device-side reinterpret to a stronger alignment" begin
function kernel(y, a, offset)
ptr = reinterpret(Core.LLVMPtr{Int32,AS.CrossWorkgroup}, pointer(a)) + offset
x = oneDeviceArray{Int32,1,AS.CrossWorkgroup}((2,), ptr)
b = reinterpret(Int64, x)
@inbounds y[1] = b[1]
return
end
a = oneArray([1, 2])
y = oneAPI.zeros(Int, 1)
@oneapi kernel(y, a, 8)
@test kernel_exception() === nothing
@test Array(y)[] == 2
# the work-item that throws exits without storing
y = oneAPI.zeros(Int, 1)
@oneapi debug_level=1 kernel(y, a, 4)
err = kernel_exception()
@test err isa KernelException
@test err.name == "ArgumentError"
@test occursin("not aligned", err.reason)
@test Array(y)[] == 0
end
Loading