alpaka
Abstraction Library for Parallel Kernel Acceleration
Loading...
Searching...
No Matches
atomic.hpp
Go to the documentation of this file.
1/* Copyright 2025 Jan Stephan, Andrea Bocci, Luca Ferragina
2 * SPDX-License-Identifier: MPL-2.0
3 */
4
5#pragma once
6
11#include "alpaka/operation.hpp"
12
13#if ALPAKA_LANG_SYCL
14
15# include <sycl/sycl.hpp>
16
17# include <cstdint>
18# include <type_traits>
19
20namespace alpaka::detail
21{
22 template<typename T_Scope>
23 struct SyclMemoryScope
24 {
25 };
26
27 template<>
28 struct SyclMemoryScope<alpaka::onAcc::scope::System>
29 {
30 static constexpr auto value = sycl::memory_scope::system;
31 };
32
33 template<>
34 struct SyclMemoryScope<alpaka::onAcc::scope::Device>
35 {
36 static constexpr auto value = sycl::memory_scope::device;
37 };
38
39 template<>
40 struct SyclMemoryScope<alpaka::onAcc::scope::Block>
41 {
42 static constexpr auto value = sycl::memory_scope::work_group;
43 };
44
45 template<typename T, typename T_Scope>
46 using sycl_atomic_ref = sycl::atomic_ref<T, sycl::memory_order::relaxed, SyclMemoryScope<T_Scope>::value>;
47
48 template<typename T_Scope, typename T, typename TOp>
49 inline auto callAtomicOp(T* const addr, TOp&& op)
50 {
51 auto ref = sycl_atomic_ref<T, T_Scope>{*addr};
52 return op(ref);
53 }
54
55 template<typename TRef, typename T, typename TEval>
56 inline auto casWithCondition(T* const addr, TEval&& eval)
57 {
58 auto ref = TRef{*addr};
59 auto old_val = ref.load();
60
61 // prefer compare_exchange_weak when in a loop, assuming that eval is not expensive
62 while(!ref.compare_exchange_weak(old_val, eval(old_val)))
63 {
64 }
65
66 return old_val;
67 }
68} // namespace alpaka::detail
69
71{
72 // Add.
73 //! The SYCL accelerator atomic operation.
74 template<typename T, typename T_Scope>
75 struct Atomic::Op<alpaka::operation::Add, onAcc::internal::SyclAtomic, T, T_Scope>
76 {
77 static_assert(std::is_integral_v<T> || std::is_floating_point_v<T>, "SYCL atomics do not support this type");
78
79 static auto atomicOp(onAcc::internal::SyclAtomic const&, T* const addr, T const& value) -> T
80 {
81 return alpaka::detail::callAtomicOp<T_Scope>(addr, [&value](auto& ref) { return ref.fetch_add(value); });
82 }
83 };
84
85 // Sub.
86 //! The SYCL accelerator atomic operation.
87 template<typename T, typename T_Scope>
88 struct Atomic::Op<alpaka::operation::Sub, onAcc::internal::SyclAtomic, T, T_Scope>
89 {
90 static_assert(std::is_integral_v<T> || std::is_floating_point_v<T>, "SYCL atomics do not support this type");
91
92 static auto atomicOp(onAcc::internal::SyclAtomic const&, T* const addr, T const& value) -> T
93 {
94 return alpaka::detail::callAtomicOp<T_Scope>(addr, [&value](auto& ref) { return ref.fetch_sub(value); });
95 }
96 };
97
98 // Min.
99 //! The SYCL accelerator atomic operation.
100 template<typename T, typename T_Scope>
101 struct Atomic::Op<alpaka::operation::Min, onAcc::internal::SyclAtomic, T, T_Scope>
102 {
103 static_assert(std::is_integral_v<T> || std::is_floating_point_v<T>, "SYCL atomics do not support this type");
104
105 static auto atomicOp(onAcc::internal::SyclAtomic const&, T* const addr, T const& value) -> T
106 {
107 return alpaka::detail::callAtomicOp<T_Scope>(addr, [&value](auto& ref) { return ref.fetch_min(value); });
108 }
109 };
110
111 // Max.
112 //! The SYCL accelerator atomic operation.
113 template<typename T, typename T_Scope>
114 struct Atomic::Op<alpaka::operation::Max, onAcc::internal::SyclAtomic, T, T_Scope>
115 {
116 static_assert(std::is_integral_v<T> || std::is_floating_point_v<T>, "SYCL atomics do not support this type");
117
118 static auto atomicOp(onAcc::internal::SyclAtomic const&, T* const addr, T const& value) -> T
119 {
120 return alpaka::detail::callAtomicOp<T_Scope>(addr, [&value](auto& ref) { return ref.fetch_max(value); });
121 }
122 };
123
124 // Exch.
125 //! The SYCL accelerator atomic operation.
126 template<typename T, typename T_Scope>
127 struct Atomic::Op<alpaka::operation::Exch, onAcc::internal::SyclAtomic, T, T_Scope>
128 {
129 static_assert(
130 (std::is_integral_v<T> || std::is_floating_point_v<T>) and (sizeof(T) == 4 || sizeof(T) == 8),
131 "SYCL atomics do not support this type");
132
133 static auto atomicOp(onAcc::internal::SyclAtomic const&, T* const addr, T const& value) -> T
134 {
135 return alpaka::detail::callAtomicOp<T_Scope>(addr, [&value](auto& ref) { return ref.exchange(value); });
136 }
137 };
138
139 // Inc.
140 //! The SYCL accelerator atomic operation.
141 template<typename T, typename T_Scope>
142 struct Atomic::Op<alpaka::operation::Inc, onAcc::internal::SyclAtomic, T, T_Scope>
143 {
144 static_assert(
145 std::is_unsigned_v<T> && (sizeof(T) == 4 || sizeof(T) == 8),
146 "SYCL atomics support only 32- and 64-bits unsigned integral types");
147
148 static auto atomicOp(onAcc::internal::SyclAtomic const&, T* const addr, T const& value) -> T
149 {
150 auto inc = [&value](auto old_val)
151 { return (old_val >= value) ? static_cast<T>(0) : (old_val + static_cast<T>(1)); };
152 return alpaka::detail::casWithCondition<alpaka::detail::sycl_atomic_ref<T, T_Scope>>(addr, inc);
153 }
154 };
155
156 // Dec.
157 //! The SYCL accelerator atomic operation.
158 template<typename T, typename T_Scope>
159 struct Atomic::Op<alpaka::operation::Dec, onAcc::internal::SyclAtomic, T, T_Scope>
160 {
161 static_assert(
162 std::is_unsigned_v<T> && (sizeof(T) == 4 || sizeof(T) == 8),
163 "SYCL atomics support only 32- and 64-bits unsigned integral types");
164
165 static auto atomicOp(onAcc::internal::SyclAtomic const&, T* const addr, T const& value) -> T
166 {
167 auto dec = [&value](auto& old_val)
168 { return ((old_val == 0) || (old_val > value)) ? value : (old_val - static_cast<T>(1)); };
169 return alpaka::detail::casWithCondition<alpaka::detail::sycl_atomic_ref<T, T_Scope>>(addr, dec);
170 }
171 };
172
173 // And.
174 //! The SYCL accelerator atomic operation.
175 template<typename T, typename T_Scope>
176 struct Atomic::Op<alpaka::operation::And, onAcc::internal::SyclAtomic, T, T_Scope>
177 {
178 static_assert(std::is_integral_v<T>, "Bitwise operations only supported for integral types.");
179
180 static auto atomicOp(onAcc::internal::SyclAtomic const&, T* const addr, T const& value) -> T
181 {
182 return alpaka::detail::callAtomicOp<T_Scope>(addr, [&value](auto& ref) { return ref.fetch_and(value); });
183 }
184 };
185
186 // Or.
187 //! The SYCL accelerator atomic operation.
188 template<typename T, typename T_Scope>
189 struct Atomic::Op<alpaka::operation::Or, onAcc::internal::SyclAtomic, T, T_Scope>
190 {
191 static_assert(std::is_integral_v<T>, "Bitwise operations only supported for integral types.");
192
193 static auto atomicOp(onAcc::internal::SyclAtomic const&, T* const addr, T const& value) -> T
194 {
195 return alpaka::detail::callAtomicOp<T_Scope>(addr, [&value](auto& ref) { return ref.fetch_or(value); });
196 }
197 };
198
199 // Xor.
200 //! The SYCL accelerator atomic operation.
201 template<typename T, typename T_Scope>
202 struct Atomic::Op<alpaka::operation::Xor, onAcc::internal::SyclAtomic, T, T_Scope>
203 {
204 static_assert(std::is_integral_v<T>, "Bitwise operations only supported for integral types.");
205
206 static auto atomicOp(onAcc::internal::SyclAtomic const&, T* const addr, T const& value) -> T
207 {
208 return alpaka::detail::callAtomicOp<T_Scope>(addr, [&value](auto& ref) { return ref.fetch_xor(value); });
209 }
210 };
211
212 // Cas.
213 //! The SYCL accelerator atomic operation.
214 template<typename T, typename T_Scope>
215 struct Atomic::Op<alpaka::operation::Cas, onAcc::internal::SyclAtomic, T, T_Scope>
216 {
217 static_assert(std::is_integral_v<T> || std::is_floating_point_v<T>, "SYCL atomics do not support this type");
218
219 static auto atomicOp(onAcc::internal::SyclAtomic const&, T* const addr, T const& expected, T const& desired)
220 -> T
221 {
222 auto cas = [&expected, &desired](auto& ref)
223 {
224 auto expected_ = expected;
225 // Atomically compares the value of `ref` with the value of `expected`.
226 // If the values are equal, replaces the value of `ref` with `desired`.
227 // Otherwise updates `expected` with the value of `ref`.
228 // Returns a bool telling us if the exchange happened or not, but the Alpaka API does not make use of
229 // it.
230 ref.compare_exchange_strong(expected_, desired);
231
232 // If the update succeded, return the previous value of `ref`.
233 // Otherwise, return the current value of `ref`.
234 return expected_;
235 };
236
237 return alpaka::detail::callAtomicOp<T_Scope>(addr, cas);
238 }
239 };
240} // namespace alpaka::onAcc::internalCompute
241
242#endif
constexpr auto alpaka
Definition fn.hpp:66
constexpr auto atomicOp(auto const &acc, T *const addr, T const &value, T_Scope const scope=T_Scope()) -> T
Executes the given operation atomically.
Definition atomic.hpp:26
Implements a atomic operation.
Definition interface.hpp:74