[llvm] [KnownFPClass] Refine known classes for `KnownFPClass::bitcast` (PR #215708)

via llvm-commits llvm-commits at lists.llvm.org
Tue Aug 11 19:02:07 PDT 2026


https://github.com/ZERICO2005 created https://github.com/llvm/llvm-project/pull/215708

`KnownFPClass::bitcast` is now able to correctly determine the `KnownFPClass` of any constant value. For non-constants, it is able to rule out normal, and subnormal results, in addition to ruling out specifically `qNaN`/`sNaN` based off of the quiet bit.

I also added unit tests for `KnownFPClass::bitcast`.

AI Disclosure:
I used ChatGPT Codex (sol 5.6) to help generate the tests, which I reviewed, built, and tested locally.

>From 0e4b9b138409072bb34221b0a6516fcc26dc4780 Mon Sep 17 00:00:00 2001
From: zerico <zerico2005 at gmail.com>
Date: Tue, 11 Aug 2026 16:00:45 -0600
Subject: [PATCH] [KnownFPClass] Refine known classes for bitcast

---
 llvm/lib/Support/KnownFPClass.cpp           |  78 ++++----
 llvm/unittests/Support/CMakeLists.txt       |   1 +
 llvm/unittests/Support/KnownFPClassTest.cpp | 191 ++++++++++++++++++++
 3 files changed, 236 insertions(+), 34 deletions(-)
 create mode 100644 llvm/unittests/Support/KnownFPClassTest.cpp

diff --git a/llvm/lib/Support/KnownFPClass.cpp b/llvm/lib/Support/KnownFPClass.cpp
index eccd83451a05f..ab18f59f61990 100644
--- a/llvm/lib/Support/KnownFPClass.cpp
+++ b/llvm/lib/Support/KnownFPClass.cpp
@@ -242,40 +242,50 @@ KnownFPClass KnownFPClass::bitcast(const fltSemantics &FltSemantics,
     Known.signBitMustBeOne();
 
   if (APFloat::isIEEELikeFP(FltSemantics)) {
-    // IEEE floats are NaN when all bits of the exponent plus at least one of
-    // the fraction bits are 1. This means:
-    //   - If we assume unknown bits are 0 and the value is NaN, it will
-    //     always be NaN
-    //   - If we assume unknown bits are 1 and the value is not NaN, it can
-    //     never be NaN
-    // Note: They do not hold for x86_fp80 format.
-    if (APFloat(FltSemantics, Bits.One).isNaN())
-      Known.KnownFPClasses = fcNan;
-    else if (!APFloat(FltSemantics, ~Bits.Zero).isNaN())
-      Known.knownNot(fcNan);
-
-    // Build KnownBits representing Inf and check if it must be equal or
-    // unequal to this value.
-    auto InfKB =
-        KnownBits::makeConstant(APFloat::getInf(FltSemantics).bitcastToAPInt());
-    InfKB.Zero.clearSignBit();
-    if (const auto InfResult = KnownBits::eq(Bits, InfKB)) {
-      assert(!InfResult.value());
-      Known.knownNot(fcInf);
-    } else if (Bits == InfKB) {
-      Known.KnownFPClasses = fcInf;
-    }
-
-    // Build KnownBits representing Zero and check if it must be equal or
-    // unequal to this value.
-    auto ZeroKB = KnownBits::makeConstant(
-        APFloat::getZero(FltSemantics).bitcastToAPInt());
-    ZeroKB.Zero.clearSignBit();
-    if (const auto ZeroResult = KnownBits::eq(Bits, ZeroKB)) {
-      assert(!ZeroResult.value());
-      Known.knownNot(fcZero);
-    } else if (Bits == ZeroKB) {
-      Known.KnownFPClasses = fcZero;
+    const unsigned MantissaBits = FltSemantics.precision - 1;
+    const APInt ExponentMask = APInt::getBitsSet(
+        FltSemantics.sizeInBits, MantissaBits, FltSemantics.sizeInBits - 1);
+    const APInt MantissaMask =
+        APInt::getLowBitsSet(FltSemantics.sizeInBits, MantissaBits);
+
+    const bool ExponentKnownAllZeros =
+        (Bits.Zero & ExponentMask) == ExponentMask;
+    const bool ExponentKnownAllOnes = (Bits.One & ExponentMask) == ExponentMask;
+    const bool ExponentKnownNotAllZeros = !(Bits.One & ExponentMask).isZero();
+    const bool ExponentKnownNotAllOnes = !(Bits.Zero & ExponentMask).isZero();
+
+    const bool MantissaKnownAllZeros =
+        (Bits.Zero & MantissaMask) == MantissaMask;
+    const bool MantissaKnownNotAllZeros = !(Bits.One & MantissaMask).isZero();
+
+    // Zero and subnormal require an exponent with all zero bits.
+    if (ExponentKnownNotAllZeros)
+      Known.knownNot(fcZero | fcSubnormal);
+
+    // Infinity and NaN require an exponent with all one bits.
+    if (ExponentKnownNotAllOnes)
+      Known.knownNot(fcInf | fcNan);
+
+    // Normal values have an exponent that is not all zeros or all ones.
+    if (ExponentKnownAllZeros || ExponentKnownAllOnes)
+      Known.knownNot(fcNormal);
+
+    // Zero and infinity require a mantissa with all zero bits.
+    if (MantissaKnownNotAllZeros)
+      Known.knownNot(fcZero | fcInf);
+
+    // Subnormal and NaN require a non-zero mantissa.
+    if (MantissaKnownAllZeros)
+      Known.knownNot(fcSubnormal | fcNan);
+
+    if (!Known.isKnownNeverNaN()) {
+      const bool QuietBitKnownSet = Bits.One[MantissaBits - 1];
+      const bool QuietBitKnownClear = Bits.Zero[MantissaBits - 1];
+
+      if (QuietBitKnownSet)
+        Known.knownNot(fcSNan);
+      if (QuietBitKnownClear)
+        Known.knownNot(fcQNan);
     }
   }
 
diff --git a/llvm/unittests/Support/CMakeLists.txt b/llvm/unittests/Support/CMakeLists.txt
index e808e668a87ad..cc88a6c5670ca 100644
--- a/llvm/unittests/Support/CMakeLists.txt
+++ b/llvm/unittests/Support/CMakeLists.txt
@@ -57,6 +57,7 @@ add_llvm_unittest(SupportTests
   JobserverTest.cpp
   JSONTest.cpp
   KnownBitsTest.cpp
+  KnownFPClassTest.cpp
   LEB128Test.cpp
   LineIteratorTest.cpp
   LockFileManagerTest.cpp
diff --git a/llvm/unittests/Support/KnownFPClassTest.cpp b/llvm/unittests/Support/KnownFPClassTest.cpp
new file mode 100644
index 0000000000000..6e38458b9e863
--- /dev/null
+++ b/llvm/unittests/Support/KnownFPClassTest.cpp
@@ -0,0 +1,191 @@
+//===- KnownFPClassTest.cpp - KnownFPClass tests --------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Support/KnownFPClass.h"
+#include "llvm/ADT/APFloat.h"
+#include "llvm/ADT/APInt.h"
+#include "llvm/ADT/FloatingPointMode.h"
+#include "llvm/Support/KnownBits.h"
+#include "gtest/gtest.h"
+
+using namespace llvm;
+
+namespace {
+
+static KnownFPClass bitcast(const fltSemantics &Semantics,
+                            const APInt &Value) {
+  return KnownFPClass::bitcast(Semantics, KnownBits::makeConstant(Value));
+}
+
+static void expectKnown(FPClassTest ExpectedClasses,
+                        std::optional<bool> ExpectedSignBit,
+                        const fltSemantics &Semantics,
+                        const KnownBits &Bits) {
+  KnownFPClass Known = KnownFPClass::bitcast(Semantics, Bits);
+  EXPECT_EQ(ExpectedClasses, Known.KnownFPClasses);
+  EXPECT_EQ(ExpectedSignBit, Known.SignBit);
+}
+
+static void expectConstant(const char *SemanticsName, const char *ValueName,
+                           const fltSemantics &Semantics, APInt ValueBits,
+                           FPClassTest PositiveClass, bool Negative) {
+  if (Negative)
+    ValueBits.setBit(Semantics.sizeInBits - 1);
+
+  SCOPED_TRACE(testing::Message()
+               << SemanticsName << ' ' << (Negative ? "negative " : "positive ")
+               << ValueName);
+  KnownFPClass Known = bitcast(Semantics, ValueBits);
+  FPClassTest ExpectedClass =
+      Negative ? llvm::fneg(PositiveClass) : PositiveClass;
+  EXPECT_EQ(ExpectedClass, Known.KnownFPClasses);
+  EXPECT_EQ(Negative, Known.SignBit);
+}
+
+TEST(KnownFPClassTest, BitcastExhaustiveIEEEHalf) {
+  const fltSemantics &Semantics = APFloat::IEEEhalf();
+
+  for (uint64_t RawBits = 0; RawBits != (1u << 16); ++RawBits) {
+    APInt ValueBits(16, RawBits);
+    KnownFPClass Known = bitcast(Semantics, ValueBits);
+    KnownFPClass Expected(APFloat(Semantics, ValueBits));
+
+    ASSERT_EQ(Expected.KnownFPClasses, Known.KnownFPClasses) << RawBits;
+    ASSERT_EQ(Expected.SignBit, Known.SignBit) << RawBits;
+  }
+}
+
+TEST(KnownFPClassTest, BitcastConstant) {
+  struct SemanticsCase {
+    const char *Name;
+    const fltSemantics *Semantics;
+  };
+
+  for (const SemanticsCase &TestCase :
+       {SemanticsCase{"ieee_binary16", &APFloat::IEEEhalf()},
+        SemanticsCase{"bfloat16", &APFloat::BFloat()},
+        SemanticsCase{"ieee_binary32", &APFloat::IEEEsingle()},
+        SemanticsCase{"ieee_binary64", &APFloat::IEEEdouble()},
+        SemanticsCase{"ieee_binary128", &APFloat::IEEEquad()}}) {
+    const fltSemantics &Semantics = *TestCase.Semantics;
+    const unsigned BitWidth = Semantics.sizeInBits;
+    const unsigned MantissaBits = Semantics.precision - 1;
+    const APInt ExponentMask =
+        APInt::getBitsSet(BitWidth, MantissaBits, BitWidth - 1);
+    const APInt MantissaMask = APInt::getLowBitsSet(BitWidth, MantissaBits);
+    const APInt QuietBit = APInt::getOneBitSet(BitWidth, MantissaBits - 1);
+
+    for (bool Negative : {false, true}) {
+      expectConstant(TestCase.Name, "0.0", Semantics, APInt::getZero(BitWidth),
+                     fcPosZero, Negative);
+      expectConstant(TestCase.Name, "min_subnormal", Semantics,
+                     APInt(BitWidth, 1), fcPosSubnormal, Negative);
+      expectConstant(TestCase.Name, "max_subnormal", Semantics, MantissaMask,
+                     fcPosSubnormal, Negative);
+      expectConstant(TestCase.Name, "min_normal", Semantics,
+                     APInt::getOneBitSet(BitWidth, MantissaBits), fcPosNormal,
+                     Negative);
+      expectConstant(TestCase.Name, "1.0", Semantics,
+                     APFloat::getOne(Semantics).bitcastToAPInt(), fcPosNormal,
+                     Negative);
+      expectConstant(TestCase.Name, "max_normal", Semantics,
+                     APFloat::getLargest(Semantics).bitcastToAPInt(),
+                     fcPosNormal, Negative);
+      expectConstant(TestCase.Name, "inf", Semantics, ExponentMask, fcPosInf,
+                     Negative);
+
+      // An sNaN has a clear quiet bit and a non-zero payload.
+      expectConstant(TestCase.Name, "snan_mostly_zero", Semantics,
+                     ExponentMask | APInt(BitWidth, 1), fcSNan, Negative);
+
+      // A qNaN has a set quiet bit. The remaining payload bits may be zero.
+      expectConstant(TestCase.Name, "qnan_mostly_zero", Semantics,
+                     ExponentMask | QuietBit, fcQNan, Negative);
+
+      expectConstant(TestCase.Name, "snan_mostly_one", Semantics,
+                     ExponentMask | (MantissaMask & ~QuietBit), fcSNan,
+                     Negative);
+      expectConstant(TestCase.Name, "qnan_mostly_one", Semantics,
+                     ExponentMask | MantissaMask, fcQNan, Negative);
+    }
+  }
+}
+
+TEST(KnownFPClassTest, BitcastPartialIEEESingle) {
+  const fltSemantics &Semantics = APFloat::IEEEsingle();
+  const unsigned BitWidth = Semantics.sizeInBits;
+  const unsigned MantissaBits = Semantics.precision - 1;
+  const APInt SignMask = APInt::getSignMask(BitWidth);
+  const APInt ExponentMask =
+      APInt::getBitsSet(BitWidth, MantissaBits, BitWidth - 1);
+  const APInt MantissaMask = APInt::getLowBitsSet(BitWidth, MantissaBits);
+
+  KnownBits Bits(BitWidth);
+  // We should know nothing if everything is unknown.
+  expectKnown(fcAllFlags, std::nullopt, Semantics, Bits);
+
+  Bits = KnownBits(BitWidth);
+  Bits.Zero |= SignMask;
+  expectKnown(fcAllFlags & ~fcNegative, false, Semantics, Bits);
+
+  Bits = KnownBits(BitWidth);
+  Bits.One |= SignMask;
+  expectKnown(fcAllFlags & ~fcPositive, true, Semantics, Bits);
+
+  Bits = KnownBits(BitWidth);
+  Bits.Zero |= ExponentMask;
+  expectKnown(fcZero | fcSubnormal, std::nullopt, Semantics, Bits);
+
+  Bits = KnownBits(BitWidth);
+  Bits.One |= ExponentMask;
+  expectKnown(fcInf | fcNan, std::nullopt, Semantics, Bits);
+
+  Bits = KnownBits(BitWidth);
+  Bits.One.setBit(MantissaBits);
+  Bits.Zero.setBit(MantissaBits + 1);
+  expectKnown(fcNormal, std::nullopt, Semantics, Bits);
+
+  Bits = KnownBits(BitWidth);
+  Bits.One.setBit(MantissaBits);
+  expectKnown(fcNormal | fcInf | fcNan, std::nullopt, Semantics, Bits);
+
+  Bits = KnownBits(BitWidth);
+  Bits.Zero.setBit(MantissaBits);
+  expectKnown(fcZero | fcSubnormal | fcNormal, std::nullopt, Semantics, Bits);
+
+  Bits = KnownBits(BitWidth);
+  Bits.Zero |= MantissaMask;
+  expectKnown(fcZero | fcNormal | fcInf, std::nullopt, Semantics, Bits);
+
+  Bits = KnownBits(BitWidth);
+  Bits.Zero.setBit(0);
+  expectKnown(fcAllFlags, std::nullopt, Semantics, Bits);
+
+  Bits = KnownBits(BitWidth);
+  Bits.One.setBit(0);
+  expectKnown(fcSubnormal | fcNormal | fcNan, std::nullopt, Semantics, Bits);
+
+  // A set quiet bit makes any possible NaN quiet. It also proves that the
+  // mantissa is non-zero.
+  Bits = KnownBits(BitWidth);
+  Bits.One.setBit(MantissaBits - 1);
+  expectKnown(fcSubnormal | fcNormal | fcQNan, std::nullopt, Semantics, Bits);
+
+  // A clear quiet bit rules out qNaN
+  Bits = KnownBits(BitWidth);
+  Bits.Zero.setBit(MantissaBits - 1);
+  expectKnown(fcAllFlags & ~fcQNan, std::nullopt, Semantics, Bits);
+  // Infinity and sNaN remain possible when the exponent is all ones.
+  Bits.One |= ExponentMask;
+  expectKnown(fcInf | fcSNan, std::nullopt, Semantics, Bits);
+  // A non-zero payload distinguishes sNaN from infinity.
+  Bits.One.setBit(0);
+  expectKnown(fcSNan, std::nullopt, Semantics, Bits);
+}
+
+} // end anonymous namespace



More information about the llvm-commits mailing list