alpaka
Abstraction Library for Parallel Kernel Acceleration
Loading...
Searching...
No Matches
atomic.hpp
Go to the documentation of this file.
1/* Copyright 2022 Benjamin Worpitz, René Widera, Jan Stephan, Andrea Bocci, Bernhard Manfred Gruber, Antonio Di Pilato
2 * SPDX-License-Identifier: MPL-2.0
3 */
4
5#pragma once
6
13#include "alpaka/operation.hpp"
14
15#include <bit>
16#include <limits>
17#include <type_traits>
18
19#if ALPAKA_LANG_CUDA || ALPAKA_LANG_HIP
20
21namespace alpaka::onAcc::internalCompute
22{
23 namespace detail
24 {
25 struct EmulationBase
26 {
27 template<typename T_Type>
28 using AtomicCasType = std::conditional_t<
29 sizeof(T_Type) == 4u,
30 unsigned int,
31 std::conditional_t<sizeof(T_Type) == 8u, unsigned long long int, void>>;
32
33 template<typename T_Type>
34 static __device__ auto reinterpretAddress(T_Type* address)
35 -> AtomicCasType<T_Type>* requires(sizeof(T_Type) == 4u || sizeof(T_Type) == 8u) {
36 return reinterpret_cast<AtomicCasType<T_Type>*>(address);
37 }
38
39 template<typename T_Type>
40 static __device__ auto reinterpretValue(T_Type value)
41 -> AtomicCasType<T_Type> requires(sizeof(T_Type) == 4u || sizeof(T_Type) == 8u)
42 {
43 return std::bit_cast<AtomicCasType<T_Type>>(value);
44 }
45 };
46
47 //! Emulate atomic
48 //
49 // The default implementation will emulate all atomic functions with atomicCAS.
50 template<
51 typename TOp,
52 typename TAtomic,
53 typename T,
54 typename T_Scope,
55 typename TSfinae = void,
56 typename TDefer = void>
57 struct EmulateAtomic : private EmulationBase
58 {
59 public:
60 static __device__ auto atomic(internal::CudaHipAtomic const ctx, T* const addr, T const& value) -> T
61 {
62 auto* const addressAsIntegralType = reinterpretAddress(addr);
63 using EmulatedType = std::decay_t<decltype(*addressAsIntegralType)>;
64
65 // Emulating atomics with atomicCAS is mentioned in the programming guide too.
66 // http://docs.nvidia.com/cuda/cuda-c-programming-guide/#atomic-functions
67# if ALPAKA_LANG_HIP
68# if __has_builtin(__hip_atomic_load)
69 EmulatedType old{__hip_atomic_load(addressAsIntegralType, __ATOMIC_RELAXED, __HIP_MEMORY_SCOPE_AGENT)};
70# else
71 EmulatedType old{__atomic_load_n(addressAsIntegralType, __ATOMIC_RELAXED)};
72# endif
73# else
74 EmulatedType old{*addressAsIntegralType};
75# endif
76 EmulatedType assumed;
77 do
78 {
79 assumed = old;
80 T v = std::bit_cast<T>(assumed);
81 TOp{}(&v, value);
82 using Cas = Atomic::Op<alpaka::operation::Cas, internal::CudaHipAtomic, EmulatedType, T_Scope>;
83 old = Cas::atomicOp(ctx, addressAsIntegralType, assumed, reinterpretValue(v));
84 // Note: uses integer comparison to avoid hang in case of NaN (since NaN != NaN)
85 } while(assumed != old);
86 return std::bit_cast<T>(old);
87 }
88 };
89
90 //! Emulate operation::Cas with equivalent unisigned integral type
91 template<typename T, typename T_Scope>
92 struct EmulateAtomic<alpaka::operation::Cas, internal::CudaHipAtomic, T, T_Scope> : private EmulationBase
93 {
94 static __device__ auto atomic(
95 internal::CudaHipAtomic const ctx,
96 T* const addr,
97 T const& compare,
98 T const& value) -> T
99 {
100 auto* const addressAsIntegralType = reinterpretAddress(addr);
101 using EmulatedType = std::decay_t<decltype(*addressAsIntegralType)>;
102 EmulatedType reinterpretedCompare = reinterpretValue(compare);
103 EmulatedType reinterpretedValue = reinterpretValue(value);
104
105 auto old
106 = Atomic::Op<alpaka::operation::Cas, internal::CudaHipAtomic, EmulatedType, T_Scope>::atomicOp(
107 ctx,
108 addressAsIntegralType,
109 reinterpretedCompare,
110 reinterpretedValue);
111
112 return std::bit_cast<T>(old);
113 }
114 };
115
116 //! Emulate operation::Sub with atomicAdd
117 template<typename T, typename T_Scope>
118 struct EmulateAtomic<alpaka::operation::Sub, internal::CudaHipAtomic, T, T_Scope>
119 {
120 static __device__ auto atomic(internal::CudaHipAtomic const ctx, T* const addr, T const& value) -> T
121 {
122 return Atomic::Op<alpaka::operation::Add, internal::CudaHipAtomic, T, T_Scope>::atomicOp(
123 ctx,
124 addr,
125 -value);
126 }
127 };
128
129 //! operation::Dec can not be implemented for floating point types!
130 template<typename T, typename T_Scope>
131 struct EmulateAtomic<
132 operation::Dec,
133 internal::CudaHipAtomic,
134 T,
135 T_Scope,
136 std::enable_if_t<std::is_floating_point_v<T>>>
137 {
138 static __device__ auto atomic(internal::CudaHipAtomic const&, T* const, T const&) -> T
139 {
140 static_assert(
141 !sizeof(T),
142 "EmulateAtomic<alpaka::operation::Dec> is not supported for floating point data types!");
143 return T{};
144 }
145 };
146
147 //! operation::Inc can not be implemented for floating point types!
148 template<typename T, typename T_Scope>
149 struct EmulateAtomic<
150 operation::Inc,
151 internal::CudaHipAtomic,
152 T,
153 T_Scope,
154 std::enable_if_t<std::is_floating_point_v<T>>>
155 {
156 static __device__ auto atomic(internal::CudaHipAtomic const&, T* const, T const&) -> T
157 {
158 static_assert(
159 !sizeof(T),
160 "EmulateAtomic<alpaka::operation::Inc> is not supported for floating point data types!");
161 return T{};
162 }
163 };
164
165 //! operation::And can not be implemented for floating point types!
166 template<typename T, typename T_Scope>
167 struct EmulateAtomic<
168 operation::And,
169 internal::CudaHipAtomic,
170 T,
171 T_Scope,
172 std::enable_if_t<std::is_floating_point_v<T>>>
173 {
174 static __device__ auto atomic(internal::CudaHipAtomic const&, T* const, T const&) -> T
175 {
176 static_assert(
177 !sizeof(T),
178 "EmulateAtomic<alpaka::operation::And> is not supported for floating point data types!");
179 return T{};
180 }
181 };
182
183 //! operation::Or can not be implemented for floating point types!
184 template<typename T, typename T_Scope>
185 struct EmulateAtomic<
186 operation::Or,
187 internal::CudaHipAtomic,
188 T,
189 T_Scope,
190 std::enable_if_t<std::is_floating_point_v<T>>>
191 {
192 static __device__ auto atomic(internal::CudaHipAtomic const&, T* const, T const&) -> T
193 {
194 static_assert(
195 !sizeof(T),
196 "EmulateAtomic<alpaka::operation::Or> is not supported for floating point data types!");
197 return T{};
198 }
199 };
200
201 //! operation::Xor can not be implemented for floating point types!
202 template<typename T, typename T_Scope>
203 struct EmulateAtomic<
204 operation::Xor,
205 internal::CudaHipAtomic,
206 T,
207 T_Scope,
208 std::enable_if_t<std::is_floating_point_v<T>>>
209 {
210 static __device__ auto atomic(internal::CudaHipAtomic const&, T* const, T const&) -> T
211 {
212 static_assert(
213 !sizeof(T),
214 "EmulateAtomic<alpaka::operation::Xor> is not supported for floating point data types!");
215 return T{};
216 }
217 };
218
219 } // namespace detail
220
221 //! Generic atomic implementation
222 //
223 // - unsigned long int will be redirected to unsigned long long int or unsigned int implementation depending if
224 // unsigned long int is a 64 or 32bit data type.
225 // - Atomics which are not available as builtin atomic will be emulated.
226 template<typename TOp, typename T, typename T_Scope>
227 struct Atomic::Op<TOp, internal::CudaHipAtomic, T, T_Scope>
228 {
229 static __device__ auto atomicOp(
230 internal::CudaHipAtomic const ctx,
231 [[maybe_unused]] T* const addr,
232 [[maybe_unused]] T const& value) -> T
233 {
234 static_assert(
235 sizeof(T) == 4u || sizeof(T) == 8u,
236 "atomicOp<TOp, internal::CudaHipAtomic, T>(atomic, addr, value) is not supported! Only 64 and "
237 "32bit atomics are supported.");
238
239 if constexpr(::AlpakaBuiltInAtomic<TOp, T, T_Scope>::value)
240 return ::AlpakaBuiltInAtomic<TOp, T, T_Scope>::atomic(addr, value);
241
242 else if constexpr(std::is_same_v<unsigned long int, T>)
243 {
244 if constexpr(sizeof(T) == 4u && ::AlpakaBuiltInAtomic<TOp, unsigned int, T_Scope>::value)
245 return ::AlpakaBuiltInAtomic<TOp, unsigned int, T_Scope>::atomic(
246 reinterpret_cast<unsigned int*>(addr),
247 static_cast<unsigned int>(value));
248 else if constexpr(
249 sizeof(T) == 8u && ::AlpakaBuiltInAtomic<TOp, unsigned long long int, T_Scope>::value) // LP64
250 {
251 return ::AlpakaBuiltInAtomic<TOp, unsigned long long int, T_Scope>::atomic(
252 reinterpret_cast<unsigned long long int*>(addr),
253 static_cast<unsigned long long int>(value));
254 }
255 }
256
257 return detail::EmulateAtomic<TOp, internal::CudaHipAtomic, T, T_Scope>::atomic(ctx, addr, value);
258 }
259 };
260
261 template<typename T, typename T_Scope>
262 struct Atomic::Op<alpaka::operation::Cas, internal::CudaHipAtomic, T, T_Scope>
263 {
264 static __device__ auto atomicOp(
265 [[maybe_unused]] internal::CudaHipAtomic const ctx,
266 [[maybe_unused]] T* const addr,
267 [[maybe_unused]] T const& compare,
268 [[maybe_unused]] T const& value) -> T
269 {
270 static_assert(
271 sizeof(T) == 4u || sizeof(T) == 8u,
272 "atomicOp<alpaka::operation::Cas, internal::CudaHipAtomic, T>(atomic, addr, compare, value) is not "
273 "supported! Only 64 and "
274 "32bit atomics are supported.");
275
276 if constexpr(::AlpakaBuiltInAtomic<alpaka::operation::Cas, T, T_Scope>::value)
277 return ::AlpakaBuiltInAtomic<alpaka::operation::Cas, T, T_Scope>::atomic(addr, compare, value);
278
279 else if constexpr(std::is_same_v<unsigned long int, T>)
280 {
281 if constexpr(
282 sizeof(T) == 4u && ::AlpakaBuiltInAtomic<alpaka::operation::Cas, unsigned int, T_Scope>::value)
283 return ::AlpakaBuiltInAtomic<alpaka::operation::Cas, unsigned int, T_Scope>::atomic(
284 reinterpret_cast<unsigned int*>(addr),
285 static_cast<unsigned int>(compare),
286 static_cast<unsigned int>(value));
287 else if constexpr(
288 sizeof(T) == 8u
289 && ::AlpakaBuiltInAtomic<alpaka::operation::Cas, unsigned long long int, T_Scope>::value) // LP64
290 {
291 return ::AlpakaBuiltInAtomic<alpaka::operation::Cas, unsigned long long int, T_Scope>::atomic(
292 reinterpret_cast<unsigned long long int*>(addr),
293 static_cast<unsigned long long int>(compare),
294 static_cast<unsigned long long int>(value));
295 }
296 }
297
298 return detail::EmulateAtomic<alpaka::operation::Cas, internal::CudaHipAtomic, T, T_Scope>::atomic(
299 ctx,
300 addr,
301 compare,
302 value);
303 }
304 };
305} // namespace alpaka::onAcc::internalCompute
306#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