[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