Skip to content

Commit 9abbe99

Browse files
Add atomic operations support for Complex numbers
Implements atomic operations for Complex{Float32} and Complex{Float64} by reinterpreting them as UInt64/UInt128 and using integer atomics. - Uses CAS loops for modify! operations on Complex types - Adds tests for all atomic operations with complex numbers - Maintains full compatibility with existing functionality Fixes #37 Generated with GitHub Copilot
1 parent 4298b4e commit 9abbe99

File tree

2 files changed

+84
-4
lines changed

2 files changed

+84
-4
lines changed

src/core.jl

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
# Atomic operations with Complex number support via integer reinterpretation
2+
13
@inline function Atomix.get(ref, order)
24
ptr = Atomix.pointer(ref)
35
root = Atomix.gcroot(ref)
46
GC.@preserve root begin
5-
UnsafeAtomics.load(ptr, order)
7+
_atomic_load(ptr, order)
68
end
79
end
810

@@ -11,7 +13,7 @@ end
1113
ptr = Atomix.pointer(ref)
1214
root = Atomix.gcroot(ref)
1315
GC.@preserve root begin
14-
UnsafeAtomics.store!(ptr, v, order)
16+
_atomic_store!(ptr, v, order)
1517
end
1618
end
1719

@@ -21,7 +23,7 @@ end
2123
ptr = Atomix.pointer(ref)
2224
root = Atomix.gcroot(ref)
2325
GC.@preserve root begin
24-
UnsafeAtomics.cas!(ptr, expected, desired, success_ordering, failure_ordering)
26+
_atomic_cas!(ptr, expected, desired, success_ordering, failure_ordering)
2527
end
2628
end
2729

@@ -30,8 +32,57 @@ end
3032
ptr = Atomix.pointer(ref)
3133
root = Atomix.gcroot(ref)
3234
GC.@preserve root begin
33-
UnsafeAtomics.modify!(ptr, op, x, ord)
35+
_atomic_modify!(ptr, op, x, ord)
3436
end
3537
end
3638

39+
# Generic atomic operations - dispatch on type
40+
_atomic_load(ptr::Ptr{T}, order) where {T} =
41+
_with_int_repr(UnsafeAtomics.load, ptr, order)
42+
43+
_atomic_store!(ptr::Ptr{T}, val::T, order) where {T} =
44+
_with_int_repr(UnsafeAtomics.store!, ptr, val, order)
45+
46+
_atomic_cas!(ptr::Ptr{T}, expected::T, desired::T, success_order, failure_order) where {T} =
47+
_with_int_repr(UnsafeAtomics.cas!, ptr, expected, desired, success_order, failure_order)
48+
49+
# Multiple dispatch for modify! - native atomics for non-Complex types
50+
function _atomic_modify!(ptr::Ptr{T}, op::OP, x::T, ord) where {T,OP}
51+
UnsafeAtomics.modify!(ptr, op, x, ord)
52+
end
53+
54+
# CAS loop fallback for Complex types (no native atomic modify!)
55+
function _atomic_modify!(ptr::Ptr{Complex{T}}, op::OP, x::Complex{T}, ord) where {T,OP}
56+
old = _atomic_load(ptr, ord)
57+
while true
58+
new = op(old, x)
59+
result = _atomic_cas!(ptr, old, new, ord, ord)
60+
result.success && return (old => new)
61+
old = result.old
62+
end
63+
end
64+
65+
# Helper: apply atomic operation with integer reinterpretation for Complex types
66+
function _with_int_repr(f, ptr::Ptr{Complex{T}}, args...) where {T}
67+
IntType = _int_type_for_complex(T)
68+
int_ptr = reinterpret(Ptr{IntType}, ptr)
69+
result = f(int_ptr, _to_int.(IntType, args)...)
70+
return _from_int(Complex{T}, result)
71+
end
72+
73+
_with_int_repr(f, ptr::Ptr{T}, args...) where {T} = f(ptr, args...)
74+
75+
# Integer type mapping for Complex types
76+
_int_type_for_complex(::Type{Float32}) = UInt64
77+
_int_type_for_complex(::Type{Float64}) = UInt128
78+
79+
# Convert to/from integer representation
80+
_to_int(::Type{I}, x::Complex{T}) where {I,T} = reinterpret(I, x)
81+
_to_int(::Type{I}, x) where {I} = x
82+
83+
_from_int(::Type{Complex{T}}, x::Integer) where {T} = reinterpret(Complex{T}, x)
84+
_from_int(::Type{Complex{T}}, result::NamedTuple) where {T} =
85+
(old = reinterpret(Complex{T}, result.old), success = result.success)
86+
_from_int(::Type{T}, x) where {T} = x
87+
3788
Atomix.asstorable(ref, v) = convert(eltype(ref), v)

test/runtests.jl

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,35 @@ end
133133
end
134134

135135

136+
@testset "test_complex" begin
137+
# Test ComplexF64 basic operations
138+
A = ones(ComplexF64, 3)
139+
@test (@atomic A[1]) === ComplexF64(1, 0)
140+
@atomic A[1] = ComplexF64(2, 3)
141+
@test A[1] === ComplexF64(2, 3)
142+
@test (@atomic A[1] += ComplexF64(1, 1)) === ComplexF64(3, 4)
143+
@test A[1] === ComplexF64(3, 4)
144+
@test (@atomicswap A[1] = ComplexF64(10, 10)) === ComplexF64(3, 4)
145+
@test A[1] === ComplexF64(10, 10)
146+
@test (@atomicreplace A[1] ComplexF64(10, 10) => ComplexF64(5, 5)) ==
147+
(old = ComplexF64(10, 10), success = true)
148+
@test (@atomicreplace A[1] ComplexF64(99, 99) => ComplexF64(1, 1)) ==
149+
(old = ComplexF64(5, 5), success = false)
150+
151+
# Test ComplexF32
152+
B = ones(ComplexF32, 3)
153+
@test (@atomic B[1]) === ComplexF32(1, 0)
154+
@test (@atomic B[1] += ComplexF32(1, 1)) === ComplexF32(2, 1)
155+
156+
# Test IndexableRef with Complex
157+
ref = Atomix.IndexableRef(A, (1,))
158+
@test Atomix.modify!(ref, +, ComplexF64(1, 1)) === (ComplexF64(5, 5) => ComplexF64(6, 6))
159+
@test Atomix.swap!(ref, ComplexF64(7, 7)) == ComplexF64(6, 6)
160+
@test Atomix.replace!(ref, ComplexF64(7, 7), ComplexF64(8, 8)) ===
161+
(old = ComplexF64(7, 7), success = true)
162+
end
163+
164+
136165
# KernelAbstractions backend tests
137166
# Pass command-line argument to test suite to install the right backend, e.g.
138167
# julia> import Pkg

0 commit comments

Comments
 (0)