SuperDex Physics C++ API
Loading...
Searching...
No Matches
simd_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
20
21#include <array>
22#include <cmath>
23#include <cstring>
24#include <limits>
25#include <type_traits>
26#include <utility>
27
28// Reverse include for intellisense
29#include "../simd.h"
30
31namespace superdex {
32
33namespace details {
34#if MOCHI_ARCH_X64_SVML
35inline constexpr bool kUseSvml = true;
36#else
37inline constexpr bool kUseSvml = false;
38#endif
39} // namespace details
40
41/***********************************************************************************************
42 Overloaded Operators for Simd<T, N>
43*/
44
45// Defines operators like += for Simd types. The right hand side can be any type as long as
46// the corresponding binary operator (e.g. operator+) exists.
47#define MOCHI_DEFINE_SIMD_OP_EQ(OP_EQ, OP) \
48 template <class T, int N, class RHS> \
49 MOCHI_ANY MOCHI_FORCE_INLINE Simd<T, N>& operator OP_EQ(Simd<T, N>& lhs, RHS rhs) { \
50 lhs = lhs OP rhs; \
51 return lhs; \
52 }
53
54// Defines binary operators between Simd<T, N> and T (either order).
55#define MOCHI_DEFINE_MIXED_SIMD_SCALAR_OP(OP) \
56 template <class T, int N> \
57 MOCHI_ANY MOCHI_FORCE_INLINE auto operator OP(Simd<T, N> lhs, T rhs) { \
58 return lhs OP Simd<T, N>{rhs}; \
59 } \
60 template <class T, int N> \
61 MOCHI_ANY MOCHI_FORCE_INLINE auto operator OP(T lhs, Simd<T, N> rhs) { \
62 return Simd<T, N>{lhs} OP rhs; \
63 }
64
72#undef MOCHI_DEFINE_SIMD_OP_EQ
73
78#undef MOCHI_DEFINE_MIXED_SIMD_SCALAR_OP
79
80namespace details {
81// Returns true if every lane is either all-bits-0 (logical false) or all-bits-1 (logical true).
82template <class T, int N>
83[[nodiscard]] MOCHI_ANY MOCHI_FORCE_INLINE bool IsValidLogicalMask(Simd<T, N> a) {
84 static_assert(sizeof(T) == sizeof(int) || sizeof(T) == sizeof(int64_t));
85 using I = std::conditional_t<sizeof(T) == sizeof(int), int, int64_t>;
86 using IVec = Simd<I, N>;
87 auto const ia = ReinterpretCast<IVec>(a);
88 auto const zero = IVec{};
89 auto const ones = ~zero;
90 return AllTrue(VEqual(ia, zero) | VEqual(ia, ones));
91}
92} // namespace details
93
94template <class T, int N>
97 superdex::details::IsValidLogicalMask(lhs) && superdex::details::IsValidLogicalMask(rhs),
98 "operator&& requires all lanes to have all-bits-0 or all-bits-1");
99 return lhs & rhs;
100}
101
102template <class T, int N>
105 superdex::details::IsValidLogicalMask(lhs) && superdex::details::IsValidLogicalMask(rhs),
106 "operator|| requires all lanes to have all-bits-0 or all-bits-1");
107 return lhs | rhs;
108}
109
110/***********************************************************************************************
111 Simd Function Definitions
112*/
113
114template <class V, MOCHI_CONCEPT_DEF(IsSimd<V>)>
115MOCHI_ANY MOCHI_FORCE_INLINE V Broadcast(typename V::Scalar a) {
116 return V{a};
117}
118
119// Default implementation for most vector type
120template <class V, MOCHI_CONCEPT_DEF(IsSimd<V>)>
121MOCHI_ANY MOCHI_FORCE_INLINE V Broadcast(typename V::Scalar const* p) {
122 return V::Broadcast(p);
123}
124
125template <int i, class T, int N>
129
130template <class T, int N>
132 // TODO: Other implementations may be faster for specific vector sizes, but this covers the bases.
133 return Simd<T, N>{Simd<T, N>::Get(v, i)};
134}
135
136template <class V, class... MoreBools>
137MOCHI_ANY MOCHI_FORCE_INLINE V SimdMask(bool b0, bool b1, MoreBools... bs) {
138 static_assert(V::kSize == sizeof...(bs) + 2, "Incorrect number of arguments");
139 using I = std::conditional_t<sizeof(typename V::Scalar) == sizeof(int), int, int64_t>;
140 using IVec = Simd<I, V::kSize>;
141 constexpr I kSimdBool[2] = {0, -1}; // false, true
142 return ReinterpretCast<V>(IVec{kSimdBool[b0], kSimdBool[b1], kSimdBool[bs]...});
143}
144
145template <class V>
147 return V::Zero();
148}
149
150template <int i, class V>
152 return V::template SetBasisVector<i>();
153}
154
155template <class V>
157 static_assert(V::kSize == 4, "Unsupported SIMD size");
158 switch (axis) {
159 case 0:
160 return V::template SetBasisVector<0>();
161 case 1:
162 return V::template SetBasisVector<1>();
163 case 2:
164 return V::template SetBasisVector<2>();
165 case 3:
166 return V::template SetBasisVector<3>();
167 default:
168 MOCHI_ASSERT(axis >= 0 && axis <= 3, "Invalid component index");
169 return {};
170 }
171}
172
173template <class V>
175 return V::AsPoint(a);
176}
177
178template <class V>
180 return V::AsDirection(a);
181}
182
183template <class V, MOCHI_CONCEPT_DEF(IsSimd<V>)>
184MOCHI_ANY MOCHI_FORCE_INLINE V Load(typename V::Scalar const* ptr) {
185 return V::template Load<V::kSize>(ptr);
186}
187
188template <int N, class V, MOCHI_CONCEPT_DEF(IsSimd<V>)>
189MOCHI_ANY MOCHI_FORCE_INLINE V Load(typename V::Scalar const* ptr) {
190 return V::template Load<N>(ptr);
191}
192
193template <class V, MOCHI_CONCEPT_DEF(IsSimd<V>)>
194MOCHI_ANY MOCHI_FORCE_INLINE V Load(typename V::Scalar const* ptr, int n) {
195 return V::Load(ptr, n);
196}
197
198template <class V, class I, MOCHI_CONCEPT_DEF(IsSimd<V>)>
200LoadIndexed(typename V::Scalar const* ptr, Simd<I, V::kSize> indices) {
201 static_assert(std::is_integral_v<I>, "Requires integral type");
202 return V::LoadIndexed(ptr, indices);
203}
204
205template <int kTupleCount, class T, int N>
207LoadTransposed(T const* ptr, Simd<T, N>& out0, Simd<T, N>& out1, Simd<T, N>& out2) {
208 constexpr int kTupleCount_ = (kTupleCount == -1) ? N : kTupleCount;
210}
211
212template <int COUNT, class T, int N>
214 constexpr int COUNT_ = (COUNT == -1) ? N : COUNT;
216}
217
218template <class T, int N>
219MOCHI_ANY MOCHI_FORCE_INLINE void Store(T* ptr, Simd<T, N> a, int count) {
220 Simd<T, N>::Store(ptr, a, count);
221}
222
223template <class T, int N, class MaskT>
225StoreSelected(T* ptr, Simd<MaskT, N> condition, Simd<T, N> values) {
227 superdex::details::IsValidLogicalMask(condition),
228 "Not a valid logical mask. Each lane must be all-bits-0 or all-bits-1.");
229 if constexpr (sizeof(MaskT) == 8 && sizeof(T) == 4) { // Example: MaskT = double, T = int
230 auto conditionI64 = ReinterpretCast<Simd<int64_t, N>>(condition);
231 auto conditionI32 = StaticCast<Simd<int, N>>(conditionI64);
233 (conditionI32 == StaticCast<Simd<int, N>>(conditionI64 & int64_t(0x00000000FFFFFFFFLL))),
234 "Expected StaticCast from int64_t to int32_t to return the lower 32 bits. This is not guaranteed by the C++ standard for static_cast, "
235 "but it is guaranteed by the x64 and ARM implementations. Your new CPU architecture behaves differenty. Therefore, this code will "
236 "need to perform a masking operation, or use Simd<uint64_t, N> and Simd<uint32_t, N> (not supported at the time of writing).");
237 return Simd<T, N>::StoreSelected(ptr, ReinterpretCast<Simd<T, N>>(conditionI32), values);
238 } else {
239 return Simd<T, N>::StoreSelected(ptr, ReinterpretCast<Simd<T, N>>(condition), values);
240 }
241}
242
243template <int kTupleCount, class T, int N>
246 constexpr int kTupleCount_ = (kTupleCount == -1) ? N : kTupleCount;
248}
249
250template <class T, int N, class MaskT>
254 superdex::details::IsValidLogicalMask(conditionMask),
255 "Not a valid logical mask. Each lane must be all-bits-0 or all-bits-1.");
256 return Simd<T, N>::Select(ReinterpretCast<Simd<T, N>>(conditionMask), a, b);
257}
258
259template <int kShift, class T, int N>
261 static_assert(kShift >= 0 && kShift < (8 * sizeof(T)), "Shift amount out-of-range");
262 if constexpr (kShift == 0) {
263 return a;
264 } else {
266 }
267}
268
269template <int x, int y, class T>
273
274template <int x, int y, int z, int w, class T>
278
279template <int x, int y, int z, int w, class T>
283
284template <int x, int y, class T, int N>
288
289template <int x, int y, int z, int w, class T, int N>
293
294template <class T, int N>
298
299template <int i, class T, int N>
303
304template <class T, int N>
306 return Simd<T, N>::Get(v, i);
307}
308
309template <int iHalf, class T, int N>
313
314template <int i, class T, int N>
315[[nodiscard]] Simd<T, N> Set(Simd<T, N> a, T value) {
316 return Simd<T, N>::template Set<i>(a, value);
317}
318
319template <class T, int N>
320[[nodiscard]] MOCHI_ANY MOCHI_FORCE_INLINE Simd<T, N> Set(Simd<T, N> a, int i, T value) {
321 return Simd<T, N>::Set(a, i, value);
322}
323
324template <class V>
326 using T = typename V::Scalar;
327 static_assert(V::kIsSupported && std::is_integral_v<T>, "Must be a supported integral Simd type");
328 if constexpr (V::kIsComposite) {
329 return V::Sequence();
330 } else {
331 alignas(V) T constexpr kSequence[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
332 static_assert(
333 std::size(kSequence) >= V::kSize,
334 "Vector size is too large. Size of kSequence must be increased.");
335 return V::template Load<V::kSize>(kSequence);
336 }
337}
338
339template <bool x, bool y, bool z, bool w, class T, int N>
341 static_assert(
342 std::is_floating_point_v<T> && (N == 4),
343 "This implementation is intended for Vec4f or Vec4d only");
344 return Blend<x, y, z, w>(a, -a);
345}
346
347template <class T, int N>
349 static_assert(std::is_floating_point_v<T>, "Requires float or double");
350 return Simd<T, N>::Sqrt(a);
351}
352
353template <class T, int N>
357
358template <class T, int N>
362
363template <class T, int N>
367
368template <class T, int N>
372
373template <class T, int N>
377
378template <int COUNT, class T, int N>
380 constexpr int COUNT_ = (COUNT == -1) ? N : COUNT;
382}
383
384template <int COUNT, class T, int N>
386 constexpr int COUNT_ = (COUNT == -1) ? N : COUNT;
388}
389
390template <int COUNT, class T, int N>
392 constexpr int COUNT_ = (COUNT == -1) ? N : COUNT;
394}
395
396template <int COUNT, class T, int N>
398 constexpr int COUNT_ = (COUNT == -1) ? N : COUNT;
400}
401
402#define MOCHI_SIMD_MEMBERWISE_FALLBACK(T, N, FN, inVec) \
403 alignas(alignof(Simd<T, N>)) T buf[N]; \
404 Store(buf, inVec); \
405 for (int i = 0; i < N; ++i) { \
406 buf[i] = FN(buf[i]); \
407 } \
408 return Load<Simd<T, N>>(buf)
409
410template <class T, int N>
414
415template <class T, int N>
419
420namespace details {
421// We have a custom SIMD implementation of single-precision sine and cosine. These are computed by
422// summing the first few terms of the Taylor series. The problem is that precision decreases as
423// abs(x) increases. Therefore, the first step is to reduce the range of x. Depending on the
424// quadrant, we may return either the sine or the cosine, and we may need to flip the sign. All of
425// this can be done quickly with branchless SIMD instructions.
426//
427// Some details of this implementation were inspired by code provided by ARM Software:
428// https://github.com/ARM-software/optimized-routines
429//
430template <int N>
432SinCosImpl(Simd<float, N> xf, Simd<float, N>& outSin, Simd<float, N>& outCos, Simd<int, N>& outN) {
433 // Convert to double precision
434 auto x = StaticCast<Simd<double, N>>(xf);
435
436 // Reduce x to [-pi/4, pi/4] in quadrant n
437 auto r = FastRound(x * 0x1.45f306dc9c883p-1); // round(x * (2/pi))
438 outN = StaticCast<Simd<int, N>>(r);
439 x = x - r * 0x1.921fb54442d18p0; // x - n * (pi/2)
440
441 // Compute Taylor series for both sin and cos
442 auto x2 = x * x; // x^2
443 auto x3 = x * x2; // x^3
444 auto x4 = x2 * x2; // x^4
445 auto s1 = 0x1.1107605230bc4p-7 - x2 * 0x1.994eb3774cf24p-13; // 1/5! - (x^2)/7!
446 auto c1 = 1.0 - x2 * 0.5;
447 auto c2 = -0x1.6c087e89a359dp-10 + x2 * 0x1.99343027bf8c3p-16; // -1/6! + (x^2)/8!
448 auto x5 = x3 * x2; // x^5
449 auto x6 = x4 * x2; // x^6
450 auto s0 = x - x3 * 0x1.555545995a603p-3; // x - (x^3)/3!
451 auto c0 = c1 + x4 * 0x1.55553e1068f19p-5; // 1 - (x^2)/2! + (x^4)/4!
452
453 // outSin = x - (x^3)/3! + (x^5)/5! - (x^7)/7!
454 outSin = StaticCast<Simd<float, N>>(s0 + x5 * s1);
455
456 // outCos = 1 - (x^2)/2! + (x^4)/4! - (x^6)/6! + (x^8)/8!
457 outCos = StaticCast<Simd<float, N>>(c0 + x6 * c2);
458}
459} // namespace details
460
461template <class T, int N>
463 // TODO: When C++20 syntax in this header is legal, gate based on whether Simd<T, N>::Cos() is
464 // implemented. Same for all other functions with a SIMD memberwise fallback.
465 if constexpr (details::kUseSvml || Simd<T, N>::kIsEmulated) {
466 // There are x64 intrinsics if SVML extension is available
467 return Simd<T, N>::Cos(a);
468 } else if constexpr (std::is_same_v<T, float>) {
469 // Reduce x to [-pi/4, pi/4) in quadrant n and compute Taylor series
470 Simd<float, N> sin, cos;
471 Simd<int, N> n;
472 details::SinCosImpl(a, sin, cos, n);
473 // Use bitwise ops to select cos for (n == 0) || (n == 2). Else select sin.
474 auto result = Select((n & 1) - 1, cos, sin);
475 // Then flip the sign if (n == 1) || (n == 2).
476 return result ^ ReinterpretCast<Simd<float, N>>((n ^ ShiftRight<1>(n)) << 31);
477 } else {
478 MOCHI_SIMD_MEMBERWISE_FALLBACK(T, N, std::cos, a); // Fallback
479 }
480}
481
482template <class T, int N>
484 if constexpr (details::kUseSvml || Simd<T, N>::kIsEmulated) {
485 // There are x64 intrinsics if SVML extension is available
486 return Simd<T, N>::Sin(a);
487 } else if constexpr (std::is_same_v<T, float>) {
488 // Reduce x to [-pi/4, pi/4) in quadrant n and compute Taylor series
489 Simd<T, N> sin, cos;
490 Simd<int, N> n;
491 details::SinCosImpl(a, sin, cos, n);
492 // Use bitwise ops to select sin for (n == 0) || (n == 2). Else select cos.
493 auto result = Select((n & 1) - 1, sin, cos);
494 // Then flip the sign if (n == 2) || (n == 3).
495 return result ^ ReinterpretCast<Simd<T, N>>(ShiftRight<1>(n) << 31);
496 } else {
497 MOCHI_SIMD_MEMBERWISE_FALLBACK(T, N, std::sin, a);
498 }
499}
500
501template <class T, int N>
502inline std::pair<Simd<T, N>, Simd<T, N>> SinCos(Simd<T, N> a) {
503 // Our custom single-precision implementation can efficiently compute sin and cos at the same
504 // time. However, we still use call Sin and Cos separately when they are implemented with SVML, so
505 // that the results will be exactly the same.
506 if constexpr (!MOCHI_ARCH_X64_SVML && std::is_same_v<T, float>) {
507 Simd<float, N> sin, cos;
508 Simd<int, N> n;
509 details::SinCosImpl(a, sin, cos, n);
510 auto mask = ReinterpretCast<Simd<float, N>>((n & 1) - 1);
511 auto nr = ShiftRight<1>(n);
512 auto sresult = Select(mask, sin, cos) ^ ReinterpretCast<Simd<float, N>>(nr << 31);
513 auto cresult = Select(mask, cos, sin) ^ ReinterpretCast<Simd<float, N>>((n ^ nr) << 31);
514 return {sresult, cresult};
515 } else {
516 return {Sin(a), Cos(a)};
517 }
518}
519
520template <class T, int N>
522 if constexpr (details::kUseSvml || Simd<T, N>::kIsEmulated) {
523 // Simd<T, N>::Tan only implemented in this case.
524 return Simd<T, N>::Tan(a);
525 } else {
526 MOCHI_SIMD_MEMBERWISE_FALLBACK(T, N, std::tan, a);
527 }
528}
529
530template <class T, int N>
532 if constexpr (details::kUseSvml || Simd<T, N>::kIsEmulated) {
533 // Simd<T, N>::ACos only implemented in this case.
534 return Simd<T, N>::ACos(a);
535 } else {
536 MOCHI_SIMD_MEMBERWISE_FALLBACK(T, N, std::acos, a);
537 }
538}
539
540template <class T, int N>
542 if constexpr (details::kUseSvml || Simd<T, N>::kIsEmulated) {
543 // Simd<T, N>::ASin only implemented in this case.
544 return Simd<T, N>::ASin(a);
545 } else {
546 MOCHI_SIMD_MEMBERWISE_FALLBACK(T, N, std::asin, a);
547 }
548}
549
550template <class T, int N>
552 if constexpr (details::kUseSvml || Simd<T, N>::kIsEmulated) {
553 // Simd<T, N>::ATan only implemented in this case.
554 return Simd<T, N>::ATan(a);
555 } else {
556 MOCHI_SIMD_MEMBERWISE_FALLBACK(T, N, std::atan, a);
557 }
558}
559
560template <class T, int N>
562 if constexpr (details::kUseSvml || Simd<T, N>::kIsEmulated) {
563 // Simd<T, N>::Exp only implemented in this case.
564 return Simd<T, N>::Exp(a);
565 } else {
566 //
567 // This code is mimicking the following implementations:
568 // Vc (float):
569 // Vc (double): https://github.com/VcDevel/Vc/blob/1.4/Vc/common/math.h
570 // cephes: https://github.com/jeremybarnes/cephes/blob/master/cmath/exp.c
571 // avx_mathfun: https://github.com/reyoung/avx_mathfun/blob/master/avx_mathfun.h
572 // and the references therein.
573 //
574 //--- Treat the case of float and double
575 Simd<T, N> const infinity(std::numeric_limits<T>::infinity());
576 Simd<T, N> const log2_e(T(1.44269504088896341)); // = ln(e) / ln(2)
577 Simd<T, N> const one(T(1.0));
578 Simd<T, N> const half(T(0.5));
579 Simd<T, N> const zero(T(0.0));
580 //
581 auto x = a;
582 auto n = log2_e * x;
583 n += half;
584 n = Floor(n);
585 if constexpr (std::is_same_v<T const, float const>) {
586 Simd<T, N> const C1(T(0.693359375));
587 Simd<T, N> const C2(T(-2.121944400547138e-04));
588 x -= C1 * n;
589 x -= C2 * n;
590 } else {
591 static_assert(std::is_same_v<T const, double const>);
592 Simd<T, N> const C1(T(0.693145751953125));
593 Simd<T, N> const C2(T(1.42860682030941723212e-06));
594 x -= C1 * n;
595 x -= C2 * n;
596 }
597 //--- Polynomial approximation
598 Simd<T, N> y;
599 if constexpr (std::is_same_v<T const, float const>) {
600 float const P[] = {
601 1.9875691500e-04,
602 1.3982999507e-03,
603 8.3334519073e-03,
604 4.1665795894e-02,
605 1.6666665459e-01,
606 5.0000001201e-01};
607 auto z = x * x;
608 y = Simd<T, N>(P[0]);
609 for (int i = 1; i <= 5; ++i) {
610 y *= x;
611 y += Simd<T, N>(P[i]);
612 }
613 y *= z;
614 y += x;
615 y += one;
616 auto imm0 = StaticCast<Simd<int, N>>(n);
617 imm0 += Simd<int, N>(127); // 127 <- 0x7f
618 imm0 = imm0 << 23;
619 x = y * ReinterpretCast<Simd<T, N>>(imm0); // x = y * Exp2(n);
620 //
621 Simd<T, N> const exp_lo(T(-88.37626226647949));
622 auto const underFlow = (a < exp_lo);
623 x = Simd<T, N>::Select(underFlow, zero, x);
624 //
625 Simd<T, N> const exp_hi(T(88.37626226647949));
626 auto const overFlow = (a > exp_hi);
627 x = Simd<T, N>::Select(overFlow, infinity, x);
628 return x;
629 }
630 //
631 // --- Treating the 'double' case
632 //
633 if constexpr (std::is_same_v<T const, double const>) {
634 double const P[] = {
635 1.26177193074810590878E-4,
636 3.02994407707441961300E-2,
637 9.99999999999999999910E-1,
638 };
639 double const Q[] = {
640 3.00198505138664455042E-6,
641 2.52448340349684104192E-3,
642 2.27265548208155028766E-1,
643 2.00000000000000000009E0,
644 };
645 auto z = x * x;
646 Simd<T, N> pz(P[0]);
647 for (int i = 1; i <= 2; ++i) {
648 pz *= z;
649 pz += Simd<T, N>(P[i]);
650 }
651 pz *= x;
652 Simd<T, N> qz(Q[0]);
653 for (int i = 1; i <= 3; ++i) {
654 qz *= z;
655 qz += Simd<T, N>(Q[i]);
656 }
657 y = pz / (qz - pz);
658 y *= Simd<T, N>(T(2.0));
659 y += one;
660 auto jmm = StaticCast<Simd<int64_t, N>>(n);
661 jmm += Simd<int64_t, N>(1023);
662 jmm = jmm << 52;
663 x = y * ReinterpretCast<Simd<T, N>>(jmm);
664 //
665 Simd<T, N> const exp_lo(T(-709.0));
666 auto const underFlow = (a < exp_lo);
667 x = Simd<T, N>::Select(underFlow, zero, x);
668 //
669 Simd<T, N> const exp_hi(T(709.0));
670 auto const overFlow = (a > exp_hi);
671 x = Simd<T, N>::Select(overFlow, infinity, x);
672 return x;
673 }
674 }
675}
676
677template <class T, int N>
679 if constexpr (details::kUseSvml || Simd<T, N>::kIsEmulated) {
680 // Simd<T, N>::Ln only implemented in this case.
681 return Simd<T, N>::Ln(a);
682 } else {
683 MOCHI_SIMD_MEMBERWISE_FALLBACK(T, N, std::log, a);
684 }
685}
686
687template <class T, int N>
689 if constexpr (details::kUseSvml || Simd<T, N>::kIsEmulated) {
690 // Simd<T, N>::Tanh only implemented in this case.
691 return Simd<T, N>::Tanh(a);
692 } else {
693 MOCHI_SIMD_MEMBERWISE_FALLBACK(T, N, std::tanh, a);
694 }
695}
696
697#undef MOCHI_SIMD_MEMBERWISE_FALLBACK
698
699template <class V>
701 return V::Equal(a, b);
702}
703
704template <class V>
706 return V::NotEqual(a, b);
707}
708
709template <int COUNT, class T, int N>
711 if constexpr (COUNT == 1) {
712 return Get0(a) == Get0(b);
713 } else {
714 return AllTrue<COUNT>(VEqual(a, b));
715 }
716}
717
718template <int COUNT, class T, int N>
720#if MOCHI_PLATFORM_MACOS && MOCHI_ARCH_X64 && MOCHI_OPTIMIZED
721 // Work-around for a compiler bug in optimized builds on Intel macOS.
722 return !Equal<COUNT>(a, b);
723#else
724 if constexpr (COUNT == 1) {
725 return Get0(a) != Get0(b);
726 } else {
727 return AnyTrue<COUNT>(VNotEqual(a, b));
728 }
729#endif
730}
731
732template <class V>
734 return Abs(a - b) <= epsilon;
735}
736
737template <class V>
739 return Abs(a) <= epsilon;
740}
741
742template <int COUNT, class T, int N>
744 constexpr int COUNT_ = (COUNT == -1) ? N : COUNT; // -1 means "all"
746}
747
748template <int COUNT, class T, int N>
750 constexpr int COUNT_ = (COUNT == -1) ? N : COUNT; // -1 means "all"
752}
753
754template <class T, int N>
756 if constexpr (std::is_same_v<T, float>) {
757 constexpr int kMask = 0x7F800000; // These bits set for inf and NaN variants
758 auto mask = Simd<int, N>{kMask};
759 return ReinterpretCast<Simd<T, N>>(VNotEqual(ReinterpretCast<Simd<int, N>>(a) & mask, mask));
760 } else {
761 static_assert(std::is_same_v<T, double>, "VIsFinite only supports float and double.");
762 constexpr int kMask = 0x7FF00000; // These bits set for inf and NaN variants
763 if constexpr (N == 2 && Simd<int, 4>::kIsSupported) {
764 // Use an Int4 mask. Result comes from the 2 that correspond to the high bits of each
765 // double.
766 auto mask = Simd<int, 4>{kMask};
767 auto temp4i32 = VNotEqual(ReinterpretCast<Simd<int, 4>>(a) & mask, mask);
768 auto temp2i64 = Shuffle<1, 1, 3, 3>(temp4i32);
769 return ReinterpretCast<Simd<double, 2>>(temp2i64);
770 } else if constexpr (N == 4 && Simd<int, 8>::kIsSupported) {
771 // Use an int8 mask. Result comes from the 4 that correspond to the high bits of each
772 // double.
773 auto mask = Simd<int, 8>{kMask};
774 auto temp8i32 = VNotEqual(ReinterpretCast<Simd<int, 8>>(a) & mask, mask);
775 auto low2i64 = Shuffle<1, 1, 3, 3>(GetHalf<0>(temp8i32)); // no 8-way shuffle currently
776 auto high2i64 = Shuffle<1, 1, 3, 3>(GetHalf<1>(temp8i32));
777 auto temp4i64 = Simd<int, 8>(low2i64, high2i64);
778 return ReinterpretCast<Simd<double, 4>>(temp4i64);
779 } else if constexpr (Simd<T, N>::kIsComposite) {
780 return Simd<T, N>{VIsFinite(a.first), VIsFinite(a.second)};
781 } else if constexpr (!MOCHI_USE_SIMD) {
782 // Simd Emulated
783 Simd<T, N> result;
784 for (int i = 0; i < N; ++i) {
785 uint64_t isFinite = -static_cast<uint64_t>(IsFinite(a.raw[i])); // true = -1, false = 0
786 memcpy(&result.raw[i], &isFinite, sizeof(T));
787 }
788 return result;
789 } else {
790 static_assert(std::is_void_v<T>, "Unsupported type or size");
791 }
792 }
793}
794
795template <int i, class T, int N>
797 // The bits of mask[i] should be all zeros for "false", or all ones for "true". Therefore we can
798 // test any byte(s) within mask[i]. We never return a floating-point type so that the caller
799 // doesn't have to worry about comparisons with NaN.
800 constexpr int kNumInts{(sizeof(T) * N) / sizeof(int)};
801 constexpr int kStride = kNumInts / N;
802 return Get<i * kStride>(ReinterpretCast<Simd<int, kNumInts>>(mask));
803}
804
805template <class T, int N>
809
810template <class T, int N>
814
815template <class T, int N>
819
820template <class T, int N>
824
825template <int COUNT, class V>
827 constexpr int COUNT_ = (COUNT == -1) ? V::kSize : COUNT;
828 return V::template Dot<COUNT_>(a, b);
829}
830
831template <class T, int N>
835
836template <int COUNT, class T, int N>
838 static_assert(COUNT == -1 || COUNT >= 2, "Unsupported COUNT");
839 static_assert(std::is_floating_point_v<T>, "Requires float or double");
840 return VDot<COUNT>(a, a);
841}
842
843template <int COUNT, class T, int N>
847
848template <int COUNT, class T, int N>
852
853template <int COUNT, class T, int N>
857
858template <int COUNT, class T, int N>
860 // By adding the smallest possible scalar we prevent divide-by-zero and get a zero vector result
861 // There is no change in result for any vector longer than.... something very very very small
862 return a / (VNorm<COUNT>(a) + std::numeric_limits<T>::min());
863}
864
865template <class T, int N>
867 // By adding the smallest possible scalar we prevent divide-by-zero and get a zero vector result
868 // There is no change in result for any vector longer than.... something very very very small
869 return a / (Sqrt(normSqr) + std::numeric_limits<T>::min());
870}
871
872template <class T, int N>
874 return Normalize(a, Simd<T, N>{normSqr});
875}
876
877template <class T>
879 static_assert(std::is_floating_point_v<T>, "Requires float or double");
880
881 // Project the coordinate of the minimum absolute value
882 // then build the orthogonal vector in that subspace
883
884 // Compute mask selecting the minimum absolute value(s)
885 auto abs = Abs(a);
886 auto absMin = HMin<3>(abs);
887
888 // Build possible orthogonal vectors and select them according to the mask.
889 a = ToSimdDirection(a);
890 if (absMin == Get<0>(abs)) { // abs(v[0]) was the smallest value (or tied for smallest)
892 } else if (absMin == Get<1>(abs)) { // abs(v[1]) was the smallest value (or tied for smallest)
894 } else { // abs(v[2]) was the smallest value
896 }
897 return a;
898}
899
900namespace details {
901
902/**
903 Utilities to determine the smallest supported SIMD size that is greater than or equal to a given
904 size.
905*/
906template <typename T, int kSize>
907struct NextSupportedSimdSizeHelper {
908 static_assert(Simd<T>::kIsSupported, "Type T is not supported for any size N.");
909 static constexpr int value = std::conditional_t<
911 std::integral_constant<int, kSize>,
912 NextSupportedSimdSizeHelper<T, kSize + 1>>::value;
913};
914
915template <typename T, int kSize>
916inline constexpr int kNextSupportedSimdSize = NextSupportedSimdSizeHelper<T, kSize>::value;
917
918} // namespace details
919
920} // namespace superdex
921
922/************************************************************************************
923 Reflection support for Simd<T, N>
924 Serializes like std::array<T, N>.
925*/
926#if MOCHI_USE_REFLECTION
927template <typename T, int N>
928struct SReflectTypeTraits<superdex::Simd<T, N>> {
929 static constexpr SReflect::CoreType coreType = SReflect::CoreType::CT_array;
930 static SReflect::ArrayTypeInfo const& GetTypeInfo() {
931 static auto* s_typeInfo =
932 SReflect::MakeFixedArrayTypeInfo<superdex::Simd<T, N>, T, N>("superdex::Simd", true);
933 return *s_typeInfo;
934 }
935};
936#endif // MOCHI_USE_REFLECTION
NativeType raw
Definition simd.h:174
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(condition_without_side_effects,...)
Definition debug.h:81
#define MOCHI_ASSERT_VERBOSE(condition_without_side_effects,...)
Definition debug.h:102
#define MOCHI_USE_SIMD
#define MOCHI_ARCH_X64_SVML
#define MOCHI_FORCE_INLINE
#define MOCHI_ANY
T Dot(Simd< T, N > a, Simd< T, N > b)
Definition simd.h:666
constexpr T ACos(T a)
Simd< T, N > VIsFinite(Simd< T, N > a)
Definition simd_inl.h:755
Simd< T, 2 > Shuffle(Simd< T, 2 > a)
Definition simd_inl.h:270
V LoadIndexed(typename V::Scalar const *ptr, Simd< I, V::kSize > indices)
Definition simd_inl.h:200
constexpr T const & Min(T const &a, T const &b)
T NormSqr(Simd< T, N > a)
Definition simd_inl.h:849
constexpr auto Equal(T const &a, T const &b)
Simd< T, N > Cross3(Simd< T, N > a, Simd< T, N > b)
Definition simd_inl.h:832
constexpr T Sin(T a)
T HSum(Simd< T, N > a)
Definition simd_inl.h:379
constexpr To StaticCast(From const &a)
Definition basic_utils.h:79
V VDot(V a, V b)
Definition simd_inl.h:826
T HMin(Simd< T, N > a)
Definition simd_inl.h:391
T Norm(Simd< T, N > a)
Definition simd_inl.h:854
bool AllTrue(T const &a)
Definition basic_utils.h:60
constexpr auto MulAdd(A a, B b, C c)
V SimdBasisVector()
Definition simd_inl.h:151
Simd< T, N > Tanh(Simd< T, N > a)
Definition simd_inl.h:688
V VEqual(V a, V b)
Definition simd_inl.h:700
V ToSimdDirection(V a)
Definition simd_inl.h:179
Simd< T, N > Set(Simd< T, N > a, T value)
Definition simd_inl.h:315
V Sequence()
Definition simd_inl.h:325
constexpr auto NotEqual(T const &a, T const &b)
constexpr T Exp(T a)
constexpr T Cos(T a)
V VNearEqual(V a, V b, V epsilon)
Definition simd_inl.h:733
V SimdZero()
Definition simd_inl.h:146
T HMax(Simd< T, N > a)
Definition simd_inl.h:397
Simd< T, N > VNormSqr(Simd< T, N > a)
Definition simd_inl.h:837
int IsTrue(Simd< T, N > mask)
Definition simd_inl.h:796
V Broadcast(typename V::Scalar a)
Definition simd_inl.h:115
constexpr T Abs(T a)
Definition basic_utils.h:50
V VNotEqual(V a, V b)
Definition simd_inl.h:705
constexpr auto MulSub(A a, B b, C c)
Simd< T, N > Normalize(Simd< T, N > a)
Definition simd_inl.h:859
Simd< T, N > Blend(Simd< T, N > a, Simd< T, N > b)
Definition simd_inl.h:285
constexpr T Tan(T a)
bool AnyTrue(T const &a)
Definition basic_utils.h:66
constexpr T Select(bool condition, T a, T b)
constexpr T Sqrt(T a)
constexpr T ATan(T a)
Simd< T, N > Ln(Simd< T, N > a)
Definition simd_inl.h:678
std::pair< Simd< T, N >, Simd< T, N > > SinCos(Simd< T, N > a)
Definition simd_inl.h:502
constexpr auto NegMulAdd(A a, B b, C c)
T Get(Simd< T, N > v)
Definition simd_inl.h:300
constexpr T Floor(T a)
constexpr T ASin(T a)
Simd< T, N > operator||(Simd< T, N > lhs, Simd< T, N > rhs)
Definition simd_inl.h:103
Simd< T, N > operator&&(Simd< T, N > lhs, Simd< T, N > rhs)
Definition simd_inl.h:95
V SimdMask(bool b0, bool b1, MoreBools... bs)
Definition simd_inl.h:137
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
Simd< T, 4 > OrthogonalVector3(Simd< T, 4 > a)
T Get0(Simd< T, N > v)
Definition simd_inl.h:295
Simd< T, N > VNorm(Simd< T, N > a)
Definition simd_inl.h:844
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
Simd< T, N > FastRound(Simd< T, N > a)
Definition simd_inl.h:416
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
Simd< T, N > RcpSqrtApprox(Simd< T, N > a)
Definition simd_inl.h:359
constexpr T RcpApprox(T a)
constexpr auto NegMulSub(A a, B b, C c)
V VNearZero(V a, V epsilon)
Definition simd_inl.h:738
int StoreSelected(T *ptr, Simd< MaskT, N > condition, Simd< T, N > values)
Definition simd_inl.h:225
V Load(typename V::Scalar const *ptr)
Definition simd_inl.h:184
Simd< T, N > Neg(Simd< T, N > a)
Definition simd_inl.h:340
V ToSimdPoint(V a)
Definition simd_inl.h:174
bool IsFinite(TransformRT const &a)
Simd< T, N > ShiftRight(Simd< T, N > a)
Definition simd_inl.h:260
#define MOCHI_SIMD_MEMBERWISE_FALLBACK(T, N, FN, inVec)
Definition simd_inl.h:402
#define MOCHI_DEFINE_MIXED_SIMD_SCALAR_OP(OP)
Definition simd_inl.h:55
#define MOCHI_DEFINE_SIMD_OP_EQ(OP_EQ, OP)
Definition simd_inl.h:47