diff --git a/libc/cmake/modules/LLVMLibCTestRules.cmake b/libc/cmake/modules/LLVMLibCTestRules.cmake --- a/libc/cmake/modules/LLVMLibCTestRules.cmake +++ b/libc/cmake/modules/LLVMLibCTestRules.cmake @@ -63,6 +63,7 @@ # HDRS # DEPENDS # COMPILE_OPTIONS +# LINK_OPTIONS # ) function(add_libc_unittest target_name) if(NOT LLVM_INCLUDE_TESTS) @@ -71,9 +72,9 @@ cmake_parse_arguments( "LIBC_UNITTEST" - "" # No optional arguments + "NO_RUN_POSTBUILD" # Optional arguments "SUITE" # Single value arguments - "SRCS;HDRS;DEPENDS;COMPILE_OPTIONS" # Multi-value arguments + "SRCS;HDRS;DEPENDS;COMPILE_OPTIONS;LINK_OPTIONS" # Multi-value arguments "NO_LIBC_UNITTEST_TEST_MAIN" ${ARGN} ) @@ -140,6 +141,12 @@ endif() target_link_libraries(${fq_target_name} PRIVATE ${link_object_files}) + if(LIBC_UNITTEST_LINK_OPTIONS) + target_link_options( + ${fq_target_name} + PRIVATE ${LIBC_UNITTEST_LINK_OPTIONS} + ) + endif() set_target_properties(${fq_target_name} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) @@ -155,11 +162,14 @@ target_link_libraries(${fq_target_name} PRIVATE LibcUnitTest LibcUnitTestMain libc_test_utils) endif() - add_custom_command( - TARGET ${fq_target_name} - POST_BUILD - COMMAND $ - ) + if(NOT LIBC_UNITTEST_NO_RUN_POSTBUILD) + add_custom_command( + TARGET ${fq_target_name} + POST_BUILD + COMMAND $ + ) + endif() + if(LIBC_UNITTEST_SUITE) add_dependencies( ${LIBC_UNITTEST_SUITE} diff --git a/libc/test/src/math/exhaustive/CMakeLists.txt b/libc/test/src/math/exhaustive/CMakeLists.txt --- a/libc/test/src/math/exhaustive/CMakeLists.txt +++ b/libc/test/src/math/exhaustive/CMakeLists.txt @@ -1,5 +1,13 @@ add_libc_exhaustive_testsuite(libc_math_exhaustive_tests) +add_header_library( + exhaustive_test + HDRS + exhaustive_test.hpp + DEPENDS + libc.src.__support.CPP.standalone_cpp +) + add_fp_unittest( sqrtf_test NEED_MPFR @@ -54,13 +62,17 @@ add_fp_unittest( logf_test + NO_RUN_POSTBUILD NEED_MPFR SUITE libc_math_exhaustive_tests SRCS logf_test.cpp DEPENDS + .exhaustive_test libc.include.math libc.src.math.logf libc.src.__support.FPUtil.fputil + LINK_OPTIONS + -lpthread ) diff --git a/libc/test/src/math/exhaustive/exhaustive_test.hpp b/libc/test/src/math/exhaustive/exhaustive_test.hpp new file mode 100644 --- /dev/null +++ b/libc/test/src/math/exhaustive/exhaustive_test.hpp @@ -0,0 +1,54 @@ +//===-- Exhaustive test template for math functions -----------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include +#include +#include +#include +#include +#include +#include + +#include "utils/UnitTest/Test.h" + +template +class LlvmLibcExhaustiveTest : public __llvm_libc::testing::Test { +public: + using Func = std::function; + + void testFullRange(T start, T stop, int nthreads, Func check) { + std::vector thread_list(nthreads); + T increment = (stop - start - 1) / nthreads + 1; + T begin = start; + T end = start + increment - 1; + for (int i = 0; i < nthreads; ++i) { + thread_list.emplace_back([begin, end, check]() { + std::stringstream msg; + msg << "-- Testing from " << begin << " to " << end << " [0x" + << std::hex << begin << ", 0x" << end << ") ..." << std::endl; + std::cout << msg.str(); + msg.str(""); + + check(begin, end); + + msg << "** Finished testing from " << std::dec << begin << " to " << end + << " [0x" << std::hex << begin << ", 0x" << end << ")" << std::endl; + std::cout << msg.str(); + }); + begin += increment; + end += increment; + if (end > stop) + end = stop; + } + for (auto &thread : thread_list) { + if (thread.joinable()) { + thread.join(); + } + } + } +}; diff --git a/libc/test/src/math/exhaustive/logf_test.cpp b/libc/test/src/math/exhaustive/logf_test.cpp --- a/libc/test/src/math/exhaustive/logf_test.cpp +++ b/libc/test/src/math/exhaustive/logf_test.cpp @@ -6,21 +6,67 @@ // //===----------------------------------------------------------------------===// +#include +#include + +#include "exhaustive_test.hpp" #include "src/__support/FPUtil/FPBits.h" #include "src/math/logf.h" #include "utils/MPFRWrapper/MPFRUtils.h" #include "utils/UnitTest/FPMatcher.h" -#include "utils/UnitTest/Test.h" using FPBits = __llvm_libc::fputil::FPBits; namespace mpfr = __llvm_libc::testing::mpfr; -TEST(LlvmLibcLogfExhaustiveTest, AllValues) { - uint32_t bits = 0U; - do { - FPBits xbits(bits); - float x = float(xbits); - EXPECT_MPFR_MATCH(mpfr::Operation::Log, x, __llvm_libc::logf(x), 0.5); - } while (bits++ < 0x7f7f'ffffU); +class LlvmLibcLogfExhaustiveTest : public LlvmLibcExhaustiveTest { + +public: + template + void check(uint32_t start, uint32_t stop) { + int old_rounding = fegetround(); + fesetround(mpfr::get_fe_rounding(Rounding)); + uint32_t bits = start; + do { + FPBits xbits(bits); + float x = float(xbits); + EXPECT_MPFR_MATCH(mpfr::Operation::Log, x, __llvm_libc::logf(x), 0.5, + Rounding); + } while (bits++ < stop); + fesetround(old_rounding); + } +}; + +TEST_F(LlvmLibcLogfExhaustiveTest, RoundUpward) { + using namespace std::placeholders; + testFullRange( + /*start=*/0U, /*stop=*/0x7f80'0000U, /*nthreads=*/16, + std::bind(&LlvmLibcLogfExhaustiveTest::check, + *this, _1, _2)); +} + +TEST_F(LlvmLibcLogfExhaustiveTest, RoundDownward) { + using namespace std::placeholders; + testFullRange( + /*start=*/0U, /*stop=*/0x7f80'0000U, /*nthreads=*/16, + std::bind( + &LlvmLibcLogfExhaustiveTest::check, + *this, _1, _2)); +} + +TEST_F(LlvmLibcLogfExhaustiveTest, RoundTowardZero) { + using namespace std::placeholders; + testFullRange( + /*start=*/0U, /*stop=*/0x7f80'0000U, /*nthreads=*/16, + std::bind( + &LlvmLibcLogfExhaustiveTest::check, + *this, _1, _2)); +} + +TEST_F(LlvmLibcLogfExhaustiveTest, RoundToNearestTieToEven) { + using namespace std::placeholders; + testFullRange( + /*start=*/0U, /*stop=*/0x7f80'0000U, /*nthreads=*/16, + std::bind(&LlvmLibcLogfExhaustiveTest::check, + *this, _1, _2)); } diff --git a/libc/utils/MPFRWrapper/MPFRUtils.h b/libc/utils/MPFRWrapper/MPFRUtils.h --- a/libc/utils/MPFRWrapper/MPFRUtils.h +++ b/libc/utils/MPFRWrapper/MPFRUtils.h @@ -73,6 +73,8 @@ enum class RoundingMode : uint8_t { Upward, Downward, TowardZero, Nearest }; +int get_fe_rounding(RoundingMode); + template struct BinaryInput { static_assert( __llvm_libc::cpp::IsFloatingPointType::Value, diff --git a/libc/utils/MPFRWrapper/MPFRUtils.cpp b/libc/utils/MPFRWrapper/MPFRUtils.cpp --- a/libc/utils/MPFRWrapper/MPFRUtils.cpp +++ b/libc/utils/MPFRWrapper/MPFRUtils.cpp @@ -14,7 +14,9 @@ #include "utils/UnitTest/FPMatcher.h" #include +#include #include +#include #include #include @@ -86,6 +88,23 @@ } } +int get_fe_rounding(RoundingMode mode) { + switch (mode) { + case RoundingMode::Upward: + return FE_UPWARD; + break; + case RoundingMode::Downward: + return FE_DOWNWARD; + break; + case RoundingMode::TowardZero: + return FE_TOWARDZERO; + break; + case RoundingMode::Nearest: + return FE_TONEAREST; + break; + } +} + class MPFRNumber { // A precision value which allows sufficiently large additional // precision even compared to quad-precision floating point values. @@ -536,18 +555,20 @@ MPFRNumber mpfrInput(input); MPFRNumber mpfr_result = unary_operation(op, input, rounding); MPFRNumber mpfrMatchValue(matchValue); - OS << "Match value not within tolerance value of MPFR result:\n" + std::stringstream ss; + ss << "Match value not within tolerance value of MPFR result:\n" << " Input decimal: " << mpfrInput.str() << '\n'; - __llvm_libc::fputil::testing::describeValue(" Input bits: ", input, OS); - OS << '\n' << " Match decimal: " << mpfrMatchValue.str() << '\n'; + __llvm_libc::fputil::testing::describeValue(" Input bits: ", input, ss); + ss << '\n' << " Match decimal: " << mpfrMatchValue.str() << '\n'; __llvm_libc::fputil::testing::describeValue(" Match bits: ", matchValue, - OS); - OS << '\n' << " MPFR result: " << mpfr_result.str() << '\n'; + ss); + ss << '\n' << " MPFR result: " << mpfr_result.str() << '\n'; __llvm_libc::fputil::testing::describeValue( - " MPFR rounded: ", mpfr_result.as(), OS); - OS << '\n'; - OS << " ULP error: " << std::to_string(mpfr_result.ulp(matchValue)) + " MPFR rounded: ", mpfr_result.as(), ss); + ss << '\n'; + ss << " ULP error: " << std::to_string(mpfr_result.ulp(matchValue)) << '\n'; + OS << ss.str(); } template void explain_unary_operation_single_output_error( @@ -566,31 +587,33 @@ int mpfrIntResult; MPFRNumber mpfr_result = unary_operation_two_outputs(op, input, mpfrIntResult, rounding); + std::stringstream ss; if (mpfrIntResult != libc_result.i) { - OS << "MPFR integral result: " << mpfrIntResult << '\n' + ss << "MPFR integral result: " << mpfrIntResult << '\n' << "Libc integral result: " << libc_result.i << '\n'; } else { - OS << "Integral result from libc matches integral result from MPFR.\n"; + ss << "Integral result from libc matches integral result from MPFR.\n"; } MPFRNumber mpfrMatchValue(libc_result.f); - OS << "Libc floating point result is not within tolerance value of the MPFR " + ss << "Libc floating point result is not within tolerance value of the MPFR " << "result.\n\n"; - OS << " Input decimal: " << mpfrInput.str() << "\n\n"; + ss << " Input decimal: " << mpfrInput.str() << "\n\n"; - OS << "Libc floating point value: " << mpfrMatchValue.str() << '\n'; + ss << "Libc floating point value: " << mpfrMatchValue.str() << '\n'; __llvm_libc::fputil::testing::describeValue( - " Libc floating point bits: ", libc_result.f, OS); - OS << "\n\n"; + " Libc floating point bits: ", libc_result.f, ss); + ss << "\n\n"; - OS << " MPFR result: " << mpfr_result.str() << '\n'; + ss << " MPFR result: " << mpfr_result.str() << '\n'; __llvm_libc::fputil::testing::describeValue( - " MPFR rounded: ", mpfr_result.as(), OS); - OS << '\n' + " MPFR rounded: ", mpfr_result.as(), ss); + ss << '\n' << " ULP error: " << std::to_string(mpfr_result.ulp(libc_result.f)) << '\n'; + OS << ss.str(); } template void explain_unary_operation_two_outputs_error( @@ -614,17 +637,19 @@ MPFRNumber mpfr_result = binary_operation_two_outputs( op, input.x, input.y, mpfrIntResult, rounding); MPFRNumber mpfrMatchValue(libc_result.f); + std::stringstream ss; - OS << "Input decimal: x: " << mpfrX.str() << " y: " << mpfrY.str() << '\n' + ss << "Input decimal: x: " << mpfrX.str() << " y: " << mpfrY.str() << '\n' << "MPFR integral result: " << mpfrIntResult << '\n' << "Libc integral result: " << libc_result.i << '\n' << "Libc floating point result: " << mpfrMatchValue.str() << '\n' << " MPFR result: " << mpfr_result.str() << '\n'; __llvm_libc::fputil::testing::describeValue( - "Libc floating point result bits: ", libc_result.f, OS); + "Libc floating point result bits: ", libc_result.f, ss); __llvm_libc::fputil::testing::describeValue( - " MPFR rounded bits: ", mpfr_result.as(), OS); - OS << "ULP error: " << std::to_string(mpfr_result.ulp(libc_result.f)) << '\n'; + " MPFR rounded bits: ", mpfr_result.as(), ss); + ss << "ULP error: " << std::to_string(mpfr_result.ulp(libc_result.f)) << '\n'; + OS << ss.str(); } template void explain_binary_operation_two_outputs_error( @@ -651,20 +676,22 @@ MPFRNumber mpfr_result = binary_operation_one_output(op, input.x, input.y, rounding); MPFRNumber mpfrMatchValue(libc_result); + std::stringstream ss; - OS << "Input decimal: x: " << mpfrX.str() << " y: " << mpfrY.str() << '\n'; + ss << "Input decimal: x: " << mpfrX.str() << " y: " << mpfrY.str() << '\n'; __llvm_libc::fputil::testing::describeValue("First input bits: ", input.x, - OS); + ss); __llvm_libc::fputil::testing::describeValue("Second input bits: ", input.y, - OS); + ss); - OS << "Libc result: " << mpfrMatchValue.str() << '\n' + ss << "Libc result: " << mpfrMatchValue.str() << '\n' << "MPFR result: " << mpfr_result.str() << '\n'; __llvm_libc::fputil::testing::describeValue( - "Libc floating point result bits: ", libc_result, OS); + "Libc floating point result bits: ", libc_result, ss); __llvm_libc::fputil::testing::describeValue( - " MPFR rounded bits: ", mpfr_result.as(), OS); - OS << "ULP error: " << std::to_string(mpfr_result.ulp(libc_result)) << '\n'; + " MPFR rounded bits: ", mpfr_result.as(), ss); + ss << "ULP error: " << std::to_string(mpfr_result.ulp(libc_result)) << '\n'; + OS << ss.str(); } template void explain_binary_operation_one_output_error( @@ -692,23 +719,25 @@ MPFRNumber mpfr_result = ternary_operation_one_output(op, input.x, input.y, input.z, rounding); MPFRNumber mpfrMatchValue(libc_result); + std::stringstream ss; - OS << "Input decimal: x: " << mpfrX.str() << " y: " << mpfrY.str() + ss << "Input decimal: x: " << mpfrX.str() << " y: " << mpfrY.str() << " z: " << mpfrZ.str() << '\n'; __llvm_libc::fputil::testing::describeValue("First input bits: ", input.x, - OS); + ss); __llvm_libc::fputil::testing::describeValue("Second input bits: ", input.y, - OS); + ss); __llvm_libc::fputil::testing::describeValue("Third input bits: ", input.z, - OS); + ss); - OS << "Libc result: " << mpfrMatchValue.str() << '\n' + ss << "Libc result: " << mpfrMatchValue.str() << '\n' << "MPFR result: " << mpfr_result.str() << '\n'; __llvm_libc::fputil::testing::describeValue( - "Libc floating point result bits: ", libc_result, OS); + "Libc floating point result bits: ", libc_result, ss); __llvm_libc::fputil::testing::describeValue( - " MPFR rounded bits: ", mpfr_result.as(), OS); - OS << "ULP error: " << std::to_string(mpfr_result.ulp(libc_result)) << '\n'; + " MPFR rounded bits: ", mpfr_result.as(), ss); + ss << "ULP error: " << std::to_string(mpfr_result.ulp(libc_result)) << '\n'; + OS << ss.str(); } template void explain_ternary_operation_one_output_error( @@ -729,9 +758,9 @@ // is rounded to the nearest even. MPFRNumber mpfr_result = unary_operation(op, input, rounding); double ulp = mpfr_result.ulp(libc_result); - bool bits_are_even = ((FPBits(libc_result).uintval() & 1) == 0); return (ulp < ulp_error) || - ((ulp == ulp_error) && ((ulp != 0.5) || bits_are_even)); + ((ulp == ulp_error) && + ((ulp != 0.5) || mpfr_result.as() == libc_result)); } template bool compare_unary_operation_single_output(Operation, float, @@ -813,9 +842,9 @@ binary_operation_one_output(op, input.x, input.y, rounding); double ulp = mpfr_result.ulp(libc_result); - bool bits_are_even = ((FPBits(libc_result).uintval() & 1) == 0); return (ulp < ulp_error) || - ((ulp == ulp_error) && ((ulp != 0.5) || bits_are_even)); + ((ulp == ulp_error) && + ((ulp != 0.5) || mpfr_result.as() == libc_result)); } template bool compare_binary_operation_one_output( @@ -835,9 +864,9 @@ ternary_operation_one_output(op, input.x, input.y, input.z, rounding); double ulp = mpfr_result.ulp(libc_result); - bool bits_are_even = ((FPBits(libc_result).uintval() & 1) == 0); return (ulp < ulp_error) || - ((ulp == ulp_error) && ((ulp != 0.5) || bits_are_even)); + ((ulp == ulp_error) && + ((ulp != 0.5) || mpfr_result.as() == libc_result)); } template bool compare_ternary_operation_one_output( diff --git a/libc/utils/UnitTest/FPMatcher.h b/libc/utils/UnitTest/FPMatcher.h --- a/libc/utils/UnitTest/FPMatcher.h +++ b/libc/utils/UnitTest/FPMatcher.h @@ -17,10 +17,9 @@ namespace fputil { namespace testing { -template +template cpp::EnableIfType::Value, void> -describeValue(const char *label, ValType value, - testutils::StreamWrapper &stream); +describeValue(const char *label, ValType value, StreamType &stream); template class FPMatcher : public __llvm_libc::testing::Matcher { diff --git a/libc/utils/UnitTest/FPMatcher.cpp b/libc/utils/UnitTest/FPMatcher.cpp --- a/libc/utils/UnitTest/FPMatcher.cpp +++ b/libc/utils/UnitTest/FPMatcher.cpp @@ -10,6 +10,7 @@ #include "src/__support/FPUtil/FPBits.h" +#include #include namespace __llvm_libc { @@ -30,10 +31,9 @@ return s; } -template +template cpp::EnableIfType::Value, void> -describeValue(const char *label, ValType value, - testutils::StreamWrapper &stream) { +describeValue(const char *label, ValType value, StreamType &stream) { stream << label; FPBits bits(value); @@ -49,15 +49,19 @@ (fputil::ExponentWidth::VALUE - 1) / 4 + 1; constexpr int mantissaWidthInHex = (fputil::MantissaWidth::VALUE - 1) / 4 + 1; + constexpr int bitsWidthInHex = + sizeof(typename fputil::FPBits::UIntType) * 2; - stream << "Sign: " << (bits.get_sign() ? '1' : '0') << ", " - << "Exponent: 0x" + stream << "0x" + << uintToHex::UIntType>( + bits.uintval(), bitsWidthInHex) + << ", (S | E | M) = (" << (bits.get_sign() ? '1' : '0') << " | 0x" << uintToHex(bits.get_unbiased_exponent(), exponentWidthInHex) - << ", " - << "Mantissa: 0x" + << " | 0x" << uintToHex::UIntType>( - bits.get_mantissa(), mantissaWidthInHex); + bits.get_mantissa(), mantissaWidthInHex) + << ")"; } stream << '\n'; @@ -70,6 +74,11 @@ template void describeValue(const char *, long double, testutils::StreamWrapper &); +template void describeValue(const char *, float, std::stringstream &); +template void describeValue(const char *, double, std::stringstream &); +template void describeValue(const char *, long double, + std::stringstream &); + } // namespace testing } // namespace fputil } // namespace __llvm_libc