[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