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
26namespace details {
27
28template <class T, int N, int Candidate>
29[[nodiscard]] constexpr int FindCompositeFirstSize() {
30 if constexpr (Candidate <= 0) {
31 return 0;
32 } else if constexpr (Candidate >= N) {
33 return FindCompositeFirstSize<T, N, Candidate / 2>();
34 } else if constexpr (
36 Simd<T, N - Candidate>::kIsSupported) {
37 return Candidate;
38 } else {
39 return FindCompositeFirstSize<T, N, Candidate / 2>();
40 }
41}
42
43template <class T, int N>
44inline constexpr int kCompositeFirstSize = FindCompositeFirstSize<T, N, kSimdDefaultSize<T>>();
45
46template <class V, size_t... Is>
47[[nodiscard]] MOCHI_ANY MOCHI_FORCE_INLINE V MakeSimdSequence(std::index_sequence<Is...>) {
48 return V{static_cast<typename V::Scalar>(Is)...};
49}
50
51} // namespace details
52
53/**
54 Simd partial specialization for larger values of N.
55 Supports any N as long as it can be composed of smaller supported Simd objects.
56*/
57template <class T, int N>
58class Simd<T, N, std::enable_if_t<(details::kCompositeFirstSize<T, N> > 0), SimdConcept>> {
59 public:
60 using Scalar = T;
61
62 // Data storage is split into two parts
64 using First = NativeType; // Largest native size that leaves a supported remainder
65 using Second = Simd<T, N - First::kSize>; // What's left
68
69 static constexpr int kSize = N;
70 static constexpr int kSizeFirst = First::kSize;
71 static constexpr int kSizeSecond = Second::kSize;
73 static constexpr bool kIsComposite = true;
75 static_assert(
77 "Inconsistent SIMD emulation flags");
78
79 // Default construct (may be uninitialized)
81
82 // Copy construct
84
85 // Construct from Simd parts
86 MOCHI_ANY MOCHI_FORCE_INLINE Simd(First const& p0, Second const& p1) : first(p0), second(p1) {}
87
88 // Construct from halves, independent of this type's composite tree shape.
89 template <
90 class Half,
92 (IsSimd<Half> && std::is_same_v<Scalar, typename Half::Scalar> &&
93 (Half::kSize * 2 == kSize)))>
94 MOCHI_ANY MOCHI_FORCE_INLINE Simd(Half const& a, Half const& b) {
95 if constexpr (Half::kIsComposite && Second::kIsComposite) {
96 using SecondTail = typename Second::Second;
97 if constexpr (SecondTail::kIsComposite) {
98 if constexpr (
99 std::is_same_v<First, typename Half::First> &&
100 std::is_same_v<typename Second::First, typename Half::Second> &&
101 std::is_same_v<typename SecondTail::First, typename Half::First> &&
102 std::is_same_v<typename SecondTail::Second, typename Half::Second>) {
103 first = a.first;
104 second.first = a.second;
105 second.second.first = b.first;
106 second.second.second = b.second;
107 return;
108 }
109 }
110 }
111 alignas(First) Scalar values[kSize];
112 Half::Store(values, a);
113 Half::Store(values + Half::kSize, b);
114 first = First::Load(values);
115 second = Second::Load(values + kSizeFirst);
116 }
117
118 // Construct by broadcasting a scalar
119 template <class U, MOCHI_REQUIRES_NON_BOOL_SCALAR(U, Scalar)>
121
122 // Construct from 2 scalars. Any unspecified values are zero.
124
125 // Construct from 3 or 4 scalars. Any unspecified values are zero.
127 if constexpr (kSizeFirst == 2) {
128 first = First{a, b};
129 second = Second{c, d};
130 } else {
131 first = First{a, b, c, d};
132 second = Second{};
133 }
134 }
135
136 // Construct from 5 or 6 scalars. Any unspecified values are zero.
139 if constexpr (kSizeFirst == 2) {
140 first = First{a, b};
141 second = Second{c, d, e, f};
142 } else if constexpr (kSizeFirst == 4) {
143 first = First{a, b, c, d};
144 second = Second{e, f};
145 } else {
146 first = First{a, b, c, d, e, f};
147 second = {};
148 }
149 }
150
151 // Construct from 7 scalars. Any unspecified values are zero.
154 if constexpr (kSizeFirst == 2) {
155 first = First{a, b};
156 second = Second{c, d, e, f, g};
157 } else if constexpr (kSizeFirst == 4) {
158 first = First{a, b, c, d};
159 second = Second{e, f, g};
160 } else {
161 first = First{a, b, c, d, e, f, g};
162 second = {};
163 }
164 }
165
166 // Construct from 8 or more scalars. Any unspecified values are zero.
167 template <
168 class... MoreScalars,
170 (sizeof...(MoreScalars) + 8 <= kSize) &&
171 (kSizeFirst != 16 || sizeof...(MoreScalars) + 8 <= 16) &&
172 (std::is_convertible_v<MoreScalars, Scalar> && ...))>
174 Scalar a,
175 Scalar b,
176 Scalar c,
177 Scalar d,
178 Scalar e,
179 Scalar f,
180 Scalar g,
181 Scalar h,
182 MoreScalars... args) {
183 if constexpr (kSizeFirst == 2) {
184 first = First{a, b};
185 second = Second{c, d, e, f, g, h, args...};
186 } else if constexpr (kSizeFirst == 4) {
187 first = First{a, b, c, d};
188 second = Second{e, f, g, h, args...};
189 } else if constexpr (kSizeFirst == 8) {
190 first = First{a, b, c, d, e, f, g, h};
191 static constexpr auto kNumArgs = sizeof...(args);
192 if constexpr (kNumArgs == 0) {
193 second = Second{};
194 } else if constexpr (kNumArgs == 1) {
195 second = Second{args..., Scalar(0)};
196 } else {
197 second = Second{args...};
198 }
199 } else if constexpr (sizeof...(args) + 8 <= kSizeFirst) {
200 first = First{a, b, c, d, e, f, g, h, args...};
201 second = {};
202 } else {
203 alignas(First) Scalar lanes[kSize] = {a, b, c, d, e, f, g, h, static_cast<Scalar>(args)...};
204 first = First::Load(lanes);
205 second = Second::Load(lanes + kSizeFirst);
206 }
207 }
208
209 // Construct from 16 or more scalars. Any unspecified values are zero.
210 template <
211 class... MoreScalars,
213 (kSizeFirst == 16) && (sizeof...(MoreScalars) + 16 <= kSize) &&
214 (std::is_convertible_v<MoreScalars, Scalar> && ...))>
216 Scalar a,
217 Scalar b,
218 Scalar c,
219 Scalar d,
220 Scalar e,
221 Scalar f,
222 Scalar g,
223 Scalar h,
224 Scalar i,
225 Scalar j,
226 Scalar k,
227 Scalar l,
228 Scalar m,
229 Scalar n,
230 Scalar o,
231 Scalar p,
232 MoreScalars... args) {
233 first = First{a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p};
234 static constexpr auto kNumArgs = sizeof...(args);
235 if constexpr (kNumArgs == 0) {
236 second = Second{};
237 } else if constexpr (kNumArgs == 1) {
238 second = Second{args..., Scalar(0)};
239 } else {
240 second = Second{args...};
241 }
242 }
243
244 MOCHI_ANY MOCHI_FORCE_INLINE static constexpr size_t size() {
245 return kSize;
246 }
247
248 template <int i>
249 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Scalar Get(Simd a) {
250 static_assert(i >= 0 && i < kSize, "Index out of range");
251 return a[i];
252 }
253
254 template <int i>
255 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE auto GetHalf(Simd a) {
256 using Half = Simd<Scalar, N / 2>;
257 static_assert(i == 0 || i == 1, "Half index out of range");
258 static_assert(N % 2 == 0, "Vector size must be even");
259 static_assert(Half::kIsSupported);
260 if constexpr (kSizeFirst == kSizeSecond) {
261 if constexpr (i == 0) {
262 return a.first;
263 } else {
264 return a.second;
265 }
266 } else {
267 if constexpr (Half::kIsComposite && Second::kIsComposite) {
268 using SecondTail = typename Second::Second;
269 if constexpr (SecondTail::kIsComposite) {
270 if constexpr (
271 std::is_same_v<First, typename Half::First> &&
272 std::is_same_v<typename Second::First, typename Half::Second> &&
273 std::is_same_v<typename SecondTail::First, typename Half::First> &&
274 std::is_same_v<typename SecondTail::Second, typename Half::Second>) {
275 if constexpr (i == 0) {
276 return Half{a.first, a.second.first};
277 } else {
278 return Half{a.second.second.first, a.second.second.second};
279 }
280 }
281 }
282 }
283 alignas(First) Scalar values[kSize];
284 Store(values, a);
285 return Half::Load(values + i * Half::kSize);
286 }
287 }
288
289 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Simd Set(Simd a, int i, Scalar value) {
290 if (i < kSizeFirst) {
291 return {First::Set(a.first, i, value), a.second};
292 } else {
293 return {a.first, Second::Set(a.second, i - kSizeFirst, value)};
294 }
295 }
296
297 template <int i>
298 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Simd Set(Simd a, Scalar value) {
299 if constexpr (i < kSizeFirst) {
300 return {First::template Set<i>(a.first, value), a.second};
301 } else {
302 return {a.first, Second::template Set<i - kSizeFirst>(a.second, value)};
303 }
304 }
305
306 template <int i>
308 static_assert(kSize == 4, "Unsupported size");
309 return Set<i>(Zero(), Scalar(1));
310 }
311
312 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Simd Sequence() {
313 if constexpr (IsHalf<Scalar>) {
314 return details::MakeSimdSequence<Simd>(std::make_index_sequence<kSize>{});
315 } else {
316 alignas(First) Scalar constexpr kSequence[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
317 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
318 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
319 static_assert(First::kSize <= 32, "Vector size is too large");
320 auto first = First::Load(kSequence);
322 if constexpr (std::is_same_v<First, Second>) {
323 second = first;
324 } else if constexpr (Second::kIsComposite) {
325 second = Second::Sequence();
326 } else {
327 static_assert(Second::kSize <= 32, "Vector size is too large");
328 second = Second::Load(kSequence);
329 }
330 return Simd{first, second + Second{static_cast<T>(First::kSize)}};
331 }
332 }
333
334 template <int x = 0, int y = 1, int z = 2, int w = 3>
335 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Simd Shuffle(Simd a, Simd b) {
336 static_assert(kSize == 4, "Unsupported size");
337 return {Get<x>(a), Get<y>(a), Get<z>(b), Get<w>(b)}; // Not optimized
338 }
339
340 template <int x = 0, int y = 1, int z = 2, int w = 3>
341 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Simd Shuffle(Simd a) {
342 static_assert(kSize == 4, "Unsupported size");
343 return Shuffle<x, y, z, w>(a, a);
344 }
345
346 template <int SZ = kSize>
347 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE bool AllTrue(Simd a) {
348 static_assert(SZ <= kSize);
349 if constexpr (SZ <= kSizeFirst) {
350 return First::template AllTrue<SZ>(a.first);
351 } else {
352 return First::template AllTrue<kSizeFirst>(a.first) &&
353 Second::template AllTrue<SZ - kSizeFirst>(a.second);
354 }
355 }
356
357 template <int SZ = kSize>
358 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE bool AnyTrue(Simd a) {
359 if constexpr (SZ <= kSizeFirst) {
360 return First::template AnyTrue<SZ>(a.first);
361 } else {
362 return First::template AnyTrue<kSizeFirst>(a.first) ||
363 Second::template AnyTrue<SZ - kSizeFirst>(a.second);
364 }
365 }
366
368 static_assert(kSize == 4, "Unsupported size");
369 return {a.first, Second::template Set<1>(a.second, Scalar(0))};
370 }
371
372 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Simd AsPoint(Simd a) {
373 static_assert(kSize == 4, "Unsupported size");
374 return {a.first, Second::template Set<1>(a.second, Scalar(1))};
375 }
376
377 template <int x, int y, int z, int w>
378 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Simd Blend(Simd a, Simd b) {
379 static_assert(kSize == 4, "Unsupported size");
380 return {
381 First::template Blend<x, y>(a.first, b.first),
382 Second::template Blend<z, w>(a.second, b.second)};
383 }
384
385 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Simd Broadcast(Scalar const* p) {
386 return {First::Broadcast(p), Second::Broadcast(p)};
387 }
388
390 return {First{a}, Second{a}};
391 }
392
393 template <int i>
395 if constexpr (kSizeFirst == kSizeSecond) {
396 if constexpr (i < kSizeFirst) {
397 auto x = First::template Broadcast<i>(a.first);
398 return {x, x};
399 } else {
400 auto x = Second::template Broadcast<i - kSizeFirst>(a.second);
401 return {x, x};
402 }
403 } else {
404 Scalar s = Get<i>(a); // Not optimized
405 return {First{s}, Second{s}};
406 }
407 }
408
409 template <int SZ = kSize>
410 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Simd Load(T const* ptr) {
411 if constexpr (SZ <= kSizeFirst) {
412 return {First::template Load<SZ>(ptr), Second{}};
413 } else {
414 return {
415 First::template Load<kSizeFirst>(ptr),
416 Second::template Load<SZ - kSizeFirst>(ptr + kSizeFirst)};
417 }
418 }
419
420 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Simd Load(Scalar const* ptr, int n) {
421 MOCHI_ASSERT_VERBOSE(n >= 0 && n <= kSize, "Invalid size parameter");
422 if (n <= kSizeFirst) {
423 return {First::Load(ptr, n), Second{}};
424 } else {
425 return {First::Load(ptr), Second::Load(ptr + kSizeFirst, n - kSizeFirst)};
426 }
427 }
428
429 template <typename IntT, MOCHI_CONCEPT(std::is_integral_v<IntT>)>
430 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Simd
431 LoadIndexed(Scalar const* ptr, Simd<IntT, kSize> const& indices) {
432 using IMatch = std::conditional_t<sizeof(Scalar) == 4, int, int64_t>; // int same size as Scalar
433 using IVec = Simd<IMatch, kSize>;
434 static_assert(IVec::kIsComposite && sizeof(IVec) == sizeof(Simd));
435 auto matchingIndices = StaticCast<IVec>(indices); // maybe no change
436 return {
437 First::LoadIndexed(ptr, matchingIndices.first),
438 Second::LoadIndexed(ptr, matchingIndices.second)};
439 }
440
441 template <int kTupleCount = kSize, class... OutputVectors>
443 Scalar const* ptr,
444 OutputVectors&... out) {
445 static_assert(kTupleCount >= 1 && kTupleCount <= kSize, "Invalid kTupleCount");
446 if constexpr (kTupleCount <= kSizeFirst) {
447 First::template LoadTransposed<kTupleCount>(ptr, out.first...);
448 ((out.second = {}), ...); // Zero-fill the second part
449 } else {
450 First::template LoadTransposed<kSizeFirst>(ptr, out.first...);
451 Second::template LoadTransposed<kTupleCount - kSizeFirst>(
452 ptr + sizeof...(OutputVectors) * kSizeFirst, out.second...);
453 }
454 }
455
456 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Simd Select(Simd mask, Simd a, Simd b) {
457 return {
458 First::Select(mask.first, a.first, b.first),
459 Second::Select(mask.second, a.second, b.second)};
460 }
461
462 template <int kShift>
464 static_assert(kShift >= 0 && kShift < (8 * sizeof(T)), "Shift amount out-of-range");
465 if constexpr (kShift == 0) {
466 return a;
467 } else {
468 return {
469 First::template ShiftRight<kShift>(a.first),
470 Second::template ShiftRight<kShift>(a.second)};
471 }
472 }
473
474 template <int SZ = kSize>
476 if constexpr (SZ <= kSizeFirst) {
477 First::template Store<SZ>(ptr, a.first);
478 } else {
479 First::template Store<kSizeFirst>(ptr, a.first);
480 Second::template Store<SZ - kSizeFirst>(ptr + kSizeFirst, a.second);
481 }
482 }
483
484 static MOCHI_ANY MOCHI_FORCE_INLINE void Store(Scalar* ptr, Simd v, int n) {
485 // Hopefully the compiler can figure out the best thing to do here.
486 // The alternative is to have a runtime branch at each stage.
487 memcpy(ptr, &v, sizeof(Scalar) * n);
488 }
489
490 static MOCHI_ANY MOCHI_FORCE_INLINE int StoreSelected(Scalar* ptr, Simd condition, Simd values) {
491 int count = First::StoreSelected(ptr, condition.first, values.first);
492 count += Second::StoreSelected(ptr + count, condition.second, values.second);
493 return count;
494 }
495
496 template <int kTupleCount = kSize, class... InputVectors>
497 static MOCHI_ANY MOCHI_FORCE_INLINE void StoreTransposed(Scalar* ptr, InputVectors... v) {
498 First::template StoreTransposed<superdex::Min(kTupleCount, First::kSize)>(ptr, v.first...);
499 if constexpr (kTupleCount > First::kSize) {
500 Second::template StoreTransposed<kTupleCount - First::kSize>(
501 ptr + sizeof...(InputVectors) * kSizeFirst, v.second...);
502 }
503 }
504
506 return {First::SignBitMask(), Second::SignBitMask()};
507 }
508
509 template <int SZ = kSize>
510 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Scalar HMin(Simd a) {
511 if constexpr (SZ <= kSizeFirst) {
512 return First::template HMin<SZ>(a.first);
513 } else if constexpr (SZ - kSizeFirst == 1) {
514 // HMin<1> is not normally supported, so use a scalar
515 return superdex::Min(
516 First::template HMin<kSizeFirst>(a.first), Second::template Get<0>(a.second));
517 } else if constexpr (kSizeFirst == kSizeSecond && SZ == kSize) {
518 // Special case for 2x native size. One horizontal operation.
519 return First::template HMin<kSizeFirst>(First::Min(a.first, a.second));
520 } else if constexpr (kSizeFirst * 2 == kSizeSecond && SZ == kSize) {
521 // Special case for 3x native size. One horizontal operation.
522 return First::template HMin<kSizeFirst>(
523 First::Min(a.first, First::Min(a.second.first, a.second.second)));
524 } else if constexpr (kSizeFirst * 3 == kSizeSecond && SZ == kSize) {
525 // Special case for 4x native size. One horizontal operation.
526 return First::template HMin<kSizeFirst>(First::Min(
527 a.first,
528 First::Min(a.second.first, First::Min(a.second.second.first, a.second.second.second))));
529 } else {
530 // May not be as fast due to repeated horizontal operations
531 return superdex::Min(
532 First::template HMin<kSizeFirst>(a.first),
533 Second::template HMin<SZ - kSizeFirst>(a.second));
534 }
535 }
536
537 template <int SZ = kSize>
538 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Scalar HMax(Simd a) {
539 if constexpr (SZ <= kSizeFirst) {
540 return First::template HMax<SZ>(a.first);
541 } else if constexpr (SZ - kSizeFirst == 1) {
542 // HMax<1> is not normally supported, so use a scalar
543 return superdex::Max(
544 First::template HMax<kSizeFirst>(a.first), Second::template Get<0>(a.second));
545 } else if constexpr (kSizeFirst == kSizeSecond && SZ == kSize) {
546 // Special case for 2x native size. One horizontal operation.
547 return First::template HMax<kSizeFirst>(First::Max(a.first, a.second));
548 } else if constexpr (kSizeFirst * 2 == kSizeSecond && SZ == kSize) {
549 // Special case for 3x native size. One horizontal operation.
550 return First::template HMax<kSizeFirst>(
551 First::Max(a.first, First::Max(a.second.first, a.second.second)));
552 } else if constexpr (kSizeFirst * 3 == kSizeSecond && SZ == kSize) {
553 // Special case for 4x native size. One horizontal operation.
554 return First::template HMax<kSizeFirst>(First::Max(
555 a.first,
556 First::Max(a.second.first, First::Max(a.second.second.first, a.second.second.second))));
557 } else {
558 // May not be as fast due to repeated horizontal operations
559 return superdex::Max(
560 First::template HMax<kSizeFirst>(a.first),
561 Second::template HMax<SZ - kSizeFirst>(a.second));
562 }
563 }
564
565 template <int SZ = kSize>
566 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Scalar HSum(Simd a) {
567 if constexpr (SZ <= kSizeFirst) {
568 return First::template HSum<SZ>(a.first);
569 } else if constexpr (SZ - kSizeFirst == 1) {
570 // HSum<1> is not normally supported, so add a scalar
571 return First::template HSum<kSizeFirst>(a.first) + Second::template Get<0>(a.second);
572 } else if constexpr (kSizeFirst == kSizeSecond && SZ == kSize) {
573 // Special case for 2x native size. One horizontal operation.
574 return First::template HSum<kSizeFirst>(a.first + a.second);
575 } else if constexpr (kSizeFirst * 2 == kSizeSecond && SZ == kSize) {
576 // Special case for 3x native size. One horizontal operation.
577 return First::template HSum<kSizeFirst>(a.first + a.second.first + a.second.second);
578 } else if constexpr (kSizeFirst * 3 == kSizeSecond && SZ == kSize) {
579 // Special case for 4x native size. One horizontal operation.
580 return First::template HSum<kSizeFirst>(
581 a.first + a.second.first + a.second.second.first + a.second.second.second);
582 } else {
583 // May not be as fast due to repeated horizontal operations
584 return First::template HSum<kSizeFirst>(a.first) +
585 Second::template HSum<SZ - kSizeFirst>(a.second);
586 }
587 }
588
589 template <int SZ = kSize>
590 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Scalar HProd(Simd a) {
591 if constexpr (SZ <= kSizeFirst) {
592 return First::template HProd<SZ>(a.first);
593 } else if constexpr (SZ - kSizeFirst == 1) {
594 // HProd<1> is not normally supported, so multiply a scalar
595 return First::template HProd<kSizeFirst>(a.first) * Second::template Get<0>(a.second);
596 } else if constexpr (kSizeFirst == kSizeSecond && SZ == kSize) {
597 return First::template HProd<kSizeFirst>(a.first * a.second);
598 } else {
599 // May not be as fast due to repeated horizontal operations
600 return First::template HProd<kSizeFirst>(a.first) *
601 Second::template HProd<SZ - kSizeFirst>(a.second);
602 }
603 }
604
605 template <int SZ = kSize>
606 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Simd Dot(Simd a, Simd b) {
607 return Broadcast(HSum<SZ>(a * b)); // Use HSum to hopefully avoid repeated horizontal operations
608 }
609
610 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Simd Zero() {
611 return {First::Zero(), Second::Zero()};
612 }
613
615
616 template <class U, MOCHI_REQUIRES_NON_BOOL_SCALAR(U, Scalar)>
618 first = rhs;
619 second = rhs;
620 return *this;
621 }
622
623 [[nodiscard]] MOCHI_ANY MOCHI_FORCE_INLINE bool operator==(Simd rhs) const {
624 if constexpr (kSizeFirst == kSizeSecond) {
625 // Special case for 2x native size. Just one AllTrue.
626 return First::template AllTrue<kSizeFirst>(
627 First::Equal(this->first, rhs.first) & Second::Equal(this->second, rhs.second));
628 } else {
629 return (this->first == rhs.first) && (this->second == rhs.second);
630 }
631 }
632
633 [[nodiscard]] MOCHI_ANY MOCHI_FORCE_INLINE bool operator!=(Simd rhs) const {
634 return !(*this == rhs);
635 }
636
637 [[nodiscard]] MOCHI_ANY MOCHI_FORCE_INLINE Scalar operator[](int i) const {
638 MOCHI_ASSERT_VERBOSE(i >= 0 && i < kSize, "Index out of range");
639 return i < kSizeFirst ? first[i] : second[i - kSizeFirst];
640 }
641
642 [[nodiscard]] MOCHI_ANY MOCHI_FORCE_INLINE Simd operator<<(int i) const {
643 return {this->first << i, this->second << i};
644 }
645
647 this->first <<= i;
648 this->second <<= i;
649 return *this;
650 }
651
652 [[nodiscard]] MOCHI_ANY MOCHI_FORCE_INLINE Simd operator>>(int i) const {
653 return {this->first >> i, this->second >> i};
654 }
655
657 this->first >>= i;
658 this->second >>= i;
659 return *this;
660 }
661
662#define MOCHI_SIMD_COMPOSITE_FN_1(FnName) \
663 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Simd FnName(Simd a) { \
664 return {First::FnName(a.first), Second::FnName(a.second)}; \
665 }
666
667#define MOCHI_SIMD_COMPOSITE_FN_2(FnName) \
668 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Simd FnName(Simd a, Simd b) { \
669 return {First::FnName(a.first, b.first), Second::FnName(a.second, b.second)}; \
670 }
671
672#define MOCHI_SIMD_COMPOSITE_FN_3(FnName) \
673 [[nodiscard]] static MOCHI_ANY MOCHI_FORCE_INLINE Simd FnName(Simd a, Simd b, Simd c) { \
674 return { \
675 First::FnName(a.first, b.first, c.first), Second::FnName(a.second, b.second, c.second)}; \
676 }
677
678#define MOCHI_SIMD_COMPOSITE_OP_1(OP) \
679 [[nodiscard]] MOCHI_ANY MOCHI_FORCE_INLINE Simd operator OP() const { \
680 return {OP this->first, OP this->second}; \
681 }
682
683#define MOCHI_SIMD_COMPOSITE_OP_2(OP) \
684 [[nodiscard]] MOCHI_ANY MOCHI_FORCE_INLINE Simd operator OP(Simd rhs) const { \
685 return {this->first OP rhs.first, this->second OP rhs.second}; \
686 }
687
688#define MOCHI_SIMD_COMPOSITE_OP_EQ(OP_EQ, OP) \
689 MOCHI_ANY MOCHI_FORCE_INLINE Simd& operator OP_EQ(Simd a) { \
690 this->first = this->first OP a.first; \
691 this->second = this->second OP a.second; \
692 return *this; \
693 }
694
695#define MOCHI_SIMD_COMPOSITE_OP_EQ_WITH_SCALAR(OP_EQ, OP) \
696 MOCHI_SIMD_COMPOSITE_OP_EQ(OP_EQ, OP); \
697 MOCHI_ANY MOCHI_FORCE_INLINE Simd& operator OP_EQ(Scalar a) { \
698 this->first = this->first OP First{a}; \
699 this->second = this->second OP Second{a}; \
700 return *this; \
701 }
702
703 // Unary functions:
719
720 // Binary functions
725
726 // Ternary functions
731
732 // Unary operators
735
736 // Binary operators
748
749 // Math assignment operators
750 //
751 // VS2019 WORKAROUND: These operators should not be necessary because there are generic templates
752 // in simd_inl.h which convert operations like `a += b;` into `a = a + b;`. However, there is
753 // a bug in the VS2019 compiler resulting in incorrect runtime behavior in some of these cases.
754 // The operators here take precedence and shortcut a couple layers of template abstraction,
755 // which appears to be a sufficient work-around. The bug was only observed with VS2019
756 // optimized builds, not with VS2022, Clang, nor GCC.
764
765#undef MOCHI_SIMD_COMPOSITE_FN_1
766#undef MOCHI_SIMD_COMPOSITE_FN_2
767#undef MOCHI_SIMD_COMPOSITE_FN_3
768#undef MOCHI_SIMD_COMPOSITE_OP_1
769#undef MOCHI_SIMD_COMPOSITE_OP_2
770#undef MOCHI_SIMD_COMPOSITE_OP_EQ
771#undef MOCHI_SIMD_COMPOSITE_OP_EQ_WITH_SCALAR
772};
773
774// ReinterpretCast for composite Simd types
775template <
776 class To,
777 class FromT,
778 int FromN,
779 MOCHI_CONCEPT((Simd<FromT, FromN>::kIsComposite) && To::kIsComposite)>
780[[nodiscard]] MOCHI_ANY MOCHI_FORCE_INLINE To ReinterpretCast(Simd<FromT, FromN> const& a) {
781 using From = Simd<FromT, FromN>;
782 static_assert(sizeof(typename To::Scalar) * To::kSize == sizeof(FromT) * FromN, "Size mismatch");
783 static_assert(
784 sizeof(typename To::Scalar) * To::kSizeFirst == sizeof(FromT) * From::kSizeFirst,
785 "Component size mismatch");
786 return {
787 ReinterpretCast<typename To::First>(a.first), ReinterpretCast<typename To::Second>(a.second)};
788}
789
790namespace details {
791template <typename T>
792[[nodiscard]] constexpr bool IsCompositeWithEqualHalves() {
793 if constexpr (T::kIsComposite) {
794 return T::kSizeFirst == T::kSizeSecond;
795 } else {
796 return false;
797 }
798}
799
800template <class To, class From, std::size_t... Is>
801[[nodiscard]] MOCHI_ANY MOCHI_FORCE_INLINE To
802StaticCastElementwise(From const& a, std::index_sequence<Is...>) {
803 return To{static_cast<typename To::Scalar>(a[Is])...};
804}
805} // namespace details
806
807// StaticCast when at least one of the types is a Simd composite.
808// Also handles the pass-through case where To and From are the same.
809template <class To, class From, MOCHI_CONCEPT_DEF(IsSimd<To>&& IsSimd<From>)>
810[[nodiscard]] MOCHI_ANY MOCHI_FORCE_INLINE To StaticCast(From const& a) {
811 static_assert(To::kSize == From::kSize, "Size mismatch");
812 static_assert(To::kIsSupported, "Unsupported type");
813 using ToHalf = Simd<typename To::Scalar, To::kSize / 2>;
814 using FromHalf = Simd<typename From::Scalar, From::kSize / 2>;
815 if constexpr (std::is_same_v<typename To::Scalar, typename From::Scalar>) {
816 return a; // No change
817 } else if constexpr (
818 To::kIsComposite && From::kIsComposite &&
819 (sizeof(typename To::Scalar) == sizeof(typename From::Scalar))) {
820 // Casting a composite to another composite, where the native sizes match
821 // Examples: Vec16f <--> Vec16i
823 } else if constexpr (
824 From::kIsComposite && !To::kIsComposite && ToHalf::kIsSupported &&
825 (sizeof(typename To::Scalar) * 2 == sizeof(typename From::Scalar))) {
826 // Casting from a composite to a native vector, where the destination scalar type is smaller
827 // Examples: Vec8i <-- Vec8d, Vec8f <-- Vec8d (using AVX)
828 return {StaticCast<ToHalf>(a.first), StaticCast<ToHalf>(a.second)};
829 } else if constexpr (
830 To::kIsComposite && !From::kIsComposite && FromHalf::kIsSupported &&
831 (sizeof(typename To::Scalar) == 2 * sizeof(typename From::Scalar))) {
832 // Casting from a native size to a composite, where the destination scalar type is larger
833 // Examples: Vec8d <-- Vec8i, Vec8d <-- Vec8f (using AVX)
834 return {
836 StaticCast<typename To::Second>(From::template GetHalf<1>(a))}; // Not optimized
837 } else if constexpr (
838 ::superdex::details::IsCompositeWithEqualHalves<From>() &&
839 ::superdex::details::IsCompositeWithEqualHalves<To>() &&
840 sizeof(typename To::Scalar) != sizeof(typename From::Scalar)) {
841 // Both sides are composite with equal-sized halves (i.e. GetHalf<0/1> can split them
842 // symmetrically), but their tree shapes may differ due to different native SIMD widths
843 // (e.g. Half↔float or double↔float on ARM), so we can't recurse via .first/.second directly.
844 return To{
845 StaticCast<ToHalf>(From::template GetHalf<0>(a)),
846 StaticCast<ToHalf>(From::template GetHalf<1>(a))};
847 } else {
848 return superdex::details::StaticCastElementwise<To>(a, std::make_index_sequence<From::kSize>{});
849 }
850}
851
852} // namespace superdex
Simd(Scalar a, Scalar b, Scalar c, Scalar d, Scalar e, Scalar f, Scalar g, Scalar h, MoreScalars... args)
Simd(Scalar a, Scalar b, Scalar c, Scalar d, Scalar e, Scalar f, Scalar g, Scalar h, Scalar i, Scalar j, Scalar k, Scalar l, Scalar m, Scalar n, Scalar o, Scalar p, MoreScalars... args)
static constexpr bool kIsComposite
Definition simd.h:98
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:273
constexpr T const & Min(T const &a, T const &b)
T HSum(Simd< T, N > a)
Definition simd_inl.h:377
constexpr To StaticCast(From const &a)
Definition basic_utils.h:79
T HMin(Simd< T, N > a)
Definition simd_inl.h:389
bool AllTrue(T const &a)
Definition basic_utils.h:60
Simd< T, N > Set(Simd< T, N > a, T value)
Definition simd_inl.h:313
T HMax(Simd< T, N > a)
Definition simd_inl.h:395
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:288
bool AnyTrue(T const &a)
Definition basic_utils.h:66
T Get(Simd< T, N > v)
Definition simd_inl.h:303
Simd< T, N/2 > GetHalf(Simd< T, N > a)
Definition simd_inl.h:308
T HProd(Simd< T, N > a)
Definition simd_inl.h:383
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)