Skip to content

Commit 0b462f6

Browse files
authored
[ConstantFPRange] Add support for cast operations (llvm#162686)
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.
1 parent d7e7cd0 commit 0b462f6

File tree

3 files changed

+132
-0
lines changed

3 files changed

+132
-0
lines changed

llvm/include/llvm/IR/ConstantFPRange.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,12 @@ class [[nodiscard]] ConstantFPRange {
216216
/// Get the range without infinities. It is useful when we apply ninf flag to
217217
/// range of operands/results.
218218
LLVM_ABI ConstantFPRange getWithoutInf() const;
219+
220+
/// Return a new range in the specified format with the specified rounding
221+
/// mode.
222+
LLVM_ABI ConstantFPRange
223+
cast(const fltSemantics &DstSem,
224+
APFloat::roundingMode RM = APFloat::rmNearestTiesToEven) const;
219225
};
220226

221227
inline raw_ostream &operator<<(raw_ostream &OS, const ConstantFPRange &CR) {

llvm/lib/IR/ConstantFPRange.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,8 @@ std::optional<bool> ConstantFPRange::getSignBit() const {
326326
}
327327

328328
bool ConstantFPRange::operator==(const ConstantFPRange &CR) const {
329+
assert(&getSemantics() == &CR.getSemantics() &&
330+
"Should only use the same semantics");
329331
if (MayBeSNaN != CR.MayBeSNaN || MayBeQNaN != CR.MayBeQNaN)
330332
return false;
331333
return Lower.bitwiseIsEqual(CR.Lower) && Upper.bitwiseIsEqual(CR.Upper);
@@ -425,3 +427,20 @@ ConstantFPRange ConstantFPRange::getWithoutInf() const {
425427
return ConstantFPRange(std::move(NewLower), std::move(NewUpper), MayBeQNaN,
426428
MayBeSNaN);
427429
}
430+
431+
ConstantFPRange ConstantFPRange::cast(const fltSemantics &DstSem,
432+
APFloat::roundingMode RM) const {
433+
bool LosesInfo;
434+
APFloat NewLower = Lower;
435+
APFloat NewUpper = Upper;
436+
// For conservative, return full range if conversion is invalid.
437+
if (NewLower.convert(DstSem, RM, &LosesInfo) == APFloat::opInvalidOp ||
438+
NewLower.isNaN())
439+
return getFull(DstSem);
440+
if (NewUpper.convert(DstSem, RM, &LosesInfo) == APFloat::opInvalidOp ||
441+
NewUpper.isNaN())
442+
return getFull(DstSem);
443+
return ConstantFPRange(std::move(NewLower), std::move(NewUpper),
444+
/*MayBeQNaNVal=*/MayBeQNaN || MayBeSNaN,
445+
/*MayBeSNaNVal=*/false);
446+
}

llvm/unittests/IR/ConstantFPRangeTest.cpp

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
//===----------------------------------------------------------------------===//
88

99
#include "llvm/IR/ConstantFPRange.h"
10+
#include "llvm/ADT/APFloat.h"
1011
#include "llvm/IR/Instructions.h"
1112
#include "llvm/IR/Operator.h"
1213
#include "gtest/gtest.h"
@@ -818,4 +819,110 @@ TEST_F(ConstantFPRangeTest, getWithout) {
818819
APFloat::getLargest(Sem, /*Negative=*/true), APFloat(3.0)));
819820
}
820821

822+
TEST_F(ConstantFPRangeTest, cast) {
823+
const fltSemantics &F16Sem = APFloat::IEEEhalf();
824+
const fltSemantics &BF16Sem = APFloat::BFloat();
825+
const fltSemantics &F32Sem = APFloat::IEEEsingle();
826+
const fltSemantics &F8NanOnlySem = APFloat::Float8E4M3FN();
827+
// normal -> normal (exact)
828+
EXPECT_EQ(ConstantFPRange::getNonNaN(APFloat(1.0), APFloat(2.0)).cast(F32Sem),
829+
ConstantFPRange::getNonNaN(APFloat(1.0f), APFloat(2.0f)));
830+
EXPECT_EQ(
831+
ConstantFPRange::getNonNaN(APFloat(-2.0f), APFloat(-1.0f)).cast(Sem),
832+
ConstantFPRange::getNonNaN(APFloat(-2.0), APFloat(-1.0)));
833+
// normal -> normal (inexact)
834+
EXPECT_EQ(
835+
ConstantFPRange::getNonNaN(APFloat(3.141592653589793),
836+
APFloat(6.283185307179586))
837+
.cast(F32Sem),
838+
ConstantFPRange::getNonNaN(APFloat(3.14159274f), APFloat(6.28318548f)));
839+
// normal -> subnormal
840+
EXPECT_EQ(ConstantFPRange::getNonNaN(APFloat(-5e-8), APFloat(5e-8))
841+
.cast(F16Sem)
842+
.classify(),
843+
fcSubnormal | fcZero);
844+
// normal -> zero
845+
EXPECT_EQ(ConstantFPRange::getNonNaN(
846+
APFloat::getSmallestNormalized(Sem, /*Negative=*/true),
847+
APFloat::getSmallestNormalized(Sem, /*Negative=*/false))
848+
.cast(F32Sem)
849+
.classify(),
850+
fcZero);
851+
// normal -> inf
852+
EXPECT_EQ(ConstantFPRange::getNonNaN(APFloat(-65536.0), APFloat(65536.0))
853+
.cast(F16Sem),
854+
ConstantFPRange::getNonNaN(F16Sem));
855+
// nan -> qnan
856+
EXPECT_EQ(
857+
ConstantFPRange::getNaNOnly(Sem, /*MayBeQNaN=*/true, /*MayBeSNaN=*/false)
858+
.cast(F32Sem),
859+
ConstantFPRange::getNaNOnly(F32Sem, /*MayBeQNaN=*/true,
860+
/*MayBeSNaN=*/false));
861+
EXPECT_EQ(
862+
ConstantFPRange::getNaNOnly(Sem, /*MayBeQNaN=*/false, /*MayBeSNaN=*/true)
863+
.cast(F32Sem),
864+
ConstantFPRange::getNaNOnly(F32Sem, /*MayBeQNaN=*/true,
865+
/*MayBeSNaN=*/false));
866+
EXPECT_EQ(
867+
ConstantFPRange::getNaNOnly(Sem, /*MayBeQNaN=*/true, /*MayBeSNaN=*/true)
868+
.cast(F32Sem),
869+
ConstantFPRange::getNaNOnly(F32Sem, /*MayBeQNaN=*/true,
870+
/*MayBeSNaN=*/false));
871+
// For BF16 -> F32, signaling bit is still lost.
872+
EXPECT_EQ(ConstantFPRange::getNaNOnly(BF16Sem, /*MayBeQNaN=*/true,
873+
/*MayBeSNaN=*/true)
874+
.cast(F32Sem),
875+
ConstantFPRange::getNaNOnly(F32Sem, /*MayBeQNaN=*/true,
876+
/*MayBeSNaN=*/false));
877+
// inf -> nan only (return full set for now)
878+
EXPECT_EQ(ConstantFPRange::getNonNaN(APFloat::getInf(Sem, /*Negative=*/true),
879+
APFloat::getInf(Sem, /*Negative=*/false))
880+
.cast(F8NanOnlySem),
881+
ConstantFPRange::getFull(F8NanOnlySem));
882+
// other rounding modes
883+
EXPECT_EQ(
884+
ConstantFPRange::getNonNaN(APFloat::getSmallest(Sem, /*Negative=*/true),
885+
APFloat::getSmallest(Sem, /*Negative=*/false))
886+
.cast(F32Sem, APFloat::rmTowardNegative),
887+
ConstantFPRange::getNonNaN(
888+
APFloat::getSmallest(F32Sem, /*Negative=*/true),
889+
APFloat::getZero(F32Sem, /*Negative=*/false)));
890+
EXPECT_EQ(
891+
ConstantFPRange::getNonNaN(APFloat::getSmallest(Sem, /*Negative=*/true),
892+
APFloat::getSmallest(Sem, /*Negative=*/false))
893+
.cast(F32Sem, APFloat::rmTowardPositive),
894+
ConstantFPRange::getNonNaN(
895+
APFloat::getZero(F32Sem, /*Negative=*/true),
896+
APFloat::getSmallest(F32Sem, /*Negative=*/false)));
897+
EXPECT_EQ(
898+
ConstantFPRange::getNonNaN(
899+
APFloat::getSmallestNormalized(Sem, /*Negative=*/true),
900+
APFloat::getSmallestNormalized(Sem, /*Negative=*/false))
901+
.cast(F32Sem, APFloat::rmTowardZero),
902+
ConstantFPRange::getNonNaN(APFloat::getZero(F32Sem, /*Negative=*/true),
903+
APFloat::getZero(F32Sem, /*Negative=*/false)));
904+
905+
EnumerateValuesInConstantFPRange(
906+
ConstantFPRange::getFull(APFloat::Float8E4M3()),
907+
[&](const APFloat &V) {
908+
bool LosesInfo = false;
909+
910+
APFloat DoubleV = V;
911+
DoubleV.convert(Sem, APFloat::rmNearestTiesToEven, &LosesInfo);
912+
ConstantFPRange DoubleCR = ConstantFPRange(V).cast(Sem);
913+
EXPECT_TRUE(DoubleCR.contains(DoubleV))
914+
<< "Casting " << V << " to double failed. " << DoubleCR
915+
<< " doesn't contain " << DoubleV;
916+
917+
auto &FP4Sem = APFloat::Float4E2M1FN();
918+
APFloat FP4V = V;
919+
FP4V.convert(FP4Sem, APFloat::rmNearestTiesToEven, &LosesInfo);
920+
ConstantFPRange FP4CR = ConstantFPRange(V).cast(FP4Sem);
921+
EXPECT_TRUE(FP4CR.contains(FP4V))
922+
<< "Casting " << V << " to FP4E2M1FN failed. " << FP4CR
923+
<< " doesn't contain " << FP4V;
924+
},
925+
/*IgnoreNaNPayload=*/true);
926+
}
927+
821928
} // anonymous namespace

0 commit comments

Comments
 (0)