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>{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(
262 Simd<T, N>::kIsSupported && std::is_integral_v<T> && std::is_signed_v<T>,
263 "ShiftRight requires a supported signed integer Simd type");
264 static_assert(kShift >= 0 && kShift < (8 * sizeof(T)), "Shift amount out-of-range");
265 if constexpr (kShift == 0) {
266 return a;
267 } else {
269 }
270}
271
272template <int x, int y, class T>
276
277template <int x, int y, int z, int w, class T>
281
282template <int x, int y, int z, int w, class T>
286
287template <int x, int y, class T, int N>
291
292template <int x, int y, int z, int w, class T, int N>
296
297template <class T, int N>
301
302template <int i, class T, int N>
306
307template <int iHalf, class T, int N>
311
312template <int i, class T, int N>
313[[nodiscard]] Simd<T, N> Set(Simd<T, N> a, T value) {
314 return Simd<T, N>::template Set<i>(a, value);
315}
316
317template <class T, int N>
318[[nodiscard]] MOCHI_ANY MOCHI_FORCE_INLINE Simd<T, N> Set(Simd<T, N> a, int i, T value) {
319 return Simd<T, N>::Set(a, i, value);
320}
321
322template <class V>
324 using T = typename V::Scalar;
325 static_assert(V::kIsSupported && std::is_integral_v<T>, "Must be a supported integral Simd type");
326 if constexpr (V::kIsComposite) {
327 return V::Sequence();
328 } else {
329 alignas(V) T constexpr kSequence[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
330 static_assert(
331 std::size(kSequence) >= V::kSize,
332 "Vector size is too large. Size of kSequence must be increased.");
333 return V::template Load<V::kSize>(kSequence);
334 }
335}
336
337template <bool x, bool y, bool z, bool w, class T, int N>
339 static_assert(
340 std::is_floating_point_v<T> && (N == 4),
341 "This implementation is intended for Vec4f or Vec4d only");
342 return Blend<x, y, z, w>(a, -a);
343}
344
345template <class T, int N>
347 static_assert(std::is_floating_point_v<T>, "Requires float or double");
348 return Simd<T, N>::Sqrt(a);
349}
350
351template <class T, int N>
355
356template <class T, int N>
360
361template <class T, int N>
365
366template <class T, int N>
370
371template <class T, int N>
375
376template <int COUNT, class T, int N>
378 constexpr int COUNT_ = (COUNT == -1) ? N : COUNT;
380}
381
382template <int COUNT, class T, int N>
384 constexpr int COUNT_ = (COUNT == -1) ? N : COUNT;
386}
387
388template <int COUNT, class T, int N>
390 constexpr int COUNT_ = (COUNT == -1) ? N : COUNT;
392}
393
394template <int COUNT, class T, int N>
396 constexpr int COUNT_ = (COUNT == -1) ? N : COUNT;
398}
399
400#define MOCHI_SIMD_MEMBERWISE_FALLBACK(T, N, FN, inVec) \
401 ([&]() { \
402 alignas(alignof(Simd<T, N>)) T buf[N]; \
403 Store(buf, inVec); \
404 for (int i = 0; i < N; ++i) { \
405 buf[i] = FN(buf[i]); \
406 } \
407 return Load<Simd<T, N>>(buf); \
408 }())
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
460// Preserve the documented accuracy bound and keep the quadrant index
461// representable by falling back for larger inputs.
462inline constexpr float kMaxFastSinCosInput = 1e6f;
463
464template <int N>
465MOCHI_ANY MOCHI_FORCE_INLINE bool IsFastSinCosInput(Simd<float, N> a) {
466 return AllTrue(Abs(a) <= Simd<float, N>{kMaxFastSinCosInput});
467}
468} // namespace details
469
470template <class T, int N>
472 // TODO: When C++20 syntax in this header is legal, gate based on whether Simd<T, N>::Cos() is
473 // implemented. Same for all other functions with a SIMD memberwise fallback.
474 if constexpr (details::kUseSvml || Simd<T, N>::kIsEmulated) {
475 // There are x64 intrinsics if SVML extension is available
476 return Simd<T, N>::Cos(a);
477 } else if constexpr (std::is_same_v<T, float>) {
478 if (!details::IsFastSinCosInput(a))
480 return MOCHI_SIMD_MEMBERWISE_FALLBACK(T, N, std::cos, a);
481 }
482 // Reduce x to [-pi/4, pi/4) in quadrant n and compute Taylor series
483 Simd<float, N> sin, cos;
484 Simd<int, N> n;
485 details::SinCosImpl(a, sin, cos, n);
486 // Use bitwise ops to select cos for (n == 0) || (n == 2). Else select sin.
487 auto result = Select((n & 1) - 1, cos, sin);
488 // Then flip the sign if (n == 1) || (n == 2).
489 return result ^ ReinterpretCast<Simd<float, N>>((n ^ ShiftRight<1>(n)) << 31);
490 } else {
491 return MOCHI_SIMD_MEMBERWISE_FALLBACK(T, N, std::cos, a); // Fallback
492 }
493}
494
495template <class T, int N>
497 if constexpr (details::kUseSvml || Simd<T, N>::kIsEmulated) {
498 // There are x64 intrinsics if SVML extension is available
499 return Simd<T, N>::Sin(a);
500 } else if constexpr (std::is_same_v<T, float>) {
501 if (!details::IsFastSinCosInput(a))
503 return MOCHI_SIMD_MEMBERWISE_FALLBACK(T, N, std::sin, a);
504 }
505 // Reduce x to [-pi/4, pi/4) in quadrant n and compute Taylor series
506 Simd<T, N> sin, cos;
507 Simd<int, N> n;
508 details::SinCosImpl(a, sin, cos, n);
509 // Use bitwise ops to select sin for (n == 0) || (n == 2). Else select cos.
510 auto result = Select((n & 1) - 1, sin, cos);
511 // Then flip the sign if (n == 2) || (n == 3).
512 return result ^ ReinterpretCast<Simd<T, N>>(ShiftRight<1>(n) << 31);
513 } else {
514 return MOCHI_SIMD_MEMBERWISE_FALLBACK(T, N, std::sin, a);
515 }
516}
517
518template <class T, int N>
519inline std::pair<Simd<T, N>, Simd<T, N>> SinCos(Simd<T, N> a) {
520 // Our custom single-precision implementation can efficiently compute sin and cos at the same
521 // time. However, we still use call Sin and Cos separately when they are implemented with SVML, so
522 // that the results will be exactly the same.
523 if constexpr (!MOCHI_ARCH_X64_SVML && std::is_same_v<T, float>) {
524 if (!details::IsFastSinCosInput(a))
526 return {Sin(a), Cos(a)};
527 }
528 Simd<float, N> sin, cos;
529 Simd<int, N> n;
530 details::SinCosImpl(a, sin, cos, n);
531 auto mask = ReinterpretCast<Simd<float, N>>((n & 1) - 1);
532 auto nr = ShiftRight<1>(n);
533 auto sresult = Select(mask, sin, cos) ^ ReinterpretCast<Simd<float, N>>(nr << 31);
534 auto cresult = Select(mask, cos, sin) ^ ReinterpretCast<Simd<float, N>>((n ^ nr) << 31);
535 return {sresult, cresult};
536 } else {
537 return {Sin(a), Cos(a)};
538 }
539}
540
541template <class T, int N>
543 if constexpr (details::kUseSvml || Simd<T, N>::kIsEmulated) {
544 // Simd<T, N>::Tan only implemented in this case.
545 return Simd<T, N>::Tan(a);
546 } else {
547 return MOCHI_SIMD_MEMBERWISE_FALLBACK(T, N, std::tan, a);
548 }
549}
550
551template <class T, int N>
553 if constexpr (details::kUseSvml || Simd<T, N>::kIsEmulated) {
554 // Simd<T, N>::ACos only implemented in this case.
555 return Simd<T, N>::ACos(a);
556 } else {
557 return MOCHI_SIMD_MEMBERWISE_FALLBACK(T, N, std::acos, a);
558 }
559}
560
561template <class T, int N>
563 if constexpr (details::kUseSvml || Simd<T, N>::kIsEmulated) {
564 // Simd<T, N>::ASin only implemented in this case.
565 return Simd<T, N>::ASin(a);
566 } else {
567 return MOCHI_SIMD_MEMBERWISE_FALLBACK(T, N, std::asin, a);
568 }
569}
570
571template <class T, int N>
573 if constexpr (details::kUseSvml || Simd<T, N>::kIsEmulated) {
574 // Simd<T, N>::ATan only implemented in this case.
575 return Simd<T, N>::ATan(a);
576 } else {
577 return MOCHI_SIMD_MEMBERWISE_FALLBACK(T, N, std::atan, a);
578 }
579}
580
581template <class T, int N>
583 if constexpr (details::kUseSvml || Simd<T, N>::kIsEmulated) {
584 // Simd<T, N>::Exp only implemented in this case.
585 return Simd<T, N>::Exp(a);
586 } else {
587 //
588 // This code is mimicking the following implementations:
589 // Vc (float):
590 // Vc (double): https://github.com/VcDevel/Vc/blob/1.4/Vc/common/math.h
591 // cephes: https://github.com/jeremybarnes/cephes/blob/master/cmath/exp.c
592 // avx_mathfun: https://github.com/reyoung/avx_mathfun/blob/master/avx_mathfun.h
593 // and the references therein.
594 //
595 //--- Treat the case of float and double
596 Simd<T, N> const infinity(std::numeric_limits<T>::infinity());
597 Simd<T, N> const log2_e(T(1.44269504088896341)); // = ln(e) / ln(2)
598 Simd<T, N> const one(T(1.0));
599 Simd<T, N> const half(T(0.5));
600 Simd<T, N> const zero(T(0.0));
601 //
602 auto x = a;
603 auto n = log2_e * x;
604 n += half;
605 n = Floor(n);
606 if constexpr (std::is_same_v<T const, float const>) {
607 Simd<T, N> const C1(T(0.693359375));
608 Simd<T, N> const C2(T(-2.121944400547138e-04));
609 x -= C1 * n;
610 x -= C2 * n;
611 } else {
612 static_assert(std::is_same_v<T const, double const>);
613 Simd<T, N> const C1(T(0.693145751953125));
614 Simd<T, N> const C2(T(1.42860682030941723212e-06));
615 x -= C1 * n;
616 x -= C2 * n;
617 }
618 //--- Polynomial approximation
619 Simd<T, N> y;
620 if constexpr (std::is_same_v<T const, float const>) {
621 float const P[] = {
622 1.9875691500e-04,
623 1.3982999507e-03,
624 8.3334519073e-03,
625 4.1665795894e-02,
626 1.6666665459e-01,
627 5.0000001201e-01};
628 auto z = x * x;
629 y = Simd<T, N>(P[0]);
630 for (int i = 1; i <= 5; ++i) {
631 y *= x;
632 y += Simd<T, N>(P[i]);
633 }
634 y *= z;
635 y += x;
636 y += one;
637 auto imm0 = StaticCast<Simd<int, N>>(n);
638 imm0 += Simd<int, N>(127); // 127 <- 0x7f
639 imm0 = imm0 << 23;
640 x = y * ReinterpretCast<Simd<T, N>>(imm0); // x = y * Exp2(n);
641 //
642 Simd<T, N> const exp_lo(T(-88.37626226647949));
643 auto const underFlow = (a < exp_lo);
644 x = Simd<T, N>::Select(underFlow, zero, x);
645 //
646 Simd<T, N> const exp_hi(T(88.37626226647949));
647 auto const overFlow = (a > exp_hi);
648 x = Simd<T, N>::Select(overFlow, infinity, x);
649 return x;
650 }
651 //
652 // --- Treating the 'double' case
653 //
654 if constexpr (std::is_same_v<T const, double const>) {
655 double const P[] = {
656 1.26177193074810590878E-4,
657 3.02994407707441961300E-2,
658 9.99999999999999999910E-1,
659 };
660 double const Q[] = {
661 3.00198505138664455042E-6,
662 2.52448340349684104192E-3,
663 2.27265548208155028766E-1,
664 2.00000000000000000009E0,
665 };
666 auto z = x * x;
667 Simd<T, N> pz(P[0]);
668 for (int i = 1; i <= 2; ++i) {
669 pz *= z;
670 pz += Simd<T, N>(P[i]);
671 }
672 pz *= x;
673 Simd<T, N> qz(Q[0]);
674 for (int i = 1; i <= 3; ++i) {
675 qz *= z;
676 qz += Simd<T, N>(Q[i]);
677 }
678 y = pz / (qz - pz);
679 y *= Simd<T, N>(T(2.0));
680 y += one;
681 auto jmm = StaticCast<Simd<int64_t, N>>(n);
682 jmm += Simd<int64_t, N>(1023);
683 jmm = jmm << 52;
684 x = y * ReinterpretCast<Simd<T, N>>(jmm);
685 //
686 Simd<T, N> const exp_lo(T(-709.0));
687 auto const underFlow = (a < exp_lo);
688 x = Simd<T, N>::Select(underFlow, zero, x);
689 //
690 Simd<T, N> const exp_hi(T(709.0));
691 auto const overFlow = (a > exp_hi);
692 x = Simd<T, N>::Select(overFlow, infinity, x);
693 return x;
694 }
695 }
696}
697
698template <class T, int N>
700 if constexpr (details::kUseSvml || Simd<T, N>::kIsEmulated) {
701 // Simd<T, N>::Ln only implemented in this case.
702 return Simd<T, N>::Ln(a);
703 } else {
704 return MOCHI_SIMD_MEMBERWISE_FALLBACK(T, N, std::log, a);
705 }
706}
707
708template <class T, int N>
710 if constexpr (details::kUseSvml || Simd<T, N>::kIsEmulated) {
711 // Simd<T, N>::Tanh only implemented in this case.
712 return Simd<T, N>::Tanh(a);
713 } else {
714 return MOCHI_SIMD_MEMBERWISE_FALLBACK(T, N, std::tanh, a);
715 }
716}
717
718#undef MOCHI_SIMD_MEMBERWISE_FALLBACK
719
720template <class V>
722 return V::Equal(a, b);
723}
724
725template <class V>
727 return V::NotEqual(a, b);
728}
729
730template <int COUNT, class T, int N>
732 if constexpr (COUNT == 1) {
733 return Get0(a) == Get0(b);
734 } else {
735 return AllTrue<COUNT>(VEqual(a, b));
736 }
737}
738
739template <int COUNT, class T, int N>
741#if MOCHI_PLATFORM_MACOS && MOCHI_ARCH_X64 && MOCHI_OPTIMIZED
742 // Work-around for a compiler bug in optimized builds on Intel macOS.
743 return !Equal<COUNT>(a, b);
744#else
745 if constexpr (COUNT == 1) {
746 return Get0(a) != Get0(b);
747 } else {
748 return AnyTrue<COUNT>(VNotEqual(a, b));
749 }
750#endif
751}
752
753template <class V>
755 return Abs(a - b) <= epsilon;
756}
757
758template <class V>
760 return Abs(a) <= epsilon;
761}
762
763template <int COUNT, class T, int N>
765 constexpr int COUNT_ = (COUNT == -1) ? N : COUNT; // -1 means "all"
767}
768
769template <int COUNT, class T, int N>
771 constexpr int COUNT_ = (COUNT == -1) ? N : COUNT; // -1 means "all"
773}
774
775template <class T, int N>
777 if constexpr (std::is_same_v<T, float>) {
778 constexpr int kMask = 0x7F800000; // These bits set for inf and NaN variants
779 auto mask = Simd<int, N>{kMask};
780 return ReinterpretCast<Simd<T, N>>(VNotEqual(ReinterpretCast<Simd<int, N>>(a) & mask, mask));
781 } else {
782 static_assert(std::is_same_v<T, double>, "VIsFinite only supports float and double.");
783 constexpr int kMask = 0x7FF00000; // These bits set for inf and NaN variants
784 if constexpr (N == 2 && Simd<int, 4>::kIsSupported) {
785 // Use an Int4 mask. Result comes from the 2 that correspond to the high bits of each
786 // double.
787 auto mask = Simd<int, 4>{kMask};
788 auto temp4i32 = VNotEqual(ReinterpretCast<Simd<int, 4>>(a) & mask, mask);
789 auto temp2i64 = Shuffle<1, 1, 3, 3>(temp4i32);
790 return ReinterpretCast<Simd<double, 2>>(temp2i64);
791 } else if constexpr (N == 4 && Simd<int, 8>::kIsSupported) {
792 // Use an int8 mask. Result comes from the 4 that correspond to the high bits of each
793 // double.
794 auto mask = Simd<int, 8>{kMask};
795 auto temp8i32 = VNotEqual(ReinterpretCast<Simd<int, 8>>(a) & mask, mask);
796 auto low2i64 = Shuffle<1, 1, 3, 3>(GetHalf<0>(temp8i32)); // no 8-way shuffle currently
797 auto high2i64 = Shuffle<1, 1, 3, 3>(GetHalf<1>(temp8i32));
798 auto temp4i64 = Simd<int, 8>(low2i64, high2i64);
799 return ReinterpretCast<Simd<double, 4>>(temp4i64);
800 } else if constexpr (N == 8 && Simd<int64_t, 8>::kIsSupported) {
801 auto mask = Simd<int64_t, 8>{INT64_C(0x7FF0000000000000)};
802 auto bits = ReinterpretCast<Simd<int64_t, 8>>(a);
803 return ReinterpretCast<Simd<double, 8>>(VNotEqual(bits & mask, mask));
804 } else if constexpr (Simd<T, N>::kIsComposite) {
805 return Simd<T, N>{VIsFinite(a.first), VIsFinite(a.second)};
806 } else if constexpr (!MOCHI_USE_SIMD) {
807 // Simd Emulated
808 Simd<T, N> result;
809 for (int i = 0; i < N; ++i) {
810 uint64_t isFinite = -static_cast<uint64_t>(IsFinite(a.raw[i])); // true = -1, false = 0
811 memcpy(&result.raw[i], &isFinite, sizeof(T));
812 }
813 return result;
814 } else {
815 static_assert(std::is_void_v<T>, "Unsupported type or size");
816 }
817 }
818}
819
820template <int i, class T, int N>
822 // The bits of mask[i] should be all zeros for "false", or all ones for "true". Therefore we can
823 // test any byte(s) within mask[i]. We never return a floating-point type so that the caller
824 // doesn't have to worry about comparisons with NaN.
825 constexpr int kNumInts{(sizeof(T) * N) / sizeof(int)};
826 constexpr int kStride = kNumInts / N;
827 return Get<i * kStride>(ReinterpretCast<Simd<int, kNumInts>>(mask));
828}
829
830template <class T, int N>
834
835template <class T, int N>
839
840template <class T, int N>
844
845template <class T, int N>
849
850template <int COUNT, class V>
852 constexpr int COUNT_ = (COUNT == -1) ? V::kSize : COUNT;
853 return V::template Dot<COUNT_>(a, b);
854}
855
856template <class T, int N>
860
861template <int COUNT, class T, int N>
863 static_assert(COUNT == -1 || COUNT >= 2, "Unsupported COUNT");
864 static_assert(std::is_floating_point_v<T>, "Requires float or double");
865 return VDot<COUNT>(a, a);
866}
867
868template <int COUNT, class T, int N>
872
873template <int COUNT, class T, int N>
877
878template <int COUNT, class T, int N>
882
883template <int COUNT, class T, int N>
885 // By adding the smallest possible scalar we prevent divide-by-zero and get a zero vector result
886 // There is no change in result for any vector longer than.... something very very very small
887 return a / (VNorm<COUNT>(a) + std::numeric_limits<T>::min());
888}
889
890template <class T, int N>
892 // By adding the smallest possible scalar we prevent divide-by-zero and get a zero vector result
893 // There is no change in result for any vector longer than.... something very very very small
894 return a / (Sqrt(normSqr) + std::numeric_limits<T>::min());
895}
896
897template <class T, int N>
899 return Normalize(a, Simd<T, N>{normSqr});
900}
901
902template <class T>
904 static_assert(std::is_floating_point_v<T>, "Requires float or double");
905
906 // Project the coordinate of the minimum absolute value
907 // then build the orthogonal vector in that subspace
908
909 // Compute mask selecting the minimum absolute value(s)
910 auto abs = Abs(a);
911 auto absMin = HMin<3>(abs);
912
913 // Build possible orthogonal vectors and select them according to the mask.
914 a = ToSimdDirection(a);
915 if (absMin == Get<0>(abs)) { // abs(v[0]) was the smallest value (or tied for smallest)
917 } else if (absMin == Get<1>(abs)) { // abs(v[1]) was the smallest value (or tied for smallest)
919 } else { // abs(v[2]) was the smallest value
921 }
922 return a;
923}
924
925namespace details {
926
927/**
928 Determines the smallest supported SIMD size that uses the minimum possible number of native
929 registers for the requested lane count.
930*/
931template <typename T, int kSize, bool kExceedsNativeSize = (kSize > kSimdDefaultSize<T>)>
932struct NextSupportedSimdSizeHelper;
933
934template <typename T, int kSize>
935struct NextSupportedSimdSizeHelper<T, kSize, true> {
936 static_assert(kSize > 0, "SIMD size must be positive.");
937 static_assert(Simd<T>::kIsSupported, "Type T is not supported for any size N.");
938 static constexpr int value =
939 kSimdDefaultSize<T> + NextSupportedSimdSizeHelper<T, kSize - kSimdDefaultSize<T>>::value;
940};
941
942template <typename T, int kSize>
943struct NextSupportedSimdSizeHelper<T, kSize, false> {
944 static_assert(kSize > 0, "SIMD size must be positive.");
945 static_assert(Simd<T>::kIsSupported, "Type T is not supported for any size N.");
946 static constexpr bool kIsSingleRegister =
948 static constexpr int value = std::conditional_t<
949 kIsSingleRegister,
950 std::integral_constant<int, kSize>,
951 NextSupportedSimdSizeHelper<T, kSize + 1>>::value;
952};
953
954template <typename T, int kSize>
955inline constexpr int kNextSupportedSimdSize = NextSupportedSimdSizeHelper<T, kSize>::value;
956
957} // namespace details
958
959} // namespace superdex
960
961/************************************************************************************
962 Reflection support for Simd<T, N>
963 Serializes like std::array<T, N>.
964*/
965#if MOCHI_USE_REFLECTION
966template <typename T, int N>
967struct SReflectTypeTraits<superdex::Simd<T, N>> {
968 static constexpr SReflect::CoreType coreType = SReflect::CoreType::CT_array;
969 static SReflect::ArrayTypeInfo const& GetTypeInfo() {
970 static auto* s_typeInfo =
971 SReflect::MakeFixedArrayTypeInfo<superdex::Simd<T, N>, T, N>("superdex::Simd", true);
972 return *s_typeInfo;
973 }
974};
975#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_UNLIKELY
#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:673
constexpr T ACos(T a)
Simd< T, N > VIsFinite(Simd< T, N > a)
Definition simd_inl.h:776
Simd< T, 2 > Shuffle(Simd< T, 2 > a)
Definition simd_inl.h:273
constexpr int kSimdDefaultSize
Definition simd.h:33
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:874
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:857
constexpr T Sin(T a)
T HSum(Simd< T, N > a)
Definition simd_inl.h:377
constexpr To StaticCast(From const &a)
Definition basic_utils.h:79
V VDot(V a, V b)
Definition simd_inl.h:851
T HMin(Simd< T, N > a)
Definition simd_inl.h:389
T Norm(Simd< T, N > a)
Definition simd_inl.h:879
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:709
V VEqual(V a, V b)
Definition simd_inl.h:721
V ToSimdDirection(V a)
Definition simd_inl.h:179
Simd< T, N > Set(Simd< T, N > a, T value)
Definition simd_inl.h:313
V Sequence()
Definition simd_inl.h:323
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:754
V SimdZero()
Definition simd_inl.h:146
T HMax(Simd< T, N > a)
Definition simd_inl.h:395
Simd< T, N > VNormSqr(Simd< T, N > a)
Definition simd_inl.h:862
int IsTrue(Simd< T, N > mask)
Definition simd_inl.h:821
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:726
constexpr auto MulSub(A a, B b, C c)
Simd< T, N > Normalize(Simd< T, N > a)
Definition simd_inl.h:884
Simd< T, N > Blend(Simd< T, N > a, Simd< T, N > b)
Definition simd_inl.h:288
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:699
std::pair< Simd< T, N >, Simd< T, N > > SinCos(Simd< T, N > a)
Definition simd_inl.h:519
constexpr auto NegMulAdd(A a, B b, C c)
T Get(Simd< T, N > v)
Definition simd_inl.h:303
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:308
T HProd(Simd< T, N > a)
Definition simd_inl.h:383
Simd< T, 4 > OrthogonalVector3(Simd< T, 4 > a)
T Get0(Simd< T, N > v)
Definition simd_inl.h:298
Simd< T, N > VNorm(Simd< T, N > a)
Definition simd_inl.h:869
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:357
constexpr T RcpApprox(T a)
constexpr auto NegMulSub(A a, B b, C c)
V VNearZero(V a, V epsilon)
Definition simd_inl.h:759
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:338
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:400
#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