[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:35 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-adt
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