// 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_