From 08d300fe5e6409b38e335a293e80273ca0e38ee4 Mon Sep 17 00:00:00 2001 From: Yingwei Zheng Date: Thu, 9 Oct 2025 23:32:54 +0800 Subject: [PATCH 1/4] [ConstantFPRange] Add support for cast operations --- llvm/include/llvm/IR/ConstantFPRange.h | 3 + llvm/lib/IR/ConstantFPRange.cpp | 16 +++++ llvm/unittests/IR/ConstantFPRangeTest.cpp | 78 +++++++++++++++++++++++ 3 files changed, 97 insertions(+) diff --git a/llvm/include/llvm/IR/ConstantFPRange.h b/llvm/include/llvm/IR/ConstantFPRange.h index face5da80ddc0..7a1348c0d582d 100644 --- a/llvm/include/llvm/IR/ConstantFPRange.h +++ b/llvm/include/llvm/IR/ConstantFPRange.h @@ -216,6 +216,9 @@ class [[nodiscard]] ConstantFPRange { /// Get the range without infinities. It is useful when we apply ninf flag to /// range of operands/results. LLVM_ABI ConstantFPRange getWithoutInf() const; + + /// Return a new range in the specified format. + LLVM_ABI ConstantFPRange cast(const fltSemantics &DstSem) const; }; inline raw_ostream &operator<<(raw_ostream &OS, const ConstantFPRange &CR) { diff --git a/llvm/lib/IR/ConstantFPRange.cpp b/llvm/lib/IR/ConstantFPRange.cpp index 2477e22aef085..8254b4114f6d6 100644 --- a/llvm/lib/IR/ConstantFPRange.cpp +++ b/llvm/lib/IR/ConstantFPRange.cpp @@ -425,3 +425,19 @@ ConstantFPRange ConstantFPRange::getWithoutInf() const { return ConstantFPRange(std::move(NewLower), std::move(NewUpper), MayBeQNaN, MayBeSNaN); } + +ConstantFPRange ConstantFPRange::cast(const fltSemantics &DstSem) const { + bool LosesInfo; + APFloat NewLower = Lower; + APFloat NewUpper = Upper; + // For conservative, return full range if conversion is invalid. + if (NewLower.convert(DstSem, APFloat::rmNearestTiesToEven, &LosesInfo) == + APFloat::opInvalidOp) + return getFull(DstSem); + if (NewUpper.convert(DstSem, APFloat::rmNearestTiesToEven, &LosesInfo) == + APFloat::opInvalidOp) + return getFull(DstSem); + return ConstantFPRange(std::move(NewLower), std::move(NewUpper), + /*MayBeQNaNVal=*/MayBeQNaN || MayBeSNaN, + /*MayBeSNaNVal=*/false); +} diff --git a/llvm/unittests/IR/ConstantFPRangeTest.cpp b/llvm/unittests/IR/ConstantFPRangeTest.cpp index 5bc516d0dc56c..e4fe6a598a9cb 100644 --- a/llvm/unittests/IR/ConstantFPRangeTest.cpp +++ b/llvm/unittests/IR/ConstantFPRangeTest.cpp @@ -818,4 +818,82 @@ TEST_F(ConstantFPRangeTest, getWithout) { APFloat::getLargest(Sem, /*Negative=*/true), APFloat(3.0))); } +TEST_F(ConstantFPRangeTest, cast) { + const fltSemantics &F16Sem = APFloat::IEEEhalf(); + const fltSemantics &BF16Sem = APFloat::BFloat(); + const fltSemantics &F32Sem = APFloat::IEEEsingle(); + // normal -> normal (exact) + EXPECT_EQ(ConstantFPRange::getNonNaN(APFloat(1.0), APFloat(2.0)).cast(F32Sem), + ConstantFPRange::getNonNaN(APFloat(1.0f), APFloat(2.0f))); + EXPECT_EQ( + ConstantFPRange::getNonNaN(APFloat(-2.0f), APFloat(-1.0f)).cast(Sem), + ConstantFPRange::getNonNaN(APFloat(-2.0), APFloat(-1.0))); + // normal -> normal (inexact) + EXPECT_EQ( + ConstantFPRange::getNonNaN(APFloat(3.141592653589793), + APFloat(6.283185307179586)) + .cast(F32Sem), + ConstantFPRange::getNonNaN(APFloat(3.14159274f), APFloat(6.28318548f))); + // normal -> subnormal + EXPECT_EQ(ConstantFPRange::getNonNaN(APFloat(-5e-8), APFloat(5e-8)) + .cast(F16Sem) + .classify(), + fcSubnormal | fcZero); + // normal -> zero + EXPECT_EQ(ConstantFPRange::getNonNaN( + APFloat::getSmallestNormalized(Sem, /*Negative=*/true), + APFloat::getSmallestNormalized(Sem, /*Negative=*/false)) + .cast(F32Sem) + .classify(), + fcZero); + // normal -> inf + EXPECT_EQ(ConstantFPRange::getNonNaN(APFloat(-65536.0), APFloat(65536.0)) + .cast(F16Sem), + ConstantFPRange::getNonNaN(F16Sem)); + // nan -> qnan + EXPECT_EQ( + ConstantFPRange::getNaNOnly(Sem, /*MayBeQNaN=*/true, /*MayBeSNaN=*/false) + .cast(F32Sem), + ConstantFPRange::getNaNOnly(F32Sem, /*MayBeQNaN=*/true, + /*MayBeSNaN=*/false)); + EXPECT_EQ( + ConstantFPRange::getNaNOnly(Sem, /*MayBeQNaN=*/false, /*MayBeSNaN=*/true) + .cast(F32Sem), + ConstantFPRange::getNaNOnly(F32Sem, /*MayBeQNaN=*/true, + /*MayBeSNaN=*/false)); + EXPECT_EQ( + ConstantFPRange::getNaNOnly(Sem, /*MayBeQNaN=*/true, /*MayBeSNaN=*/true) + .cast(F32Sem), + ConstantFPRange::getNaNOnly(F32Sem, /*MayBeQNaN=*/true, + /*MayBeSNaN=*/false)); + // For BF16 -> F32, signaling bit is still lost. + EXPECT_EQ(ConstantFPRange::getNaNOnly(BF16Sem, /*MayBeQNaN=*/true, + /*MayBeSNaN=*/true) + .cast(F32Sem), + ConstantFPRange::getNaNOnly(F32Sem, /*MayBeQNaN=*/true, + /*MayBeSNaN=*/false)); + + EnumerateValuesInConstantFPRange( + ConstantFPRange::getFull(APFloat::Float8E4M3()), + [&](const APFloat &V) { + bool LosesInfo = false; + + APFloat DoubleV = V; + DoubleV.convert(Sem, APFloat::rmNearestTiesToEven, &LosesInfo); + ConstantFPRange DoubleCR = ConstantFPRange(V).cast(Sem); + EXPECT_TRUE(DoubleCR.contains(DoubleV)) + << "Casting " << V << " to double failed. " << DoubleCR + << " doesn't contain " << DoubleV; + + auto &FP4Sem = APFloat::Float4E2M1FN(); + APFloat FP4V = V; + FP4V.convert(FP4Sem, APFloat::rmNearestTiesToEven, &LosesInfo); + ConstantFPRange FP4CR = ConstantFPRange(V).cast(FP4Sem); + EXPECT_TRUE(FP4CR.contains(FP4V)) + << "Casting " << V << " to FP4E2M1FN failed. " << FP4CR + << " doesn't contain " << FP4V; + }, + /*IgnoreNaNPayload=*/true); +} + } // anonymous namespace From cc0000bd8d0607726b7de02f65e369e39e7f5faf Mon Sep 17 00:00:00 2001 From: Yingwei Zheng Date: Thu, 9 Oct 2025 23:50:13 +0800 Subject: [PATCH 2/4] [ConstantFPRange] Add an edge case --- llvm/lib/IR/ConstantFPRange.cpp | 6 ++++-- llvm/unittests/IR/ConstantFPRangeTest.cpp | 7 +++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/llvm/lib/IR/ConstantFPRange.cpp b/llvm/lib/IR/ConstantFPRange.cpp index 8254b4114f6d6..cc064c3a06405 100644 --- a/llvm/lib/IR/ConstantFPRange.cpp +++ b/llvm/lib/IR/ConstantFPRange.cpp @@ -432,10 +432,12 @@ ConstantFPRange ConstantFPRange::cast(const fltSemantics &DstSem) const { APFloat NewUpper = Upper; // For conservative, return full range if conversion is invalid. if (NewLower.convert(DstSem, APFloat::rmNearestTiesToEven, &LosesInfo) == - APFloat::opInvalidOp) + APFloat::opInvalidOp || + NewLower.isNaN()) return getFull(DstSem); if (NewUpper.convert(DstSem, APFloat::rmNearestTiesToEven, &LosesInfo) == - APFloat::opInvalidOp) + APFloat::opInvalidOp || + NewUpper.isNaN()) return getFull(DstSem); return ConstantFPRange(std::move(NewLower), std::move(NewUpper), /*MayBeQNaNVal=*/MayBeQNaN || MayBeSNaN, diff --git a/llvm/unittests/IR/ConstantFPRangeTest.cpp b/llvm/unittests/IR/ConstantFPRangeTest.cpp index e4fe6a598a9cb..11fb7ae138628 100644 --- a/llvm/unittests/IR/ConstantFPRangeTest.cpp +++ b/llvm/unittests/IR/ConstantFPRangeTest.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "llvm/IR/ConstantFPRange.h" +#include "llvm/ADT/APFloat.h" #include "llvm/IR/Instructions.h" #include "llvm/IR/Operator.h" #include "gtest/gtest.h" @@ -822,6 +823,7 @@ TEST_F(ConstantFPRangeTest, cast) { const fltSemantics &F16Sem = APFloat::IEEEhalf(); const fltSemantics &BF16Sem = APFloat::BFloat(); const fltSemantics &F32Sem = APFloat::IEEEsingle(); + const fltSemantics &F8NanOnlySem = APFloat::Float8E4M3FN(); // normal -> normal (exact) EXPECT_EQ(ConstantFPRange::getNonNaN(APFloat(1.0), APFloat(2.0)).cast(F32Sem), ConstantFPRange::getNonNaN(APFloat(1.0f), APFloat(2.0f))); @@ -872,6 +874,11 @@ TEST_F(ConstantFPRangeTest, cast) { .cast(F32Sem), ConstantFPRange::getNaNOnly(F32Sem, /*MayBeQNaN=*/true, /*MayBeSNaN=*/false)); + // inf -> nan only (return full set for now) + EXPECT_EQ(ConstantFPRange::getNonNaN(APFloat::getInf(Sem, /*Negative=*/true), + APFloat::getInf(Sem, /*Negative=*/false)) + .cast(F8NanOnlySem), + ConstantFPRange::getFull(F8NanOnlySem)); EnumerateValuesInConstantFPRange( ConstantFPRange::getFull(APFloat::Float8E4M3()), From 89736bb22215a13add0ef8b0ab4c1515fdc9fda2 Mon Sep 17 00:00:00 2001 From: Yingwei Zheng Date: Sat, 11 Oct 2025 00:37:06 +0800 Subject: [PATCH 3/4] [ConstantFPRange] Add rounding mode parameter --- llvm/include/llvm/IR/ConstantFPRange.h | 23 +++++++++++- llvm/lib/IR/ConstantFPRange.cpp | 45 ++++++++++++++++++++--- llvm/unittests/IR/ConstantFPRangeTest.cpp | 22 +++++++++++ 3 files changed, 83 insertions(+), 7 deletions(-) diff --git a/llvm/include/llvm/IR/ConstantFPRange.h b/llvm/include/llvm/IR/ConstantFPRange.h index 7a1348c0d582d..8053aaeccbddc 100644 --- a/llvm/include/llvm/IR/ConstantFPRange.h +++ b/llvm/include/llvm/IR/ConstantFPRange.h @@ -217,8 +217,27 @@ class [[nodiscard]] ConstantFPRange { /// range of operands/results. LLVM_ABI ConstantFPRange getWithoutInf() const; - /// Return a new range in the specified format. - LLVM_ABI ConstantFPRange cast(const fltSemantics &DstSem) const; + /// Calculate absolute value range. + LLVM_ABI ConstantFPRange abs() const; + + /// Calculate range of negated values. + LLVM_ABI ConstantFPRange negate() const; + + /// Get the range without NaNs. It is useful when we apply nnan flag to range + /// of operands/results. + ConstantFPRange getWithoutNaN() const { + return ConstantFPRange(Lower, Upper, false, false); + } + + /// Get the range without infinities. It is useful when we apply ninf flag to + /// range of operands/results. + LLVM_ABI ConstantFPRange getWithoutInf() const; + + /// Return a new range in the specified format with the specified rounding + /// mode. + LLVM_ABI ConstantFPRange + cast(const fltSemantics &DstSem, + APFloat::roundingMode RM = APFloat::rmNearestTiesToEven) const; }; inline raw_ostream &operator<<(raw_ostream &OS, const ConstantFPRange &CR) { diff --git a/llvm/lib/IR/ConstantFPRange.cpp b/llvm/lib/IR/ConstantFPRange.cpp index cc064c3a06405..45df7fc298b5b 100644 --- a/llvm/lib/IR/ConstantFPRange.cpp +++ b/llvm/lib/IR/ConstantFPRange.cpp @@ -326,6 +326,8 @@ std::optional ConstantFPRange::getSignBit() const { } bool ConstantFPRange::operator==(const ConstantFPRange &CR) const { + assert(&getSemantics() == &CR.getSemantics() && + "Should only use the same semantics"); if (MayBeSNaN != CR.MayBeSNaN || MayBeQNaN != CR.MayBeQNaN) return false; return Lower.bitwiseIsEqual(CR.Lower) && Upper.bitwiseIsEqual(CR.Upper); @@ -426,17 +428,50 @@ ConstantFPRange ConstantFPRange::getWithoutInf() const { MayBeSNaN); } -ConstantFPRange ConstantFPRange::cast(const fltSemantics &DstSem) const { +ConstantFPRange ConstantFPRange::abs() const { + if (isNaNOnly()) + return *this; + // Check if the range is all non-negative or all non-positive. + if (Lower.isNegative() == Upper.isNegative()) { + if (Lower.isNegative()) + return negate(); + return *this; + } + // The range contains both positive and negative values. + APFloat NewLower = APFloat::getZero(getSemantics()); + APFloat NewUpper = maxnum(-Lower, Upper); + return ConstantFPRange(std::move(NewLower), std::move(NewUpper), MayBeQNaN, + MayBeSNaN); +} + +ConstantFPRange ConstantFPRange::negate() const { + return ConstantFPRange(-Upper, -Lower, MayBeQNaN, MayBeSNaN); +} + +ConstantFPRange ConstantFPRange::getWithoutInf() const { + if (isNaNOnly()) + return *this; + APFloat NewLower = Lower; + APFloat NewUpper = Upper; + if (Lower.isNegInfinity()) + NewLower = APFloat::getLargest(getSemantics(), /*Negative=*/true); + if (Upper.isPosInfinity()) + NewUpper = APFloat::getLargest(getSemantics(), /*Negative=*/false); + canonicalizeRange(NewLower, NewUpper); + return ConstantFPRange(std::move(NewLower), std::move(NewUpper), MayBeQNaN, + MayBeSNaN); +} + +ConstantFPRange ConstantFPRange::cast(const fltSemantics &DstSem, + APFloat::roundingMode RM) const { bool LosesInfo; APFloat NewLower = Lower; APFloat NewUpper = Upper; // For conservative, return full range if conversion is invalid. - if (NewLower.convert(DstSem, APFloat::rmNearestTiesToEven, &LosesInfo) == - APFloat::opInvalidOp || + if (NewLower.convert(DstSem, RM, &LosesInfo) == APFloat::opInvalidOp || NewLower.isNaN()) return getFull(DstSem); - if (NewUpper.convert(DstSem, APFloat::rmNearestTiesToEven, &LosesInfo) == - APFloat::opInvalidOp || + if (NewUpper.convert(DstSem, RM, &LosesInfo) == APFloat::opInvalidOp || NewUpper.isNaN()) return getFull(DstSem); return ConstantFPRange(std::move(NewLower), std::move(NewUpper), diff --git a/llvm/unittests/IR/ConstantFPRangeTest.cpp b/llvm/unittests/IR/ConstantFPRangeTest.cpp index 11fb7ae138628..58a65b9a96ab8 100644 --- a/llvm/unittests/IR/ConstantFPRangeTest.cpp +++ b/llvm/unittests/IR/ConstantFPRangeTest.cpp @@ -879,6 +879,28 @@ TEST_F(ConstantFPRangeTest, cast) { APFloat::getInf(Sem, /*Negative=*/false)) .cast(F8NanOnlySem), ConstantFPRange::getFull(F8NanOnlySem)); + // other rounding modes + EXPECT_EQ( + ConstantFPRange::getNonNaN(APFloat::getSmallest(Sem, /*Negative=*/true), + APFloat::getSmallest(Sem, /*Negative=*/false)) + .cast(F32Sem, APFloat::rmTowardNegative), + ConstantFPRange::getNonNaN( + APFloat::getSmallest(F32Sem, /*Negative=*/true), + APFloat::getZero(F32Sem, /*Negative=*/false))); + EXPECT_EQ( + ConstantFPRange::getNonNaN(APFloat::getSmallest(Sem, /*Negative=*/true), + APFloat::getSmallest(Sem, /*Negative=*/false)) + .cast(F32Sem, APFloat::rmTowardPositive), + ConstantFPRange::getNonNaN( + APFloat::getZero(F32Sem, /*Negative=*/true), + APFloat::getSmallest(F32Sem, /*Negative=*/false))); + EXPECT_EQ( + ConstantFPRange::getNonNaN( + APFloat::getSmallestNormalized(Sem, /*Negative=*/true), + APFloat::getSmallestNormalized(Sem, /*Negative=*/false)) + .cast(F32Sem, APFloat::rmTowardZero), + ConstantFPRange::getNonNaN(APFloat::getZero(F32Sem, /*Negative=*/true), + APFloat::getZero(F32Sem, /*Negative=*/false))); EnumerateValuesInConstantFPRange( ConstantFPRange::getFull(APFloat::Float8E4M3()), From c797835b4c66359a7ec82311f537ee3d5f3f7b2f Mon Sep 17 00:00:00 2001 From: Yingwei Zheng Date: Sat, 11 Oct 2025 10:15:46 +0800 Subject: [PATCH 4/4] [ConstantFPRange] Fix conflits after rebasing --- llvm/include/llvm/IR/ConstantFPRange.h | 16 ------------ llvm/lib/IR/ConstantFPRange.cpp | 34 -------------------------- 2 files changed, 50 deletions(-) diff --git a/llvm/include/llvm/IR/ConstantFPRange.h b/llvm/include/llvm/IR/ConstantFPRange.h index 8053aaeccbddc..d47f6c02c883d 100644 --- a/llvm/include/llvm/IR/ConstantFPRange.h +++ b/llvm/include/llvm/IR/ConstantFPRange.h @@ -217,22 +217,6 @@ class [[nodiscard]] ConstantFPRange { /// range of operands/results. LLVM_ABI ConstantFPRange getWithoutInf() const; - /// Calculate absolute value range. - LLVM_ABI ConstantFPRange abs() const; - - /// Calculate range of negated values. - LLVM_ABI ConstantFPRange negate() const; - - /// Get the range without NaNs. It is useful when we apply nnan flag to range - /// of operands/results. - ConstantFPRange getWithoutNaN() const { - return ConstantFPRange(Lower, Upper, false, false); - } - - /// Get the range without infinities. It is useful when we apply ninf flag to - /// range of operands/results. - LLVM_ABI ConstantFPRange getWithoutInf() const; - /// Return a new range in the specified format with the specified rounding /// mode. LLVM_ABI ConstantFPRange diff --git a/llvm/lib/IR/ConstantFPRange.cpp b/llvm/lib/IR/ConstantFPRange.cpp index 45df7fc298b5b..070e833f4d1c0 100644 --- a/llvm/lib/IR/ConstantFPRange.cpp +++ b/llvm/lib/IR/ConstantFPRange.cpp @@ -428,40 +428,6 @@ ConstantFPRange ConstantFPRange::getWithoutInf() const { MayBeSNaN); } -ConstantFPRange ConstantFPRange::abs() const { - if (isNaNOnly()) - return *this; - // Check if the range is all non-negative or all non-positive. - if (Lower.isNegative() == Upper.isNegative()) { - if (Lower.isNegative()) - return negate(); - return *this; - } - // The range contains both positive and negative values. - APFloat NewLower = APFloat::getZero(getSemantics()); - APFloat NewUpper = maxnum(-Lower, Upper); - return ConstantFPRange(std::move(NewLower), std::move(NewUpper), MayBeQNaN, - MayBeSNaN); -} - -ConstantFPRange ConstantFPRange::negate() const { - return ConstantFPRange(-Upper, -Lower, MayBeQNaN, MayBeSNaN); -} - -ConstantFPRange ConstantFPRange::getWithoutInf() const { - if (isNaNOnly()) - return *this; - APFloat NewLower = Lower; - APFloat NewUpper = Upper; - if (Lower.isNegInfinity()) - NewLower = APFloat::getLargest(getSemantics(), /*Negative=*/true); - if (Upper.isPosInfinity()) - NewUpper = APFloat::getLargest(getSemantics(), /*Negative=*/false); - canonicalizeRange(NewLower, NewUpper); - return ConstantFPRange(std::move(NewLower), std::move(NewUpper), MayBeQNaN, - MayBeSNaN); -} - ConstantFPRange ConstantFPRange::cast(const fltSemantics &DstSem, APFloat::roundingMode RM) const { bool LosesInfo;