[libc-commits] [libc] [libc] Keep the compiler from folding the inputs of floating point tests (PR #236613)

Mike Lothian via libc-commits libc-commits at lists.llvm.org
Wed Oct 28 20:11:21 PDT 2026


https://github.com/FireBurn updated https://github.com/llvm/llvm-project/pull/236613

>From ed84991aecea9c5c4961f5c1f81d3f736e214f3e Mon Sep 17 00:00:00 2001
From: Mike Lothian <mike at fireburn.co.uk>
Date: Thu, 29 Oct 2026 00:48:18 +0000
Subject: [PATCH] [libc] Keep the compiler from folding the inputs of floating
 point tests

With LTO the function under test is inlined into the test, and a call on
a constant is then evaluated at compile time. That skips what the test
is there to check: the exception flag for a signaling NaN, the rounding
mode that gives an exact fma cancellation its sign, and the flush and
denormals-are-zero modes set through MXCSR. cosh, cbrt, log2 and fma
all failed this way when built with -flto=thin.

Pass those inputs through an empty asm that takes them by reference, so
they are run time values. sNaN, min_denormal and max_denormal in FPTest
become members rather than static constexpr, as the same names in
DECLARE_SPECIAL_CONSTANTS already are

Assisted-by: Claude
---
 libc/test/UnitTest/FPMatcher.h     | 33 ++++++++++++++++++++++++------
 libc/test/src/math/smoke/FmaTest.h | 28 +++++++++++++++----------
 2 files changed, 44 insertions(+), 17 deletions(-)

diff --git a/libc/test/UnitTest/FPMatcher.h b/libc/test/UnitTest/FPMatcher.h
index 314d8f4f1ef1e8..bb91ce2b8ea020 100644
--- a/libc/test/UnitTest/FPMatcher.h
+++ b/libc/test/UnitTest/FPMatcher.h
@@ -18,6 +18,7 @@
 #include "src/__support/macros/config.h"
 #include "src/__support/macros/optimization.h"
 #include "src/__support/macros/properties/architectures.h"
+#include "src/__support/macros/properties/compiler.h"
 #include "test/UnitTest/ErrnoCheckingTest.h"
 #include "test/UnitTest/RoundingModeUtils.h"
 #include "test/UnitTest/StringUtils.h"
@@ -175,6 +176,23 @@ CFPMatcher<T, C> getMatcherComplex(T expectedValue) {
   return CFPMatcher<T, C>(expectedValue);
 }
 
+// Returns its argument as a value the compiler cannot see through. A call on a
+// constant that is inlined, as it is under LTO, is otherwise folded at compile
+// time, which skips the exception flags, the rounding mode and the flush or
+// denormals-are-zero mode that tests of floating point functions are there to
+// check.
+template <typename T> LIBC_INLINE T opaque(T value) {
+#ifdef LIBC_COMPILER_IS_MSVC
+  // MSVC has no inline assembly on every target; a volatile object cannot be
+  // folded either.
+  volatile T copy = value;
+  return copy;
+#else
+  __asm__ volatile("" : "+m"(value));
+  return value;
+#endif
+}
+
 template <typename T> struct FPTest : public ErrnoCheckingTest {
   using FPBits = LIBC_NAMESPACE::fputil::FPBits<T>;
   using StorageType = typename FPBits::StorageType;
@@ -184,14 +202,14 @@ template <typename T> struct FPTest : public ErrnoCheckingTest {
   static constexpr T neg_zero = FPBits::zero(Sign::NEG).get_val();
   static constexpr T aNaN = FPBits::quiet_nan(Sign::POS).get_val();
   static constexpr T neg_aNaN = FPBits::quiet_nan(Sign::NEG).get_val();
-  static constexpr T sNaN = FPBits::signaling_nan().get_val();
+  const T sNaN = opaque(FPBits::signaling_nan().get_val());
   static constexpr T inf = FPBits::inf(Sign::POS).get_val();
   static constexpr T neg_inf = FPBits::inf(Sign::NEG).get_val();
   static constexpr T min_normal = FPBits::min_normal().get_val();
   static constexpr T max_normal = FPBits::max_normal(Sign::POS).get_val();
   static constexpr T neg_max_normal = FPBits::max_normal(Sign::NEG).get_val();
-  static constexpr T min_denormal = FPBits::min_subnormal().get_val();
-  static constexpr T max_denormal = FPBits::max_subnormal().get_val();
+  const T min_denormal = opaque(FPBits::min_subnormal().get_val());
+  const T max_denormal = opaque(FPBits::max_subnormal().get_val());
 
 #ifdef LIBC_MATH_HAS_ASSUME_ROUND_NEAREST_ONLY
   static constexpr int N_ROUNDING_MODES = 1;
@@ -254,16 +272,19 @@ struct ModifyMXCSR {
   const T neg_zero = FPBits::zero(Sign::NEG).get_val();                        \
   const T aNaN = FPBits::quiet_nan(Sign::POS).get_val();                       \
   const T neg_aNaN = FPBits::quiet_nan(Sign::NEG).get_val();                   \
-  const T sNaN = FPBits::signaling_nan(Sign::POS).get_val();                   \
+  const T sNaN = LIBC_NAMESPACE::testing::opaque(                              \
+      FPBits::signaling_nan(Sign::POS).get_val());                             \
   const T neg_sNaN = FPBits::signaling_nan(Sign::NEG).get_val();               \
   const T inf = FPBits::inf(Sign::POS).get_val();                              \
   const T neg_inf = FPBits::inf(Sign::NEG).get_val();                          \
   const T min_normal = FPBits::min_normal().get_val();                         \
   const T max_normal = FPBits::max_normal(Sign::POS).get_val();                \
   const T neg_max_normal = FPBits::max_normal(Sign::NEG).get_val();            \
-  const T min_denormal = FPBits::min_subnormal(Sign::POS).get_val();           \
+  const T min_denormal = LIBC_NAMESPACE::testing::opaque(                      \
+      FPBits::min_subnormal(Sign::POS).get_val());                             \
   const T neg_min_denormal = FPBits::min_subnormal(Sign::NEG).get_val();       \
-  const T max_denormal = FPBits::max_subnormal().get_val();                    \
+  const T max_denormal =                                                       \
+      LIBC_NAMESPACE::testing::opaque(FPBits::max_subnormal().get_val());      \
   static constexpr int UNKNOWN_MATH_ROUNDING_DIRECTION = 99;                   \
   static constexpr LIBC_NAMESPACE::cpp::array<int, 6>                          \
       MATH_ROUNDING_DIRECTIONS_INCLUDING_UNKNOWN = {                           \
diff --git a/libc/test/src/math/smoke/FmaTest.h b/libc/test/src/math/smoke/FmaTest.h
index dfd8b7ffb3fce8..f34cc9184f7875 100644
--- a/libc/test/src/math/smoke/FmaTest.h
+++ b/libc/test/src/math/smoke/FmaTest.h
@@ -90,24 +90,30 @@ class FmaTestTemplate : public LIBC_NAMESPACE::testing::FEnvSafeTest {
     EXPECT_FP_EQ_ALL_ROUNDING(func(InType(-0.25), in_z, in_z),
                               func(InType(1.75), in_z, -in_z));
 
-    // Exact cancellation.
+    // Exact cancellation. The rounding mode decides the sign of the zero, so
+    // the inputs must not be known to the compiler.
+    auto exact = [&](InType x, InType y, InType z) {
+      return func(LIBC_NAMESPACE::testing::opaque(x),
+                  LIBC_NAMESPACE::testing::opaque(y),
+                  LIBC_NAMESPACE::testing::opaque(z));
+    };
     EXPECT_FP_EQ_ROUNDING_NEAREST(
-        out.zero, func(InType(3.0), InType(5.0), InType(-15.0)));
-    EXPECT_FP_EQ_ROUNDING_UPWARD(out.zero,
-                                 func(InType(3.0), InType(5.0), InType(-15.0)));
+        out.zero, exact(InType(3.0), InType(5.0), InType(-15.0)));
+    EXPECT_FP_EQ_ROUNDING_UPWARD(
+        out.zero, exact(InType(3.0), InType(5.0), InType(-15.0)));
     EXPECT_FP_EQ_ROUNDING_TOWARD_ZERO(
-        out.zero, func(InType(3.0), InType(5.0), InType(-15.0)));
+        out.zero, exact(InType(3.0), InType(5.0), InType(-15.0)));
     EXPECT_FP_EQ_ROUNDING_DOWNWARD(
-        out.neg_zero, func(InType(3.0), InType(5.0), InType(-15.0)));
+        out.neg_zero, exact(InType(3.0), InType(5.0), InType(-15.0)));
 
     EXPECT_FP_EQ_ROUNDING_NEAREST(
-        out.zero, func(InType(-3.0), InType(5.0), InType(15.0)));
-    EXPECT_FP_EQ_ROUNDING_UPWARD(out.zero,
-                                 func(InType(-3.0), InType(5.0), InType(15.0)));
+        out.zero, exact(InType(-3.0), InType(5.0), InType(15.0)));
+    EXPECT_FP_EQ_ROUNDING_UPWARD(
+        out.zero, exact(InType(-3.0), InType(5.0), InType(15.0)));
     EXPECT_FP_EQ_ROUNDING_TOWARD_ZERO(
-        out.zero, func(InType(-3.0), InType(5.0), InType(15.0)));
+        out.zero, exact(InType(-3.0), InType(5.0), InType(15.0)));
     EXPECT_FP_EQ_ROUNDING_DOWNWARD(
-        out.neg_zero, func(InType(-3.0), InType(5.0), InType(15.0)));
+        out.neg_zero, exact(InType(-3.0), InType(5.0), InType(15.0)));
   }
 };
 



More information about the libc-commits mailing list