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,15 @@ add_libc_exhaustive_testsuite(libc_math_exhaustive_tests) +add_object_library( + exhaustive_test + HDRS + exhaustive_test.h + SRCS + exhaustive_test.cpp + DEPENDS + libc.src.__support.CPP.standalone_cpp +) + add_fp_unittest( sqrtf_test NEED_MPFR @@ -54,13 +64,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.h b/libc/test/src/math/exhaustive/exhaustive_test.h new file mode 100644 --- /dev/null +++ b/libc/test/src/math/exhaustive/exhaustive_test.h @@ -0,0 +1,26 @@ +//===-- Exhaustive test template for math functions -------------*- C++ -*-===// +// +// 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 "utils/MPFRWrapper/MPFRUtils.h" +#include "utils/UnitTest/Test.h" + +// To test exhaustively for inputs in the range [start, stop) in parallel: +// 1. Inherit from LlvmLibcExhaustiveTest class +// 2. Overide the test method: void check(T, T, RoundingMode) +// 4. Call: test_full_range(start, stop, nthreads, rounding) +namespace mpfr = __llvm_libc::testing::mpfr; + +template +struct LlvmLibcExhaustiveTest : public __llvm_libc::testing::Test { + // Break [start, stop) into `nthreads` subintervals and apply *check to each + // subinterval in parallel. + void test_full_range(T start, T stop, int nthreads, + mpfr::RoundingMode rounding); + + virtual void check(T start, T stop, mpfr::RoundingMode rounding); +}; diff --git a/libc/test/src/math/exhaustive/exhaustive_test.cpp b/libc/test/src/math/exhaustive/exhaustive_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/math/exhaustive/exhaustive_test.cpp @@ -0,0 +1,58 @@ +//===-- Exhaustive test template for math functions -------------*- C++ -*-===// +// +// 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 "exhaustive_test.h" +#include "utils/UnitTest/Test.h" + +template +void LlvmLibcExhaustiveTest::test_full_range(T start, T stop, int nthreads, + mpfr::RoundingMode rounding) { + 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([this, begin, end, rounding]() { + 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, rounding); + + 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(); + } + } +} + +template void +LlvmLibcExhaustiveTest::test_full_range(uint32_t, uint32_t, int, + mpfr::RoundingMode); +template void +LlvmLibcExhaustiveTest::test_full_range(uint64_t, uint64_t, int, + mpfr::RoundingMode); 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,46 @@ // //===----------------------------------------------------------------------===// +#include "exhaustive_test.h" #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); +struct LlvmLibcLogfExhaustiveTest : public LlvmLibcExhaustiveTest { + void check(uint32_t start, uint32_t stop, + mpfr::RoundingMode rounding) override { + mpfr::ForceRoundingMode r(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); + } +}; + +TEST_F(LlvmLibcLogfExhaustiveTest, RoundNearestTieToEven) { + test_full_range(/*start=*/0U, /*stop=*/0x7f80'0000U, /*nthreads=*/16, + mpfr::RoundingMode::Nearest); +} + +TEST_F(LlvmLibcLogfExhaustiveTest, RoundUp) { + test_full_range(/*start=*/0U, /*stop=*/0x7f80'0000U, /*nthreads=*/16, + mpfr::RoundingMode::Upward); +} + +TEST_F(LlvmLibcLogfExhaustiveTest, RoundDown) { + test_full_range(/*start=*/0U, /*stop=*/0x7f80'0000U, /*nthreads=*/16, + mpfr::RoundingMode::Downward); +} + +TEST_F(LlvmLibcLogfExhaustiveTest, RoundTowardZero) { + test_full_range(/*start=*/0U, /*stop=*/0x7f80'0000U, /*nthreads=*/16, + mpfr::RoundingMode::TowardZero); } 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 @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -571,6 +572,10 @@ } } +// Remark: For all the explain_*_error functions, we will use std::stringstream +// to build the complete error messages before sending it to the outstream `OS` +// once at the end. This will stop the error messages from interleaving when +// the tests are running concurrently. template void explain_unary_operation_single_output_error(Operation op, T input, T matchValue, @@ -582,18 +587,20 @@ MPFRNumber mpfr_result; mpfr_result = unary_operation(op, input, precision, 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 @@ -616,31 +623,33 @@ int mpfrIntResult; MPFRNumber mpfr_result = unary_operation_two_outputs(op, input, mpfrIntResult, precision, 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( @@ -665,17 +674,19 @@ MPFRNumber mpfr_result = binary_operation_two_outputs( op, input.x, input.y, mpfrIntResult, precision, 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( @@ -701,20 +712,22 @@ MPFRNumber mpfr_result = binary_operation_one_output(op, input.x, input.y, precision, 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( @@ -741,23 +754,25 @@ MPFRNumber mpfr_result = ternary_operation_one_output( op, input.x, input.y, input.z, precision, 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( 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