Skip to content

Conversation

dtcxzyw
Copy link
Member

@dtcxzyw dtcxzyw commented Oct 9, 2025

This patch adds support for fpext/fptrunc operations.

I noticed that finite-only semantics are not supported by the current representation of constant FP ranges. It should be okay for now, as we don't expose these types in the IR.

@llvmbot
Copy link
Member

llvmbot commented Oct 9, 2025

@llvm/pr-subscribers-llvm-ir

Author: Yingwei Zheng (dtcxzyw)

Changes

This patch adds support for fpext/fptrunc operations. It assumes the round-to-nearest rounding mode.

I noticed that finite-only semantics are not supported by the current representation of constant FP ranges. It should be okay for now, as we don't expose these types in the IR.


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

3 Files Affected:

  • (modified) llvm/include/llvm/IR/ConstantFPRange.h (+3)
  • (modified) llvm/lib/IR/ConstantFPRange.cpp (+18)
  • (modified) llvm/unittests/IR/ConstantFPRangeTest.cpp (+85)
diff --git a/llvm/include/llvm/IR/ConstantFPRange.h b/llvm/include/llvm/IR/ConstantFPRange.h
index 930c6f98c033f..21ef7a1bfdc4c 100644
--- a/llvm/include/llvm/IR/ConstantFPRange.h
+++ b/llvm/include/llvm/IR/ConstantFPRange.h
@@ -200,6 +200,9 @@ class [[nodiscard]] ConstantFPRange {
   /// with another range.  The resultant range is guaranteed to include the
   /// elements of both sets, but may contain more.
   LLVM_ABI ConstantFPRange unionWith(const ConstantFPRange &CR) 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 7509188128524..9a81c07c4a616 100644
--- a/llvm/lib/IR/ConstantFPRange.cpp
+++ b/llvm/lib/IR/ConstantFPRange.cpp
@@ -391,3 +391,21 @@ ConstantFPRange ConstantFPRange::unionWith(const ConstantFPRange &CR) const {
   return ConstantFPRange(minnum(Lower, CR.Lower), maxnum(Upper, CR.Upper),
                          MayBeQNaN | CR.MayBeQNaN, MayBeSNaN | CR.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 ||
+      NewLower.isNaN())
+    return getFull(DstSem);
+  if (NewUpper.convert(DstSem, APFloat::rmNearestTiesToEven, &LosesInfo) ==
+          APFloat::opInvalidOp ||
+      NewUpper.isNaN())
+    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 255f62d77b748..54b7019f5ef49 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"
@@ -767,4 +768,88 @@ TEST_F(ConstantFPRangeTest, makeExactFCmpRegion) {
   }
 }
 
+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)));
+  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));
+  // 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()),
+      [&](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

@dtcxzyw dtcxzyw added the floating-point Floating-point math label Oct 10, 2025
@@ -391,3 +391,21 @@ ConstantFPRange ConstantFPRange::unionWith(const ConstantFPRange &CR) const {
return ConstantFPRange(minnum(Lower, CR.Lower), maxnum(Upper, CR.Upper),
MayBeQNaN | CR.MayBeQNaN, MayBeSNaN | CR.MayBeSNaN);
}

ConstantFPRange ConstantFPRange::cast(const fltSemantics &DstSem) const {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just pass through the rounding mode argument? Is there anything more to it necessary than just passing it through to convert?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there anything more to it necessary than just passing it through to convert?

No. The invariant Lower <= Upper holds for all rounding modes.

@dtcxzyw dtcxzyw enabled auto-merge (squash) October 11, 2025 02:16
@dtcxzyw dtcxzyw merged commit 0b462f6 into llvm:main Oct 11, 2025
9 of 10 checks passed
@dtcxzyw dtcxzyw deleted the cfr-cast branch October 11, 2025 02:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

floating-point Floating-point math llvm:ir

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants