1 //===-- MPFRUtils.h ---------------------------------------------*- C++ -*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #ifndef LLVM_LIBC_UTILS_TESTUTILS_MPFRUTILS_H 10 #define LLVM_LIBC_UTILS_TESTUTILS_MPFRUTILS_H 11 12 #include "src/__support/CPP/TypeTraits.h" 13 #include "utils/UnitTest/Test.h" 14 15 #include <stdint.h> 16 17 namespace __llvm_libc { 18 namespace testing { 19 namespace mpfr { 20 21 enum class Operation : int { 22 // Operations with take a single floating point number as input 23 // and produce a single floating point number as output. The input 24 // and output floating point numbers are of the same kind. 25 BeginUnaryOperationsSingleOutput, 26 Abs, 27 Ceil, 28 Cos, 29 Exp, 30 Exp2, 31 Expm1, 32 Floor, 33 Log, 34 Log2, 35 Log10, 36 Log1p, 37 Mod2PI, 38 ModPIOver2, 39 ModPIOver4, 40 Round, 41 Sin, 42 Sqrt, 43 Tan, 44 Trunc, 45 EndUnaryOperationsSingleOutput, 46 47 // Operations which take a single floating point nubmer as input 48 // but produce two outputs. The first ouput is a floating point 49 // number of the same type as the input. The second output is of type 50 // 'int'. 51 BeginUnaryOperationsTwoOutputs, 52 Frexp, // Floating point output, the first output, is the fractional part. 53 EndUnaryOperationsTwoOutputs, 54 55 // Operations wich take two floating point nubmers of the same type as 56 // input and produce a single floating point number of the same type as 57 // output. 58 BeginBinaryOperationsSingleOutput, 59 Hypot, 60 EndBinaryOperationsSingleOutput, 61 62 // Operations which take two floating point numbers of the same type as 63 // input and produce two outputs. The first output is a floating nubmer of 64 // the same type as the inputs. The second output is af type 'int'. 65 BeginBinaryOperationsTwoOutputs, 66 RemQuo, // The first output, the floating point output, is the remainder. 67 EndBinaryOperationsTwoOutputs, 68 69 // Operations which take three floating point nubmers of the same type as 70 // input and produce a single floating point number of the same type as 71 // output. 72 BeginTernaryOperationsSingleOuput, 73 Fma, 74 EndTernaryOperationsSingleOutput, 75 }; 76 77 enum class RoundingMode : uint8_t { Upward, Downward, TowardZero, Nearest }; 78 79 int get_fe_rounding(RoundingMode mode); 80 81 struct ForceRoundingMode { 82 ForceRoundingMode(RoundingMode); 83 ~ForceRoundingMode(); 84 85 int old_rounding_mode; 86 int rounding_mode; 87 }; 88 89 template <typename T> struct BinaryInput { 90 static_assert( 91 __llvm_libc::cpp::IsFloatingPointType<T>::Value, 92 "Template parameter of BinaryInput must be a floating point type."); 93 94 using Type = T; 95 T x, y; 96 }; 97 98 template <typename T> struct TernaryInput { 99 static_assert( 100 __llvm_libc::cpp::IsFloatingPointType<T>::Value, 101 "Template parameter of TernaryInput must be a floating point type."); 102 103 using Type = T; 104 T x, y, z; 105 }; 106 107 template <typename T> struct BinaryOutput { 108 T f; 109 int i; 110 }; 111 112 namespace internal { 113 114 template <typename T1, typename T2> 115 struct AreMatchingBinaryInputAndBinaryOutput { 116 static constexpr bool VALUE = false; 117 }; 118 119 template <typename T> 120 struct AreMatchingBinaryInputAndBinaryOutput<BinaryInput<T>, BinaryOutput<T>> { 121 static constexpr bool VALUE = cpp::IsFloatingPointType<T>::Value; 122 }; 123 124 template <typename T> 125 bool compare_unary_operation_single_output(Operation op, T input, T libc_output, 126 double ulp_tolerance, 127 RoundingMode rounding); 128 template <typename T> 129 bool compare_unary_operation_two_outputs(Operation op, T input, 130 const BinaryOutput<T> &libc_output, 131 double ulp_tolerance, 132 RoundingMode rounding); 133 template <typename T> 134 bool compare_binary_operation_two_outputs(Operation op, 135 const BinaryInput<T> &input, 136 const BinaryOutput<T> &libc_output, 137 double ulp_tolerance, 138 RoundingMode rounding); 139 140 template <typename T> 141 bool compare_binary_operation_one_output(Operation op, 142 const BinaryInput<T> &input, 143 T libc_output, double ulp_tolerance, 144 RoundingMode rounding); 145 146 template <typename T> 147 bool compare_ternary_operation_one_output(Operation op, 148 const TernaryInput<T> &input, 149 T libc_output, double ulp_tolerance, 150 RoundingMode rounding); 151 152 template <typename T> 153 void explain_unary_operation_single_output_error(Operation op, T input, 154 T match_value, 155 double ulp_tolerance, 156 RoundingMode rounding, 157 testutils::StreamWrapper &OS); 158 template <typename T> 159 void explain_unary_operation_two_outputs_error( 160 Operation op, T input, const BinaryOutput<T> &match_value, 161 double ulp_tolerance, RoundingMode rounding, testutils::StreamWrapper &OS); 162 template <typename T> 163 void explain_binary_operation_two_outputs_error( 164 Operation op, const BinaryInput<T> &input, 165 const BinaryOutput<T> &match_value, double ulp_tolerance, 166 RoundingMode rounding, testutils::StreamWrapper &OS); 167 168 template <typename T> 169 void explain_binary_operation_one_output_error( 170 Operation op, const BinaryInput<T> &input, T match_value, 171 double ulp_tolerance, RoundingMode rounding, testutils::StreamWrapper &OS); 172 173 template <typename T> 174 void explain_ternary_operation_one_output_error( 175 Operation op, const TernaryInput<T> &input, T match_value, 176 double ulp_tolerance, RoundingMode rounding, testutils::StreamWrapper &OS); 177 178 template <Operation op, typename InputType, typename OutputType> 179 class MPFRMatcher : public testing::Matcher<OutputType> { 180 InputType input; 181 OutputType match_value; 182 double ulp_tolerance; 183 RoundingMode rounding; 184 185 public: 186 MPFRMatcher(InputType testInput, double ulp_tolerance, RoundingMode rounding) 187 : input(testInput), ulp_tolerance(ulp_tolerance), rounding(rounding) {} 188 189 bool match(OutputType libcResult) { 190 match_value = libcResult; 191 return match(input, match_value); 192 } 193 194 // This method is marked with NOLINT because it the name `explainError` 195 // does not confirm to the coding style. 196 void explainError(testutils::StreamWrapper &OS) override { // NOLINT 197 explain_error(input, match_value, OS); 198 } 199 200 private: 201 template <typename T> bool match(T in, T out) { 202 return compare_unary_operation_single_output(op, in, out, ulp_tolerance, 203 rounding); 204 } 205 206 template <typename T> bool match(T in, const BinaryOutput<T> &out) { 207 return compare_unary_operation_two_outputs(op, in, out, ulp_tolerance, 208 rounding); 209 } 210 211 template <typename T> bool match(const BinaryInput<T> &in, T out) { 212 return compare_binary_operation_one_output(op, in, out, ulp_tolerance, 213 rounding); 214 } 215 216 template <typename T> 217 bool match(BinaryInput<T> in, const BinaryOutput<T> &out) { 218 return compare_binary_operation_two_outputs(op, in, out, ulp_tolerance, 219 rounding); 220 } 221 222 template <typename T> bool match(const TernaryInput<T> &in, T out) { 223 return compare_ternary_operation_one_output(op, in, out, ulp_tolerance, 224 rounding); 225 } 226 227 template <typename T> 228 void explain_error(T in, T out, testutils::StreamWrapper &OS) { 229 explain_unary_operation_single_output_error(op, in, out, ulp_tolerance, 230 rounding, OS); 231 } 232 233 template <typename T> 234 void explain_error(T in, const BinaryOutput<T> &out, 235 testutils::StreamWrapper &OS) { 236 explain_unary_operation_two_outputs_error(op, in, out, ulp_tolerance, 237 rounding, OS); 238 } 239 240 template <typename T> 241 void explain_error(const BinaryInput<T> &in, const BinaryOutput<T> &out, 242 testutils::StreamWrapper &OS) { 243 explain_binary_operation_two_outputs_error(op, in, out, ulp_tolerance, 244 rounding, OS); 245 } 246 247 template <typename T> 248 void explain_error(const BinaryInput<T> &in, T out, 249 testutils::StreamWrapper &OS) { 250 explain_binary_operation_one_output_error(op, in, out, ulp_tolerance, 251 rounding, OS); 252 } 253 254 template <typename T> 255 void explain_error(const TernaryInput<T> &in, T out, 256 testutils::StreamWrapper &OS) { 257 explain_ternary_operation_one_output_error(op, in, out, ulp_tolerance, 258 rounding, OS); 259 } 260 }; 261 262 } // namespace internal 263 264 // Return true if the input and ouput types for the operation op are valid 265 // types. 266 template <Operation op, typename InputType, typename OutputType> 267 constexpr bool is_valid_operation() { 268 return (Operation::BeginUnaryOperationsSingleOutput < op && 269 op < Operation::EndUnaryOperationsSingleOutput && 270 cpp::IsSame<InputType, OutputType>::Value && 271 cpp::IsFloatingPointType<InputType>::Value) || 272 (Operation::BeginUnaryOperationsTwoOutputs < op && 273 op < Operation::EndUnaryOperationsTwoOutputs && 274 cpp::IsFloatingPointType<InputType>::Value && 275 cpp::IsSame<OutputType, BinaryOutput<InputType>>::Value) || 276 (Operation::BeginBinaryOperationsSingleOutput < op && 277 op < Operation::EndBinaryOperationsSingleOutput && 278 cpp::IsFloatingPointType<OutputType>::Value && 279 cpp::IsSame<InputType, BinaryInput<OutputType>>::Value) || 280 (Operation::BeginBinaryOperationsTwoOutputs < op && 281 op < Operation::EndBinaryOperationsTwoOutputs && 282 internal::AreMatchingBinaryInputAndBinaryOutput<InputType, 283 OutputType>::VALUE) || 284 (Operation::BeginTernaryOperationsSingleOuput < op && 285 op < Operation::EndTernaryOperationsSingleOutput && 286 cpp::IsFloatingPointType<OutputType>::Value && 287 cpp::IsSame<InputType, TernaryInput<OutputType>>::Value); 288 } 289 290 template <Operation op, typename InputType, typename OutputType> 291 __attribute__((no_sanitize("address"))) 292 cpp::EnableIfType<is_valid_operation<op, InputType, OutputType>(), 293 internal::MPFRMatcher<op, InputType, OutputType>> 294 get_mpfr_matcher(InputType input, OutputType output_unused, 295 double ulp_tolerance, RoundingMode rounding) { 296 return internal::MPFRMatcher<op, InputType, OutputType>(input, ulp_tolerance, 297 rounding); 298 } 299 300 template <typename T> T round(T x, RoundingMode mode); 301 302 template <typename T> bool round_to_long(T x, long &result); 303 template <typename T> bool round_to_long(T x, RoundingMode mode, long &result); 304 305 } // namespace mpfr 306 } // namespace testing 307 } // namespace __llvm_libc 308 309 // GET_MPFR_DUMMY_ARG is going to be added to the end of GET_MPFR_MACRO as a 310 // simple way to avoid the compiler warning `gnu-zero-variadic-macro-arguments`. 311 #define GET_MPFR_DUMMY_ARG(...) 0 312 313 #define GET_MPFR_MACRO(__1, __2, __3, __4, __5, __NAME, ...) __NAME 314 315 #define EXPECT_MPFR_MATCH_DEFAULT(op, input, match_value, ulp_tolerance) \ 316 EXPECT_THAT(match_value, \ 317 __llvm_libc::testing::mpfr::get_mpfr_matcher<op>( \ 318 input, match_value, ulp_tolerance, \ 319 __llvm_libc::testing::mpfr::RoundingMode::Nearest)) 320 321 #define EXPECT_MPFR_MATCH_ROUNDING(op, input, match_value, ulp_tolerance, \ 322 rounding) \ 323 EXPECT_THAT(match_value, __llvm_libc::testing::mpfr::get_mpfr_matcher<op>( \ 324 input, match_value, ulp_tolerance, rounding)) 325 326 #define EXPECT_MPFR_MATCH(...) \ 327 GET_MPFR_MACRO(__VA_ARGS__, EXPECT_MPFR_MATCH_ROUNDING, \ 328 EXPECT_MPFR_MATCH_DEFAULT, GET_MPFR_DUMMY_ARG) \ 329 (__VA_ARGS__) 330 331 #define EXPECT_MPFR_MATCH_ALL_ROUNDING(op, input, match_value, ulp_tolerance) \ 332 { \ 333 namespace mpfr = __llvm_libc::testing::mpfr; \ 334 mpfr::ForceRoundingMode __r1(mpfr::RoundingMode::Nearest); \ 335 EXPECT_MPFR_MATCH(op, input, match_value, ulp_tolerance, \ 336 mpfr::RoundingMode::Nearest); \ 337 mpfr::ForceRoundingMode __r2(mpfr::RoundingMode::Upward); \ 338 EXPECT_MPFR_MATCH(op, input, match_value, ulp_tolerance, \ 339 mpfr::RoundingMode::Upward); \ 340 mpfr::ForceRoundingMode __r3(mpfr::RoundingMode::Downward); \ 341 EXPECT_MPFR_MATCH(op, input, match_value, ulp_tolerance, \ 342 mpfr::RoundingMode::Downward); \ 343 mpfr::ForceRoundingMode __r4(mpfr::RoundingMode::TowardZero); \ 344 EXPECT_MPFR_MATCH(op, input, match_value, ulp_tolerance, \ 345 mpfr::RoundingMode::TowardZero); \ 346 } 347 348 #define ASSERT_MPFR_MATCH_DEFAULT(op, input, match_value, ulp_tolerance) \ 349 ASSERT_THAT(match_value, \ 350 __llvm_libc::testing::mpfr::get_mpfr_matcher<op>( \ 351 input, match_value, ulp_tolerance, \ 352 __llvm_libc::testing::mpfr::RoundingMode::Nearest)) 353 354 #define ASSERT_MPFR_MATCH_ROUNDING(op, input, match_value, ulp_tolerance, \ 355 rounding) \ 356 ASSERT_THAT(match_value, __llvm_libc::testing::mpfr::get_mpfr_matcher<op>( \ 357 input, match_value, ulp_tolerance, rounding)) 358 359 #define ASSERT_MPFR_MATCH(...) \ 360 GET_MPFR_MACRO(__VA_ARGS__, ASSERT_MPFR_MATCH_ROUNDING, \ 361 ASSERT_MPFR_MATCH_DEFAULT, GET_MPFR_DUMMY_ARG) \ 362 (__VA_ARGS__) 363 364 #define ASSERT_MPFR_MATCH_ALL_ROUNDING(op, input, match_value, ulp_tolerance) \ 365 { \ 366 namespace mpfr = __llvm_libc::testing::mpfr; \ 367 mpfr::ForceRoundingMode __r1(mpfr::RoundingMode::Nearest); \ 368 ASSERT_MPFR_MATCH(op, input, match_value, ulp_tolerance, \ 369 mpfr::RoundingMode::Nearest); \ 370 mpfr::ForceRoundingMode __r2(mpfr::RoundingMode::Upward); \ 371 ASSERT_MPFR_MATCH(op, input, match_value, ulp_tolerance, \ 372 mpfr::RoundingMode::Upward); \ 373 mpfr::ForceRoundingMode __r3(mpfr::RoundingMode::Downward); \ 374 ASSERT_MPFR_MATCH(op, input, match_value, ulp_tolerance, \ 375 mpfr::RoundingMode::Downward); \ 376 mpfr::ForceRoundingMode __r4(mpfr::RoundingMode::TowardZero); \ 377 ASSERT_MPFR_MATCH(op, input, match_value, ulp_tolerance, \ 378 mpfr::RoundingMode::TowardZero); \ 379 } 380 381 #endif // LLVM_LIBC_UTILS_TESTUTILS_MPFRUTILS_H 382