[llvm] [ConstantFPRange] Add support for cast operations (PR #162686)

Yingwei Zheng via llvm-commits llvm-commits at lists.llvm.org
Thu Oct 9 08:53:33 PDT 2025


https://github.com/dtcxzyw created https://github.com/llvm/llvm-project/pull/162686

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.


>From 838b145725428c66aaed304280480c4014a65262 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Thu, 9 Oct 2025 23:32:54 +0800
Subject: [PATCH 1/2] [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 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..0795293a63ded 100644
--- a/llvm/lib/IR/ConstantFPRange.cpp
+++ b/llvm/lib/IR/ConstantFPRange.cpp
@@ -391,3 +391,19 @@ 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)
+    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 255f62d77b748..9981c526d21f5 100644
--- a/llvm/unittests/IR/ConstantFPRangeTest.cpp
+++ b/llvm/unittests/IR/ConstantFPRangeTest.cpp
@@ -767,4 +767,82 @@ TEST_F(ConstantFPRangeTest, makeExactFCmpRegion) {
   }
 }
 
+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 8cd76c14ea16162cbd77df76f36bda5cbf98abd3 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Thu, 9 Oct 2025 23:50:13 +0800
Subject: [PATCH 2/2] [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 0795293a63ded..9a81c07c4a616 100644
--- a/llvm/lib/IR/ConstantFPRange.cpp
+++ b/llvm/lib/IR/ConstantFPRange.cpp
@@ -398,10 +398,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 9981c526d21f5..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"
@@ -771,6 +772,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)));
@@ -821,6 +823,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()),



More information about the llvm-commits mailing list