| // Test framework for <simd> -*- C++ -*- | |
| // Copyright The GNU Toolchain Authors. | |
| // | |
| // This file is part of the GNU ISO C++ Library. This library is free | |
| // software; you can redistribute it and/or modify it under the | |
| // terms of the GNU General Public License as published by the | |
| // Free Software Foundation; either version 3, or (at your option) | |
| // any later version. | |
| // This library is distributed in the hope that it will be useful, | |
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| // GNU General Public License for more details. | |
| // Under Section 7 of GPL version 3, you are granted additional | |
| // permissions described in the GCC Runtime Library Exception, version | |
| // 3.1, as published by the Free Software Foundation. | |
| // You should have received a copy of the GNU General Public License and | |
| // a copy of the GCC Runtime Library Exception along with this program; | |
| // see the files COPYING3 and COPYING.RUNTIME respectively. If not, see | |
| // <http://www.gnu.org/licenses/>. | |
| #ifndef SIMD_TEST_SETUP_H | |
| #define SIMD_TEST_SETUP_H | |
| #include <bits/simd_details.h> | |
| #include <string_view> | |
| namespace test | |
| { | |
| struct precondition_failure | |
| { | |
| std::string_view file; | |
| int line; | |
| std::string_view expr; | |
| std::string_view msg; | |
| }; | |
| #undef __glibcxx_simd_precondition | |
| #define __glibcxx_simd_precondition(expr, msg, ...) \ | |
| do { \ | |
| if (__builtin_expect(!bool(expr), false)) \ | |
| throw test::precondition_failure{__FILE__, __LINE__, #expr, msg}; \ | |
| } while(false) | |
| } | |
| #undef _GLIBCXX_SIMD_NOEXCEPT | |
| #define _GLIBCXX_SIMD_NOEXCEPT noexcept(false) | |
| #include <simd> | |
| #include <source_location> | |
| #include <iostream> | |
| #include <concepts> | |
| #include <cfenv> | |
| #include <vector> | |
| #include <cstdint> | |
| #include <climits> | |
| // global objects | |
| static std::vector<void(*)()> test_functions = {}; | |
| static std::int64_t passed_tests = 0; | |
| static std::int64_t failed_tests = 0; | |
| static std::string_view test_name = "unknown"; | |
| // ------------------------------------------------ | |
| namespace simd = std::simd; | |
| template <typename T, typename... Us> | |
| concept any_type_of = (std::same_as<T, Us> || ...); | |
| template <typename T> | |
| concept complex_like = std::simd::__complex_like<T>; | |
| template <typename T> | |
| struct is_character_type | |
| : std::bool_constant<false> | |
| {}; | |
| template <typename T> | |
| inline constexpr bool is_character_type_v = is_character_type<T>::value; | |
| template <typename T> | |
| struct is_character_type<const T> | |
| : is_character_type<T> | |
| {}; | |
| template <typename T> | |
| struct is_character_type<T&> | |
| : is_character_type<T> | |
| {}; | |
| template <> struct is_character_type<char> : std::bool_constant<true> {}; | |
| template <> struct is_character_type<wchar_t> : std::bool_constant<true> {}; | |
| template <> struct is_character_type<char8_t> : std::bool_constant<true> {}; | |
| template <> struct is_character_type<char16_t> : std::bool_constant<true> {}; | |
| template <> struct is_character_type<char32_t> : std::bool_constant<true> {}; | |
| std::ostream& operator<<(std::ostream& s, std::byte b) | |
| { return s << std::hex << static_cast<unsigned>(b) << std::dec; } | |
| template <typename T, typename Abi> | |
| std::ostream& operator<<(std::ostream& s, std::simd::basic_vec<T, Abi> const& v) | |
| { | |
| if constexpr (std::is_arithmetic_v<T>) | |
| { | |
| using U = std::conditional_t< | |
| sizeof(T) == 1, int, std::conditional_t< | |
| is_character_type_v<T>, | |
| std::simd::_UInt<sizeof(T)>, T>>; | |
| s << '[' << U(v[0]); | |
| for (int i = 1; i < v.size(); ++i) | |
| s << ", " << U(v[i]); | |
| } | |
| else | |
| { | |
| s << '[' << v[0]; | |
| for (int i = 1; i < v.size(); ++i) | |
| s << ", " << v[i]; | |
| } | |
| return s << ']'; | |
| } | |
| template <std::size_t B, typename Abi> | |
| std::ostream& operator<<(std::ostream& s, std::simd::basic_mask<B, Abi> const& v) | |
| { | |
| s << '<'; | |
| for (int i = 0; i < v.size(); ++i) | |
| s << int(v[i]); | |
| return s << '>'; | |
| } | |
| template <std::simd::__vec_builtin V> | |
| std::ostream& operator<<(std::ostream& s, V v) | |
| { return s << std::simd::vec<std::simd::__vec_value_type<V>, std::simd::__width_of<V>>(v); } | |
| template <typename T, typename U> | |
| std::ostream& operator<<(std::ostream& s, const std::pair<T, U>& x) | |
| { return s << '{' << x.first << ", " << x.second << '}'; } | |
| template <typename T> | |
| concept is_string_type | |
| = is_character_type_v<std::ranges::range_value_t<T>> | |
| && std::is_convertible_v<T, std::basic_string_view<std::ranges::range_value_t<T>>>; | |
| template <std::ranges::range R> | |
| requires (!is_string_type<R>) | |
| std::ostream& operator<<(std::ostream& s, R&& x) | |
| { | |
| s << '['; | |
| auto it = std::ranges::begin(x); | |
| if (it != std::ranges::end(x)) | |
| { | |
| s << *it; | |
| while (++it != std::ranges::end(x)) | |
| s << ',' << *it; | |
| } | |
| return s << ']'; | |
| } | |
| struct additional_info | |
| { | |
| const bool failed = false; | |
| additional_info | |
| operator()(auto const& value0, auto const&... more) | |
| { | |
| if (failed) | |
| [&] { | |
| std::cout << " " << value0; | |
| ((std::cout << ' ' << more), ...); | |
| std::cout << std::endl; | |
| }(); | |
| return *this; | |
| } | |
| }; | |
| struct log_novalue {}; | |
| template <typename T> | |
| struct unwrap_value_types | |
| { using type = T; }; | |
| template <typename T> | |
| requires requires { typename T::value_type; } | |
| struct unwrap_value_types<T> | |
| { using type = typename unwrap_value_types<typename T::value_type>::type; }; | |
| template <typename T> | |
| using value_type_t = typename unwrap_value_types<std::remove_cvref_t<T>>::type; | |
| template <typename T> | |
| struct as_unsigned; | |
| template <typename T> | |
| using as_unsigned_t = typename as_unsigned<T>::type; | |
| template <typename T> | |
| requires (sizeof(T) == sizeof(unsigned char)) | |
| struct as_unsigned<T> | |
| { using type = unsigned char; }; | |
| template <typename T> | |
| requires (sizeof(T) == sizeof(unsigned short)) | |
| struct as_unsigned<T> | |
| { using type = unsigned short; }; | |
| template <typename T> | |
| requires (sizeof(T) == sizeof(unsigned int)) | |
| struct as_unsigned<T> | |
| { using type = unsigned int; }; | |
| template <typename T> | |
| requires (sizeof(T) == sizeof(unsigned long long)) | |
| struct as_unsigned<T> | |
| { using type = unsigned long long; }; | |
| template <typename T, typename Abi> | |
| struct as_unsigned<std::simd::basic_vec<T, Abi>> | |
| { using type = std::simd::rebind_t<as_unsigned_t<T>, std::simd::basic_vec<T, Abi>>; }; | |
| template <typename T0, typename T1> | |
| constexpr T0 | |
| ulp_distance_signed(T0 val0, const T1& ref1) | |
| { | |
| if constexpr (std::is_floating_point_v<T1>) | |
| return ulp_distance_signed(val0, std::simd::rebind_t<T1, T0>(ref1)); | |
| else if constexpr (std::is_floating_point_v<value_type_t<T0>>) | |
| { | |
| int fp_exceptions = 0; | |
| if !consteval | |
| { | |
| fp_exceptions = std::fetestexcept(FE_ALL_EXCEPT); | |
| } | |
| using std::isnan; | |
| using std::abs; | |
| using T = value_type_t<T0>; | |
| using L = std::numeric_limits<T>; | |
| constexpr T0 signexp_mask = -L::infinity(); | |
| T0 ref0(ref1); | |
| T1 val1(val0); | |
| const auto subnormal = fabs(ref1) < L::min(); | |
| using I = as_unsigned_t<T1>; | |
| const T1 eps1 = select(subnormal, L::denorm_min(), | |
| L::epsilon() * std::bit_cast<T0>( | |
| std::bit_cast<I>(ref1) | |
| & std::bit_cast<I>(signexp_mask))); | |
| const T0 ulp = select(val0 == ref0 || (isnan(val0) && isnan(ref0)), | |
| T0(), T0((ref1 - val1) / eps1)); | |
| if !consteval | |
| { | |
| std::feclearexcept(FE_ALL_EXCEPT ^ fp_exceptions); | |
| } | |
| return ulp; | |
| } | |
| else | |
| return ref1 - val0; | |
| } | |
| template <typename T0, typename T1> | |
| constexpr T0 | |
| ulp_distance(const T0& val, const T1& ref) | |
| { | |
| auto ulp = ulp_distance_signed(val, ref); | |
| using T = value_type_t<decltype(ulp)>; | |
| if constexpr (std::is_unsigned_v<T>) | |
| return ulp; | |
| else | |
| { | |
| using std::abs; | |
| return fabs(ulp); | |
| } | |
| } | |
| template <typename T> | |
| constexpr bool | |
| bit_equal(const T& a, const T& b) | |
| { | |
| using std::simd::_UInt; | |
| if constexpr (sizeof(T) <= sizeof(0ull)) | |
| return std::bit_cast<_UInt<sizeof(T)>>(a) == std::bit_cast<_UInt<sizeof(T)>>(b); | |
| else if constexpr (std::simd::__simd_vec_or_mask_type<T>) | |
| { | |
| using TT = typename T::value_type; | |
| if constexpr (std::is_integral_v<TT>) | |
| return all_of(a == b); | |
| else if constexpr (T::abi_type::_S_nreg > 1) | |
| { | |
| return bit_equal(a._M_get_low(), b._M_get_low()) | |
| && bit_equal(a._M_get_high(), b._M_get_high()); | |
| } | |
| else | |
| { | |
| // float, 4 -> unsigned, 4 (uint_size = 4) | |
| // double, 4 -> ullong, 4 (uint_size = 8) | |
| // complex<double>, 4 -> ullong, 8 (uint_size = 8) | |
| constexpr size_t uint_size = std::min(size_t(8), sizeof(TT)); | |
| struct B | |
| { | |
| alignas(T) simd::rebind_t<_UInt<uint_size>, | |
| simd::resize_t<T::size() * sizeof(TT) / uint_size, T>> data; | |
| }; | |
| if constexpr (sizeof(B) == sizeof(a)) | |
| return all_of(std::bit_cast<B>(a).data == std::bit_cast<B>(b).data); | |
| else | |
| { | |
| auto [a0, a1] = chunk<std::bit_ceil(unsigned(T::size())) / 2>(a); | |
| auto [b0, b1] = chunk<std::bit_ceil(unsigned(T::size())) / 2>(b); | |
| return bit_equal(a0, b0) && bit_equal(a1, b1); | |
| } | |
| } | |
| } | |
| else if constexpr (complex_like<T>) | |
| return bit_equal(a.real(), b.real()) && bit_equal(a.imag(), b.imag()); | |
| else | |
| static_assert(false); | |
| } | |
| // true iff real or imag parts of x are +/-inf. This matches the C23 Annex G interpretation. | |
| template <complex_like T, typename Abi> | |
| constexpr typename simd::basic_vec<T, Abi>::mask_type | |
| cx_isinf(const simd::basic_vec<T, Abi>& x) | |
| { | |
| using M = typename simd::basic_vec<T, Abi>::mask_type; | |
| return M(isinf(x.real()) || isinf(x.imag())); | |
| } | |
| // treat as equal if either: | |
| // - operator== yields true | |
| // - or for floats, a and b are NaNs | |
| // - or for complex, a and b are any infinity (see cx_isinf) | |
| // - or for complex, a and b are NaNs in real *and* imag components | |
| template <typename V> | |
| constexpr bool | |
| equal_with_nan_and_inf_fixup(const V& a, const V& b) | |
| { | |
| auto eq = a == b; | |
| if (std::simd::all_of(eq)) | |
| return true; | |
| else if constexpr (std::simd::__simd_vec_type<V>) | |
| { | |
| using M = typename V::mask_type; | |
| using T = typename V::value_type; | |
| if constexpr (complex_like<T>) | |
| { // fix up nan == nan and (inf,nan) == (inf,?) | |
| eq |= M(isnan(a.real()) && isnan(a.imag()) && isnan(b.real()) && isnan(a.imag())) | |
| // a and b are "an infinity" according to C23 Annex G.3 | |
| || (cx_isinf(a) && cx_isinf(b)); | |
| } | |
| else if constexpr (std::is_floating_point_v<T>) | |
| { // fix up nan == nan results | |
| eq |= isnan(a) && isnan(b); | |
| } | |
| else | |
| return false; | |
| return std::simd::all_of(eq); | |
| } | |
| else if constexpr (std::is_floating_point_v<V>) | |
| return std::isnan(a) && std::isnan(b); | |
| else | |
| return false; | |
| } | |
| struct constexpr_verifier | |
| { | |
| struct ignore_the_rest | |
| { | |
| constexpr ignore_the_rest | |
| operator()(auto const&, auto const&...) | |
| { return *this; } | |
| }; | |
| bool okay = true; | |
| constexpr ignore_the_rest | |
| verify_precondition_failure(std::string_view expected_msg, auto&& f) & | |
| { | |
| try | |
| { | |
| f(); | |
| okay = false; | |
| } | |
| catch (const test::precondition_failure& failure) | |
| { | |
| okay = okay && failure.msg == expected_msg; | |
| } | |
| catch (...) | |
| { | |
| okay = false; | |
| } | |
| return {}; | |
| } | |
| constexpr ignore_the_rest | |
| verify(const auto& k) & | |
| { | |
| okay = okay && std::simd::all_of(k); | |
| return {}; | |
| } | |
| constexpr ignore_the_rest | |
| verify_equal(const auto& v, const auto& ref) & | |
| { | |
| using V = decltype(std::simd::select(v == ref, v, ref)); | |
| okay = okay && equal_with_nan_and_inf_fixup<V>(v, ref); | |
| return {}; | |
| } | |
| constexpr ignore_the_rest | |
| verify_bit_equal(const auto& v, const auto& ref) & | |
| { | |
| using V = decltype(std::simd::select(v == ref, v, ref)); | |
| okay = okay && bit_equal<V>(v, ref); | |
| return {}; | |
| } | |
| template <typename T, typename U> | |
| constexpr ignore_the_rest | |
| verify_equal(const std::pair<T, U>& x, const std::pair<T, U>& y) & | |
| { | |
| verify_equal(x.first, y.first); | |
| verify_equal(x.second, y.second); | |
| return {}; | |
| } | |
| constexpr ignore_the_rest | |
| verify_not_equal(const auto& v, const auto& ref) & | |
| { | |
| okay = okay && std::simd::all_of(v != ref); | |
| return {}; | |
| } | |
| constexpr ignore_the_rest | |
| verify_equal_to_ulp(const auto& x, const auto& y, float allowed_distance) & | |
| { | |
| okay = okay && std::simd::all_of(ulp_distance(x, y) <= allowed_distance); | |
| return {}; | |
| } | |
| constexpr_verifier() = default; | |
| constexpr_verifier(const constexpr_verifier&) = delete; | |
| constexpr_verifier(constexpr_verifier&&) = delete; | |
| }; | |
| template <int... is> | |
| [[nodiscard]] | |
| consteval bool | |
| constexpr_test(auto&& fun, auto&&... args) | |
| { | |
| constexpr_verifier t; | |
| try | |
| { | |
| fun.template operator()<is...>(t, args...); | |
| } | |
| catch(const test::precondition_failure& fail) | |
| { | |
| return false; | |
| } | |
| return t.okay; | |
| } | |
| template <typename T> | |
| T | |
| make_value_unknown(const T& x) | |
| { return *std::start_lifetime_as<T>(&x); } | |
| template <typename T> | |
| concept pair_specialization | |
| = std::same_as<std::remove_cvref_t<T>, std::pair<typename std::remove_cvref_t<T>::first_type, | |
| typename std::remove_cvref_t<T>::second_type>>; | |
| struct runtime_verifier | |
| { | |
| const std::string_view test_kind; | |
| template <typename X, typename Y> | |
| additional_info | |
| log_failure(const X& x, const Y& y, std::source_location loc, std::string_view s) | |
| { | |
| ++failed_tests; | |
| std::cout << loc.file_name() << ':' << loc.line() << ':' << loc.column() << ": in " | |
| << test_kind << " test of '" << test_name | |
| << "' " << s << " failed"; | |
| if constexpr (!std::is_same_v<X, log_novalue>) | |
| { | |
| std::cout << ":\n result: " << std::boolalpha; | |
| if constexpr (is_character_type_v<X>) | |
| std::cout << int(x); | |
| else | |
| std::cout << x; | |
| if constexpr (!std::is_same_v<decltype(y), const log_novalue&>) | |
| { | |
| std::cout << "\n expected: "; | |
| if constexpr (is_character_type_v<Y>) | |
| std::cout << int(y); | |
| else | |
| std::cout << y; | |
| } | |
| } | |
| std::cout << std::endl; | |
| return additional_info {true}; | |
| } | |
| [[gnu::always_inline]] | |
| additional_info | |
| verify_precondition_failure(std::string_view expected_msg, auto&& f, | |
| std::source_location loc = std::source_location::current()) & | |
| { | |
| try | |
| { | |
| f(); | |
| ++failed_tests; | |
| return log_failure(log_novalue(), log_novalue(), loc, "precondition failure not detected"); | |
| } | |
| catch (const test::precondition_failure& failure) | |
| { | |
| if (failure.msg != expected_msg) | |
| { | |
| ++failed_tests; | |
| return log_failure(failure.msg, expected_msg, loc, "unexpected exception"); | |
| } | |
| else | |
| { | |
| ++passed_tests; | |
| return {}; | |
| } | |
| } | |
| catch (...) | |
| { | |
| ++failed_tests; | |
| return log_failure(log_novalue(), log_novalue(), loc, "unexpected exception"); | |
| } | |
| } | |
| [[gnu::always_inline]] | |
| additional_info | |
| verify(auto&& k, std::source_location loc = std::source_location::current()) | |
| { | |
| if (std::simd::all_of(k)) | |
| { | |
| ++passed_tests; | |
| return {}; | |
| } | |
| else | |
| return log_failure(log_novalue(), log_novalue(), loc, "verify"); | |
| } | |
| [[gnu::always_inline]] | |
| additional_info | |
| verify_equal(auto&& x, auto&& y, | |
| std::source_location loc = std::source_location::current()) | |
| { | |
| bool ok; | |
| if constexpr (pair_specialization<decltype(x)> && pair_specialization<decltype(y)>) | |
| ok = std::simd::all_of(x.first == y.first) && std::simd::all_of(x.second == y.second); | |
| else | |
| ok = equal_with_nan_and_inf_fixup<decltype(std::simd::select(x == y, x, y))>(x, y); | |
| if (ok) | |
| { | |
| ++passed_tests; | |
| return {}; | |
| } | |
| else | |
| return log_failure(x, y, loc, "verify_equal"); | |
| } | |
| [[gnu::always_inline]] | |
| additional_info | |
| verify_bit_equal(auto&& x, auto&& y, | |
| std::source_location loc = std::source_location::current()) | |
| { | |
| using V = decltype(std::simd::select(x == y, x, y)); | |
| if (bit_equal<V>(x, y)) | |
| { | |
| ++passed_tests; | |
| return {}; | |
| } | |
| else | |
| return log_failure(x, y, loc, "verify_bit_equal"); | |
| } | |
| [[gnu::always_inline]] | |
| additional_info | |
| verify_not_equal(auto&& x, auto&& y, | |
| std::source_location loc = std::source_location::current()) | |
| { | |
| if (std::simd::all_of(x != y)) | |
| { | |
| ++passed_tests; | |
| return {}; | |
| } | |
| else | |
| return log_failure(x, y, loc, "verify_not_equal"); | |
| } | |
| // ulp_distance_signed can raise FP exceptions and thus must be conditionally executed | |
| [[gnu::always_inline]] | |
| additional_info | |
| verify_equal_to_ulp(auto&& x, auto&& y, float allowed_distance, | |
| std::source_location loc = std::source_location::current()) | |
| { | |
| const bool success = std::simd::all_of(ulp_distance(x, y) <= allowed_distance); | |
| if (success) | |
| { | |
| ++passed_tests; | |
| return {}; | |
| } | |
| else | |
| return log_failure(x, y, loc, "verify_equal_to_ulp") | |
| ("distance:", ulp_distance_signed(x, y), | |
| "\n allowed:", allowed_distance); | |
| } | |
| }; | |
| template <int... is> | |
| [[gnu::noinline, gnu::noipa]] | |
| void | |
| runtime_test(auto&& fun, auto&&... args) | |
| { | |
| runtime_verifier t {"runtime"}; | |
| fun.template operator()<is...>(t, make_value_unknown(args)...); | |
| } | |
| template <typename T> | |
| concept constant_value = requires { | |
| typename std::integral_constant<std::remove_cvref_t<decltype(T::value)>, T::value>; | |
| }; | |
| template <typename T> | |
| [[gnu::always_inline]] inline bool | |
| is_const_known(const T& x) | |
| { return constant_value<T> || __builtin_constant_p(x); } | |
| template <typename T, typename Abi> | |
| [[gnu::always_inline]] inline bool | |
| is_const_known(const std::simd::basic_vec<T, Abi>& x) | |
| { return __is_const_known(x); } | |
| template <std::size_t B, typename Abi> | |
| [[gnu::always_inline]] inline bool | |
| is_const_known(const std::simd::basic_mask<B, Abi>& x) | |
| { return __is_const_known(x); } | |
| template <typename T> | |
| [[gnu::always_inline]] inline bool | |
| is_const_known(const std::complex<T>& x) | |
| { return is_const_known(x.real()) && is_const_known(x.imag()); } | |
| template <std::ranges::sized_range R> | |
| [[gnu::always_inline]] inline bool | |
| is_const_known(const R& arr) | |
| { | |
| constexpr std::size_t N = std::ranges::size(arr); | |
| constexpr auto [...is] = std::_IotaArray<N>; | |
| return (is_const_known(arr[is]) && ...); | |
| } | |
| template <int... is> | |
| [[gnu::always_inline, gnu::flatten]] | |
| inline void | |
| constprop_test(auto&& fun, auto... args) | |
| { | |
| runtime_verifier t{"constprop"}; | |
| #ifndef __clang__ | |
| t.verify((is_const_known(args) && ...))("=> Some argument(s) failed to constant-propagate."); | |
| #endif | |
| fun.template operator()<is...>(t, args...); | |
| } | |
| /** | |
| * The value of the largest element in test_iota<V, Init>. | |
| */ | |
| template <typename V, int Init = 0, int Max = V::size() + Init - 1> | |
| constexpr value_type_t<V> test_iota_max | |
| = sizeof(value_type_t<V>) < sizeof(int) | |
| ? std::min(int(std::numeric_limits<value_type_t<V>>::max()), | |
| Max < 0 ? std::min(V::size() + Init - 1, | |
| int(std::numeric_limits<value_type_t<V>>::max()) + Max) | |
| : Max) | |
| : V::size() + Init - 1; | |
| template <typename T, typename Abi, int Init, int Max> | |
| requires std::is_enum_v<T> | |
| constexpr T test_iota_max<simd::basic_vec<T, Abi>, Init, Max> | |
| = static_cast<T>(test_iota_max<simd::basic_vec<std::underlying_type_t<T>, Abi>, Init, Max>); | |
| /** | |
| * Starts iota sequence at Init. | |
| * | |
| * With `Max == 0`: Wrap-around on overflow | |
| * With `Max < 0`: Subtract from numeric_limits::max (to leave room for arithmetic ops) | |
| * Otherwise: [Init..Max, Init..Max, ...] (inclusive) | |
| * | |
| * Use simd::__iota if a non-monotonic sequence is a bug. | |
| */ | |
| template <typename V, int Init = 0, int MaxArg = int(test_iota_max<V, Init>)> | |
| constexpr V test_iota = V([](int i) { | |
| constexpr int Max = MaxArg < 0 ? int(test_iota_max<V, Init, MaxArg>) : MaxArg; | |
| static_assert(Max == 0 || Max > Init || V::size() == 1); | |
| i += Init; | |
| if constexpr (Max > Init) | |
| { | |
| while (i > Max) | |
| i -= Max - Init + 1; | |
| } | |
| using T = value_type_t<V>; | |
| if constexpr (std::simd::__simd_complex<V>) | |
| return std::complex<T>(T(i), T()); | |
| else | |
| return static_cast<T>(i); | |
| }); | |
| /** | |
| * A data-parallel object initialized with {values..., values..., ...} | |
| */ | |
| template <typename V, auto... values> | |
| constexpr V init_vec = [] { | |
| using T = typename V::value_type; | |
| constexpr std::array<T, sizeof...(values)> arr = {T(values)...}; | |
| return V([&](size_t i) { return arr[i % arr.size()]; }); | |
| }(); | |
| template <typename V> | |
| struct Tests; | |
| template <typename T> | |
| concept array_specialization | |
| = std::same_as<T, std::array<typename T::value_type, std::tuple_size_v<T>>>; | |
| template <typename Args = void, typename Fun = void> | |
| struct add_test | |
| { | |
| alignas(std::bit_floor(sizeof(Args))) Args args; | |
| Fun fun; | |
| }; | |
| struct dummy_test | |
| { | |
| static constexpr std::array<int, 0> args = {}; | |
| static constexpr auto fun = [](auto&, auto...) {}; | |
| }; | |
| template <auto test_ref, int... is, std::size_t... arg_idx> | |
| void | |
| invoke_test_impl(std::index_sequence<arg_idx...>) | |
| { | |
| constexpr auto fun = test_ref->fun; | |
| [[maybe_unused]] constexpr auto args = test_ref->args; | |
| #ifdef EXPENSIVE_TESTS | |
| constprop_test<is...>(fun, std::get<arg_idx>(args)...); | |
| constexpr bool passed = constexpr_test<is...>(fun, std::get<arg_idx>(args)...); | |
| if (passed) | |
| ++passed_tests; | |
| else | |
| { | |
| ++failed_tests; | |
| std::cout << "=> constexpr test of '" << test_name << "' failed.\n"; | |
| } | |
| #endif | |
| runtime_test<is...>(fun, std::get<arg_idx>(args)...); | |
| } | |
| template <auto test_ref, int... is> | |
| void | |
| invoke_test(std::string_view name) | |
| { | |
| test_name = name; | |
| constexpr auto args = test_ref->args; | |
| using A = std::remove_const_t<decltype(args)>; | |
| if constexpr (array_specialization<A>) | |
| { // call for each element | |
| template for (constexpr std::size_t I : std::_IotaArray<args.size()>) | |
| { | |
| std::string tmp_name = std::string(name) + '|' + std::to_string(I); | |
| test_name = tmp_name; | |
| ((std::cout << "Testing '" << test_name) << ... << (' ' + std::to_string(is))) | |
| << ' ' << args[I] << "'\n"; | |
| invoke_test_impl<test_ref, is...>(std::index_sequence<I>()); | |
| } | |
| } | |
| else | |
| { | |
| ((std::cout << "Testing '" << test_name) << ... << (' ' + std::to_string(is))) << "'\n"; | |
| invoke_test_impl<test_ref, is...>(std::make_index_sequence<std::tuple_size_v<A>>()); | |
| } | |
| } | |
| #define ADD_TEST(name, ...) \ | |
| template <int> \ | |
| static constexpr auto name##_tmpl = dummy_test {}; \ | |
| \ | |
| const int init_##name = [] { \ | |
| test_functions.push_back([] { invoke_test<&name##_tmpl<0>>(#name); }); \ | |
| return 0; \ | |
| }(); \ | |
| \ | |
| template <int Tmp> \ | |
| requires (Tmp == 0) __VA_OPT__(&& (__VA_ARGS__)) \ | |
| static constexpr auto name##_tmpl<Tmp> = add_test | |
| #define ADD_TEST_N(name, N, ...) \ | |
| template <int> \ | |
| static constexpr auto name##_tmpl = dummy_test {}; \ | |
| \ | |
| static void \ | |
| name() \ | |
| { \ | |
| template for (constexpr int i : std::_IotaArray<N, int>) \ | |
| invoke_test<&name##_tmpl<0>, i>(#name); \ | |
| } \ | |
| \ | |
| const int init_##name = [] { \ | |
| test_functions.push_back(name); \ | |
| return 0; \ | |
| }(); \ | |
| \ | |
| template <int Tmp> \ | |
| requires (Tmp == 0) __VA_OPT__(&& (__VA_ARGS__)) \ | |
| static constexpr auto name##_tmpl<Tmp> = add_test | |
| void create_tests(); | |
| int main() | |
| { | |
| create_tests(); | |
| try | |
| { | |
| for (auto f : test_functions) | |
| f(); | |
| } | |
| catch(const test::precondition_failure& fail) | |
| { | |
| std::cout << fail.file << ':' << fail.line << ": Error: precondition '" << fail.expr | |
| << "' does not hold: " << fail.msg << '\n'; | |
| return EXIT_FAILURE; | |
| } | |
| std::cout << "Passed tests: " << passed_tests << "\nFailed tests: " << failed_tests << '\n'; | |
| return failed_tests != 0 ? EXIT_FAILURE : EXIT_SUCCESS; | |
| } | |
| #endif // SIMD_TEST_SETUP_H |