diff --git a/libc/test/src/math/CMakeLists.txt b/libc/test/src/math/CMakeLists.txt --- a/libc/test/src/math/CMakeLists.txt +++ b/libc/test/src/math/CMakeLists.txt @@ -1134,3 +1134,4 @@ add_subdirectory(generic) add_subdirectory(exhaustive) +add_subdirectory(differential_testing) diff --git a/libc/test/src/math/differential_testing/CMakeLists.txt b/libc/test/src/math/differential_testing/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/libc/test/src/math/differential_testing/CMakeLists.txt @@ -0,0 +1,87 @@ + +function(add_diff_binary target_name) + cmake_parse_arguments( + "DIFF" + "" # No optional arguments + "SUITE" # Single value arguments + "SRCS;HDRS;DEPENDS;COMPILE_OPTIONS" # Multi-value arguments + ${ARGN} + ) + if(NOT DIFF_SRCS) + message(FATAL_ERROR "'add_diff_binary' target requires a SRCS list of .cpp " + "files.") + endif() + if(NOT DIFF_DEPENDS) + message(FATAL_ERROR "'add_diff_binary' target requires a DEPENDS list of " + "'add_entrypoint_object' targets.") + endif() + + get_fq_target_name(${target_name} fq_target_name) + get_fq_deps_list(fq_deps_list ${DIFF_DEPENDS}) + get_object_files_for_test( + link_object_files skipped_entrypoints_list ${fq_deps_list}) + if(skipped_entrypoints_list) + set(msg "Will not build ${fq_target_name} as it has missing deps: " + "${skipped_entrypoints_list}.") + message(STATUS ${msg}) + return() + endif() + + add_executable( + ${fq_target_name} + EXCLUDE_FROM_ALL + ${DIFF_SRCS} + ${DIFF_HDRS} + ) + target_include_directories( + ${fq_target_name} + PRIVATE + ${LIBC_SOURCE_DIR} + ${LIBC_BUILD_DIR} + ${LIBC_BUILD_DIR}/include + ) + if(DIFF_COMPILE_OPTIONS) + target_compile_options( + ${fq_target_name} + PRIVATE ${DIFF_COMPILE_OPTIONS} + ) + endif() + + target_link_libraries( + ${fq_target_name} + PRIVATE ${link_object_files} libc_test_utils) + + set_target_properties(${fq_target_name} + PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + + add_dependencies( + ${fq_target_name} + libc.utils.FPUtil.fputil + ${fq_deps_list} + ) +endfunction() + +add_header_library( + single_input_single_output_diff + HDRS + SingleInputSingleOutputDiff.h +) + +add_diff_binary( + sinf_diff + SRCS + sinf_diff.cpp + DEPENDS + .single_input_single_output_diff + libc.src.math.sinf +) + +add_diff_binary( + sinf_perf + SRCS + sinf_perf.cpp + DEPENDS + libc.src.math.sinf + COMPILE_OPTIONS + -fno-builtin +) diff --git a/libc/test/src/math/differential_testing/SingleInputSingleOutputDiff.h b/libc/test/src/math/differential_testing/SingleInputSingleOutputDiff.h new file mode 100644 --- /dev/null +++ b/libc/test/src/math/differential_testing/SingleInputSingleOutputDiff.h @@ -0,0 +1,89 @@ +//===-- Common utility class for differential analysis --------------------===// +// +// 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/FPUtil/FPBits.h" +#include "utils/testutils/StreamWrapper.h" +#include "utils/testutils/Timer.h" + +namespace __llvm_libc { +namespace testing { + +template class SingleInputSingleOutputDiff { + using FPBits = fputil::FPBits; + using UIntType = typename FPBits::UIntType; + static constexpr UIntType MSBit = UIntType(1) << (8 * sizeof(UIntType) - 1); + static constexpr UIntType UIntMax = (MSBit - 1) + MSBit; + +public: + typedef T Func(T); + + static void runDiff(Func myFunc, Func otherFunc, const char *logFile) { + UIntType diffCount = 0; + testutils::OutputFileStream log(logFile); + log << "Starting diff for values from 0 to " << UIntMax << '\n' + << "Only differing results will be logged.\n\n"; + for (UIntType bits = 0;; ++bits) { + T x = T(FPBits(bits)); + T myResult = myFunc(x); + T otherResult = otherFunc(x); + UIntType myBits = FPBits(myResult).uintval(); + UIntType otherBits = FPBits(otherResult).uintval(); + if (myBits != otherBits) { + ++diffCount; + log << " Input: " << bits << " (" << x << ")\n" + << " My result: " << myBits << " (" << myResult << ")\n" + << "Other result: " << otherBits << " (" << otherResult << ")\n" + << '\n'; + } + if (bits == UIntMax) + break; + } + log << "Total number of differing results: " << diffCount << '\n'; + } + + static void runPerf(Func myFunc, Func otherFunc, const char *logFile) { + auto runner = [](Func func) { + volatile T result; + for (UIntType bits = 0;; ++bits) { + T x = T(FPBits(bits)); + result = func(x); + if (bits == UIntMax) + break; + } + }; + + testutils::OutputFileStream log(logFile); + Timer timer; + timer.start(); + runner(myFunc); + timer.stop(); + log << " Run time of my function: " << timer.nanoseconds() << " ns \n"; + + timer.start(); + runner(otherFunc); + timer.stop(); + log << "Run time of other function: " << timer.nanoseconds() << " ns \n"; + } +}; + +} // namespace testing +} // namespace __llvm_libc + +#define SINGLE_INPUT_SINGLE_OUTPUT_DIFF(T, myFunc, otherFunc, filename) \ + int main() { \ + __llvm_libc::testing::SingleInputSingleOutputDiff::runDiff( \ + &myFunc, &otherFunc, filename); \ + return 0; \ + } + +#define SINGLE_INPUT_SINGLE_OUTPUT_PERF(T, myFunc, otherFunc, filename) \ + int main() { \ + __llvm_libc::testing::SingleInputSingleOutputDiff::runPerf( \ + &myFunc, &otherFunc, filename); \ + return 0; \ + } diff --git a/libc/test/src/math/differential_testing/sinf_diff.cpp b/libc/test/src/math/differential_testing/sinf_diff.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/math/differential_testing/sinf_diff.cpp @@ -0,0 +1,16 @@ +//===-- Differential test for sinf ----------------------------------------===// +// +// 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 "SingleInputSingleOutputDiff.h" + +#include "src/math/sinf.h" + +#include + +SINGLE_INPUT_SINGLE_OUTPUT_DIFF(float, __llvm_libc::sinf, ::sinf, + "sinf_diff.log") diff --git a/libc/test/src/math/differential_testing/sinf_perf.cpp b/libc/test/src/math/differential_testing/sinf_perf.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/math/differential_testing/sinf_perf.cpp @@ -0,0 +1,16 @@ +//===-- Differential test for sinf ----------------------------------------===// +// +// 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 "SingleInputSingleOutputDiff.h" + +#include "src/math/sinf.h" + +#include + +SINGLE_INPUT_SINGLE_OUTPUT_PERF(float, __llvm_libc::sinf, ::sinf, + "sinf_perf.log") 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 @@ -12,3 +12,16 @@ libc.src.math.sqrtf libc.utils.FPUtil.fputil ) + +add_fp_unittest( + sinf_test + NEED_MPFR + SUITE + libc_math_exhaustive_tests + SRCS + sinf_test.cpp + DEPENDS + libc.include.math + libc.src.math.sinf + libc.utils.FPUtil.fputil +) diff --git a/libc/test/src/math/exhaustive/sqrtf_test.cpp b/libc/test/src/math/exhaustive/sinf_test.cpp copy from libc/test/src/math/exhaustive/sqrtf_test.cpp copy to libc/test/src/math/exhaustive/sinf_test.cpp --- a/libc/test/src/math/exhaustive/sqrtf_test.cpp +++ b/libc/test/src/math/exhaustive/sinf_test.cpp @@ -1,12 +1,12 @@ -//===-- Exhaustive test for sqrtf -----------------------------------------===// +//===-- Exhaustive test for sinf ------------------------------------------===// // // 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 "src/math/sqrtf.h" +#include "src/math/sinf.h" #include "utils/FPUtil/FPBits.h" #include "utils/FPUtil/TestHelpers.h" #include "utils/MPFRWrapper/MPFRUtils.h" @@ -16,11 +16,11 @@ namespace mpfr = __llvm_libc::testing::mpfr; -TEST(LlvmLibcSqrtfExhaustiveTest, AllValues) { +TEST(LlvmLibcsinffExhaustiveTest, AllValues) { uint32_t bits = 0; do { - FPBits x(bits); - ASSERT_MPFR_MATCH(mpfr::Operation::Sqrt, float(x), __llvm_libc::sqrtf(x), - 0.5); + FPBits xbits(bits); + float x = float(xbits); + ASSERT_MPFR_MATCH(mpfr::Operation::Sin, x, __llvm_libc::sinf(x), 1.0); } while (bits++ < 0xffff'ffffU); } diff --git a/libc/test/src/math/exhaustive/sqrtf_test.cpp b/libc/test/src/math/exhaustive/sqrtf_test.cpp --- a/libc/test/src/math/exhaustive/sqrtf_test.cpp +++ b/libc/test/src/math/exhaustive/sqrtf_test.cpp @@ -19,8 +19,8 @@ TEST(LlvmLibcSqrtfExhaustiveTest, AllValues) { uint32_t bits = 0; do { - FPBits x(bits); - ASSERT_MPFR_MATCH(mpfr::Operation::Sqrt, float(x), __llvm_libc::sqrtf(x), - 0.5); + FPBits xbits(bits); + float x = float(xbits); + ASSERT_MPFR_MATCH(mpfr::Operation::Sqrt, x, __llvm_libc::sqrtf(x), 0.5); } while (bits++ < 0xffff'ffffU); } diff --git a/libc/utils/testutils/CMakeLists.txt b/libc/utils/testutils/CMakeLists.txt --- a/libc/utils/testutils/CMakeLists.txt +++ b/libc/utils/testutils/CMakeLists.txt @@ -13,4 +13,6 @@ ExecuteFunction.h ${FDReaderFile} FDReader.h + Timer.h + Timer.cpp ) diff --git a/libc/utils/testutils/StreamWrapper.h b/libc/utils/testutils/StreamWrapper.h --- a/libc/utils/testutils/StreamWrapper.h +++ b/libc/utils/testutils/StreamWrapper.h @@ -16,6 +16,7 @@ // standard headers so we must provide streams through indirection to not // expose the system libc headers. class StreamWrapper { +protected: void *OS; public: @@ -26,6 +27,12 @@ StreamWrapper outs(); +class OutputFileStream : public StreamWrapper { +public: + explicit OutputFileStream(const char *FN); + ~OutputFileStream(); +}; + } // namespace testutils } // namespace __llvm_libc diff --git a/libc/utils/testutils/StreamWrapper.cpp b/libc/utils/testutils/StreamWrapper.cpp --- a/libc/utils/testutils/StreamWrapper.cpp +++ b/libc/utils/testutils/StreamWrapper.cpp @@ -8,6 +8,7 @@ #include "StreamWrapper.h" #include +#include #include #include #include @@ -21,6 +22,7 @@ assert(OS); std::ostream &Stream = *reinterpret_cast(OS); Stream << t; + Stream.flush(); return *this; } @@ -43,6 +45,15 @@ StreamWrapper::operator<<(unsigned long long t); template StreamWrapper &StreamWrapper::operator<<(bool t); template StreamWrapper &StreamWrapper::operator<<(std::string t); +template StreamWrapper &StreamWrapper::operator<<(float t); +template StreamWrapper &StreamWrapper::operator<<(double t); + +OutputFileStream::OutputFileStream(const char *FN) + : StreamWrapper(new std::fstream(FN)) {} + +OutputFileStream::~OutputFileStream() { + delete reinterpret_cast(OS); +} } // namespace testutils } // namespace __llvm_libc diff --git a/libc/utils/testutils/Timer.h b/libc/utils/testutils/Timer.h new file mode 100644 --- /dev/null +++ b/libc/utils/testutils/Timer.h @@ -0,0 +1,33 @@ +//===-- Timer.h -------------------------------------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_UTILS_TESTUTILS_TIMER_H +#define LLVM_LIBC_UTILS_TESTUTILS_TIMER_H + +#include + +namespace __llvm_libc { +namespace testing { + +class Timer { + void *Impl; + +public: + Timer(); + ~Timer(); + + void start(); + void stop(); + + uint64_t nanoseconds() const; +}; + +} // namespace testing +} // namespace __llvm_libc + +#endif // LLVM_LIBC_UTILS_TESTUTILS_TIMER_H diff --git a/libc/utils/testutils/Timer.cpp b/libc/utils/testutils/Timer.cpp new file mode 100644 --- /dev/null +++ b/libc/utils/testutils/Timer.cpp @@ -0,0 +1,42 @@ +//===-- Timer.cpp --------------------------------------------------------===// +// +// 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 "Timer.h" + +#include +#include + +namespace __llvm_libc { +namespace testing { + +struct TimerImplementation { + std::chrono::high_resolution_clock::time_point Start; + std::chrono::high_resolution_clock::time_point End; +}; + +Timer::Timer() : Impl(new TimerImplementation) {} + +Timer::~Timer() { delete reinterpret_cast(Impl); } + +void Timer::start() { + auto T = reinterpret_cast(Impl); + T->Start = std::chrono::high_resolution_clock::now(); +} + +void Timer::stop() { + auto T = reinterpret_cast(Impl); + T->End = std::chrono::high_resolution_clock::now(); +} + +uint64_t Timer::nanoseconds() const { + auto T = reinterpret_cast(Impl); + return std::chrono::nanoseconds(T->End - T->Start).count(); +} + +} // namespace testing +} // namespace __llvm_libc