// 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 // . #ifndef TESTS_BITS_VERIFY_H_ #define TESTS_BITS_VERIFY_H_ #include #include #include #include "ulp.h" #ifdef _GLIBCXX_SIMD_HAVE_NEON // work around PR89357: #define alignas(...) __attribute__((aligned(__VA_ARGS__))) #endif using schar = signed char; using uchar = unsigned char; using ushort = unsigned short; using uint = unsigned int; using ulong = unsigned long; using llong = long long; using ullong = unsigned long long; using ldouble = long double; using wchar = wchar_t; using char16 = char16_t; using char32 = char32_t; template T make_value_unknown(const T& x) { if constexpr (std::is_constructible_v) { const volatile T& y = x; return y; } else { T y = x; asm("" : "+m"(y)); return y; } } class verify { const bool m_failed = false; size_t m_ip = 0; template () << std::declval())> void print(const T& x, int) const { std::stringstream ss; ss << x; __builtin_fprintf(stderr, "%s", ss.str().c_str()); } template void print(const T& x, ...) const { if constexpr (std::experimental::is_simd_v) { std::stringstream ss; if constexpr (std::is_floating_point_v) { ss << '(' << x[0] << " == " << std::hexfloat << x[0] << std::defaultfloat << ')'; for (unsigned i = 1; i < x.size(); ++i) { ss << (i % 4 == 0 ? ",\n(" : ", (") << x[i] << " == " << std::hexfloat << x[i] << std::defaultfloat << ')'; } } else { ss << +x[0]; for (unsigned i = 1; i < x.size(); ++i) { ss << ", " << +x[i]; } } __builtin_fprintf(stderr, "%s", ss.str().c_str()); } else if constexpr (std::experimental::is_simd_mask_v) { __builtin_fprintf(stderr, (x[0] ? "[1" : "[0")); for (unsigned i = 1; i < x.size(); ++i) { __builtin_fprintf(stderr, (x[i] ? "1" : "0")); } __builtin_fprintf(stderr, "]"); } else { print_hex(&x, sizeof(T)); } } void print_hex(const void* x, std::size_t n) const { __builtin_fprintf(stderr, "0x"); const auto* bytes = static_cast(x); for (std::size_t i = 0; i < n; ++i) { __builtin_fprintf(stderr, (i && i % 4 == 0) ? "'%02x" : "%02x", bytes[i]); } } public: template [[gnu::always_inline]] verify(bool ok, const char* file, const int line, const char* func, const char* cond, const Ts&... extra_info) : m_failed(!ok), m_ip(get_ip()) { if (m_failed) [&] { __builtin_fprintf(stderr, "%s:%d: (%s):\nInstruction Pointer: %zx\n" "Assertion '%s' failed.\n", file, line, func, m_ip, cond); (print(extra_info, int()), ...); }(); } [[gnu::always_inline]] ~verify() { if (m_failed) { __builtin_fprintf(stderr, "\n"); __builtin_abort(); } } template [[gnu::always_inline]] const verify& operator<<(const T& x) const { if (m_failed) print(x, int()); return *this; } template [[gnu::always_inline]] const verify& on_failure(const Ts&... xs) const { if (m_failed) [&] { (print(xs, int()), ...); }(); return *this; } [[gnu::always_inline]] static inline size_t get_ip() { size_t _ip = 0; #ifdef __x86_64__ asm volatile("lea 0(%%rip),%0" : "=r"(_ip)); #elif defined __i386__ asm volatile("1: movl $1b,%0" : "=r"(_ip)); #elif defined __arm__ asm volatile("mov %0,pc" : "=r"(_ip)); #elif defined __aarch64__ asm volatile("adr %0,." : "=r"(_ip)); #endif return _ip; } }; #if __FLT_EVAL_METHOD__ != 0 template [[gnu::always_inline]] inline decltype(auto) force_fp_truncation(const T& x) { namespace stdx = std::experimental; if constexpr (stdx::is_simd_v) { using U = typename T::value_type; if constexpr (std::is_floating_point_v && sizeof(U) <= 8 && (sizeof(T) < 16 || std::is_same_v< T, stdx::fixed_size_simd>)) { T y = x; asm("" : "+m"(y)); return y; } else return x; } else if constexpr (std::is_floating_point_v && sizeof(T) <= 8) { T y = x; asm("" : "+m"(y)); return y; } else return x; } #define COMPARE(_a, _b) \ [&](auto&& _aa, auto&& _bb) { \ return verify(std::experimental::all_of(_aa == _bb), __FILE__, __LINE__, \ __PRETTY_FUNCTION__, #_a " == " #_b, #_a " = ", _aa, \ "\n" #_b " = ", _bb); \ }(force_fp_truncation(_a), force_fp_truncation(_b)) #else #define COMPARE(_a, _b) \ [&](auto&& _aa, auto&& _bb) { \ return verify(std::experimental::all_of(_aa == _bb), __FILE__, __LINE__, \ __PRETTY_FUNCTION__, #_a " == " #_b, #_a " = ", _aa, \ "\n" #_b " = ", _bb); \ }((_a), (_b)) #endif #define VERIFY(_test) \ verify(_test, __FILE__, __LINE__, __PRETTY_FUNCTION__, #_test) // ulp_distance_signed can raise FP exceptions and thus must be conditionally // executed #define ULP_COMPARE(_a, _b, _allowed_distance) \ [&](auto&& _aa, auto&& _bb) { \ const bool success = std::experimental::all_of( \ vir::test::ulp_distance(_aa, _bb) <= (_allowed_distance)); \ return verify(success, __FILE__, __LINE__, __PRETTY_FUNCTION__, \ #_a " ~~ " #_b, #_a " = ", _aa, "\n" #_b " = ", _bb, \ "\ndistance = ", \ success ? 0 : vir::test::ulp_distance_signed(_aa, _bb)); \ }((_a), (_b)) namespace vir { namespace test { template inline T _S_fuzzyness = 0; template void setFuzzyness(T x) { _S_fuzzyness = x; } } // namespace test } // namespace vir #define FUZZY_COMPARE(_a, _b) \ ULP_COMPARE( \ _a, _b, \ vir::test::_S_fuzzyness>) template void test(); template void invoke_test(...) {} template void invoke_test(int) { test(); __builtin_fprintf(stderr, "PASS: %s\n", __PRETTY_FUNCTION__); } #endif // TESTS_BITS_VERIFY_H_