| // { dg-do compile { target c++26 } } |
| // { dg-require-effective-target x86 } |
| // { dg-timeout-factor 2 } |
| |
| #include <simd> |
| #include <stdfloat> |
| |
| namespace simd = std::simd; |
| |
| // test that instantiation of the complete class is well-formed |
| template class simd::basic_vec<int, typename simd::vec<int, 1>::abi_type>; |
| template class simd::basic_vec<int, typename simd::vec<int, 5>::abi_type>; |
| template class simd::basic_vec<int, typename simd::vec<int, 8>::abi_type>; |
| template class simd::basic_vec<int, typename simd::vec<int, 13>::abi_type>; |
| template class simd::basic_vec<float, typename simd::vec<float, 8>::abi_type>; |
| template class simd::basic_vec<float, typename simd::vec<float, 13>::abi_type>; |
| |
| constexpr auto default_mask_abi_variant = |
| #ifdef __AVX512F__ |
| simd::_AbiVariant::_BitMask; |
| #else |
| simd::_AbiVariant(); |
| #endif |
| |
| namespace test01 |
| { |
| using std::same_as; |
| |
| using Abi1 = simd::_Abi_t<1, 1, default_mask_abi_variant>; |
| static_assert(same_as<simd::vec<int, 1>::abi_type, Abi1>); |
| static_assert(same_as<simd::vec<float, 1>::abi_type, Abi1>); |
| |
| #if defined __SSE__ && !defined __AVX__ |
| static_assert(same_as<simd::vec<float>::abi_type, simd::_Abi_t<4, 1>>); |
| static_assert(same_as<simd::vec<float, 3>::abi_type, simd::_Abi_t<3, 1>>); |
| static_assert(same_as<simd::vec<float, 7>::abi_type, simd::_Abi_t<7, 2>>); |
| |
| static_assert(simd::vec<float>::size > 1); |
| static_assert(alignof(simd::vec<float>) > alignof(float)); |
| static_assert(alignof(simd::vec<float, 4>) > alignof(float)); |
| static_assert(alignof(simd::vec<float, 3>) > alignof(float)); |
| static_assert(sizeof(simd::vec<float, 7>) == 2 * sizeof(simd::vec<float>)); |
| static_assert(alignof(simd::vec<float, 7>) == alignof(simd::vec<float>)); |
| #endif |
| } |
| |
| namespace test02 |
| { |
| using namespace std; |
| using namespace std::simd; |
| |
| static_assert(!destructible<simd::basic_mask<7>>); |
| |
| static_assert(same_as<simd::vec<int>::mask_type, simd::mask<int>>); |
| static_assert(same_as<simd::vec<float>::mask_type, simd::mask<float>>); |
| static_assert(same_as<simd::vec<float, 1>::mask_type, simd::mask<float, 1>>); |
| |
| // ensure 'true ? int : vec<float>' doesn't work |
| template <typename T> |
| concept has_type_member = requires { typename T::type; }; |
| static_assert(has_type_member<common_type<int, simd::vec<float>>>); |
| } |
| |
| #if defined __AVX__ && !defined __AVX2__ |
| static_assert(alignof(simd::mask<int, 8>) == 16); |
| static_assert(alignof(simd::mask<float, 8>) == 32); |
| static_assert(alignof(simd::mask<int, 16>) == 16); |
| static_assert(alignof(simd::mask<float, 16>) == 32); |
| static_assert(alignof(simd::mask<long long, 4>) == 16); |
| static_assert(alignof(simd::mask<double, 4>) == 32); |
| static_assert(alignof(simd::mask<long long, 8>) == 16); |
| static_assert(alignof(simd::mask<double, 8>) == 32); |
| static_assert(std::same_as<decltype(+simd::mask<float, 8>()), simd::vec<int, 8>>); |
| #endif |
| |
| #if defined __SSE__ && !defined __F16C__ && defined __STDCPP_FLOAT16_T__ |
| static_assert(simd::vec<std::float16_t>::size() == 1); |
| static_assert(simd::mask<std::float16_t>::size() == 1); |
| static_assert(alignof(simd::vec<std::float16_t, 8>) == alignof(std::float16_t)); |
| static_assert(alignof(simd::rebind_t<std::float16_t, simd::vec<float>>) == alignof(std::float16_t)); |
| static_assert(simd::rebind_t<std::float16_t, simd::mask<float>>::abi_type::_S_nreg |
| == simd::vec<float>::size()); |
| #endif |
| |
| template <auto X> |
| using Ic = std::integral_constant<std::remove_const_t<decltype(X)>, X>; |
| |
| static_assert( std::convertible_to<Ic<1>, simd::vec<float>>); |
| static_assert(!std::convertible_to<Ic<1.1>, simd::vec<float>>); |
| static_assert(!std::convertible_to<simd::vec<int, 4>, simd::vec<float, 4>>); |
| static_assert(!std::convertible_to<simd::vec<float, 4>, simd::vec<int, 4>>); |
| static_assert( std::convertible_to<int, simd::vec<float>>); |
| static_assert( std::convertible_to<simd::vec<int, 4>, simd::vec<double, 4>>); |
| |
| template <typename V> |
| concept has_static_size = requires { |
| { V::size } -> std::convertible_to<int>; |
| { V::size() } -> std::signed_integral; |
| { auto(V::size.value) } -> std::signed_integral; |
| }; |
| |
| template <typename V, typename T = typename V::value_type> |
| concept usable_vec_or_mask |
| = std::destructible<V> |
| && std::is_nothrow_move_constructible_v<V> |
| && std::is_nothrow_move_assignable_v<V> |
| && std::is_nothrow_default_constructible_v<V> |
| && std::is_trivially_copyable_v<V> |
| && std::is_standard_layout_v<V> |
| && std::ranges::random_access_range<V&> |
| && !std::ranges::output_range<V&, T> |
| && std::constructible_from<V, T> // broadcast |
| && has_static_size<V> |
| && simd::__simd_vec_or_mask_type<V> |
| ; |
| |
| template <typename V, typename T = typename V::value_type> |
| concept usable_vec |
| = usable_vec_or_mask<V, T> |
| && !std::convertible_to<V, std::array<T, V::size()>> |
| && std::convertible_to<std::array<T, V::size()>, V> |
| && std::constructible_from<V, simd::rebind_t<int, V>> |
| && std::constructible_from<V, simd::rebind_t<float, V>> |
| && !std::constructible_from<V, simd::resize_t<V::size() + 1, V>> |
| && !std::constructible_from<V, simd::resize_t<V::size() + 1, typename V::mask_type>> |
| && !std::constructible_from<typename V::mask_type, V> |
| ; |
| |
| template <typename M, typename T = typename M::value_type> |
| concept usable_mask |
| = std::is_same_v<T, bool> |
| && usable_vec_or_mask<M, T> |
| && std::convertible_to<std::bitset<M::size()>, M> |
| && std::constructible_from<M, unsigned long long> |
| && std::constructible_from<M, unsigned char> |
| && std::constructible_from<M, simd::rebind_t<int, M>> |
| && std::constructible_from<M, simd::rebind_t<float, M>> |
| && !std::constructible_from<M, simd::resize_t<M::size() + 1, M>> |
| && !std::convertible_to<unsigned long long, M> |
| && !std::convertible_to<unsigned char, M> |
| && !std::convertible_to<bool, M> |
| && !std::constructible_from<M, std::bitset<M::size() + 1>> |
| && !std::constructible_from<M, std::bitset<M::size() - 1>> |
| && !std::constructible_from<M, int> |
| && !std::constructible_from<M, float> |
| ; |
| |
| template <typename T> |
| struct test_usable_simd |
| { |
| static_assert(!usable_vec<simd::vec<T, 0>>); |
| static_assert(!has_static_size<simd::vec<T, 0>>); |
| static_assert(usable_vec<simd::vec<T, 1>>); |
| static_assert(usable_vec<simd::vec<T, 2>>); |
| static_assert(usable_vec<simd::vec<T, 3>>); |
| static_assert(usable_vec<simd::vec<T, 4>>); |
| static_assert(usable_vec<simd::vec<T, 7>>); |
| static_assert(usable_vec<simd::vec<T, 8>>); |
| static_assert(usable_vec<simd::vec<T, 16>>); |
| static_assert(usable_vec<simd::vec<T, 32>>); |
| static_assert(usable_vec<simd::vec<T, 63>>); |
| static_assert(usable_vec<simd::vec<T, 64>>); |
| |
| static_assert(!usable_mask<simd::mask<T, 0>>); |
| static_assert(!has_static_size<simd::mask<T, 0>>); |
| static_assert(usable_mask<simd::mask<T, 1>>); |
| static_assert(usable_mask<simd::mask<T, 2>>); |
| static_assert(usable_mask<simd::mask<T, 3>>); |
| static_assert(usable_mask<simd::mask<T, 4>>); |
| static_assert(usable_mask<simd::mask<T, 7>>); |
| static_assert(usable_mask<simd::mask<T, 8>>); |
| static_assert(usable_mask<simd::mask<T, 16>>); |
| static_assert(usable_mask<simd::mask<T, 32>>); |
| static_assert(usable_mask<simd::mask<T, 63>>); |
| static_assert(usable_mask<simd::mask<T, 64>>); |
| }; |
| |
| template <template <typename> class Tpl> |
| struct instantiate_all_vectorizable |
| { |
| Tpl<float> a; |
| Tpl<double> b; |
| Tpl<char> c; |
| Tpl<char8_t> c8; |
| Tpl<char16_t> d; |
| Tpl<char32_t> e; |
| Tpl<wchar_t> f; |
| Tpl<signed char> g; |
| Tpl<unsigned char> h; |
| Tpl<short> i; |
| Tpl<unsigned short> j; |
| Tpl<int> k; |
| Tpl<unsigned int> l; |
| Tpl<long> m; |
| Tpl<unsigned long> n; |
| Tpl<long long> o; |
| Tpl<unsigned long long> p; |
| #ifdef __STDCPP_FLOAT16_T__ |
| Tpl<std::float16_t> q; |
| #endif |
| #ifdef __STDCPP_FLOAT32_T__ |
| Tpl<std::float32_t> r; |
| #endif |
| #ifdef __STDCPP_FLOAT64_T__ |
| Tpl<std::float64_t> s; |
| #endif |
| }; |
| |
| template struct instantiate_all_vectorizable<test_usable_simd>; |
| |
| // vec generator ctor /////////////// |
| |
| namespace test_generator |
| { |
| struct udt_convertible_to_float |
| { operator float() const; }; |
| |
| static_assert( std::constructible_from<simd::vec<float>, float (&)(int)>); |
| static_assert(!std::convertible_to<float (&)(int), simd::vec<float>>); |
| static_assert(!std::constructible_from<simd::vec<float>, int (&)(int)>); |
| static_assert(!std::constructible_from<simd::vec<float>, double (&)(int)>); |
| static_assert( std::constructible_from<simd::vec<float>, short (&)(int)>); |
| static_assert(!std::constructible_from<simd::vec<float>, long double (&)(int)>); |
| static_assert( std::constructible_from<simd::vec<float>, udt_convertible_to_float (&)(int)>); |
| } |
| |
| // mask generator ctor /////////////// |
| |
| static_assert( |
| all_of(simd::mask<float, 4>([](int) { return true; }) == simd::mask<float, 4>(true))); |
| static_assert( |
| all_of(simd::mask<float, 4>([](int) { return false; }) == simd::mask<float, 4>(false))); |
| static_assert( |
| all_of(simd::mask<float, 4>([](int i) { return i < 2; }) |
| == simd::mask<float, 4>([](int i) { |
| return std::array{true, true, false, false}[i]; |
| }))); |
| |
| static_assert(all_of((simd::vec<int, 4>([](int i) { return i << 10; }) >> 10) |
| == simd::__iota<simd::vec<int, 4>>)); |
| |
| // vec iterators ///////////////////// |
| |
| #if SIMD_IS_A_RANGE |
| static_assert([] { simd::vec<float> x = {}; return x.begin() == x.begin(); }()); |
| static_assert([] { simd::vec<float> x = {}; return x.begin() == x.cbegin(); }()); |
| static_assert([] { simd::vec<float> x = {}; return x.cbegin() == x.begin(); }()); |
| static_assert([] { simd::vec<float> x = {}; return x.cbegin() == x.cbegin(); }()); |
| static_assert([] { simd::vec<float> x = {}; return x.begin() + x.size() == x.end(); }()); |
| static_assert([] { simd::vec<float> x = {}; return x.end() == x.begin() + x.size(); }()); |
| static_assert([] { simd::vec<float> x = {}; return x.begin() < x.end(); }()); |
| static_assert([] { simd::vec<float> x = {}; return x.begin() <= x.end(); }()); |
| static_assert(![] { simd::vec<float> x = {}; return x.begin() > x.end(); }()); |
| static_assert(![] { simd::vec<float> x = {}; return x.begin() >= x.end(); }()); |
| static_assert(![] { simd::vec<float> x = {}; return x.end() < x.begin(); }()); |
| static_assert(![] { simd::vec<float> x = {}; return x.end() <= x.begin(); }()); |
| static_assert([] { simd::vec<float> x = {}; return x.end() > x.begin(); }()); |
| static_assert([] { simd::vec<float> x = {}; return x.end() >= x.begin(); }()); |
| static_assert([] { simd::vec<float> x = {}; return x.end() - x.begin(); }() == simd::vec<float>::size()); |
| static_assert([] { simd::vec<float> x = {}; return x.begin() - x.end(); }() == -simd::vec<float>::size()); |
| static_assert([] { simd::vec<float> x = {}; return x.begin() - x.begin(); }() == 0); |
| static_assert([] { simd::vec<float> x = {}; return x.begin() + 1 - x.begin(); }() == 1); |
| static_assert([] { simd::vec<float> x = {}; return x.begin() + 1 - x.cbegin(); }() == 1); |
| #endif |
| |
| // mask to vec /////////////////////// |
| |
| // Clang says all kinds of expressions are not constant expressions. Why? Come on … explain! 🤷 |
| #ifdef __clang__ |
| #define AVOID_BROKEN_CLANG_FAILURES 1 |
| #endif |
| |
| #ifndef AVOID_BROKEN_CLANG_FAILURES |
| |
| static_assert([] constexpr { |
| constexpr simd::mask<float, 7> a([](int i) -> bool { return i < 3; }); |
| constexpr simd::basic_vec b = -a; |
| static_assert(b[0] == -(0 < 3)); |
| static_assert(b[1] == -(1 < 3)); |
| static_assert(b[2] == -(2 < 3)); |
| static_assert(b[3] == -(3 < 3)); |
| return all_of(b == simd::vec<int, 7>([](int i) { return -int(i < 3); })); |
| }()); |
| |
| static_assert([] constexpr { |
| constexpr simd::mask<float, 7> a([](int i) -> bool { return i < 3; }); |
| constexpr simd::basic_vec b = ~a; |
| static_assert(b[0] == ~int(0 < 3)); |
| static_assert(b[1] == ~int(1 < 3)); |
| static_assert(b[2] == ~int(2 < 3)); |
| static_assert(b[3] == ~int(3 < 3)); |
| return all_of(b == simd::vec<int, 7>([](int i) { return ~int(i < 3); })); |
| }()); |
| |
| static_assert([] constexpr { |
| constexpr simd::mask<float, 4> a([](int i) -> bool { return i < 2; }); |
| constexpr simd::basic_vec b = a; |
| static_assert(b[0] == 1); |
| static_assert(b[1] == 1); |
| static_assert(b[2] == 0); |
| return b[3] == 0; |
| }()); |
| |
| static_assert([] constexpr { |
| // Corner case on AVX w/o AVX2 systems. <float, 5> is an AVX register; |
| // <int, 5> is deduced as SSE + scalar. |
| constexpr simd::mask<float, 5> a([](int i) -> bool { return i >= 2; }); |
| constexpr simd::basic_vec b = a; |
| static_assert(b[0] == 0); |
| static_assert(b[1] == 0); |
| static_assert(b[2] == 1); |
| static_assert(b[3] == 1); |
| static_assert(b[4] == 1); |
| #if defined __AVX2__ || !defined __AVX__ |
| static_assert(all_of((b == 1) == a)); |
| #endif |
| constexpr simd::mask<float, 8> a8([](int i) -> bool { return i <= 4; }); |
| constexpr simd::basic_vec b8 = a8; |
| static_assert(b8[0] == 1); |
| static_assert(b8[1] == 1); |
| static_assert(b8[2] == 1); |
| static_assert(b8[3] == 1); |
| static_assert(b8[4] == 1); |
| static_assert(b8[5] == 0); |
| static_assert(b8[6] == 0); |
| static_assert(b8[7] == 0); |
| #if SIMD_MASK_IMPLICIT_CONVERSIONS || defined __AVX2__ || !defined __AVX__ |
| static_assert(all_of((b8 == 1) == a8)); |
| #endif |
| constexpr simd::mask<float, 15> a15([](int i) -> bool { return i <= 4; }); |
| constexpr simd::basic_vec b15 = a15; |
| static_assert(b15[0] == 1); |
| static_assert(b15[4] == 1); |
| static_assert(b15[5] == 0); |
| static_assert(b15[8] == 0); |
| static_assert(b15[14] == 0); |
| static_assert(all_of((b15 == 1) == a15)); |
| return true; |
| }()); |
| |
| static_assert([] constexpr { |
| constexpr simd::mask<float, 4> a([](int i) -> bool { return i < 2; }); |
| constexpr simd::basic_vec b = ~a; |
| constexpr simd::basic_vec c = a; |
| static_assert(c[0] == int(a[0])); |
| static_assert(c[1] == int(a[1])); |
| static_assert(c[2] == int(a[2])); |
| static_assert(c[3] == int(a[3])); |
| static_assert(b[0] == ~int(0 < 2)); |
| static_assert(b[1] == ~int(1 < 2)); |
| static_assert(b[2] == ~int(2 < 2)); |
| static_assert(b[3] == ~int(3 < 2)); |
| return all_of(b == simd::vec<int, 4>([](int i) { return ~int(i < 2); })); |
| }()); |
| #endif |
| |
| // mask conversions ////////////////// |
| namespace mask_conversion_tests |
| { |
| using simd::mask; |
| |
| struct TestResult |
| { |
| int state; |
| unsigned long long a, b; |
| }; |
| |
| template <auto Res> |
| consteval void |
| check() |
| { |
| if constexpr (Res.state != 0 && Res.a != Res.b) |
| static_assert(Res.a == Res.b); |
| else |
| static_assert(Res.state == 0); |
| } |
| |
| template <typename U> |
| consteval TestResult |
| do_test(const auto& k) |
| { |
| using M = simd::mask<U, k.size()>; |
| if constexpr (std::is_destructible_v<M>) |
| { |
| if (!std::ranges::equal(M(k), k)) |
| { |
| if constexpr (k.size() <= 64) |
| return {1, M(k).to_ullong(), k.to_ullong()}; |
| else |
| return {1, 0, 0}; |
| } |
| else |
| return {0, 0, 0}; |
| } |
| else |
| return {0, 0, 0}; |
| } |
| |
| template <typename T, int N, int P = 0> |
| consteval void |
| do_test() |
| { |
| if constexpr (std::is_destructible_v<simd::mask<T, N>>) |
| { |
| constexpr simd::mask<T, N> k([](int i) { |
| if constexpr (P == 2) |
| return std::has_single_bit(unsigned(i)); |
| else if constexpr (P == 3) |
| return !std::has_single_bit(unsigned(i)); |
| else |
| return (i & 1) == P; |
| }); |
| check<do_test<char>( k)>(); |
| check<do_test<char>(!k)>(); |
| check<do_test<short>( k)>(); |
| check<do_test<short>(!k)>(); |
| check<do_test<int>( k)>(); |
| check<do_test<int>(!k)>(); |
| check<do_test<double>( k)>(); |
| check<do_test<double>(!k)>(); |
| #ifdef __STDCPP_FLOAT16_T__ |
| check<do_test<std::float16_t>( k)>(); |
| check<do_test<std::float16_t>(!k)>(); |
| #endif |
| if constexpr (P <= 2) |
| do_test<T, N, P + 1>(); |
| } |
| } |
| |
| template <typename T> |
| consteval bool |
| test() |
| { |
| using V = simd::mask<T>; |
| do_test<T, 1>(); |
| do_test<T, V::size()>(); |
| do_test<T, 2 * V::size()>(); |
| do_test<T, 4 * V::size()>(); |
| do_test<T, 5 * V::size()>(); |
| do_test<T, 2 * V::size() + 1>(); |
| do_test<T, 2 * V::size() - 1>(); |
| do_test<T, V::size() / 2>(); |
| do_test<T, V::size() / 3>(); |
| do_test<T, V::size() / 5>(); |
| return true; |
| } |
| |
| static_assert(test<char>()); |
| static_assert(test<short>()); |
| static_assert(test<float>()); |
| static_assert(test<double>()); |
| #ifdef __STDCPP_FLOAT16_T__ |
| static_assert(test<std::float16_t>()); |
| #endif |
| } |
| |
| // vec reductions /////////////////// |
| |
| namespace simd_reduction_tests |
| { |
| static_assert(reduce(simd::vec<int, 7>(1)) == 7); |
| static_assert(reduce(simd::vec<int, 7>(2), std::multiplies<>()) == 128); |
| static_assert(reduce(simd::vec<int, 8>(2), std::bit_and<>()) == 2); |
| static_assert(reduce(simd::vec<int, 8>(2), std::bit_or<>()) == 2); |
| static_assert(reduce(simd::vec<int, 8>(2), std::bit_xor<>()) == 0); |
| static_assert(reduce(simd::vec<int, 3>(2), std::bit_and<>()) == 2); |
| static_assert(reduce(simd::vec<int, 6>(2), std::bit_and<>()) == 2); |
| static_assert(reduce(simd::vec<int, 7>(2), std::bit_and<>()) == 2); |
| static_assert(reduce(simd::vec<int, 7>(2), std::bit_or<>()) == 2); |
| static_assert(reduce(simd::vec<int, 7>(2), std::bit_xor<>()) == 2); |
| #ifndef AVOID_BROKEN_CLANG_FAILURES |
| static_assert(reduce(simd::vec<int, 4>(2), simd::mask<int, 4>(false)) == 0); |
| static_assert(reduce(simd::vec<int, 4>(2), simd::mask<int, 4>(false), std::multiplies<>()) == 1); |
| static_assert(reduce(simd::vec<int, 4>(2), simd::mask<int, 4>(false), std::bit_and<>()) == ~0); |
| static_assert(reduce(simd::vec<int, 4>(2), simd::mask<int, 4>(false), [](auto a, auto b) { |
| return select(a < b, a, b); |
| }, __INT_MAX__) == __INT_MAX__); |
| #endif |
| |
| template <typename BinaryOperation> |
| concept masked_reduce_works = requires(simd::vec<int, 4> a, simd::vec<int, 4> b) { |
| reduce(a, a < b, BinaryOperation()); |
| }; |
| |
| static_assert(!masked_reduce_works<std::minus<>>); |
| } |
| |
| // mask reductions /////////////////// |
| |
| static_assert(all_of(simd::vec<float>() == simd::vec<float>())); |
| static_assert(any_of(simd::vec<float>() == simd::vec<float>())); |
| static_assert(!none_of(simd::vec<float>() == simd::vec<float>())); |
| static_assert(reduce_count(simd::vec<float>() == simd::vec<float>()) == simd::vec<float>::size); |
| static_assert(reduce_min_index(simd::vec<float>() == simd::vec<float>()) == 0); |
| static_assert(reduce_max_index(simd::vec<float>() == simd::vec<float>()) == simd::vec<float>::size - 1); |
| |
| // chunk //////////////////////// |
| |
| static_assert([] { |
| constexpr auto a = simd::vec<int, 8>([] (int i) { return i; }); |
| auto a4 = chunk<simd::vec<int, 4>>(a); |
| auto a3 = chunk<simd::vec<int, 3>>(a); |
| auto a3_ = chunk<3>(a); |
| return a4.size() == 2 && std::same_as<decltype(a4), std::array<simd::vec<int, 4>, 2>> |
| && std::tuple_size_v<decltype(a3)> == 3 |
| && all_of(std::get<0>(a3) == simd::vec<int, 3>([] (int i) { return i; })) |
| && all_of(std::get<1>(a3) == simd::vec<int, 3>([] (int i) { return i + 3; })) |
| && all_of(std::get<2>(a3) == simd::vec<int, 2>([] (int i) { return i + 6; })) |
| && std::same_as<decltype(a3), decltype(a3_)> |
| && all_of(std::get<0>(a3) == std::get<0>(a3_)); |
| }()); |
| |
| static_assert([] { |
| constexpr simd::mask<int, 8> a([] (int i) -> bool { return i & 1; }); |
| auto a4 = chunk<simd::mask<int, 4>>(a); |
| auto a3 = chunk<simd::mask<int, 3>>(a); |
| auto a3_ = chunk<3>(a); |
| return a4.size() == 2 && std::same_as<decltype(a4), std::array<simd::mask<int, 4>, 2>> |
| && std::tuple_size_v<decltype(a3)> == 3 |
| && all_of(std::get<0>(a3) == simd::mask<int, 3>( |
| [] (int i) -> bool { return i & 1; })) |
| && all_of(std::get<1>(a3) == simd::mask<int, 3>( |
| [] (int i) -> bool { return (i + 3) & 1; })) |
| && all_of(std::get<2>(a3) == simd::mask<int, 2>( |
| [] (int i) -> bool { return (i + 6) & 1; })) |
| && std::same_as<decltype(a3), decltype(a3_)> |
| && all_of(std::get<0>(a3) == std::get<0>(a3_)); |
| }()); |
| |
| // cat /////////////////////////// |
| |
| static_assert(all_of(simd::cat(simd::__iota<simd::vec<int, 3>>, simd::vec<int, 1>(3)) |
| == simd::__iota<simd::vec<int, 4>>)); |
| |
| static_assert(all_of(simd::cat(simd::__iota<simd::vec<int, 4>>, simd::__iota<simd::vec<int, 4>> + 4) |
| == simd::__iota<simd::vec<int, 8>>)); |
| |
| static_assert(all_of(simd::cat(simd::__iota<simd::vec<double, 4>>, simd::__iota<simd::vec<double, 2>> + 4) |
| == simd::__iota<simd::vec<double, 6>>)); |
| |
| static_assert(all_of(simd::cat(simd::__iota<simd::vec<double, 4>>, simd::__iota<simd::vec<double, 4>> + 4) |
| == simd::__iota<simd::vec<double, 8>>)); |
| |
| // select //////////////////////// |
| |
| #ifndef AVOID_BROKEN_CLANG_FAILURES |
| static_assert(all_of(simd::vec<long long, 8>(std::array{0, 0, 0, 0, 4, 4, 4, 4}) |
| == select(simd::__iota<simd::vec<double, 8>> < 4, 0ll, 4ll))); |
| |
| static_assert(all_of(simd::vec<int, 8>(std::array{0, 0, 0, 0, 4, 4, 4, 4}) |
| == select(simd::__iota<simd::vec<float, 8>> < 4.f, 0, 4))); |
| #endif |
| |
| // permute //////////////////////// |
| |
| namespace permutations |
| { |
| struct _DuplicateEven |
| { |
| consteval unsigned |
| operator()(unsigned __i) const |
| { return __i & ~1u; } |
| }; |
| |
| inline constexpr _DuplicateEven duplicate_even {}; |
| |
| struct _DuplicateOdd |
| { |
| consteval unsigned |
| operator()(unsigned __i) const |
| { return __i | 1u; } |
| }; |
| |
| inline constexpr _DuplicateOdd duplicate_odd {}; |
| |
| template <unsigned _Np> |
| struct _SwapNeighbors |
| { |
| consteval unsigned |
| operator()(unsigned __i, unsigned __size) const |
| { |
| if (__size % (2 * _Np) != 0) |
| abort(); // swap_neighbors<N> permutation requires a multiple of 2N elements |
| else if (std::has_single_bit(_Np)) |
| return __i ^ _Np; |
| else if (__i % (2 * _Np) >= _Np) |
| return __i - _Np; |
| else |
| return __i + _Np; |
| } |
| }; |
| |
| template <unsigned _Np = 1u> |
| inline constexpr _SwapNeighbors<_Np> swap_neighbors {}; |
| |
| template <int _Position> |
| struct _Broadcast |
| { |
| consteval int |
| operator()(int, int __size) const |
| { return _Position < 0 ? __size + _Position : _Position; } |
| }; |
| |
| template <int _Position> |
| inline constexpr _Broadcast<_Position> broadcast {}; |
| |
| inline constexpr _Broadcast<0> broadcast_first {}; |
| |
| inline constexpr _Broadcast<-1> broadcast_last {}; |
| |
| struct _Reverse |
| { |
| consteval int |
| operator()(int __i, int __size) const |
| { return __size - 1 - __i; } |
| }; |
| |
| inline constexpr _Reverse reverse {}; |
| |
| template <int _Offset> |
| struct _Rotate |
| { |
| consteval int |
| operator()(int __i, int __size) const |
| { |
| __i += _Offset; |
| __i %= __size; |
| if (__i < 0) |
| __i += __size; |
| return __i; |
| } |
| }; |
| |
| template <int _Offset> |
| inline constexpr _Rotate<_Offset> rotate {}; |
| |
| template <int _Offset> |
| struct _Shift |
| { |
| consteval int |
| operator()(int __i, int __size) const |
| { |
| const int __j = __i + _Offset; |
| if (__j >= __size || -__j > __size) |
| return simd::zero_element; |
| else if (__j < 0) |
| return __size + __j; |
| else |
| return __j; |
| } |
| }; |
| |
| template <int _Offset> |
| inline constexpr _Shift<_Offset> shift {}; |
| } |
| |
| static_assert( |
| all_of(simd::permute(simd::__iota<simd::vec<int>>, permutations::duplicate_even) |
| == simd::__iota<simd::vec<int>> / 2 * 2)); |
| |
| static_assert( |
| all_of(simd::permute(simd::__iota<simd::vec<int>>, permutations::duplicate_odd) |
| == simd::__iota<simd::vec<int>> / 2 * 2 + 1)); |
| |
| static_assert( |
| all_of(simd::permute(simd::__iota<simd::vec<int>>, permutations::swap_neighbors<1>) |
| == simd::vec<int>([](int i) { return i ^ 1; }))); |
| |
| static_assert( |
| all_of(simd::permute(simd::__iota<simd::vec<int, 8>>, |
| permutations::swap_neighbors<2>) |
| == simd::vec<int, 8>(std::array{2, 3, 0, 1, 6, 7, 4, 5}))); |
| |
| static_assert( |
| all_of(simd::permute(simd::__iota<simd::vec<int, 12>>, |
| permutations::swap_neighbors<3>) |
| == simd::vec<int, 12>( |
| std::array{3, 4, 5, 0, 1, 2, 9, 10, 11, 6, 7, 8}))); |
| |
| static_assert( |
| all_of(simd::permute(simd::__iota<simd::vec<int>>, permutations::broadcast<1>) |
| == simd::vec<int>(1))); |
| |
| static_assert( |
| all_of(simd::permute(simd::__iota<simd::vec<int>>, permutations::broadcast_first) |
| == simd::vec<int>(0))); |
| |
| static_assert( |
| all_of(simd::permute(simd::__iota<simd::vec<int>>, permutations::broadcast_last) |
| == simd::vec<int>(int(simd::vec<int>::size() - 1)))); |
| |
| static_assert( |
| all_of(simd::permute(simd::__iota<simd::vec<int>>, permutations::reverse) |
| == simd::vec<int>([](int i) { return int(simd::vec<int>::size()) - 1 - i; }))); |
| |
| static_assert( |
| all_of(simd::permute(simd::__iota<simd::vec<int>>, permutations::rotate<1>) |
| == (simd::__iota<simd::vec<int>> + 1) % int(simd::vec<int>::size()))); |
| |
| static_assert( |
| all_of(simd::permute(simd::__iota<simd::vec<int>>, permutations::rotate<2>) |
| == (simd::__iota<simd::vec<int>> + 2) % int(simd::vec<int>::size()))); |
| |
| static_assert( |
| all_of(simd::permute(simd::__iota<simd::vec<int, 7>>, permutations::rotate<2>) |
| == simd::vec<int, 7>(std::array {2, 3, 4, 5, 6, 0, 1}))); |
| |
| static_assert( |
| all_of(simd::permute(simd::__iota<simd::vec<int, 7>>, permutations::rotate<-2>) |
| == simd::vec<int, 7>(std::array {5, 6, 0, 1, 2, 3, 4}))); // { dg-prune-output "Wpsabi" } |