From 628c4465b96070ea6d78040a562e188020d6ed8d Mon Sep 17 00:00:00 2001 From: Valentin Churavy Date: Tue, 8 Sep 2026 10:06:45 +0200 Subject: [PATCH] Complete the GPU extensions: atomic get/set, swap, float min/max The CUDA, oneAPI, OpenCL and Metal extensions implemented `Atomix.get` and `Atomix.set!` as `error("not implemented")`, and `modify!` errored for any operation without a listed backend intrinsic. As a result `@atomic A[i]`, `@atomic A[i] = v`, `@atomicswap`, and float `max`/`min` threw inside kernels (silently, on backends that do not report device exceptions). - `get`/`set!` now use the generic implementation: an LLVM atomic load/store through UnsafeAtomicsLLVM, honoring the ordering. Metal uses its `atomic_load_explicit`/`atomic_store_explicit` intrinsics instead. - Swap dispatches to the backend's exchange intrinsic; floats on CUDA go through their integer representation. - Operations without a native instruction (float min/max where the backend lacks them, arbitrary functions) use a compare-and-swap loop that compares bitwise, so NaN and signed zeros cannot make it spin. - Native dispatch is by element type, so unsupported types fall back instead of hitting a MethodError. Adds get/set, swap, ordering and floating-point tests for each backend. Assisted-by: Claude Code (Fable 5.1) --- ext/AtomixCUDAExt.jl | 84 ++++++++++++++++++++++++-------------- ext/AtomixMetalExt.jl | 72 ++++++++++++++++---------------- ext/AtomixOpenCLExt.jl | 78 +++++++++++++++++++++-------------- ext/AtomixoneAPIExt.jl | 77 ++++++++++++++++++++-------------- test/test_atomix_cuda.jl | 71 +++++++++++++++++++++++++------- test/test_atomix_metal.jl | 71 +++++++++++++++++++++++++------- test/test_atomix_oneapi.jl | 71 +++++++++++++++++++++++++------- test/test_atomix_opencl.jl | 71 +++++++++++++++++++++++++------- 8 files changed, 412 insertions(+), 183 deletions(-) diff --git a/ext/AtomixCUDAExt.jl b/ext/AtomixCUDAExt.jl index 5a79d62..1dee9ab 100644 --- a/ext/AtomixCUDAExt.jl +++ b/ext/AtomixCUDAExt.jl @@ -1,18 +1,20 @@ -# TODO: respect ordering module AtomixCUDAExt -using Atomix: Atomix, IndexableRef +using Atomix: Atomix, IndexableRef, right using CUDA: CUDA, CuDeviceArray +using Core: LLVMPtr const CuIndexableRef{Indexable<:CuDeviceArray} = IndexableRef{Indexable} -function Atomix.get(ref::CuIndexableRef, order) - error("not implemented") -end +# `Atomix.get` and `Atomix.set!` use Atomix's generic implementation: an LLVM atomic +# load/store on the device pointer (via UnsafeAtomicsLLVM), which honors the ordering. +# +# `Atomix.replace!` and `Atomix.modify!` go through CUDA.jl's intrinsics. These have no +# ordering parameter, so the requested ordering is ignored: every operation is performed +# with CUDA's default (relaxed, device-scope) semantics. -function Atomix.set!(ref::CuIndexableRef, v, order) - error("not implemented") -end +const NativeInt = Union{Int32,Int64,UInt32,UInt64} +const NativeFloat = Union{Float32,Float64} @inline function Atomix.replace!( ref::CuIndexableRef, @@ -24,35 +26,57 @@ end ptr = Atomix.pointer(ref) expected = convert(eltype(ref), expected) desired = convert(eltype(ref), desired) - begin - old = CUDA.atomic_cas!(ptr, expected, desired) - end + old = CUDA.atomic_cas!(ptr, expected, desired) return (; old = old, success = old === expected) end @inline function Atomix.modify!(ref::CuIndexableRef, op::OP, x, order) where {OP} x = convert(eltype(ref), x) ptr = Atomix.pointer(ref) - begin - old = if op === (+) - CUDA.atomic_add!(ptr, x) - elseif op === (-) - CUDA.atomic_sub!(ptr, x) - elseif op === (&) - CUDA.atomic_and!(ptr, x) - elseif op === (|) - CUDA.atomic_or!(ptr, x) - elseif op === xor - CUDA.atomic_xor!(ptr, x) - elseif op === min - CUDA.atomic_min!(ptr, x) - elseif op === max - CUDA.atomic_max!(ptr, x) - else - error("not implemented") - end - end + old = modify_native!(ptr, op, x) return old => op(old, x) end +# operations with a native atomic instruction +for (op, fn) in [(+) => :atomic_add!, (-) => :atomic_sub!, (&) => :atomic_and!, + (|) => :atomic_or!, xor => :atomic_xor!, min => :atomic_min!, + max => :atomic_max!] + @eval @inline modify_native!(ptr::LLVMPtr{<:NativeInt}, ::typeof($op), x) = + CUDA.$fn(ptr, x) +end +@inline modify_native!(ptr::LLVMPtr{Float32}, ::typeof(+), x) = CUDA.atomic_add!(ptr, x) +@inline modify_native!(ptr::LLVMPtr{Float32}, ::typeof(-), x) = CUDA.atomic_sub!(ptr, x) +# Float64 atomic add needs compute capability 6.0; use compare-and-swap below that. +@inline function modify_native!(ptr::LLVMPtr{Float64}, ::typeof(+), x) + if CUDA.compute_capability() >= v"6.0" + CUDA.atomic_add!(ptr, x) + else + modify_cas!(ptr, +, x) + end +end +@inline modify_native!(ptr::LLVMPtr{Float64}, ::typeof(-), x) = modify_native!(ptr, +, -x) + +# swap: exchange floats through their integer representation +@inline modify_native!(ptr::LLVMPtr{<:NativeInt}, ::typeof(right), x) = + CUDA.atomic_xchg!(ptr, x) +@inline function modify_native!(ptr::LLVMPtr{T,A}, ::typeof(right), x) where {T<:NativeFloat,A} + I = CUDA.inttype(T) + old = CUDA.atomic_xchg!(reinterpret(LLVMPtr{I,A}, ptr), reinterpret(I, x)) + return reinterpret(T, old) +end + +# everything else (float min/max, arbitrary functions): compare-and-swap loop +@inline modify_native!(ptr::LLVMPtr, op, x) = modify_cas!(ptr, op, x) + +@inline function modify_cas!(ptr::LLVMPtr{T}, op, x) where {T} + old = Base.unsafe_load(ptr) + while true + new = convert(T, op(old, x)) + seen = CUDA.atomic_cas!(ptr, old, new) + # bitwise comparison: `==` would spin forever on NaN + seen === old && return old + old = seen + end +end + end # module AtomixCUDAExt diff --git a/ext/AtomixMetalExt.jl b/ext/AtomixMetalExt.jl index af98afb..18f221b 100644 --- a/ext/AtomixMetalExt.jl +++ b/ext/AtomixMetalExt.jl @@ -1,17 +1,26 @@ -# TODO: respect ordering module AtomixMetalExt -using Atomix: Atomix, IndexableRef +using Atomix: Atomix, IndexableRef, right using Metal: Metal, MtlDeviceArray +using Core: LLVMPtr const MtlIndexableRef{Indexable<:MtlDeviceArray} = IndexableRef{Indexable} -function Atomix.get(ref::MtlIndexableRef, order) - error("not implemented") +# Everything goes through Metal.jl's `atomic_*_explicit` intrinsics, using their default +# (relaxed) memory order: the ordering Atomix is asked for is ignored. + +const NativeInt = Union{Int32,UInt32} + +@inline function Atomix.get(ref::MtlIndexableRef, order) + ptr = Atomix.pointer(ref) + return Metal.atomic_load_explicit(ptr) end -function Atomix.set!(ref::MtlIndexableRef, v, order) - error("not implemented") +@inline function Atomix.set!(ref::MtlIndexableRef, v, order) + v = convert(eltype(ref), v) + ptr = Atomix.pointer(ref) + Metal.atomic_store_explicit(ptr, v) + return end @inline function Atomix.replace!( @@ -24,44 +33,35 @@ end ptr = Atomix.pointer(ref) expected = convert(eltype(ref), expected) desired = convert(eltype(ref), desired) - begin - old = Metal.atomic_compare_exchange_weak_explicit(ptr, expected, desired) - end + old = Metal.atomic_compare_exchange_weak_explicit(ptr, expected, desired) return (; old = old, success = old === expected) end - -# CAS is needed for FP ops on ThreadGroup memory -@inline function Atomix.modify!(ref::IndexableRef{<:MtlDeviceArray{<:AbstractFloat, <:Any, Metal.AS.ThreadGroup}} , op::OP, x, order) where {OP} +@inline function Atomix.modify!(ref::MtlIndexableRef, op::OP, x, order) where {OP} x = convert(eltype(ref), x) ptr = Atomix.pointer(ref) - old = Metal.atomic_fetch_op_explicit(ptr, op, x) + old = modify_native!(ptr, op, x) return old => op(old, x) end -@inline function Atomix.modify!(ref::MtlIndexableRef, op::OP, x, order) where {OP} - x = convert(eltype(ref), x) - ptr = Atomix.pointer(ref) - begin - old = if op === (+) - Metal.atomic_fetch_add_explicit(ptr, x) - elseif op === (-) - Metal.atomic_fetch_sub_explicit(ptr, x) - elseif op === (&) - Metal.atomic_fetch_and_explicit(ptr, x) - elseif op === (|) - Metal.atomic_fetch_or_explicit(ptr, x) - elseif op === xor - Metal.atomic_fetch_xor_explicit(ptr, x) - elseif op === min - Metal.atomic_fetch_min_explicit(ptr, x) - elseif op === max - Metal.atomic_fetch_max_explicit(ptr, x) - else - error("not implemented") - end - end - return old => op(old, x) +# operations with a native atomic instruction +for (op, fn) in [(+) => :atomic_fetch_add_explicit, (-) => :atomic_fetch_sub_explicit, + (&) => :atomic_fetch_and_explicit, (|) => :atomic_fetch_or_explicit, + xor => :atomic_fetch_xor_explicit, min => :atomic_fetch_min_explicit, + max => :atomic_fetch_max_explicit] + @eval @inline modify_native!(ptr::LLVMPtr{<:NativeInt}, ::typeof($op), x) = + Metal.$fn(ptr, x) end +# Float32 add/sub is native on device memory only; threadgroup memory takes the +# compare-and-swap path below. +@inline modify_native!(ptr::LLVMPtr{Float32,Metal.AS.Device}, ::typeof(+), x) = + Metal.atomic_fetch_add_explicit(ptr, x) +@inline modify_native!(ptr::LLVMPtr{Float32,Metal.AS.Device}, ::typeof(-), x) = + Metal.atomic_fetch_sub_explicit(ptr, x) +@inline modify_native!(ptr::LLVMPtr{<:Union{NativeInt,Float32}}, ::typeof(right), x) = + Metal.atomic_exchange_explicit(ptr, x) + +# everything else (float min/max, arbitrary functions): compare-and-swap loop +@inline modify_native!(ptr::LLVMPtr, op, x) = Metal.atomic_fetch_op_explicit(ptr, op, x) end # module AtomixMetalExt diff --git a/ext/AtomixOpenCLExt.jl b/ext/AtomixOpenCLExt.jl index a536ca8..56eda30 100644 --- a/ext/AtomixOpenCLExt.jl +++ b/ext/AtomixOpenCLExt.jl @@ -1,18 +1,20 @@ -# TODO: respect ordering module AtomixOpenCLExt -using Atomix: Atomix, IndexableRef +using Atomix: Atomix, IndexableRef, right using OpenCL: SPIRVIntrinsics, CLDeviceArray +using Core: LLVMPtr const CLIndexableRef{Indexable<:CLDeviceArray} = IndexableRef{Indexable} -function Atomix.get(ref::CLIndexableRef, order) - error("not implemented") -end +# `Atomix.get` and `Atomix.set!` use Atomix's generic implementation: an LLVM atomic +# load/store on the device pointer (via UnsafeAtomicsLLVM), which honors the ordering. +# +# `Atomix.replace!` and `Atomix.modify!` go through the SPIR-V intrinsics. These have no +# ordering parameter, so the requested ordering is ignored: every operation is performed +# with the intrinsics' default (relaxed, workgroup-scope) semantics. -function Atomix.set!(ref::CLIndexableRef, v, order) - error("not implemented") -end +const NativeInt = Union{Int32,Int64,UInt32,UInt64} +const NativeFloat = Union{Float32,Float64} @inline function Atomix.replace!( ref::CLIndexableRef, @@ -24,36 +26,50 @@ end ptr = Atomix.pointer(ref) expected = convert(eltype(ref), expected) desired = convert(eltype(ref), desired) - begin - old = SPIRVIntrinsics.atomic_cmpxchg!(ptr, expected, desired) - end + old = SPIRVIntrinsics.atomic_cmpxchg!(ptr, expected, desired) return (; old = old, success = old === expected) end @inline function Atomix.modify!(ref::CLIndexableRef, op::OP, x, order) where {OP} x = convert(eltype(ref), x) ptr = Atomix.pointer(ref) - begin - old = if op === (+) - SPIRVIntrinsics.atomic_add!(ptr, x) - elseif op === (-) - SPIRVIntrinsics.atomic_sub!(ptr, x) - elseif op === (&) - SPIRVIntrinsics.atomic_and!(ptr, x) - elseif op === (|) - SPIRVIntrinsics.atomic_or!(ptr, x) - elseif op === xor - SPIRVIntrinsics.atomic_xor!(ptr, x) - elseif op === min - SPIRVIntrinsics.atomic_min!(ptr, x) - elseif op === max - SPIRVIntrinsics.atomic_max!(ptr, x) - else - error("not implemented") - end - end + old = modify_native!(ptr, op, x) return old => op(old, x) end -end # module AtomixOpenCLExt +# operations with a native atomic instruction +for (op, fn) in [(+) => :atomic_add!, (-) => :atomic_sub!, (&) => :atomic_and!, + (|) => :atomic_or!, xor => :atomic_xor!, min => :atomic_min!, + max => :atomic_max!] + @eval @inline modify_native!(ptr::LLVMPtr{<:NativeInt}, ::typeof($op), x) = + SPIRVIntrinsics.$fn(ptr, x) +end +@inline modify_native!(ptr::LLVMPtr{<:NativeFloat}, ::typeof(+), x) = + SPIRVIntrinsics.atomic_add!(ptr, x) +@inline modify_native!(ptr::LLVMPtr{<:NativeFloat}, ::typeof(-), x) = + SPIRVIntrinsics.atomic_sub!(ptr, x) +# floating-point min/max: native or compare-and-swap, depending on SPIRVIntrinsics +if hasmethod(SPIRVIntrinsics.atomic_min!, Tuple{LLVMPtr{Float32,1},Float32}) + @inline modify_native!(ptr::LLVMPtr{<:NativeFloat}, ::typeof(min), x) = + SPIRVIntrinsics.atomic_min!(ptr, x) + @inline modify_native!(ptr::LLVMPtr{<:NativeFloat}, ::typeof(max), x) = + SPIRVIntrinsics.atomic_max!(ptr, x) +end +@inline modify_native!(ptr::LLVMPtr{<:Union{NativeInt,NativeFloat}}, ::typeof(right), x) = + SPIRVIntrinsics.atomic_xchg!(ptr, x) + +# everything else: compare-and-swap loop +@inline modify_native!(ptr::LLVMPtr, op, x) = modify_cas!(ptr, op, x) + +@inline function modify_cas!(ptr::LLVMPtr{T}, op, x) where {T} + old = Base.unsafe_load(ptr) + while true + new = convert(T, op(old, x)) + seen = SPIRVIntrinsics.atomic_cmpxchg!(ptr, old, new) + # bitwise comparison: `==` would spin forever on NaN + seen === old && return old + old = seen + end +end +end # module AtomixOpenCLExt diff --git a/ext/AtomixoneAPIExt.jl b/ext/AtomixoneAPIExt.jl index 1866a69..efef83d 100644 --- a/ext/AtomixoneAPIExt.jl +++ b/ext/AtomixoneAPIExt.jl @@ -1,18 +1,20 @@ -# TODO: respect ordering module AtomixoneAPIExt -using Atomix: Atomix, IndexableRef +using Atomix: Atomix, IndexableRef, right using oneAPI: oneAPI, oneDeviceArray +using Core: LLVMPtr const oneIndexableRef{Indexable<:oneDeviceArray} = IndexableRef{Indexable} -function Atomix.get(ref::oneIndexableRef, order) - error("not implemented") -end +# `Atomix.get` and `Atomix.set!` use Atomix's generic implementation: an LLVM atomic +# load/store on the device pointer (via UnsafeAtomicsLLVM), which honors the ordering. +# +# `Atomix.replace!` and `Atomix.modify!` go through oneAPI.jl's SPIR-V intrinsics. These +# have no ordering parameter, so the requested ordering is ignored: every operation is +# performed with the intrinsics' default (relaxed, workgroup-scope) semantics. -function Atomix.set!(ref::oneIndexableRef, v, order) - error("not implemented") -end +const NativeInt = Union{Int32,Int64,UInt32,UInt64} +const NativeFloat = Union{Float32,Float64} @inline function Atomix.replace!( ref::oneIndexableRef, @@ -24,35 +26,50 @@ end ptr = Atomix.pointer(ref) expected = convert(eltype(ref), expected) desired = convert(eltype(ref), desired) - begin - old = oneAPI.atomic_cmpxchg!(ptr, expected, desired) - end + old = oneAPI.atomic_cmpxchg!(ptr, expected, desired) return (; old = old, success = old === expected) end @inline function Atomix.modify!(ref::oneIndexableRef, op::OP, x, order) where {OP} x = convert(eltype(ref), x) ptr = Atomix.pointer(ref) - begin - old = if op === (+) - oneAPI.atomic_add!(ptr, x) - elseif op === (-) - oneAPI.atomic_sub!(ptr, x) - elseif op === (&) - oneAPI.atomic_and!(ptr, x) - elseif op === (|) - oneAPI.atomic_or!(ptr, x) - elseif op === xor - oneAPI.atomic_xor!(ptr, x) - elseif op === min - oneAPI.atomic_min!(ptr, x) - elseif op === max - oneAPI.atomic_max!(ptr, x) - else - error("not implemented") - end - end + old = modify_native!(ptr, op, x) return old => op(old, x) end +# operations with a native atomic instruction +for (op, fn) in [(+) => :atomic_add!, (-) => :atomic_sub!, (&) => :atomic_and!, + (|) => :atomic_or!, xor => :atomic_xor!, min => :atomic_min!, + max => :atomic_max!] + @eval @inline modify_native!(ptr::LLVMPtr{<:NativeInt}, ::typeof($op), x) = + oneAPI.$fn(ptr, x) +end +@inline modify_native!(ptr::LLVMPtr{<:NativeFloat}, ::typeof(+), x) = + oneAPI.atomic_add!(ptr, x) +@inline modify_native!(ptr::LLVMPtr{<:NativeFloat}, ::typeof(-), x) = + oneAPI.atomic_sub!(ptr, x) +# floating-point min/max: native or compare-and-swap, depending on the oneAPI.jl version +if hasmethod(oneAPI.atomic_min!, Tuple{LLVMPtr{Float32,1},Float32}) + @inline modify_native!(ptr::LLVMPtr{<:NativeFloat}, ::typeof(min), x) = + oneAPI.atomic_min!(ptr, x) + @inline modify_native!(ptr::LLVMPtr{<:NativeFloat}, ::typeof(max), x) = + oneAPI.atomic_max!(ptr, x) +end +@inline modify_native!(ptr::LLVMPtr{<:Union{NativeInt,NativeFloat}}, ::typeof(right), x) = + oneAPI.atomic_xchg!(ptr, x) + +# everything else: compare-and-swap loop +@inline modify_native!(ptr::LLVMPtr, op, x) = modify_cas!(ptr, op, x) + +@inline function modify_cas!(ptr::LLVMPtr{T}, op, x) where {T} + old = Base.unsafe_load(ptr) + while true + new = convert(T, op(old, x)) + seen = oneAPI.atomic_cmpxchg!(ptr, old, new) + # bitwise comparison: `==` would spin forever on NaN + seen === old && return old + old = seen + end +end + end # module AtomixoneAPIExt diff --git a/test/test_atomix_cuda.jl b/test/test_atomix_cuda.jl index 487eae2..2103c01 100644 --- a/test/test_atomix_cuda.jl +++ b/test/test_atomix_cuda.jl @@ -16,20 +16,6 @@ function cuda(f) end -# Not implemented: -#= -function test_get_set() - A = CUDA.ones(Int, 3) - cuda() do - GC.@preserve A begin - ref = Atomix.IndexableRef(A, (1,)) - x = Atomix.get(ref) - Atomix.set!(ref, -x) - end - end - @test collect(A) == [-1, 1, 1] -end -=# @testset "AtomixCUDAExt:test_cas" begin @@ -79,3 +65,60 @@ end end @test collect(A) == [2, 1, 1] end + + +@testset "AtomixCUDAExt:test_get_set" begin + A = CUDA.ones(Int, 3) + cuda() do + GC.@preserve A begin + ref = Atomix.IndexableRef(A, (1,)) + x = Atomix.get(ref) + Atomix.set!(ref, -x) + A[2] = @atomic A[1] + @atomic :monotonic A[3] = 2 * x + end + end + @test collect(A) == [-1, -1, 2] +end + + +@testset "AtomixCUDAExt:test_swap" begin + A = CUDA.CuVector(Int[1, 0, 0]) + cuda() do + GC.@preserve A begin + ref = Atomix.IndexableRef(A, (1,)) + A[2] = Atomix.swap!(ref, Int(5)) + A[3] = @atomicswap A[1] = Int(7) + end + end + @test collect(A) == [7, 1, 5] +end + + +@testset "AtomixCUDAExt:test_ordering" begin + A = CUDA.ones(Int, 2) + cuda() do + GC.@preserve A begin + @atomic :monotonic A[1] += 1 + @atomic :acquire_release A[2] -= 1 + end + end + @test collect(A) == [2, 0] +end + + +@testset "AtomixCUDAExt:test_float" begin + A = CUDA.CuVector(Float32[1, 1, 1, 1, 1, 0]) + cuda() do + GC.@preserve A begin + @atomic A[1] += 1.5f0 + @atomic A[2] -= 0.5f0 + @atomic max(A[3], 3f0) + @atomic min(A[4], -1f0) + # no native instruction: compare-and-swap loop + pre, post = @atomic A[5] * 4f0 + A[6] = pre + post + end + end + @test collect(A) == [2.5, 0.5, 3, -1, 4, 5] +end diff --git a/test/test_atomix_metal.jl b/test/test_atomix_metal.jl index 051c9e0..7047a83 100644 --- a/test/test_atomix_metal.jl +++ b/test/test_atomix_metal.jl @@ -16,20 +16,6 @@ function metal(f) end -# Not implemented: -#= -function test_get_set() - A = CUDA.ones(Int, 3) - cuda() do - GC.@preserve A begin - ref = Atomix.IndexableRef(A, (1,)) - x = Atomix.get(ref) - Atomix.set!(ref, -x) - end - end - @test collect(A) == [-1, 1, 1] -end -=# @testset "AtomixMetalExt:test_cas" begin @@ -95,3 +81,60 @@ end end @test collect(A) == [2, 1, 1] end + + +@testset "AtomixMetalExt:test_get_set" begin + A = Metal.ones(Int32, 3) + metal() do + GC.@preserve A begin + ref = Atomix.IndexableRef(A, (1,)) + x = Atomix.get(ref) + Atomix.set!(ref, -x) + A[2] = @atomic A[1] + @atomic :monotonic A[3] = 2 * x + end + end + @test collect(A) == [-1, -1, 2] +end + + +@testset "AtomixMetalExt:test_swap" begin + A = Metal.MtlVector(Int32[1, 0, 0]) + metal() do + GC.@preserve A begin + ref = Atomix.IndexableRef(A, (1,)) + A[2] = Atomix.swap!(ref, Int32(5)) + A[3] = @atomicswap A[1] = Int32(7) + end + end + @test collect(A) == [7, 1, 5] +end + + +@testset "AtomixMetalExt:test_ordering" begin + A = Metal.ones(Int32, 2) + metal() do + GC.@preserve A begin + @atomic :monotonic A[1] += 1 + @atomic :acquire_release A[2] -= 1 + end + end + @test collect(A) == [2, 0] +end + + +@testset "AtomixMetalExt:test_float" begin + A = Metal.MtlVector(Float32[1, 1, 1, 1, 1, 0]) + metal() do + GC.@preserve A begin + @atomic A[1] += 1.5f0 + @atomic A[2] -= 0.5f0 + @atomic max(A[3], 3f0) + @atomic min(A[4], -1f0) + # no native instruction: compare-and-swap loop + pre, post = @atomic A[5] * 4f0 + A[6] = pre + post + end + end + @test collect(A) == [2.5, 0.5, 3, -1, 4, 5] +end diff --git a/test/test_atomix_oneapi.jl b/test/test_atomix_oneapi.jl index ca6dfba..8519a5f 100644 --- a/test/test_atomix_oneapi.jl +++ b/test/test_atomix_oneapi.jl @@ -16,20 +16,6 @@ function oneapi(f) end -# Not implemented: -#= -function test_get_set() - A = CUDA.ones(Int, 3) - cuda() do - GC.@preserve A begin - ref = Atomix.IndexableRef(A, (1,)) - x = Atomix.get(ref) - Atomix.set!(ref, -x) - end - end - @test collect(A) == [-1, 1, 1] -end -=# @testset "AtomixoneAPIExt:test_cas" begin @@ -79,3 +65,60 @@ end end @test collect(A) == [2, 1, 1] end + + +@testset "AtomixoneAPIExt:test_get_set" begin + A = oneAPI.ones(Int32, 3) + oneapi() do + GC.@preserve A begin + ref = Atomix.IndexableRef(A, (1,)) + x = Atomix.get(ref) + Atomix.set!(ref, -x) + A[2] = @atomic A[1] + @atomic :monotonic A[3] = 2 * x + end + end + @test collect(A) == [-1, -1, 2] +end + + +@testset "AtomixoneAPIExt:test_swap" begin + A = oneAPI.oneVector(Int32[1, 0, 0]) + oneapi() do + GC.@preserve A begin + ref = Atomix.IndexableRef(A, (1,)) + A[2] = Atomix.swap!(ref, Int32(5)) + A[3] = @atomicswap A[1] = Int32(7) + end + end + @test collect(A) == [7, 1, 5] +end + + +@testset "AtomixoneAPIExt:test_ordering" begin + A = oneAPI.ones(Int32, 2) + oneapi() do + GC.@preserve A begin + @atomic :monotonic A[1] += 1 + @atomic :acquire_release A[2] -= 1 + end + end + @test collect(A) == [2, 0] +end + + +@testset "AtomixoneAPIExt:test_float" begin + A = oneAPI.oneVector(Float32[1, 1, 1, 1, 1, 0]) + oneapi() do + GC.@preserve A begin + @atomic A[1] += 1.5f0 + @atomic A[2] -= 0.5f0 + @atomic max(A[3], 3f0) + @atomic min(A[4], -1f0) + # no native instruction: compare-and-swap loop + pre, post = @atomic A[5] * 4f0 + A[6] = pre + post + end + end + @test collect(A) == [2.5, 0.5, 3, -1, 4, 5] +end diff --git a/test/test_atomix_opencl.jl b/test/test_atomix_opencl.jl index df63338..1c94b16 100644 --- a/test/test_atomix_opencl.jl +++ b/test/test_atomix_opencl.jl @@ -16,20 +16,6 @@ function opencl(f) end -# Not implemented: -#= -function test_get_set() - A = CUDA.ones(Int, 3) - cuda() do - GC.@preserve A begin - ref = Atomix.IndexableRef(A, (1,)) - x = Atomix.get(ref) - Atomix.set!(ref, -x) - end - end - @test collect(A) == [-1, 1, 1] -end -=# @testset "AtomixOpenCLExt:test_cas" begin @@ -79,3 +65,60 @@ end end @test collect(A) == [2, 1, 1] end + + +@testset "AtomixOpenCLExt:test_get_set" begin + A = OpenCL.ones(Int32, 3) + opencl() do + GC.@preserve A begin + ref = Atomix.IndexableRef(A, (1,)) + x = Atomix.get(ref) + Atomix.set!(ref, -x) + A[2] = @atomic A[1] + @atomic :monotonic A[3] = 2 * x + end + end + @test collect(A) == [-1, -1, 2] +end + + +@testset "AtomixOpenCLExt:test_swap" begin + A = OpenCL.CLArray(Int32[1, 0, 0]) + opencl() do + GC.@preserve A begin + ref = Atomix.IndexableRef(A, (1,)) + A[2] = Atomix.swap!(ref, Int32(5)) + A[3] = @atomicswap A[1] = Int32(7) + end + end + @test collect(A) == [7, 1, 5] +end + + +@testset "AtomixOpenCLExt:test_ordering" begin + A = OpenCL.ones(Int32, 2) + opencl() do + GC.@preserve A begin + @atomic :monotonic A[1] += 1 + @atomic :acquire_release A[2] -= 1 + end + end + @test collect(A) == [2, 0] +end + + +@testset "AtomixOpenCLExt:test_float" begin + A = OpenCL.CLArray(Float32[1, 1, 1, 1, 1, 0]) + opencl() do + GC.@preserve A begin + @atomic A[1] += 1.5f0 + @atomic A[2] -= 0.5f0 + @atomic max(A[3], 3f0) + @atomic min(A[4], -1f0) + # no native instruction: compare-and-swap loop + pre, post = @atomic A[5] * 4f0 + A[6] = pre + post + end + end + @test collect(A) == [2.5, 0.5, 3, -1, 4, 5] +end