[Mlir-commits] [llvm] [mlir] [LLVM] [ADT] Move isLosslesslyConvertibleTo to APFloat (PR #218324)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Sun Aug 23 22:49:36 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-mlir

Author: Chuanqi Xu (ChuanqiXu9)

<details>
<summary>Changes</summary>

See the comments in https://github.com/llvm/llvm-project/pull/214658 for motivation.

AI assisted.

---
Full diff: https://github.com/llvm/llvm-project/pull/218324.diff


4 Files Affected:

- (modified) llvm/include/llvm/ADT/APFloat.h (+8) 
- (modified) llvm/lib/Support/APFloat.cpp (+64) 
- (modified) llvm/unittests/ADT/APFloatTest.cpp (+148) 
- (modified) mlir/lib/Dialect/Arith/IR/ArithOps.cpp (+5-69) 


``````````diff
diff --git a/llvm/include/llvm/ADT/APFloat.h b/llvm/include/llvm/ADT/APFloat.h
index a9a3dc89a1730..b51b6256ad2c6 100644
--- a/llvm/include/llvm/ADT/APFloat.h
+++ b/llvm/include/llvm/ADT/APFloat.h
@@ -337,6 +337,14 @@ class APFloatBase {
   LLVM_ABI static bool isRepresentableBy(const fltSemantics &A,
                                          const fltSemantics &B);
 
+  /// Returns whether converting a value from \p From to \p To is known to
+  /// preserve all information. If \p IgnoreNaNs is true, differences between
+  /// NaN representations are ignored, but NaNs must remain NaNs and infinities
+  /// must remain infinities.
+  LLVM_ABI static bool isLosslesslyConvertibleTo(const fltSemantics &From,
+                                                 const fltSemantics &To,
+                                                 bool IgnoreNaNs = false);
+
   /// @}
 
   /// IEEE-754R 5.11: Floating Point Comparison Relations.
diff --git a/llvm/lib/Support/APFloat.cpp b/llvm/lib/Support/APFloat.cpp
index 22528d9e93f73..59a6ba3867d40 100644
--- a/llvm/lib/Support/APFloat.cpp
+++ b/llvm/lib/Support/APFloat.cpp
@@ -233,6 +233,70 @@ bool APFloatBase::isRepresentableBy(const fltSemantics &A,
          A.precision <= B.precision;
 }
 
+bool APFloatBase::isLosslesslyConvertibleTo(const fltSemantics &From,
+                                            const fltSemantics &To,
+                                            bool IgnoreNaNs) {
+  if (&From == &To)
+    return true;
+
+  // PPC double-double cannot be described by a conventional exponent range
+  // and precision. In particular, converting it to another semantics drops
+  // its low double, so conservatively reject conversions involving it.
+  if (&From == &semPPCDoubleDouble || &To == &semPPCDoubleDouble)
+    return false;
+
+  if (!isRepresentableBy(From, To))
+    return false;
+
+  if ((From.hasZero && !To.hasZero) ||
+      (From.hasSignedRepr && !To.hasSignedRepr))
+    return false;
+
+  // NegativeZero NaN encoding repurposes the negative-zero bit pattern, so a
+  // conversion to such a format cannot preserve a source negative zero.
+  bool FromHasSignedZero = From.hasZero && From.hasSignedRepr &&
+                           From.nanEncoding != fltNanEncoding::NegativeZero;
+  bool ToHasSignedZero = To.hasZero && To.hasSignedRepr &&
+                         To.nanEncoding != fltNanEncoding::NegativeZero;
+  if (FromHasSignedZero && !ToHasSignedZero)
+    return false;
+
+  // isRepresentableBy compares normalized exponent ranges. Also ensure that
+  // the smallest source value, which may be denormal, is represented exactly
+  // by the destination semantics.
+  APFloat SmallestFrom = APFloat::getSmallest(From);
+  bool LosesInfo = false;
+  (void)SmallestFrom.convert(To, APFloat::rmNearestTiesToEven, &LosesInfo);
+  if (LosesInfo)
+    return false;
+
+  if (From.nonFiniteBehavior == fltNonfiniteBehavior::FiniteOnly)
+    return true;
+
+  // Even when NaN representations can be ignored, NaNs must remain NaNs and
+  // infinities must remain infinities. Otherwise the conversion can change
+  // whether an operation with nnan has poison-producing operands.
+  if (IgnoreNaNs) {
+    if (From.nonFiniteBehavior == fltNonfiniteBehavior::IEEE754)
+      return To.nonFiniteBehavior == fltNonfiniteBehavior::IEEE754;
+    return To.nonFiniteBehavior != fltNonfiniteBehavior::FiniteOnly;
+  }
+
+  if (From.nonFiniteBehavior == fltNonfiniteBehavior::IEEE754) {
+    // Converting an IEEE signaling NaN to another semantics quiets it, so the
+    // original value cannot be recovered by converting it back.
+    return false;
+  }
+
+  // NanOnly formats have no signaling NaNs. IEEE semantics can represent
+  // their quiet NaNs; conversions between NanOnly formats are conservatively
+  // accepted only when they use the same NaN encoding.
+  if (To.nonFiniteBehavior == fltNonfiniteBehavior::IEEE754)
+    return true;
+  return To.nonFiniteBehavior == fltNonfiniteBehavior::NanOnly &&
+         From.nanEncoding == To.nanEncoding;
+}
+
 /* A tight upper bound on number of parts required to hold the value
    pow(5, power) is
 
diff --git a/llvm/unittests/ADT/APFloatTest.cpp b/llvm/unittests/ADT/APFloatTest.cpp
index ca93953537a09..e161071e63af4 100644
--- a/llvm/unittests/ADT/APFloatTest.cpp
+++ b/llvm/unittests/ADT/APFloatTest.cpp
@@ -104,6 +104,154 @@ TEST(APFloatTest, isSignaling) {
   EXPECT_TRUE(APFloat::getSNaN(APFloat::IEEEsingle(), true, &payload).isSignaling());
 }
 
+TEST(APFloatTest, IsLosslesslyConvertibleToSelf) {
+  for (unsigned I = 0; I != APFloat::S_MaxSemantics + 1; ++I) {
+    const fltSemantics &Semantics =
+        APFloat::EnumToSemantics(static_cast<APFloat::Semantics>(I));
+    EXPECT_TRUE(
+        APFloatBase::isLosslesslyConvertibleTo(Semantics, Semantics, false))
+        << "Semantics = " << I;
+    EXPECT_TRUE(
+        APFloatBase::isLosslesslyConvertibleTo(Semantics, Semantics, true))
+        << "Semantics = " << I;
+  }
+}
+
+TEST(APFloatTest, IsLosslesslyConvertibleToKnownWiderSemantics) {
+  struct TestCase {
+    APFloat::Semantics From;
+    APFloat::Semantics To;
+    bool IgnoreNaNs;
+  };
+  const TestCase Cases[] = {
+      {APFloat::S_IEEEhalf, APFloat::S_IEEEsingle, true},
+      {APFloat::S_BFloat, APFloat::S_IEEEsingle, true},
+      {APFloat::S_IEEEsingle, APFloat::S_IEEEdouble, true},
+      {APFloat::S_IEEEdouble, APFloat::S_IEEEquad, true},
+      {APFloat::S_PPCDoubleDoubleLegacy, APFloat::S_IEEEquad, true},
+      {APFloat::S_Float8E5M2, APFloat::S_IEEEhalf, true},
+      {APFloat::S_Float8E5M2FNUZ, APFloat::S_IEEEsingle, false},
+      {APFloat::S_Float8E4M3, APFloat::S_IEEEhalf, true},
+      {APFloat::S_Float8E4M3FN, APFloat::S_IEEEhalf, false},
+      {APFloat::S_Float8E4M3FNUZ, APFloat::S_IEEEhalf, false},
+      {APFloat::S_Float8E4M3B11FNUZ, APFloat::S_IEEEhalf, false},
+      {APFloat::S_Float8E3M4, APFloat::S_IEEEhalf, true},
+      {APFloat::S_FloatTF32, APFloat::S_IEEEsingle, true},
+      {APFloat::S_Float8E8M0FNU, APFloat::S_x87DoubleExtended, false},
+      {APFloat::S_Float8E5M3FNU, APFloat::S_IEEEsingle, false},
+      {APFloat::S_Float6E3M2FN, APFloat::S_IEEEhalf, false},
+      {APFloat::S_Float6E2M3FN, APFloat::S_IEEEhalf, false},
+      {APFloat::S_Float4E2M1FN, APFloat::S_IEEEhalf, false},
+      {APFloat::S_x87DoubleExtended, APFloat::S_IEEEquad, true},
+  };
+
+  for (const TestCase &Case : Cases) {
+    const fltSemantics &From = APFloat::EnumToSemantics(Case.From);
+    const fltSemantics &To = APFloat::EnumToSemantics(Case.To);
+    EXPECT_TRUE(
+        APFloatBase::isLosslesslyConvertibleTo(From, To, Case.IgnoreNaNs))
+        << "From = " << Case.From << ", To = " << Case.To
+        << ", IgnoreNaNs = " << Case.IgnoreNaNs;
+  }
+}
+
+TEST(APFloatTest, IsLosslesslyConvertibleToRejectsInformationLoss) {
+  EXPECT_FALSE(APFloatBase::isLosslesslyConvertibleTo(APFloat::IEEEhalf(),
+                                                      APFloat::IEEEsingle()));
+  EXPECT_FALSE(APFloatBase::isLosslesslyConvertibleTo(
+      APFloat::IEEEsingle(), APFloat::IEEEhalf(), true));
+  EXPECT_FALSE(APFloatBase::isLosslesslyConvertibleTo(
+      APFloat::Float8E4M3FN(), APFloat::Float8E4M3FNUZ(), true));
+  EXPECT_FALSE(APFloatBase::isLosslesslyConvertibleTo(
+      APFloat::Float4E2M1FN(), APFloat::Float8E8M0FNU(), true));
+
+  // IgnoreNaNs does not permit a NaN or infinity to change categories. Model
+  // possible future wider formats for combinations not currently present in
+  // the supported semantics.
+  fltSemantics WiderNanOnly = APFloat::IEEEsingle();
+  WiderNanOnly.nonFiniteBehavior = fltNonfiniteBehavior::NanOnly;
+  WiderNanOnly.nanEncoding = fltNanEncoding::AllOnes;
+  EXPECT_FALSE(APFloatBase::isLosslesslyConvertibleTo(APFloat::IEEEhalf(),
+                                                      WiderNanOnly, true));
+
+  fltSemantics NarrowNanOnly = APFloat::IEEEhalf();
+  NarrowNanOnly.nonFiniteBehavior = fltNonfiniteBehavior::NanOnly;
+  NarrowNanOnly.nanEncoding = fltNanEncoding::AllOnes;
+  fltSemantics WiderFiniteOnly = APFloat::IEEEsingle();
+  WiderFiniteOnly.nonFiniteBehavior = fltNonfiniteBehavior::FiniteOnly;
+  EXPECT_FALSE(APFloatBase::isLosslesslyConvertibleTo(NarrowNanOnly,
+                                                      WiderFiniteOnly, true));
+
+  // PPC double-double's low component is not described by its fltSemantics
+  // exponent and precision fields, so only identity is known to be lossless.
+  EXPECT_FALSE(APFloatBase::isLosslesslyConvertibleTo(
+      APFloat::PPCDoubleDouble(), APFloat::IEEEquad(), true));
+  EXPECT_FALSE(APFloatBase::isLosslesslyConvertibleTo(
+      APFloat::IEEEquad(), APFloat::PPCDoubleDouble(), true));
+}
+
+TEST(APFloatTest, LosslessConversionsPreserveRepresentativeValues) {
+  for (unsigned FromIndex = 0; FromIndex != APFloat::S_MaxSemantics + 1;
+       ++FromIndex) {
+    const fltSemantics &From =
+        APFloat::EnumToSemantics(static_cast<APFloat::Semantics>(FromIndex));
+    for (unsigned ToIndex = 0; ToIndex != APFloat::S_MaxSemantics + 1;
+         ++ToIndex) {
+      const fltSemantics &To =
+          APFloat::EnumToSemantics(static_cast<APFloat::Semantics>(ToIndex));
+      for (bool IgnoreNaNs : {false, true}) {
+        if (!APFloatBase::isLosslesslyConvertibleTo(From, To, IgnoreNaNs))
+          continue;
+
+        SCOPED_TRACE("From = " + std::to_string(FromIndex) +
+                     ", To = " + std::to_string(ToIndex) +
+                     ", IgnoreNaNs = " + std::to_string(IgnoreNaNs));
+        auto CheckValue = [&](APFloat Value) {
+          APFloat Original = Value;
+          bool WasNaN = Value.isNaN();
+          bool LosesInfo = false;
+          (void)Value.convert(To, APFloat::rmNearestTiesToEven, &LosesInfo);
+
+          if (WasNaN && IgnoreNaNs) {
+            EXPECT_TRUE(Value.isNaN());
+            return;
+          }
+
+          EXPECT_FALSE(LosesInfo);
+          bool LosesInfoOnReturn = false;
+          (void)Value.convert(From, APFloat::rmNearestTiesToEven,
+                              &LosesInfoOnReturn);
+          EXPECT_TRUE(Value.bitwiseIsEqual(Original));
+        };
+
+        CheckValue(APFloat::getLargest(From));
+        CheckValue(APFloat::getSmallest(From));
+        CheckValue(APFloat::getSmallestNormalized(From));
+        if (From.hasSignedRepr) {
+          CheckValue(APFloat::getLargest(From, true));
+          CheckValue(APFloat::getSmallest(From, true));
+          CheckValue(APFloat::getSmallestNormalized(From, true));
+        }
+        if (From.hasZero) {
+          CheckValue(APFloat::getZero(From));
+          if (From.hasSignedRepr)
+            CheckValue(APFloat::getZero(From, true));
+        }
+        if (APFloat::semanticsHasInf(From)) {
+          CheckValue(APFloat::getInf(From));
+          if (From.hasSignedRepr)
+            CheckValue(APFloat::getInf(From, true));
+        }
+        if (APFloat::semanticsHasNaN(From)) {
+          CheckValue(APFloat::getQNaN(From));
+          if (From.nonFiniteBehavior == fltNonfiniteBehavior::IEEE754)
+            CheckValue(APFloat::getSNaN(From));
+        }
+      }
+    }
+  }
+}
+
 TEST(APFloatTest, next) {
 
   APFloat test(APFloat::IEEEquad(), APFloat::uninitialized);
diff --git a/mlir/lib/Dialect/Arith/IR/ArithOps.cpp b/mlir/lib/Dialect/Arith/IR/ArithOps.cpp
index 7157e29800db0..69b7e3c907022 100644
--- a/mlir/lib/Dialect/Arith/IR/ArithOps.cpp
+++ b/mlir/lib/Dialect/Arith/IR/ArithOps.cpp
@@ -1341,71 +1341,6 @@ void arith::SubFOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
 
 namespace {
 
-// A helper for the conversion:
-//
-//    trunc(extremum(ext(lhs), ext(rhs))) -> extremum(lhs, rhs)
-//
-// To make sure that type conversion won't lose information.
-//
-// For floating point conversion, we can't allow conversion between
-// -0 and +0. More importantly, we can't allow a conversion from
-// sNaN to qNaN. That changes semantics.
-// When `ignoreNaNs` is set, differences between NaN representations
-// may be ignored, but source non-finite values must not become
-// finite.
-static bool isLosslesslyConvertibleTo(const llvm::fltSemantics &from,
-                                      const llvm::fltSemantics &to,
-                                      bool ignoreNaNs = false) {
-  if (!llvm::APFloatBase::isRepresentableBy(from, to))
-    return false;
-
-  if ((from.hasZero && !to.hasZero) ||
-      (from.hasSignedRepr && !to.hasSignedRepr))
-    return false;
-
-  // NegativeZero NaN encoding repurposes the negative-zero bit pattern, so a
-  // conversion to such a format cannot preserve a source negative zero.
-  bool fromHasSignedZero =
-      from.hasZero && from.hasSignedRepr &&
-      from.nanEncoding != llvm::fltNanEncoding::NegativeZero;
-  bool toHasSignedZero = to.hasZero && to.hasSignedRepr &&
-                         to.nanEncoding != llvm::fltNanEncoding::NegativeZero;
-  if (fromHasSignedZero && !toHasSignedZero)
-    return false;
-
-  // isRepresentableBy compares the normalized exponent ranges. Also ensure
-  // that the smallest source value, which may be denormal, is represented
-  // exactly by the destination semantics.
-  llvm::APFloat smallestFrom = llvm::APFloat::getSmallest(from);
-  bool losesInfo = false;
-  (void)smallestFrom.convert(to, llvm::APFloat::rmNearestTiesToEven,
-                             &losesInfo);
-  if (losesInfo)
-    return false;
-
-  if (from.nonFiniteBehavior == llvm::fltNonfiniteBehavior::FiniteOnly)
-    return true;
-
-  // If we're ignoring NaNs, we can return early if the nonFiniteBehavior
-  // matches.
-  if (ignoreNaNs)
-    return to.nonFiniteBehavior != llvm::fltNonfiniteBehavior::FiniteOnly;
-
-  if (from.nonFiniteBehavior == llvm::fltNonfiniteBehavior::IEEE754) {
-    // Converting an IEEE signaling NaN to another semantics quiets it, so the
-    // original value cannot be recovered by converting it back.
-    return false;
-  }
-
-  // NanOnly formats have no signaling NaNs. IEEE semantics can represent
-  // their quiet NaNs; conversions between NanOnly formats are conservatively
-  // accepted only when they use the same NaN encoding.
-  if (to.nonFiniteBehavior == llvm::fltNonfiniteBehavior::IEEE754)
-    return true;
-  return to.nonFiniteBehavior == llvm::fltNonfiniteBehavior::NanOnly &&
-         from.nanEncoding == to.nanEncoding;
-}
-
 /// Narrow an extremum whose operands were extended from the result type:
 ///
 ///   trunc(extremum(ext(lhs), ext(rhs))) -> extremum(lhs, rhs)
@@ -1456,8 +1391,8 @@ struct NarrowExtremum final : OpRewritePattern<TruncOp> {
       if constexpr (std::is_same_v<TruncOp, TruncFOp>)
         ignoreNaNs =
             bitEnumContainsAll(extremumOp.getFastmath(), FastMathFlags::nnan);
-      if (!isLosslesslyConvertibleTo(narrowSemantics, wideSemantics,
-                                     ignoreNaNs))
+      if (!llvm::APFloatBase::isLosslesslyConvertibleTo(
+              narrowSemantics, wideSemantics, ignoreNaNs))
         return failure();
     }
 
@@ -2011,8 +1946,9 @@ OpFoldResult arith::TruncFOp::fold(FoldAdaptor adaptor) {
         cast<FloatType>(getElementTypeOrSelf(extOp.getType()));
     // Check whether every source value round-trips through the intermediate
     // type, including signaling NaNs and signed zero.
-    if (isLosslesslyConvertibleTo(srcType.getFloatSemantics(),
-                                  intermediateType.getFloatSemantics())) {
+    if (llvm::APFloatBase::isLosslesslyConvertibleTo(
+            srcType.getFloatSemantics(),
+            intermediateType.getFloatSemantics())) {
       // truncf(extf(a)) -> truncf(a)
       if (srcType.getWidth() > resElemType.getWidth()) {
         setOperand(src);

``````````

</details>


https://github.com/llvm/llvm-project/pull/218324


More information about the Mlir-commits mailing list