SuperDex Physics C++ API
Loading...
Searching...
No Matches
simd_composite_inl.h
Go to the documentation of this file.
1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#pragma once
18
19#include "../simd.h" // for IntelliSense
20
21#include <type_traits>
22#include <utility>
23
24namespace superdex {
25
26/**
27 Simd partial specialization for larger values of N.
28 Supports any N as long as it can be composed of smaller supported Simd objects.
29*/
30template <class T, int N>
31class Simd<
32 T,
33 N,
34 std::enable_if_t<(N > kSimdDefaultSize<T> && Simd<T>::kIsSupported), SimdConcept>> {
35 public:
36 using Scalar = T;
37
38 // Data storage is split into two parts
39 using NativeType = Simd<T, kSimdDefaultSize<T>>; // Native size
40 using First = NativeType; // First part
41 using Second = Simd<T, N - First::kSize>; // What's left
44
45 static constexpr int kSize = N;
46 static constexpr int kSizeFirst = First::kSize;
47 static constexpr int kSizeSecond = Second::kSize;
49 static constexpr bool kIsComposite = true;
51 static_assert(
52 kSizeFirst == 2 || kSizeFirst == 4 || kSizeFirst == 8,
53 "Some functions may need to be updated to support other SIMD sizes");
54 static_assert(
56 "Inconsistent SIMD emulation flags");
57
58 // Default construct (may be uninitialized)
60
61 // Copy construct
63
64 // Construct from Simd parts
65 MOCHI_ANY MOCHI_FORCE_INLINE Simd(First const& p0, Second const& p1) : first(p0), second(p1) {}
66
67 // Construct from composite halves (currently just Simd<T, 8> from two Simd<T, 4> composites)
68 template <class Half, MOCHI_CONCEPT(Half::kIsComposite && (Half::kSize * 2 == kSize))>
69 MOCHI_ANY MOCHI_FORCE_INLINE Simd(Half const& a, Half const& b) {
70 static_assert(std::is_same_v<Scalar, typename Half::Scalar>, "Type mismatch");
71 static_assert(kSizeFirst == 2 && Half::kSize == 4 && kSize == 8, "Unsupported size");
72 first = a.first;
73 second.first = a.second;
74 second.second.first = b.first;
75 second.second.second = b.second;
76 }
77
78 // Construct by broadcasting a scalar
79 template <class U, MOCHI_REQUIRES_NON_BOOL_SCALAR(U, Scalar)>
81
82 // Construct from 2 scalars. Any unspecified values are zero.
84
85 // Construct from 3 or 4 scalars. Any unspecified values are zero.
87 if constexpr (kSizeFirst == 2) {
88 first = First{a, b};
89 second = Second{c, d};
90 } else {
91 first = First{a, b, c, d};
92 second = Second{};
93 }
94 }
95
96 // Construct from 5 or 6 scalars. Any unspecified values are zero.
98 Simd(Scalar a, Scalar b, Scalar c, Scalar d, Scalar e, Scalar f = Scalar(0)) {
99 if constexpr (kSizeFirst == 2) {
100 first = First{a, b};
101 second = Second{c, d, e, f};
102 } else if constexpr (kSizeFirst == 4) {
103 first = First{a, b, c, d};
104 second = Second{e, f};
105 } else {
106 first = First{a, b, c, d, e, f};
107 second = {};
108 }
109 }
110
111 // Construct from 7 scalars. Any unspecified values are zero.
114 if constexpr (kSizeFirst == 2) {
115 first = First{a, b};
116 second = Second{c, d, e, f, g};
117 } else if constexpr (kSizeFirst == 4) {
118 first = First{a, b, c, d};
119 second = Second{e, f, g};
120 } else {
121 first = First{a, b, c, d, e, f, g};
122 second = {};
123 }
124 }
125
126 // Construct from 8 or more scalars. Any unspecified values are zero.
127 template <typename... MoreScalars>
129 Scalar a,
130 Scalar b,
131 Scalar c,
132 Scalar d,
133 Scalar e,
134 Scalar f,
135 Scalar g,
136 Scalar h,
137 MoreScalars... args) {
138 if constexpr (kSizeFirst == 2) {
139 first = First{a, b};
140 second = Second{c, d, e, f, g, h, args...};
141 } else if constexpr (kSizeFirst == 4) {
142 first = First{a, b, c, d};
143 second = Second{e, f, g, h, args...};
144 } else {
145 first = First{a, b, c, d, e, f, g, h};
146 static constexpr auto kNumArgs = sizeof...(args);
147 if constexpr (kNumArgs == 0) {
148 second = Second{};
149 } else if constexpr (kNumArgs == 1) {
150 second = Second{args..., Scalar(0)};
151 } else {
152 second = Second{args...};
153 }
154 }
155 }
156
157 MOCHI_ANY MOCHI_FORCE_INLINE static constexpr size_t size() {
158 return kSize;
159 }
160
161 template <int i>
162 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Scalar Get(Simd a) {
163 if constexpr (i < kSizeFirst) {
164 return First::template Get<i>(a.first);
165 } else {
166 return Second::template Get<i - kSizeFirst>(a.second);
167 }
168 }
169
170 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Scalar Get(Simd a, int i) {
171 if (i < kSizeFirst) {
172 return First::Get(a.first, i);
173 } else {
174 return Second::Get(a.second, i - kSizeFirst);
175 }
176 }
177
178 template <int i>
179 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE auto GetHalf(Simd a) {
180 using Half = Simd<Scalar, N / 2>;
181 static_assert(Half::kIsSupported);
182 if constexpr (kSizeFirst == kSizeSecond) {
183 if constexpr (i == 0) {
184 return a.first;
185 } else {
186 return a.second;
187 }
188 } else {
189 static_assert(
190 Half::kIsComposite && kSize == 8 && kSizeFirst == 2, "Not yet supported for other sizes");
191 if constexpr (i == 0) {
192 return Half{a.first, a.second.first};
193 } else {
194 return Half{a.second.second.first, a.second.second.second};
195 }
196 }
197 }
198
199 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Simd Set(Simd a, int i, Scalar value) {
200 if (i < kSizeFirst) {
201 return {First::Set(a.first, i, value), a.second};
202 } else {
203 return {a.first, Second::Set(a.second, i - kSizeFirst, value)};
204 }
205 }
206
207 template <int i>
208 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Simd Set(Simd a, Scalar value) {
209 if constexpr (i < kSizeFirst) {
210 return {First::template Set<i>(a.first, value), a.second};
211 } else {
212 return {a.first, Second::template Set<i - kSizeFirst>(a.second, value)};
213 }
214 }
215
216 template <int i>
218 static_assert(kSize == 4, "Unsupported size");
219 return Set<i>(Zero(), Scalar(1));
220 }
221
222 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Simd Sequence() {
223 alignas(First)
224 Scalar constexpr kSequence[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
225 static_assert(
226 std::size(kSequence) >= First::kSize,
227 "Vector size is too large. Size of kSequence must be increased.");
228 auto first = First::Load(kSequence);
230 if constexpr (std::is_same_v<First, Second>) {
231 second = first;
232 } else if constexpr (Second::kIsComposite) {
233 second = Second::Sequence();
234 } else {
235 static_assert(
236 std::size(kSequence) >= Second::kSize,
237 "Vector size is too large. Size of kSequence must be increased.");
238 second = Second::Load(kSequence);
239 }
240 return Simd{first, second + Second{static_cast<T>(First::kSize)}};
241 }
242
243 template <int x = 0, int y = 1, int z = 2, int w = 3>
244 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Simd Shuffle(Simd a, Simd b) {
245 static_assert(kSize == 4, "Unsupported size");
246 return {Get<x>(a), Get<y>(a), Get<z>(b), Get<w>(b)}; // Not optimized
247 }
248
249 template <int x = 0, int y = 1, int z = 2, int w = 3>
250 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Simd Shuffle(Simd a) {
251 static_assert(kSize == 4, "Unsupported size");
252 return Shuffle<x, y, z, w>(a, a);
253 }
254
255 template <int SZ = kSize>
256 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE bool AllTrue(Simd a) {
257 static_assert(SZ <= kSize);
258 if constexpr (SZ <= kSizeFirst) {
259 return First::template AllTrue<SZ>(a.first);
260 } else {
261 return First::template AllTrue<kSizeFirst>(a.first) &&
262 Second::template AllTrue<SZ - kSizeFirst>(a.second);
263 }
264 }
265
266 template <int SZ = kSize>
267 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE bool AnyTrue(Simd a) {
268 if constexpr (SZ <= kSizeFirst) {
269 return First::template AnyTrue<SZ>(a.first);
270 } else {
271 return First::template AnyTrue<kSizeFirst>(a.first) ||
272 Second::template AnyTrue<SZ - kSizeFirst>(a.second);
273 }
274 }
275
277 static_assert(kSize == 4, "Unsupported size");
278 return {a.first, Second::template Set<1>(a.second, Scalar(0))};
279 }
280
281 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Simd AsPoint(Simd a) {
282 static_assert(kSize == 4, "Unsupported size");
283 return {a.first, Second::template Set<1>(a.second, Scalar(1))};
284 }
285
286 template <int x, int y, int z, int w>
287 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Simd Blend(Simd a, Simd b) {
288 static_assert(kSize == 4, "Unsupported size");
289 return {
290 First::template Blend<x, y>(a.first, b.first),
291 Second::template Blend<z, w>(a.second, b.second)};
292 }
293
294 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Simd Broadcast(Scalar const* p) {
295 return {First::Broadcast(p), Second::Broadcast(p)};
296 }
297
299 return {First{a}, Second{a}};
300 }
301
302 template <int i>
304 if constexpr (kSizeFirst == kSizeSecond) {
305 if constexpr (i < kSizeFirst) {
306 auto x = First::template Broadcast<i>(a.first);
307 return {x, x};
308 } else {
309 auto x = Second::template Broadcast<i - kSizeFirst>(a.second);
310 return {x, x};
311 }
312 } else {
313 Scalar s = Get<i>(a); // Not optimized
314 return {First{s}, Second{s}};
315 }
316 }
317
318 template <int SZ = kSize>
319 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Simd Load(T const* ptr) {
320 if constexpr (SZ <= kSizeFirst) {
321 return {First::template Load<SZ>(ptr), Second{}};
322 } else {
323 return {
324 First::template Load<kSizeFirst>(ptr),
325 Second::template Load<SZ - kSizeFirst>(ptr + kSizeFirst)};
326 }
327 }
328
329 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Simd Load(Scalar const* ptr, int n) {
330 MOCHI_ASSERT_VERBOSE(n >= 0 && n <= kSize, "Invalid size parameter");
331 if (n <= kSizeFirst) {
332 return {First::Load(ptr, n), Second{}};
333 } else {
334 return {First::Load(ptr), Second::Load(ptr + kSizeFirst, n - kSizeFirst)};
335 }
336 }
337
338 template <typename IntT, MOCHI_CONCEPT(std::is_integral_v<IntT>)>
339 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Simd
340 LoadIndexed(Scalar const* ptr, Simd<IntT, kSize> const& indices) {
341 using IMatch = std::conditional_t<sizeof(Scalar) == 4, int, int64_t>; // int same size as Scalar
342 using IVec = Simd<IMatch, kSize>;
343 static_assert(IVec::kIsComposite && sizeof(IVec) == sizeof(Simd));
344 auto matchingIndices = StaticCast<IVec>(indices); // maybe no change
345 return {
346 First::LoadIndexed(ptr, matchingIndices.first),
347 Second::LoadIndexed(ptr, matchingIndices.second)};
348 }
349
350 template <int kTupleCount = kSize, class... OutputVectors>
352 Scalar const* ptr,
353 OutputVectors&... out) {
354 static_assert(kTupleCount >= 1 && kTupleCount <= kSize, "Invalid kTupleCount");
355 if constexpr (kTupleCount <= kSizeFirst) {
356 First::template LoadTransposed<kTupleCount>(ptr, out.first...);
357 ((out.second = {}), ...); // Zero-fill the second part
358 } else {
359 First::template LoadTransposed<kSizeFirst>(ptr, out.first...);
360 Second::template LoadTransposed<kTupleCount - kSizeFirst>(
361 ptr + sizeof...(OutputVectors) * kSizeFirst, out.second...);
362 }
363 }
364
365 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Simd Select(Simd mask, Simd a, Simd b) {
366 return {
367 First::Select(mask.first, a.first, b.first),
368 Second::Select(mask.second, a.second, b.second)};
369 }
370
371 template <int kShift>
373 static_assert(kShift >= 0 && kShift < (8 * sizeof(T)), "Shift amount out-of-range");
374 if constexpr (kShift == 0) {
375 return a;
376 } else {
377 return {
378 First::template ShiftRight<kShift>(a.first),
379 Second::template ShiftRight<kShift>(a.second)};
380 }
381 }
382
383 template <int SZ = kSize>
385 if constexpr (SZ <= kSizeFirst) {
386 First::template Store<SZ>(ptr, a.first);
387 } else {
388 First::template Store<kSizeFirst>(ptr, a.first);
389 Second::template Store<SZ - kSizeFirst>(ptr + kSizeFirst, a.second);
390 }
391 }
392
393 static MOCHI_ANY MOCHI_FORCE_INLINE void Store(Scalar* ptr, Simd v, int n) {
394 // Hopefully the compiler can figure out the best thing to do here.
395 // The alternative is to have a runtime branch at each stage.
396 memcpy(ptr, &v, sizeof(Scalar) * n);
397 }
398
399 static MOCHI_ANY MOCHI_FORCE_INLINE int StoreSelected(Scalar* ptr, Simd condition, Simd values) {
400 int count = First::StoreSelected(ptr, condition.first, values.first);
401 count += Second::StoreSelected(ptr + count, condition.second, values.second);
402 return count;
403 }
404
405 template <int kTupleCount = kSize, class... InputVectors>
406 static MOCHI_ANY MOCHI_FORCE_INLINE void StoreTransposed(Scalar* ptr, InputVectors... v) {
407 First::template StoreTransposed<superdex::Min(kTupleCount, First::kSize)>(ptr, v.first...);
408 if constexpr (kTupleCount > First::kSize) {
409 Second::template StoreTransposed<kTupleCount - First::kSize>(
410 ptr + sizeof...(InputVectors) * kSizeFirst, v.second...);
411 }
412 }
413
415 return {First::SignBitMask(), Second::SignBitMask()};
416 }
417
418 template <int SZ = kSize>
419 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Scalar HMin(Simd a) {
420 if constexpr (SZ <= kSizeFirst) {
421 return First::template HMin<SZ>(a.first);
422 } else if constexpr (SZ - kSizeFirst == 1) {
423 // HMin<1> is not normally supported, so use a scalar
424 return superdex::Min(
425 First::template HMin<kSizeFirst>(a.first), Second::template Get<0>(a.second));
426 } else if constexpr (kSizeFirst == kSizeSecond && SZ == kSize) {
427 // Special case for 2x native size. One horizontal operation.
428 return First::template HMin<kSizeFirst>(First::Min(a.first, a.second));
429 } else if constexpr (kSizeFirst * 2 == kSizeSecond && SZ == kSize) {
430 // Special case for 3x native size. One horizontal operation.
431 return First::template HMin<kSizeFirst>(
432 First::Min(a.first, First::Min(a.second.first, a.second.second)));
433 } else if constexpr (kSizeFirst * 3 == kSizeSecond && SZ == kSize) {
434 // Special case for 4x native size. One horizontal operation.
435 return First::template HMin<kSizeFirst>(First::Min(
436 a.first,
437 First::Min(a.second.first, First::Min(a.second.second.first, a.second.second.second))));
438 } else {
439 // May not be as fast due to repeated horizontal operations
440 return superdex::Min(
441 First::template HMin<kSizeFirst>(a.first),
442 Second::template HMin<SZ - kSizeFirst>(a.second));
443 }
444 }
445
446 template <int SZ = kSize>
447 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Scalar HMax(Simd a) {
448 if constexpr (SZ <= kSizeFirst) {
449 return First::template HMax<SZ>(a.first);
450 } else if constexpr (SZ - kSizeFirst == 1) {
451 // HMax<1> is not normally supported, so use a scalar
452 return superdex::Max(
453 First::template HMax<kSizeFirst>(a.first), Second::template Get<0>(a.second));
454 } else if constexpr (kSizeFirst == kSizeSecond && SZ == kSize) {
455 // Special case for 2x native size. One horizontal operation.
456 return First::template HMax<kSizeFirst>(First::Max(a.first, a.second));
457 } else if constexpr (kSizeFirst * 2 == kSizeSecond && SZ == kSize) {
458 // Special case for 3x native size. One horizontal operation.
459 return First::template HMax<kSizeFirst>(
460 First::Max(a.first, First::Max(a.second.first, a.second.second)));
461 } else if constexpr (kSizeFirst * 3 == kSizeSecond && SZ == kSize) {
462 // Special case for 4x native size. One horizontal operation.
463 return First::template HMax<kSizeFirst>(First::Max(
464 a.first,
465 First::Max(a.second.first, First::Max(a.second.second.first, a.second.second.second))));
466 } else {
467 // May not be as fast due to repeated horizontal operations
468 return superdex::Max(
469 First::template HMax<kSizeFirst>(a.first),
470 Second::template HMax<SZ - kSizeFirst>(a.second));
471 }
472 }
473
474 template <int SZ = kSize>
475 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Scalar HSum(Simd a) {
476 if constexpr (SZ <= kSizeFirst) {
477 return First::template HSum<SZ>(a.first);
478 } else if constexpr (SZ - kSizeFirst == 1) {
479 // HSum<1> is not normally supported, so add a scalar
480 return First::template HSum<kSizeFirst>(a.first) + Second::template Get<0>(a.second);
481 } else if constexpr (kSizeFirst == kSizeSecond && SZ == kSize) {
482 // Special case for 2x native size. One horizontal operation.
483 return First::template HSum<kSizeFirst>(a.first + a.second);
484 } else if constexpr (kSizeFirst * 2 == kSizeSecond && SZ == kSize) {
485 // Special case for 3x native size. One horizontal operation.
486 return First::template HSum<kSizeFirst>(a.first + a.second.first + a.second.second);
487 } else if constexpr (kSizeFirst * 3 == kSizeSecond && SZ == kSize) {
488 // Special case for 4x native size. One horizontal operation.
489 return First::template HSum<kSizeFirst>(
490 a.first + a.second.first + a.second.second.first + a.second.second.second);
491 } else {
492 // May not be as fast due to repeated horizontal operations
493 return First::template HSum<kSizeFirst>(a.first) +
494 Second::template HSum<SZ - kSizeFirst>(a.second);
495 }
496 }
497
498 template <int SZ = kSize>
499 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Scalar HProd(Simd a) {
500 if constexpr (SZ <= kSizeFirst) {
501 return First::template HProd<SZ>(a.first);
502 } else if constexpr (SZ - kSizeFirst == 1) {
503 // HProd<1> is not normally supported, so multiply a scalar
504 return First::template HProd<kSizeFirst>(a.first) * Second::template Get<0>(a.second);
505 } else if constexpr (kSizeFirst == kSizeSecond && SZ == kSize) {
506 return First::template HProd<kSizeFirst>(a.first * a.second);
507 } else {
508 // May not be as fast due to repeated horizontal operations
509 return First::template HProd<kSizeFirst>(a.first) *
510 Second::template HProd<SZ - kSizeFirst>(a.second);
511 }
512 }
513
514 template <int SZ = kSize>
515 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Simd Dot(Simd a, Simd b) {
516 return Broadcast(HSum<SZ>(a * b)); // Use HSum to hopefully avoid repeated horizontal operations
517 }
518
519 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Simd Zero() {
520 return {First::Zero(), Second::Zero()};
521 }
522
524
525 template <class U, MOCHI_REQUIRES_NON_BOOL_SCALAR(U, Scalar)>
527 first = rhs;
528 second = rhs;
529 return *this;
530 }
531
532 [[nodiscard]] MOCHI_ANY MOCHI_FORCE_INLINE bool operator==(Simd rhs) const {
533 if constexpr (kSizeFirst == kSizeSecond) {
534 // Special case for 2x native size. Just one AllTrue.
535 return First::template AllTrue<kSizeFirst>(
536 First::Equal(this->first, rhs.first) & Second::Equal(this->second, rhs.second));
537 } else {
538 return (this->first == rhs.first) && (this->second == rhs.second);
539 }
540 }
541
542 [[nodiscard]] MOCHI_ANY MOCHI_FORCE_INLINE bool operator!=(Simd rhs) const {
543 return !(*this == rhs);
544 }
545
546 [[nodiscard]] MOCHI_ANY MOCHI_FORCE_INLINE Scalar operator[](int i) const {
547 return Get(*this, i); /* return by value */
548 }
549
550 [[nodiscard]] MOCHI_ANY MOCHI_FORCE_INLINE Simd operator<<(int i) const {
551 return {this->first << i, this->second << i};
552 }
553
555 this->first <<= i;
556 this->second <<= i;
557 return *this;
558 }
559
560 [[nodiscard]] MOCHI_ANY MOCHI_FORCE_INLINE Simd operator>>(int i) const {
561 return {this->first >> i, this->second >> i};
562 }
563
565 this->first >>= i;
566 this->second >>= i;
567 return *this;
568 }
569
570#define MOCHI_SIMD_COMPOSITE_FN_1(FnName) \
571 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Simd FnName(Simd a) { \
572 return {First::FnName(a.first), Second::FnName(a.second)}; \
573 }
574
575#define MOCHI_SIMD_COMPOSITE_FN_2(FnName) \
576 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Simd FnName(Simd a, Simd b) { \
577 return {First::FnName(a.first, b.first), Second::FnName(a.second, b.second)}; \
578 }
579
580#define MOCHI_SIMD_COMPOSITE_FN_3(FnName) \
581 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Simd FnName(Simd a, Simd b, Simd c) { \
582 return { \
583 First::FnName(a.first, b.first, c.first), Second::FnName(a.second, b.second, c.second)}; \
584 }
585
586#define MOCHI_SIMD_COMPOSITE_OP_1(OP) \
587 [[nodiscard]] MOCHI_ANY MOCHI_FORCE_INLINE Simd operator OP() const { \
588 return {OP this->first, OP this->second}; \
589 }
590
591#define MOCHI_SIMD_COMPOSITE_OP_2(OP) \
592 [[nodiscard]] MOCHI_ANY MOCHI_FORCE_INLINE Simd operator OP(Simd rhs) const { \
593 return {this->first OP rhs.first, this->second OP rhs.second}; \
594 }
595
596#define MOCHI_SIMD_COMPOSITE_OP_EQ(OP_EQ, OP) \
597 MOCHI_ANY MOCHI_FORCE_INLINE Simd& operator OP_EQ(Simd a) { \
598 this->first = this->first OP a.first; \
599 this->second = this->second OP a.second; \
600 return *this; \
601 }
602
603#define MOCHI_SIMD_COMPOSITE_OP_EQ_WITH_SCALAR(OP_EQ, OP) \
604 MOCHI_SIMD_COMPOSITE_OP_EQ(OP_EQ, OP); \
605 MOCHI_ANY MOCHI_FORCE_INLINE Simd& operator OP_EQ(Scalar a) { \
606 this->first = this->first OP First{a}; \
607 this->second = this->second OP Second{a}; \
608 return *this; \
609 }
610
611 // Unary functions:
627
628 // Binary functions
633
634 // Ternary functions
639
640 // Unary operators
643
644 // Binary operators
656
657 // Math assignment operators
658 //
659 // VS2019 WORKAROUND: These operators should not be necessary because there are generic templates
660 // in simd_inl.h which convert operations like `a += b;` into `a = a + b;`. However, there is
661 // a bug in the VS2019 compiler resulting in incorrect runtime behavior in some of these cases.
662 // The operators here take precedence and shortcut a couple layers of template abstraction,
663 // which appears to be a sufficient work-around. The bug was only observed with VS2019
664 // optimized builds, not with VS2022, Clang, nor GCC.
672
673#undef MOCHI_SIMD_COMPOSITE_FN_1
674#undef MOCHI_SIMD_COMPOSITE_FN_2
675#undef MOCHI_SIMD_COMPOSITE_FN_3
676#undef MOCHI_SIMD_COMPOSITE_OP_1
677#undef MOCHI_SIMD_COMPOSITE_OP_2
678#undef MOCHI_SIMD_COMPOSITE_OP_EQ
679#undef MOCHI_SIMD_COMPOSITE_OP_EQ_WITH_SCALAR
680};
681
682// ReinterpretCast for composite Simd types
683template <
684 class To,
685 class FromT,
686 int FromN,
687 MOCHI_CONCEPT((Simd<FromT, FromN>::kIsComposite) && To::kIsComposite)>
688[[nodiscard]] MOCHI_ANY MOCHI_FORCE_INLINE To ReinterpretCast(Simd<FromT, FromN> const& a) {
689 return {
690 ReinterpretCast<typename To::First>(a.first), ReinterpretCast<typename To::Second>(a.second)};
691}
692
693namespace details {
694template <typename T>
695[[nodiscard]] constexpr bool IsCompositeWithEqualHalves() {
696 if constexpr (T::kIsComposite) {
697 return T::kSizeFirst == T::kSizeSecond;
698 } else {
699 return false;
700 }
701}
702
703template <class To, class From, std::size_t... Is>
704[[nodiscard]] MOCHI_ANY MOCHI_FORCE_INLINE To
705StaticCastElementwise(From const& a, std::index_sequence<Is...>) {
706 return To{static_cast<typename To::Scalar>(a[Is])...};
707}
708} // namespace details
709
710// StaticCast when at least one of the types is a Simd composite.
711// Also handles the pass-through case where To and From are the same.
712template <class To, class From, MOCHI_CONCEPT_DEF(IsSimd<To>&& IsSimd<From>)>
713[[nodiscard]] MOCHI_ANY MOCHI_FORCE_INLINE To StaticCast(From const& a) {
714 static_assert(To::kSize == From::kSize, "Size mismatch");
715 static_assert(To::kIsSupported, "Unsupported type");
716 using ToHalf = Simd<typename To::Scalar, To::kSize / 2>;
717 using FromHalf = Simd<typename From::Scalar, From::kSize / 2>;
718 if constexpr (std::is_same_v<typename To::Scalar, typename From::Scalar>) {
719 return a; // No change
720 } else if constexpr (
721 To::kIsComposite && From::kIsComposite &&
722 (sizeof(typename To::Scalar) == sizeof(typename From::Scalar))) {
723 // Casting a composite to another composite, where the native sizes match
724 // Examples: Vec16f <--> Vec16i
726 } else if constexpr (
727 From::kIsComposite && !To::kIsComposite && ToHalf::kIsSupported &&
728 (sizeof(typename To::Scalar) * 2 == sizeof(typename From::Scalar))) {
729 // Casting from a composite to a native vector, where the destination scalar type is smaller
730 // Examples: Vec8i <-- Vec8d, Vec8f <-- Vec8d (using AVX)
731 return {StaticCast<ToHalf>(a.first), StaticCast<ToHalf>(a.second)};
732 } else if constexpr (
733 To::kIsComposite && !From::kIsComposite && FromHalf::kIsSupported &&
734 (sizeof(typename To::Scalar) == 2 * sizeof(typename From::Scalar))) {
735 // Casting from a native size to a composite, where the destination scalar type is larger
736 // Examples: Vec8d <-- Vec8i, Vec8d <-- Vec8f (using AVX)
737 return {
739 StaticCast<typename To::Second>(From::template GetHalf<1>(a))}; // Not optimized
740 } else if constexpr (
741 ::superdex::details::IsCompositeWithEqualHalves<From>() &&
742 ::superdex::details::IsCompositeWithEqualHalves<To>() &&
743 sizeof(typename To::Scalar) != sizeof(typename From::Scalar)) {
744 // Both sides are composite with equal-sized halves (i.e. GetHalf<0/1> can split them
745 // symmetrically), but their tree shapes may differ due to different native SIMD widths
746 // (e.g. Half↔float or double↔float on ARM), so we can't recurse via .first/.second directly.
747 return To{
748 StaticCast<ToHalf>(From::template GetHalf<0>(a)),
749 StaticCast<ToHalf>(From::template GetHalf<1>(a))};
750 } else {
751 return superdex::details::StaticCastElementwise<To>(a, std::make_index_sequence<From::kSize>{});
752 }
753}
754
755} // namespace superdex
Simd(Scalar a, Scalar b, Scalar c, Scalar d, Scalar e, Scalar f, Scalar g, Scalar h, MoreScalars... args)
static constexpr bool kIsComposite
Definition simd.h:98
static constexpr bool kIsEmulated
Definition simd.h:99
static constexpr bool kIsSupported
Definition simd.h:97
#define MOCHI_ASSERT_VERBOSE(condition_without_side_effects,...)
Definition debug.h:102
#define MOCHI_CONCEPT(a)
#define MOCHI_FORCE_INLINE
#define MOCHI_ANY
#define MOCHI_NO_INIT
Simd< T, 2 > Shuffle(Simd< T, 2 > a)
Definition simd_inl.h:270
constexpr T const & Min(T const &a, T const &b)
T HSum(Simd< T, N > a)
Definition simd_inl.h:379
constexpr To StaticCast(From const &a)
Definition basic_utils.h:79
T HMin(Simd< T, N > a)
Definition simd_inl.h:391
bool AllTrue(T const &a)
Definition basic_utils.h:60
Simd< T, N > Set(Simd< T, N > a, T value)
Definition simd_inl.h:315
T HMax(Simd< T, N > a)
Definition simd_inl.h:397
V Broadcast(typename V::Scalar a)
Definition simd_inl.h:115
Simd< T, N > Blend(Simd< T, N > a, Simd< T, N > b)
Definition simd_inl.h:285
bool AnyTrue(T const &a)
Definition basic_utils.h:66
T Get(Simd< T, N > v)
Definition simd_inl.h:300
Simd< T, N/2 > GetHalf(Simd< T, N > a)
Definition simd_inl.h:310
T HProd(Simd< T, N > a)
Definition simd_inl.h:385
constexpr T const & Max(T const &a, T const &b)
void LoadTransposed(T const *ptr, Simd< T, N > &out0, Simd< T, N > &out1, Simd< T, N > &out2)
Definition simd_inl.h:207
void StoreTransposed(T *ptr, Simd< T, N > a, Simd< T, N > b, Simd< T, N > c)
Definition simd_inl.h:245
void Store(T *ptr, Simd< T, N > a)
Definition simd_inl.h:213
V Load(typename V::Scalar const *ptr)
Definition simd_inl.h:184
Simd< T, N > ShiftRight(Simd< T, N > a)
Definition simd_inl.h:260
#define MOCHI_SIMD_COMPOSITE_FN_3(FnName)
#define MOCHI_SIMD_COMPOSITE_OP_1(OP)
#define MOCHI_SIMD_COMPOSITE_OP_EQ(OP_EQ, OP)
#define MOCHI_SIMD_COMPOSITE_FN_2(FnName)
#define MOCHI_SIMD_COMPOSITE_OP_2(OP)
#define MOCHI_SIMD_COMPOSITE_OP_EQ_WITH_SCALAR(OP_EQ, OP)
#define MOCHI_SIMD_COMPOSITE_FN_1(FnName)