Add simd::if_then_else
Elementwise ternary ?: operations on vectors aren't supported until clang 13. This change adds a "simd::if_then_else" method that we will have to use instead, which has a fallback implementation for compilers that don't support the vector ternary.
Diffs=
440512dca Add simd::if_then_else (#4403)
diff --git a/.rive_head b/.rive_head
index b72dae7..4f308ae 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-ec9fb5bfcc6442c4e674ddac9a7bfad08ef5151a
+440512dcaee3a07b7643ce9c00d1ebd477556ef2
diff --git a/include/rive/math/simd.hpp b/include/rive/math/simd.hpp
index dfc38fb..3391c11 100644
--- a/include/rive/math/simd.hpp
+++ b/include/rive/math/simd.hpp
@@ -24,6 +24,11 @@
static_assert(std::numeric_limits<float>::is_iec559,
"Conformant IEEE 754 behavior for NaN and Inf is required.");
+// Recommended in https://clang.llvm.org/docs/LanguageExtensions.html#feature-checking-macros
+#ifndef __has_builtin
+#define __has_builtin(x) 0
+#endif
+
namespace rive
{
namespace simd
@@ -75,6 +80,24 @@
////// Math //////
+// Elementwise ternary expression: "_if ? _then : _else" for each component.
+template <typename T, int N>
+SIMD_ALWAYS_INLINE gvec<T, N> if_then_else(gvec<int32_t, N> _if, gvec<T, N> _then, gvec<T, N> _else)
+{
+#if __clang_major__ >= 13
+ // The '?:' operator supports a vector condition beginning in clang 13.
+ return _if ? _then : _else;
+#else
+#pragma message("performance: vectorized '?:' operator not supported. Consider updating clang.")
+ gvec<T, N> ret{};
+ for (int i = 0; i < N; ++i)
+ {
+ ret[i] = _if[i] ? _then[i] : _else[i];
+ }
+ return ret;
+#endif
+}
+
// Similar to std::min(), with a noteworthy difference:
// If a[i] or b[i] is NaN and the other is not, returns whichever is _not_ NaN.
template <typename T, int N> SIMD_ALWAYS_INLINE gvec<T, N> min(gvec<T, N> a, gvec<T, N> b)
@@ -84,7 +107,7 @@
#else
#pragma message("performance: __builtin_elementwise_min() not supported. Consider updating clang.")
// Generate the same behavior for NaN as the SIMD builtins. (isnan() is a no-op for int types.)
- return b < a || isnan(a) ? b : a;
+ return if_then_else(b < a || isnan(a), b, a);
#endif
}
@@ -97,7 +120,7 @@
#else
#pragma message("performance: __builtin_elementwise_max() not supported. Consider updating clang.")
// Generate the same behavior for NaN as the SIMD builtins. (isnan() is a no-op for int types.)
- return a < b || isnan(a) ? b : a;
+ return if_then_else(a < b || isnan(a), b, a);
#endif
}
@@ -121,7 +144,7 @@
return __builtin_elementwise_abs(x);
#else
#pragma message("performance: __builtin_elementwise_abs() not supported. Consider updating clang.")
- return x < 0 ? -x : x; // But the negation in the "true" side so we never negate NaN.
+ return if_then_else(x < 0, -x, x); // Do the negation on the "true" side so we never negate NaN.
#endif
}
diff --git a/test/simd_test.cpp b/test/simd_test.cpp
index 20ed98d..7517267 100644
--- a/test/simd_test.cpp
+++ b/test/simd_test.cpp
@@ -51,15 +51,15 @@
CHECK(!simd::any(test.zw == test.zw)); // NaN
}
-// Check that ?: works on vector and scalar conditions.
+// Check simd::if_then_else.
TEST_CASE("ternary-operator", "[simd]")
{
// Vector condition.
- float4 f4 = int4{1, 2, 3, 4} < int4{4, 3, 2, 1} ? float4(-1) : 1.f;
+ float4 f4 = simd::if_then_else(int4{1, 2, 3, 4} < int4{4, 3, 2, 1}, float4(-1), float4(1));
CHECK(simd::all(f4 == float4{-1, -1, 1, 1}));
// In vector, -1 is true, 0 is false.
- uint2 u2 = int2{0, -1} ? uint2{1, 2} : uint2{3, 4};
+ uint2 u2 = simd::if_then_else(int2{0, -1}, uint2{1, 2}, uint2{3, 4});
CHECK(simd::all(u2 == uint2{3, 2}));
// Scalar condition.
@@ -211,7 +211,7 @@
static float frand()
{
float kMaxBelow1 = math::bit_cast<float>(math::bit_cast<uint32_t>(1.f) - 1);
- float f = static_cast<float>(rand()) / RAND_MAX;
+ float f = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
return std::min(kMaxBelow1, f);
}
template <int N> vec<N> vrand()