alpaka
Abstraction Library for Parallel Kernel Acceleration
Loading...
Searching...
No Matches
SimdMask.hpp
Go to the documentation of this file.
1/* Copyright 2025 René Widera
2 * SPDX-License-Identifier: MPL-2.0
3 */
4
5/** @file This file provides a basic implementation of a SIMD vector.
6 *
7 * The implementation is based on the class Vec:
8 * - the storge policy should become the native SIMD implementation e.g. std::simd
9 * - load/ store and simd specifis should be implemented in the storage policy
10 * - the name of storage policy should be changed
11 *
12 * The current operator operations relay on compilers auto vectorization.
13 */
14
15#pragma once
16
17#include "alpaka/Vec.hpp"
18#include "alpaka/cast.hpp"
19#include "alpaka/core/util.hpp"
23#include "alpaka/simd/trait.hpp"
24#include "alpaka/trait.hpp"
27
28#include <array>
29#include <bit>
30#include <concepts>
31#include <cstddef>
32#include <cstdint>
33#include <functional>
34#include <iosfwd>
35#include <ranges>
36#include <sstream>
37#include <string>
38#include <type_traits>
39
40namespace alpaka
41{
42 /** Simd mask vector
43 *
44 * @attention You should not use this type to create a buffer of SIMD masks.
45 * The implementation is not ABI compatible between different API's.
46 * Using Simd masks created on the host and used in the compute kernel will be undefined behaviour.
47 *
48 * This class is designed to be used within a kernel together with the `where()` operation.
49 *
50 * @tparam T_Type data value type the mask should be applied to
51 * @tparam T_width number of lanes in the SIMD mask vector
52 * @tparam T_Storage wrapped native representation of the SIMD mask
53 */
54 template<
55 typename T_Type,
56 uint32_t T_width,
57 /** do not use ALPAKA_TYPEOF(thisApi()) here else nvcc + gcc can trigger a compile error
58 * error: use of built-in trait '__decay(alpaka::api::Host)' in function signature;
59 */
60 typename T_Storage = typename trait::GetSimdMaskStorageType<decltype(thisApi()), T_Type, T_width>::type>
61 struct SimdMask;
62
63 namespace trait
64 {
65 template<typename T_Type, uint32_t T_width, typename T_Storage>
67 {
68 };
69 } // namespace trait
70
71 // friend forward declaration
72 template<concepts::SimdMask T_Mask, concepts::Simd T_Simd>
73 struct SimdWhereExpr;
74
75 template<typename T_Type, uint32_t T_width, typename T_Storage>
76 struct SimdMask : private T_Storage
77 {
78 using Storage = T_Storage;
79 using type = bool;
80 /** type is an implementation detail, can be a proxy type. */
81 using reference = typename Storage::reference;
82
83 using index_type = uint32_t;
84 using size_type = uint32_t;
85 using rank_type = uint32_t;
86
87 // universal vec used as fallback if T_Storage is holding the state in the template signature
89
90 /*Simds without elements are not allowed*/
91 static_assert(T_width > 0u);
92
93 SimdMask() = default;
94
95 /** Initialize via a generator expression
96 *
97 * The generator must return the value for the corresponding index of the component which is passed to the
98 * generator.
99 *
100 * @note The generator needs to have the function interface `bool generator(uint32_t id)`.
101 */
102 template<typename F>
103 requires(std::is_invocable_v<F, std::integral_constant<uint32_t, 0u>>)
104 ALPAKA_FN_HOST_ACC explicit SimdMask(F&& generator)
105 : SimdMask(std::forward<F>(generator), std::make_integer_sequence<uint32_t, T_width>{})
106 {
107 }
108
109 /** Constructor for SIMD pack
110 *
111 * @attention This constructor allows implicit casts.
112 *
113 * @param args value of each lane index, x,y,z,...
114 *
115 * A constexpr vector should be initialized with {} instead of () because at least
116 * CUDA 11.6 has problems in cases where a compile time evaluation is required.
117 * @code{.cpp}
118 * constexpr auto vec1 = Simd{ 1 };
119 * constexpr auto vec2 = Simd{ 1, 2 };
120 * //or explicit
121 * constexpr auto vec3 = Simd<int, 3u>{ 1, 2, 3 };
122 * constexpr auto vec4 = Simd<int, 3u>{ {1, 2, 3} };
123 * @endcode
124 */
125 template<typename... T_Args>
126 requires(
127 ((std::is_convertible_v<T_Args, T_Type> && !std::same_as<bool, T_Args>) && ...)
128 && (sizeof...(T_Args) == T_width))
129 ALPAKA_FN_HOST_ACC SimdMask(T_Args const&... args) : Storage(static_cast<T_Type>(args)...)
130 {
131 }
132
133 template<typename... T_Args>
134 requires((std::same_as<T_Args, bool> && ...) && (sizeof...(T_Args) == T_width))
135 ALPAKA_FN_HOST_ACC SimdMask(T_Args const&... args) : Storage(args...)
136 {
137 }
138
139 SimdMask(SimdMask const& other) = default;
140
141 ALPAKA_FN_HOST_ACC SimdMask(T_Storage const& other) : T_Storage{other}
142 {
143 }
144
145 ALPAKA_FN_HOST_ACC SimdMask(typename T_Storage::BaseType const& base) : T_Storage{base}
146 {
147 }
148
149 /** constructor allows changing the storage policy
150 */
151 template<typename T_OtherStorage>
153 : T_Storage(other.asStorage())
154 {
155 }
156
157 /** Allow static_cast / explicit cast to member type for 1D vector */
158 constexpr explicit operator bool() requires(T_width == 1u)
159 {
160 return static_cast<bool>(Storage::operator[](0));
161 }
162
163 /** Number of components/lanes in the SIMD pack. */
164 static consteval uint32_t width()
165 {
166 return T_width;
167 }
168
169 constexpr void copyFrom(T_Type const* data, concepts::Alignment auto alignment)
170 {
171 Storage::copyFrom(data, alignment);
172 }
173
174 constexpr void copyTo(auto* data, concepts::Alignment auto alignment) const
175 {
176 Storage::copyTo(data, alignment);
177 }
178
179 /**
180 * Creates a Simd where all lanes are set to the same value
181 *
182 * @param value Value which is set for all lanes
183 * @return new Simd<...>
184 */
185 static constexpr auto fill(bool value)
186 {
187 return SimdMask{Storage::fill(value)};
188 }
189
190 constexpr SimdMask toRT() const
191 {
192 return *this;
193 }
194
195 constexpr SimdMask& operator=(SimdMask const&) = default;
196 constexpr SimdMask& operator=(SimdMask&&) = default;
197
198 using Storage::asNativeType;
199
200 /** static cast the instance to the storage type
201 *
202 * @attention: Do not use this method in user code, it is an implementation detail and can cause undefined
203 * behaviour if used wrong.
204 *
205 * @{
206 */
207 constexpr auto& asStorage()
208 {
209 return static_cast<Storage&>(*this);
210 }
211
212 constexpr auto const& asStorage() const
213 {
214 return static_cast<Storage const&>(*this);
215 }
216
217 /** @} */
218
219 /** assign operator
220 * @{
221 */
222#define ALPAKA_VECTOR_ASSIGN_OP(op) \
223 template<typename T_OtherStorage> \
224 constexpr SimdMask& operator op(SimdMask<T_Type, T_width, T_OtherStorage> const& rhs) \
225 { \
226 this->asStorage() op rhs.asStorage(); \
227 return *this; \
228 } \
229 constexpr SimdMask& operator op(concepts::LosslesslyConvertible<T_Type> auto const value) \
230 { \
231 this->asStorage() op static_cast<T_Type>(value); \
232 return *this; \
233 }
234
239
240#undef ALPAKA_VECTOR_ASSIGN_OP
241
242 /** @} */
243
244 constexpr reference operator[](std::integral auto const idx)
245 {
246 return Storage::operator[](idx);
247 }
248
249 constexpr type operator[](std::integral auto const idx) const
250 {
251 return static_cast<type>(Storage::operator[](idx));
252 }
253
254 /** @brief named lane access
255 *
256 * @attention The mapping from names x,y,z,w to memory indices differ from the mapping of an alpaka vector @c
257 * Vec. The availability of the naming methods depends on the SIMD width.
258 *
259 * You can have access to the same lane index via different nonspecific naming.
260 *
261 * @code
262 * lane index : 0, 1, 2, 3, ..., 9, 10, ... , 15
263 * hexadecimal : s0, s1, s2, s3, ..., s9, SA, ... , SF
264 * coordinate : x, y, z, w
265 * color channel: r, g, b, a
266 * @endcode
267 *
268 * @{
269 */
270#define ALPAKA_NAMED_ARRAY_ACCESS(functionName, laneIdx) \
271 constexpr reference functionName() requires(T_width >= laneIdx + 1) \
272 { \
273 return (*this)[laneIdx]; \
274 } \
275 constexpr type functionName() const requires(T_width >= laneIdx + 1) \
276 { \
277 return (*this)[laneIdx]; \
278 }
279
284
289
306
307#undef ALPAKA_NAMED_ARRAY_ACCESS
308
309 /** @} */
310
311 /** reduce all elements to a single value
312 *
313 * For better numerical stability a tree reduce algorithm is used.
314 *
315 * @tparam BinaryOp binary functor executed to reduce the range
316 * The binary operation must be associative.
317 * @return the type of the result depends on the binary functor
318 */
319 [[nodiscard]] constexpr type reduce(auto&& reduceFunc) const
320 {
321 return reduce_range(ALPAKA_FORWARD(reduceFunc));
322 }
323
324 /** create string out of the SIMD pack
325 *
326 * @param separator string to separate components of the SIMD pack
327 * @param enclosings string with width 2 to enclose SIMD pack
328 * width == 0 ? no enclose symbols
329 * width == 1 ? means enclose symbol begin and end are equal
330 * width >= 2 ? letter[0] = begin enclose symbol
331 * letter[1] = end enclose symbol
332 *
333 * example:
334 * .toString(";","|") -> |x;...;z|
335 * .toString(",","[]") -> [x,...,z]
336 */
337 std::string toString(std::string const separator = ",", std::string const enclosings = "{}") const
338 {
339 std::string locale_enclosing_begin;
340 std::string locale_enclosing_end;
341 size_t enclosingLaneIdx = enclosings.size();
342
343 if(enclosingLaneIdx > 0)
344 {
345 /* % avoid out of memory access */
346 locale_enclosing_begin = enclosings[0 % enclosingLaneIdx];
347 locale_enclosing_end = enclosings[1 % enclosingLaneIdx];
348 }
349
350 std::stringstream stream;
351 stream << locale_enclosing_begin << Storage::operator[](0);
352
353 for(uint32_t i = 1u; i < T_width; ++i)
354 stream << separator << Storage::operator[](i);
355 stream << locale_enclosing_end;
356 return stream.str();
357 }
358
359 private:
360 template<typename F, uint32_t... Is>
361 ALPAKA_FN_HOST_ACC explicit SimdMask(F&& generator, std::integer_sequence<uint32_t, Is...>)
362 : Storage{generator(std::integral_constant<uint32_t, Is>{})...}
363 {
364 }
365
366 /** reduce over a range of elements
367 *
368 * @tparam BinaryOp binary functor executed to reduce the range
369 * @tparam T_start start index
370 * @tparam T_end end index (excluded)
371 * @return the type of the result depends on the binary functor
372 */
373 template<uint32_t T_start = 0u, uint32_t T_end = width()>
374 [[nodiscard]] constexpr type reduce_range(auto&& reduceFunc) const
375 {
376 // elements in the range
377 constexpr uint32_t size = T_end - T_start;
378 // single element termination
379 if constexpr(size == 1u)
380 {
381 return (*this)[T_start];
382 }
383#if ALPAKA_LANG_SYCL
384 // SYCL can not call recursive functions
385 auto result = (*this)[T_start];
386 for(uint32_t i = T_start + 1u; i < T_end; ++i)
387 {
388 result = reduceFunc(result, (*this)[i]);
389 }
390 return result;
391#else
392 // split range at midpoint
393 constexpr uint32_t mid = T_start + size / 2u;
394
395 // recursively reduce both halves and combine
396 return reduceFunc(
399#endif
400 }
401
402 template<concepts::SimdMask Mask, concepts::Simd T_Simd>
403 friend struct SimdWhereExpr;
404 };
405
406 template<std::size_t I, typename T_Type, uint32_t T_width, typename T_Storage>
408 {
409 return v[I];
410 }
411
412 template<std::size_t I, typename T_Type, uint32_t T_width, typename T_Storage>
414 {
415 return v[I];
416 }
417
418 template<typename Type, uint32_t T_width, typename T_Storage>
419 std::ostream& operator<<(std::ostream& s, SimdMask<Type, T_width, T_Storage> const& vec)
420 {
421 return s << vec.toString();
422 }
423
424 // type deduction guide
425 template<typename T_1, typename... T_Args>
426 ALPAKA_FN_HOST_ACC SimdMask(T_1, T_Args...) -> SimdMask<T_1, uint32_t(sizeof...(T_Args) + 1u)>;
427
428 /** Creates a mask for the given type
429 *
430 * @tparam T value type of SIMD object which should be masked
431 * @tparam T_Args arguments forwarded to the constructor of the mask
432 */
433 template<typename T, typename... T_Args>
434 requires((std::same_as<std::remove_cvref_t<T_Args>, bool>) && ...)
435 constexpr auto makeSimdMask(T_Args... args)
436 {
437 using Storage =
438 typename trait::GetSimdMaskStorageType<ALPAKA_TYPEOF(thisApi()), T, uint32_t(sizeof...(T_Args))>::type;
439 return SimdMask<T, uint32_t(sizeof...(T_Args)), Storage>(Storage(ALPAKA_FORWARD(args)...));
440 }
441
442#define ALPAKA_VECTOR_BINARY_OP(typenameOrConcept, op) \
443 template<typenameOrConcept T_Type, uint32_t T_width, typename T_Storage, typename T_OtherStorage> \
444 constexpr auto operator op( \
445 const SimdMask<T_Type, T_width, T_Storage>& lhs, \
446 const SimdMask<T_Type, T_width, T_OtherStorage>& rhs) \
447 { \
448 using StoreageType = ALPAKA_TYPEOF(lhs.asStorage() op rhs.asStorage()); \
449 return SimdMask<T_Type, T_width, StoreageType>(lhs.asStorage() op rhs.asStorage()); \
450 } \
451 template< \
452 typenameOrConcept T_Type, \
453 concepts::LosslesslyConvertible<T_Type> T_ValueType, \
454 uint32_t T_width, \
455 typename T_Storage> \
456 constexpr auto operator op(const SimdMask<T_Type, T_width, T_Storage>& lhs, T_ValueType rhs) \
457 { \
458 using StoreageType = ALPAKA_TYPEOF(lhs.asStorage() op static_cast<T_Type>(rhs)); \
459 return SimdMask<T_Type, T_width, StoreageType>(lhs.asStorage() op static_cast<T_Type>(rhs)); \
460 } \
461 template< \
462 typenameOrConcept T_Type, \
463 concepts::LosslesslyConvertible<T_Type> T_ValueType, \
464 uint32_t T_width, \
465 typename T_Storage> \
466 constexpr auto operator op(T_ValueType lhs, const SimdMask<T_Type, T_width, T_Storage>& rhs) \
467 { \
468 using StoreageType = ALPAKA_TYPEOF(static_cast<T_Type>(lhs) op rhs.asStorage()); \
469 return SimdMask<T_Type, T_width, StoreageType>(static_cast<T_Type>(lhs) op rhs.asStorage()); \
470 }
471
479
480#undef ALPAKA_VECTOR_BINARY_OP
481
482 /** @} */
483
484
485 namespace trait
486 {
487 template<typename T_Type, uint32_t T_width, typename T_Storage>
488 struct GetDim<alpaka::SimdMask<T_Type, T_width, T_Storage>>
489 {
490 static constexpr uint32_t value = T_width;
491 };
492
493 template<typename T_Type, uint32_t T_width, typename T_Storage>
494 struct GetValueType<alpaka::SimdMask<T_Type, T_width, T_Storage>>
495 {
496 using type = T_Type;
497 };
498 } // namespace trait
499
500 namespace internal
501 {
502 template<typename T_To, typename T_Type, uint32_t T_width, typename T_Storage>
503 struct PCast::Op<T_To, alpaka::SimdMask<T_Type, T_width, T_Storage>>
504 {
505 constexpr auto operator()(auto&& input) const
506 requires std::convertible_to<T_Type, T_To> && (!std::same_as<T_To, T_Type>)
507 {
509 [&](uint32_t idx) constexpr { return static_cast<T_To>(input[idx]); });
510 }
511
512 constexpr decltype(auto) operator()(auto&& input) const requires std::same_as<T_To, T_Type>
513 {
514 return std::forward<decltype(input)>(input);
515 }
516 };
517 } // namespace internal
518}; // namespace alpaka
519
520namespace std
521{
522 template<typename T_Type, uint32_t T_width, typename T_Storage>
523 struct tuple_size<alpaka::SimdMask<T_Type, T_width, T_Storage>>
524 {
525 static constexpr std::size_t value = T_width;
526 };
527
528 template<std::size_t I, typename T_Type, uint32_t T_width, typename T_Storage>
529 struct tuple_element<I, alpaka::SimdMask<T_Type, T_width, T_Storage>>
530 {
531 using type = T_Type;
532 };
533} // namespace std
#define ALPAKA_VECTOR_BINARY_OP(typenameOrConcept, op)
Definition SimdMask.hpp:442
#define ALPAKA_NAMED_ARRAY_ACCESS(functionName, laneIdx)
Definition Simd.hpp:275
#define ALPAKA_VECTOR_ASSIGN_OP(op)
assign operator
Definition Simd.hpp:232
#define ALPAKA_VECTOR_BINARY_OP(typenameOrConcept, op)
binary operators
Definition Simd.hpp:549
#define ALPAKA_FN_HOST_ACC
All functions that can be used on an accelerator have to be attributed with ALPAKA_FN_ACC or ALPAKA_F...
Definition common.hpp:32
#define ALPAKA_TYPEOF(...)
Get the type of instance.
Definition common.hpp:154
#define ALPAKA_FORWARD(instance)
Perfectly forward an instance as argument.
Definition common.hpp:148
Concept to check for an alignment object.
Definition Alignment.hpp:89
alpaka internal implementations.
Definition generic.hpp:19
main alpaka namespace.
Definition alpaka.hpp:76
std::ostream & operator<<(std::ostream &os, concepts::BoundaryDirection auto const &bd)
constexpr auto makeSimdMask(T_Args... args)
Creates a mask for the given type.
Definition SimdMask.hpp:435
constexpr auto thisApi()
provides the API used during the execution of the current code path
Definition api.hpp:26
constexpr decltype(auto) get(concepts::SpecializationOf< Dict > auto &t) noexcept
Definition Dict.hpp:156
STL namespace.
Simd mask vector.
Definition SimdMask.hpp:77
SimdMask()=default
constexpr type reduce_range(auto &&reduceFunc) const
reduce over a range of elements
Definition SimdMask.hpp:374
constexpr type reduce(auto &&reduceFunc) const
reduce all elements to a single value
Definition SimdMask.hpp:319
ALPAKA_FN_HOST_ACC SimdMask(F &&generator)
Initialize via a generator expression.
Definition SimdMask.hpp:104
constexpr reference operator[](std::integral auto const idx)
Definition SimdMask.hpp:244
uint32_t index_type
Definition SimdMask.hpp:83
static consteval uint32_t width()
Number of components/lanes in the SIMD pack.
Definition SimdMask.hpp:164
constexpr SimdMask toRT() const
Definition SimdMask.hpp:190
uint32_t rank_type
Definition SimdMask.hpp:85
constexpr SimdMask & operator=(SimdMask &&)=default
constexpr void copyTo(auto *data, concepts::Alignment auto alignment) const
Definition SimdMask.hpp:174
ALPAKA_FN_HOST_ACC SimdMask(SimdMask< T_Type, T_width, T_OtherStorage > const &other)
constructor allows changing the storage policy
Definition SimdMask.hpp:152
uint32_t size_type
Definition SimdMask.hpp:84
std::string toString(std::string const separator=",", std::string const enclosings="{}") const
create string out of the SIMD pack
Definition SimdMask.hpp:337
constexpr SimdMask & operator=(SimdMask const &)=default
ALPAKA_FN_HOST_ACC SimdMask(typename T_Storage::BaseType const &base)
Definition SimdMask.hpp:145
SimdMask< T_Type, T_width > UniSimdMask
Definition SimdMask.hpp:88
ALPAKA_FN_HOST_ACC SimdMask(F &&generator, std::integer_sequence< uint32_t, Is... >)
Definition SimdMask.hpp:361
constexpr type operator[](std::integral auto const idx) const
Definition SimdMask.hpp:249
ALPAKA_FN_HOST_ACC SimdMask(T_Args const &... args)
Constructor for SIMD pack.
Definition SimdMask.hpp:129
ALPAKA_FN_HOST_ACC SimdMask(T_Storage const &other)
Definition SimdMask.hpp:141
T_Storage Storage
Definition SimdMask.hpp:78
typename Storage::reference reference
type is an implementation detail, can be a proxy type.
Definition SimdMask.hpp:81
constexpr auto const & asStorage() const
static cast the instance to the storage type
Definition SimdMask.hpp:212
static constexpr auto fill(bool value)
Creates a Simd where all lanes are set to the same value.
Definition SimdMask.hpp:185
constexpr void copyFrom(T_Type const *data, concepts::Alignment auto alignment)
Definition SimdMask.hpp:169
SimdMask(SimdMask const &other)=default
Get the storage type for a SIMD mask pack.