gcc/libstdc++-v3/testsuite/experimental/simd/tests/operators.cc
2024-01-03 12:19:35 +01:00

293 lines
8.9 KiB
C++

// Copyright (C) 2020-2024 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
// <http://www.gnu.org/licenses/>.
// expensive: * [1-9] * *
#include "bits/main.h"
template <typename V>
void
test()
{
using M = typename V::mask_type;
using T = typename V::value_type;
constexpr auto min = std::__finite_min_v<T>;
constexpr auto norm_min = std::__norm_min_v<T>;
constexpr auto max = std::__finite_max_v<T>;
{ // compares
COMPARE(V(0) == make_vec<V>({0, 1}, 0), make_mask<M>({1, 0}));
COMPARE(V(0) == make_vec<V>({0, 1, 2}, 0), make_mask<M>({1, 0, 0}));
COMPARE(V(1) == make_vec<V>({0, 1, 2}, 0), make_mask<M>({0, 1, 0}));
COMPARE(V(2) == make_vec<V>({0, 1, 2}, 0), make_mask<M>({0, 0, 1}));
COMPARE(V(0) < make_vec<V>({0, 1, 2}, 0), make_mask<M>({0, 1, 1}));
constexpr T half = genHalfBits<T>();
for (T lo_ : {min, T(min + 1), T(-1), T(0), norm_min, T(1), T(half - 1),
half, T(half + 1), T(max - 1)})
{
for (T hi_ : {T(min + 1), T(-1), T(0), norm_min, T(1), T(half - 1),
half, T(half + 1), T(max - 1), max})
{
if (hi_ <= lo_)
continue;
for (std::size_t pos = 0; pos < V::size(); ++pos)
{
V lo = lo_;
V hi = hi_;
lo[pos] = 0; // have a different value in the vector in case
hi[pos] = 1; // this affects neighbors
COMPARE(hi, hi);
VERIFY(all_of(hi != lo)) << "hi: " << hi << ", lo: " << lo;
VERIFY(all_of(lo != hi)) << "hi: " << hi << ", lo: " << lo;
VERIFY(none_of(hi != hi)) << "hi: " << hi << ", lo: " << lo;
VERIFY(none_of(hi == lo)) << "hi: " << hi << ", lo: " << lo;
VERIFY(none_of(lo == hi)) << "hi: " << hi << ", lo: " << lo;
VERIFY(all_of(lo < hi)) << "hi: " << hi << ", lo: " << lo
<< ", lo < hi: " << (lo < hi);
VERIFY(none_of(hi < lo)) << "hi: " << hi << ", lo: " << lo;
VERIFY(none_of(hi <= lo)) << "hi: " << hi << ", lo: " << lo;
VERIFY(all_of(hi <= hi)) << "hi: " << hi << ", lo: " << lo;
VERIFY(all_of(hi > lo)) << "hi: " << hi << ", lo: " << lo;
VERIFY(none_of(lo > hi)) << "hi: " << hi << ", lo: " << lo;
VERIFY(all_of(hi >= lo)) << "hi: " << hi << ", lo: " << lo;
VERIFY(all_of(hi >= hi)) << "hi: " << hi << ", lo: " << lo;
}
}
}
}
{ // subscripting
V x = max;
for (std::size_t i = 0; i < V::size(); ++i)
{
COMPARE(x[i], max);
x[i] = 0;
}
COMPARE(x, V{0});
for (std::size_t i = 0; i < V::size(); ++i)
{
COMPARE(x[i], T(0));
x[i] = max;
}
COMPARE(x, V{max});
COMPARE(typeid(x[0] * x[0]), typeid(T() * T()));
COMPARE(typeid(x[0] * T()), typeid(T() * T()));
COMPARE(typeid(T() * x[0]), typeid(T() * T()));
COMPARE(typeid(x * x[0]), typeid(x));
COMPARE(typeid(x[0] * x), typeid(x));
x = V([](auto i) -> T { return i; });
for (std::size_t i = 0; i < V::size(); ++i)
{
COMPARE(x[i], T(i));
}
for (std::size_t i = 0; i + 1 < V::size(); i += 2)
{
using std::swap;
swap(x[i], x[i + 1]);
}
for (std::size_t i = 0; i + 1 < V::size(); i += 2)
{
COMPARE(x[i], T(i + 1)) << x;
COMPARE(x[i + 1], T(i)) << x;
}
x = 1;
V y = 0;
COMPARE(x[0], T(1));
x[0] = y[0]; // make sure non-const smart_reference assignment works
COMPARE(x[0], T(0));
x = 1;
x[0] = x[0]; // self-assignment on smart_reference
COMPARE(x[0], T(1));
std::experimental::simd<typename V::value_type,
std::experimental::simd_abi::scalar>
z = 2;
x[0] = z[0];
COMPARE(x[0], T(2));
x = 3;
z[0] = x[0];
COMPARE(z[0], T(3));
// TODO: check that only value-preserving conversions happen on subscript
// assignment
}
{ // not
V x = 0;
COMPARE(!x, M{true});
V y = 1;
COMPARE(!y, M{false});
}
{ // unary minus
V x = 0;
COMPARE(-x, V(T(-T(0))));
V y = 1;
COMPARE(-y, V(T(-T(1))));
}
{ // plus
V x = 0;
V y = 0;
COMPARE(x + y, x);
COMPARE(x = x + T(1), V(1));
COMPARE(x + x, V(2));
y = make_vec<V>({1, 2, 3, 4, 5, 6, 7});
COMPARE(x = x + y, make_vec<V>({2, 3, 4, 5, 6, 7, 8}));
COMPARE(x = x + -y, V(1));
COMPARE(x += y, make_vec<V>({2, 3, 4, 5, 6, 7, 8}));
COMPARE(x, make_vec<V>({2, 3, 4, 5, 6, 7, 8}));
COMPARE(x += -y, V(1));
COMPARE(x, V(1));
}
{ // minus
V x = 1;
V y = 0;
COMPARE(x - y, x);
COMPARE(x - T(1), y);
COMPARE(y, x - T(1));
COMPARE(x - x, y);
y = make_vec<V>({1, 2, 3, 4, 5, 6, 7});
COMPARE(x = y - x, make_vec<V>({0, 1, 2, 3, 4, 5, 6}));
COMPARE(x = y - x, V(1));
COMPARE(y -= x, make_vec<V>({0, 1, 2, 3, 4, 5, 6}));
COMPARE(y, make_vec<V>({0, 1, 2, 3, 4, 5, 6}));
COMPARE(y -= y, V(0));
COMPARE(y, V(0));
}
{ // multiplies
V x = 1;
V y = 0;
COMPARE(x * y, y);
COMPARE(x = x * T(2), V(2));
COMPARE(x * x, V(4));
y = make_vec<V>({1, 2, 3, 4, 5, 6, 7});
COMPARE(x = x * y, make_vec<V>({2, 4, 6, 8, 10, 12, 14}));
y = 2;
// don't test norm_min/2*2 in the following. There's no guarantee, in
// general, that the result isn't flushed to zero (e.g. NEON without
// subnormals)
for (T n :
{T(max - 1), std::is_floating_point_v<T> ? T(norm_min * 3) : min})
{
x = n / 2;
COMPARE(x * y, V(n));
}
if (std::is_integral<T>::value && std::is_unsigned<T>::value)
{
// test modulo arithmetics
T n = max;
x = n;
for (T m : {T(2), T(7), T(max / 127), max})
{
y = m;
// if T is of lower rank than int, `n * m` will promote to int
// before executing the multiplication. In this case an overflow
// will be UB (and ubsan will warn about it). The solution is to
// cast to uint in that case.
using U
= std::conditional_t<(sizeof(T) < sizeof(int)), unsigned, T>;
COMPARE(x * y, V(T(U(n) * U(m))));
}
}
x = 2;
COMPARE(x *= make_vec<V>({1, 2, 3}), make_vec<V>({2, 4, 6}));
COMPARE(x, make_vec<V>({2, 4, 6}));
}
// divides
constexpr bool is_iec559 =
#ifdef __GCC_IEC_559
__GCC_IEC_559 >= 2;
#elif defined __STDC_IEC_559__
true;
#else
false;
#endif
if constexpr (std::is_floating_point_v<T> && !is_iec559)
{ // avoid testing subnormals and expect minor deltas for non-IEC559 float
V x = 2;
ULP_COMPARE(x / x, V(1), 1);
ULP_COMPARE(T(3) / x, V(T(3) / T(2)), 1);
ULP_COMPARE(x / T(3), V(T(2) / T(3)), 1);
V y = make_vec<V>({1, 2, 3, 4, 5, 6, 7});
ULP_COMPARE(y / x,
make_vec<V>(
{T(.5), T(1), T(1.5), T(2), T(2.5), T(3), T(3.5)}),
1);
test_values<V>({norm_min * 1024, T(1), T(), T(-1), max / 1024,
max / 4.1, max, min},
[&](V a) {
V b = 2;
V ref([&](auto i) { return a[i] / 2; });
ULP_COMPARE(a / b, ref, 1);
where(a == 0, a) = 1;
// -freciprocal-math together with flush-to-zero makes
// the following range restriction necessary (i.e.
// 1/|a| must be >= min). Intel vrcpps and vrcp14ps
// need some extra slack (use 1.1 instead of 1).
where(abs(a) >= T(1.1) / norm_min, a) = 1;
ULP_COMPARE(a / a, V(1), 1) << "\na = " << a;
ref = V([&](auto i) { return 2 / a[i]; });
ULP_COMPARE(b / a, ref, 1) << "\na = " << a;
ULP_COMPARE(b /= a, ref, 1);
ULP_COMPARE(b, ref, 1);
});
}
else
{
V x = 2;
COMPARE(x / x, V(1));
COMPARE(T(3) / x, V(T(3) / T(2)));
COMPARE(x / T(3), V(T(2) / T(3)));
V y = make_vec<V>({1, 2, 3, 4, 5, 6, 7});
COMPARE(y / x,
make_vec<V>({T(.5), T(1), T(1.5), T(2), T(2.5), T(3), T(3.5)}));
y = make_vec<V>({max, norm_min});
V ref = make_vec<V>({T(max / 2), T(norm_min / 2)});
COMPARE(y / x, ref);
y = make_vec<V>({norm_min, max});
ref = make_vec<V>({T(norm_min / 2), T(max / 2)});
COMPARE(y / x, ref);
y = make_vec<V>({max, T(norm_min + 1)});
COMPARE(y / y, V(1));
ref = make_vec<V>({T(2 / max), T(2 / (norm_min + 1))});
COMPARE(x / y, ref);
COMPARE(x /= y, ref);
COMPARE(x, ref);
}
{ // increment & decrement
const V from0 = make_vec<V>({0, 1, 2, 3}, 4);
V x = from0;
COMPARE(x++, from0);
COMPARE(x, from0 + 1);
COMPARE(++x, from0 + 2);
COMPARE(x, from0 + 2);
COMPARE(x--, from0 + 2);
COMPARE(x, from0 + 1);
COMPARE(--x, from0);
COMPARE(x, from0);
}
}