// Copyright (C) 2020-2023 Free Software Foundation, Inc. // // 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. // // You should have received a copy of the GNU General Public License along // with this library; see the file COPYING3. If not see // . #ifndef SIMD_TESTS_BITS_TEST_VALUES_H_ #define SIMD_TESTS_BITS_TEST_VALUES_H_ #include "verify.h" #include #include #include #include template std::experimental::simd iif(std::experimental::simd_mask k, const typename std::experimental::simd_mask::simd_type& t, const std::experimental::simd& f) { auto r = f; where(k, r) = t; return r; } template V epilogue_load(const typename V::value_type* mem, const std::size_t size) { const int rem = size % V::size(); return where(V([](int i) { return i; }) < rem, V(0)) .copy_from(mem + size / V::size() * V::size(), std::experimental::element_aligned); } template void test_values(const std::initializer_list& inputs, F&&... fun_pack) { for (auto it = inputs.begin(); it + V::size() <= inputs.end(); it += V::size()) { [](auto...) { }((fun_pack(V(&it[0], std::experimental::element_aligned)), 0)...); } [](auto...) { }((fun_pack(epilogue_load(inputs.begin(), inputs.size())), 0)...); } template struct RandomValues { using T = typename V::value_type; static constexpr bool isfp = std::is_floating_point_v; const std::size_t count; std::conditional_t, std::uniform_real_distribution, std::uniform_int_distribution> dist; const bool uniform; const T abs_max = std::__finite_max_v; RandomValues(std::size_t count_, T min, T max) : count(count_), dist(min, max), uniform(true) { if constexpr (std::is_floating_point_v) VERIFY(max - min <= std::__finite_max_v); } RandomValues(std::size_t count_) : count(count_), dist(isfp ? 1 : std::__finite_min_v, isfp ? 2 : std::__finite_max_v), uniform(!isfp) {} RandomValues(std::size_t count_, T abs_max_) : count(count_), dist(isfp ? 1 : -abs_max_, isfp ? 2 : abs_max_), uniform(!isfp), abs_max(abs_max_) {} template V operator()(URBG& gen) { if constexpr (!isfp) return V([&](int) { return dist(gen); }); else if (uniform) return V([&](int) { return dist(gen); }); else { auto exp_dist = std::normal_distribution(0.f, std::__max_exponent_v * .5f); return V([&](int) { const T mant = dist(gen); T fp = 0; do { const int exp = exp_dist(gen); fp = std::ldexp(mant, exp); } while (fp >= abs_max || fp <= std::__denorm_min_v); fp = gen() & 0x4 ? fp : -fp; return fp; }); } } }; static std::mt19937 g_mt_gen{0}; template void test_values(const std::initializer_list& inputs, RandomValues random, F&&... fun_pack) { test_values(inputs, fun_pack...); for (size_t i = 0; i < (random.count + V::size() - 1) / V::size(); ++i) { [](auto...) {}((fun_pack(random(g_mt_gen)), 0)...); } } template void test_values_2arg(const std::initializer_list& inputs, F&&... fun_pack) { for (auto scalar_it = inputs.begin(); scalar_it != inputs.end(); ++scalar_it) { for (auto it = inputs.begin(); it + V::size() <= inputs.end(); it += V::size()) { [](auto...) { }((fun_pack(V(&it[0], std::experimental::element_aligned), V(*scalar_it)), 0)...); } [](auto...) { }((fun_pack(epilogue_load(inputs.begin(), inputs.size()), V(*scalar_it)), 0)...); } } template void test_values_2arg(const std::initializer_list& inputs, RandomValues random, F&&... fun_pack) { test_values_2arg(inputs, fun_pack...); for (size_t i = 0; i < (random.count + V::size() - 1) / V::size(); ++i) { [](auto...) {}((fun_pack(random(g_mt_gen), random(g_mt_gen)), 0)...); } } template void test_values_3arg(const std::initializer_list& inputs, F&&... fun_pack) { for (auto scalar_it1 = inputs.begin(); scalar_it1 != inputs.end(); ++scalar_it1) { for (auto scalar_it2 = inputs.begin(); scalar_it2 != inputs.end(); ++scalar_it2) { for (auto it = inputs.begin(); it + V::size() <= inputs.end(); it += V::size()) { [](auto...) { }((fun_pack(V(&it[0], std::experimental::element_aligned), V(*scalar_it1), V(*scalar_it2)), 0)...); } [](auto...) { }((fun_pack(epilogue_load(inputs.begin(), inputs.size()), V(*scalar_it1), V(*scalar_it2)), 0)...); } } } template void test_values_3arg(const std::initializer_list& inputs, RandomValues random, F&&... fun_pack) { test_values_3arg(inputs, fun_pack...); for (size_t i = 0; i < (random.count + V::size() - 1) / V::size(); ++i) { [](auto...) { }((fun_pack(random(g_mt_gen), random(g_mt_gen), random(g_mt_gen)), 0)...); } } #if __GCC_IEC_559 < 2 // Without IEC559 we consider -0, subnormals, +/-inf, and all NaNs to be // invalid (potential UB when used or "produced"). This can't use isnormal (or // any other classification function), since they know about the UB. template typename V::mask_type isvalid(V x) { using namespace std::experimental::parallelism_v2; using namespace std::experimental::parallelism_v2::__proposed; using T = typename V::value_type; if constexpr (sizeof(T) <= sizeof(double)) { using I = rebind_simd_t<__int_for_sizeof_t, V>; const I abs_x = simd_bit_cast(abs(x)); const I min = simd_bit_cast(V(std::__norm_min_v)); const I max = simd_bit_cast(V(std::__finite_max_v)); return static_simd_cast( simd_bit_cast(x) == 0 || (abs_x >= min && abs_x <= max)); } else { const V abs_x = abs(x); const V min = std::__norm_min_v; // Make max non-const static to inhibit constprop. Otherwise the // compiler might decide `abs_x <= max` is constexpr true, by definition // (-ffinite-math-only) static V max = std::__finite_max_v; return (x == 0 && copysign(V(1), x) == V(1)) || (abs_x >= min && abs_x <= max); } } #define MAKE_TESTER_2(name_, reference_) \ [&](auto... inputs) { \ ((where(!isvalid(inputs), inputs) = 1), ...); \ const auto totest = name_(inputs...); \ using R = std::remove_const_t; \ auto&& expected = [&](const auto&... vs) -> const R { \ R tmp = {}; \ for (std::size_t i = 0; i < R::size(); ++i) \ tmp[i] = reference_(vs[i]...); \ return tmp; \ }; \ const R expect1 = expected(inputs...); \ if constexpr (std::is_floating_point_v) \ { \ ((where(!isvalid(expect1), inputs) = 1), ...); \ const R expect2 = expected(inputs...); \ ((FUZZY_COMPARE(name_(inputs...), expect2) << "\ninputs = ") \ << ... << inputs); \ } \ else \ ((COMPARE(name_(inputs...), expect1) << "\n" #name_ "(") \ << ... << inputs) \ << ")"; \ } #define MAKE_TESTER_NOFPEXCEPT(name_) \ [&](auto... inputs) { \ ((where(!isvalid(inputs), inputs) = 1), ...); \ using R = std::remove_const_t; \ auto&& expected = [&](const auto&... vs) -> const R { \ R tmp = {}; \ for (std::size_t i = 0; i < R::size(); ++i) \ tmp[i] = std::name_(vs[i]...); \ return tmp; \ }; \ const R expect1 = expected(inputs...); \ if constexpr (std::is_floating_point_v) \ { \ ((where(!isvalid(expect1), inputs) = 1), ...); \ std::feclearexcept(FE_ALL_EXCEPT); \ asm volatile(""); \ auto totest = name_(inputs...); \ asm volatile(""); \ ((COMPARE(std::fetestexcept(FE_ALL_EXCEPT), 0) << "\n" #name_ "(") \ << ... << inputs) \ << ")"; \ const R expect2 = expected(inputs...); \ std::feclearexcept(FE_ALL_EXCEPT); \ asm volatile(""); \ totest = name_(inputs...); \ asm volatile(""); \ ((COMPARE(std::fetestexcept(FE_ALL_EXCEPT), 0) << "\n" #name_ "(") \ << ... << inputs) \ << ")"; \ ((FUZZY_COMPARE(totest, expect2) << "\n" #name_ "(") << ... << inputs) \ << ")"; \ } \ else \ { \ std::feclearexcept(FE_ALL_EXCEPT); \ asm volatile(""); \ auto totest = name_(inputs...); \ asm volatile(""); \ ((COMPARE(std::fetestexcept(FE_ALL_EXCEPT), 0) << "\n" #name_ "(") \ << ... << inputs) \ << ")"; \ ((COMPARE(totest, expect1) << "\n" #name_ "(") << ... << inputs) \ << ")"; \ } \ } #else #define MAKE_TESTER_2(name_, reference_) \ [&](auto... inputs) { \ const auto totest = name_(inputs...); \ using R = std::remove_const_t; \ auto&& expected = [&](const auto&... vs) -> const R { \ R tmp = {}; \ for (std::size_t i = 0; i < R::size(); ++i) \ tmp[i] = reference_(vs[i]...); \ return tmp; \ }; \ const R expect1 = expected(inputs...); \ if constexpr (std::is_floating_point_v) \ { \ ((COMPARE(isnan(totest), isnan(expect1)) << #name_ "(") \ << ... << inputs) \ << ") = " << totest << " != " << expect1; \ ((where(isnan(expect1), inputs) = 0), ...); \ ((FUZZY_COMPARE(name_(inputs...), expected(inputs...)) \ << "\nclean = ") \ << ... << inputs); \ } \ else \ ((COMPARE(name_(inputs...), expect1) << "\n" #name_ "(") \ << ... << inputs) \ << ")"; \ } #define MAKE_TESTER_NOFPEXCEPT(name_) \ [&](auto... inputs) { \ std::feclearexcept(FE_ALL_EXCEPT); \ auto totest = name_(inputs...); \ ((COMPARE(std::fetestexcept(FE_ALL_EXCEPT), 0) << "\n" #name_ "(") \ << ... << inputs) \ << ")"; \ using R = std::remove_const_t; \ auto&& expected = [&](const auto&... vs) -> const R { \ R tmp = {}; \ for (std::size_t i = 0; i < R::size(); ++i) \ tmp[i] = std::name_(vs[i]...); \ return tmp; \ }; \ const R expect1 = expected(inputs...); \ if constexpr (std::is_floating_point_v) \ { \ ((COMPARE(isnan(totest), isnan(expect1)) << #name_ "(") \ << ... << inputs) \ << ") = " << totest << " != " << expect1; \ ((where(isnan(expect1), inputs) = 0), ...); \ const R expect2 = expected(inputs...); \ std::feclearexcept(FE_ALL_EXCEPT); \ asm volatile(""); \ totest = name_(inputs...); \ asm volatile(""); \ ((COMPARE(std::fetestexcept(FE_ALL_EXCEPT), 0) << "\n" #name_ "(") \ << ... << inputs) \ << ")"; \ FUZZY_COMPARE(totest, expect2); \ } \ else \ { \ ((COMPARE(totest, expect1) << "\n" #name_ "(") << ... << inputs) \ << ")"; \ } \ } #endif #define MAKE_TESTER(name_) MAKE_TESTER_2(name_, std::name_) #endif // SIMD_TESTS_BITS_TEST_VALUES_H_