Skip to content

Commit ae1cd7c

Browse files
authored
[ConstantFPRange] Add support for add/sub (llvm#162962)
This patch adds support for fadd/fsub operations. I only tested this patch with some special ranges because the exhaustive check is too expensive.
1 parent 6c5cb97 commit ae1cd7c

File tree

3 files changed

+237
-27
lines changed

3 files changed

+237
-27
lines changed

llvm/include/llvm/IR/ConstantFPRange.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,14 @@ class [[nodiscard]] ConstantFPRange {
222222
LLVM_ABI ConstantFPRange
223223
cast(const fltSemantics &DstSem,
224224
APFloat::roundingMode RM = APFloat::rmNearestTiesToEven) const;
225+
226+
/// Return a new range representing the possible values resulting
227+
/// from an addition of a value in this range and a value in \p Other.
228+
LLVM_ABI ConstantFPRange add(const ConstantFPRange &Other) const;
229+
230+
/// Return a new range representing the possible values resulting
231+
/// from a subtraction of a value in this range and a value in \p Other.
232+
LLVM_ABI ConstantFPRange sub(const ConstantFPRange &Other) const;
225233
};
226234

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

llvm/lib/IR/ConstantFPRange.cpp

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -414,15 +414,31 @@ ConstantFPRange ConstantFPRange::negate() const {
414414
return ConstantFPRange(-Upper, -Lower, MayBeQNaN, MayBeSNaN);
415415
}
416416

417+
/// Return true if the finite part is not empty after removing infinities.
418+
static bool removeInf(APFloat &Lower, APFloat &Upper, bool &HasPosInf,
419+
bool &HasNegInf) {
420+
assert(strictCompare(Lower, Upper) != APFloat::cmpGreaterThan &&
421+
"Non-NaN part is empty.");
422+
auto &Sem = Lower.getSemantics();
423+
if (Lower.isNegInfinity()) {
424+
Lower = APFloat::getLargest(Sem, /*Negative=*/true);
425+
HasNegInf = true;
426+
}
427+
if (Upper.isPosInfinity()) {
428+
Upper = APFloat::getLargest(Sem, /*Negative=*/false);
429+
HasPosInf = true;
430+
}
431+
return strictCompare(Lower, Upper) != APFloat::cmpGreaterThan;
432+
}
433+
417434
ConstantFPRange ConstantFPRange::getWithoutInf() const {
418435
if (isNaNOnly())
419436
return *this;
420437
APFloat NewLower = Lower;
421438
APFloat NewUpper = Upper;
422-
if (Lower.isNegInfinity())
423-
NewLower = APFloat::getLargest(getSemantics(), /*Negative=*/true);
424-
if (Upper.isPosInfinity())
425-
NewUpper = APFloat::getLargest(getSemantics(), /*Negative=*/false);
439+
bool UnusedFlag;
440+
removeInf(NewLower, NewUpper, /*HasPosInf=*/UnusedFlag,
441+
/*HasNegInf=*/UnusedFlag);
426442
canonicalizeRange(NewLower, NewUpper);
427443
return ConstantFPRange(std::move(NewLower), std::move(NewUpper), MayBeQNaN,
428444
MayBeSNaN);
@@ -444,3 +460,49 @@ ConstantFPRange ConstantFPRange::cast(const fltSemantics &DstSem,
444460
/*MayBeQNaNVal=*/MayBeQNaN || MayBeSNaN,
445461
/*MayBeSNaNVal=*/false);
446462
}
463+
464+
ConstantFPRange ConstantFPRange::add(const ConstantFPRange &Other) const {
465+
bool ResMayBeQNaN = ((MayBeQNaN || MayBeSNaN) && !Other.isEmptySet()) ||
466+
((Other.MayBeQNaN || Other.MayBeSNaN) && !isEmptySet());
467+
if (isNaNOnly() || Other.isNaNOnly())
468+
return getNaNOnly(getSemantics(), /*MayBeQNaN=*/ResMayBeQNaN,
469+
/*MayBeSNaN=*/false);
470+
bool LHSHasNegInf = false, LHSHasPosInf = false;
471+
APFloat LHSLower = Lower, LHSUpper = Upper;
472+
bool LHSFiniteIsNonEmpty =
473+
removeInf(LHSLower, LHSUpper, LHSHasPosInf, LHSHasNegInf);
474+
bool RHSHasNegInf = false, RHSHasPosInf = false;
475+
APFloat RHSLower = Other.Lower, RHSUpper = Other.Upper;
476+
bool RHSFiniteIsNonEmpty =
477+
removeInf(RHSLower, RHSUpper, RHSHasPosInf, RHSHasNegInf);
478+
// -inf + +inf = QNaN
479+
ResMayBeQNaN |=
480+
(LHSHasNegInf && RHSHasPosInf) || (LHSHasPosInf && RHSHasNegInf);
481+
// +inf + finite/+inf = +inf, -inf + finite/-inf = -inf
482+
bool HasNegInf = (LHSHasNegInf && (RHSFiniteIsNonEmpty || RHSHasNegInf)) ||
483+
(RHSHasNegInf && (LHSFiniteIsNonEmpty || LHSHasNegInf));
484+
bool HasPosInf = (LHSHasPosInf && (RHSFiniteIsNonEmpty || RHSHasPosInf)) ||
485+
(RHSHasPosInf && (LHSFiniteIsNonEmpty || LHSHasPosInf));
486+
if (LHSFiniteIsNonEmpty && RHSFiniteIsNonEmpty) {
487+
APFloat NewLower =
488+
HasNegInf ? APFloat::getInf(LHSLower.getSemantics(), /*Negative=*/true)
489+
: LHSLower + RHSLower;
490+
APFloat NewUpper =
491+
HasPosInf ? APFloat::getInf(LHSUpper.getSemantics(), /*Negative=*/false)
492+
: LHSUpper + RHSUpper;
493+
return ConstantFPRange(NewLower, NewUpper, ResMayBeQNaN,
494+
/*MayBeSNaN=*/false);
495+
}
496+
// If both HasNegInf and HasPosInf are false, the non-NaN part is empty.
497+
// We just return the canonical form [+inf, -inf] for the empty non-NaN set.
498+
return ConstantFPRange(
499+
APFloat::getInf(Lower.getSemantics(), /*Negative=*/HasNegInf),
500+
APFloat::getInf(Upper.getSemantics(), /*Negative=*/!HasPosInf),
501+
ResMayBeQNaN,
502+
/*MayBeSNaN=*/false);
503+
}
504+
505+
ConstantFPRange ConstantFPRange::sub(const ConstantFPRange &Other) const {
506+
// fsub X, Y = fadd X, (fneg Y)
507+
return add(Other.negate());
508+
}

llvm/unittests/IR/ConstantFPRangeTest.cpp

Lines changed: 163 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class ConstantFPRangeTest : public ::testing::Test {
2222
static ConstantFPRange Full;
2323
static ConstantFPRange Empty;
2424
static ConstantFPRange Finite;
25+
static ConstantFPRange NonNaN;
2526
static ConstantFPRange One;
2627
static ConstantFPRange PosZero;
2728
static ConstantFPRange NegZero;
@@ -44,6 +45,8 @@ ConstantFPRange ConstantFPRangeTest::Empty =
4445
ConstantFPRange::getEmpty(APFloat::IEEEdouble());
4546
ConstantFPRange ConstantFPRangeTest::Finite =
4647
ConstantFPRange::getFinite(APFloat::IEEEdouble());
48+
ConstantFPRange ConstantFPRangeTest::NonNaN =
49+
ConstantFPRange::getNonNaN(APFloat::IEEEdouble());
4750
ConstantFPRange ConstantFPRangeTest::One = ConstantFPRange(APFloat(1.0));
4851
ConstantFPRange ConstantFPRangeTest::PosZero = ConstantFPRange(
4952
APFloat::getZero(APFloat::IEEEdouble(), /*Negative=*/false));
@@ -79,26 +82,35 @@ static void strictNext(APFloat &V) {
7982
V.next(/*nextDown=*/false);
8083
}
8184

85+
enum class SparseLevel {
86+
Dense,
87+
SpecialValuesWithAllPowerOfTwos,
88+
SpecialValuesOnly,
89+
};
90+
8291
template <typename Fn>
83-
static void EnumerateConstantFPRangesImpl(Fn TestFn, bool Exhaustive,
92+
static void EnumerateConstantFPRangesImpl(Fn TestFn, SparseLevel Level,
8493
bool MayBeQNaN, bool MayBeSNaN) {
8594
const fltSemantics &Sem = APFloat::Float8E4M3();
8695
APFloat PosInf = APFloat::getInf(Sem, /*Negative=*/false);
8796
APFloat NegInf = APFloat::getInf(Sem, /*Negative=*/true);
8897
TestFn(ConstantFPRange(PosInf, NegInf, MayBeQNaN, MayBeSNaN));
8998

90-
if (!Exhaustive) {
99+
if (Level != SparseLevel::Dense) {
91100
SmallVector<APFloat, 36> Values;
92101
Values.push_back(APFloat::getInf(Sem, /*Negative=*/true));
93102
Values.push_back(APFloat::getLargest(Sem, /*Negative=*/true));
94103
unsigned BitWidth = APFloat::semanticsSizeInBits(Sem);
95104
unsigned Exponents = APFloat::semanticsMaxExponent(Sem) -
96105
APFloat::semanticsMinExponent(Sem) + 3;
97106
unsigned MantissaBits = APFloat::semanticsPrecision(Sem) - 1;
98-
// Add -2^(max exponent), -2^(max exponent-1), ..., -2^(min exponent)
99-
for (unsigned M = Exponents - 2; M != 0; --M)
100-
Values.push_back(
101-
APFloat(Sem, APInt(BitWidth, (M + Exponents) << MantissaBits)));
107+
if (Level == SparseLevel::SpecialValuesWithAllPowerOfTwos) {
108+
// Add -2^(max exponent), -2^(max exponent-1), ..., -2^(min exponent)
109+
for (unsigned M = Exponents - 2; M != 0; --M)
110+
Values.push_back(
111+
APFloat(Sem, APInt(BitWidth, (M + Exponents) << MantissaBits)));
112+
}
113+
Values.push_back(APFloat::getSmallestNormalized(Sem, /*Negative=*/true));
102114
Values.push_back(APFloat::getSmallest(Sem, /*Negative=*/true));
103115
Values.push_back(APFloat::getZero(Sem, /*Negative=*/true));
104116
size_t E = Values.size();
@@ -127,26 +139,30 @@ static void EnumerateConstantFPRangesImpl(Fn TestFn, bool Exhaustive,
127139
}
128140

129141
template <typename Fn>
130-
static void EnumerateConstantFPRanges(Fn TestFn, bool Exhaustive) {
131-
EnumerateConstantFPRangesImpl(TestFn, Exhaustive, /*MayBeQNaN=*/false,
142+
static void EnumerateConstantFPRanges(Fn TestFn, SparseLevel Level,
143+
bool IgnoreSNaNs = false) {
144+
EnumerateConstantFPRangesImpl(TestFn, Level, /*MayBeQNaN=*/false,
132145
/*MayBeSNaN=*/false);
133-
EnumerateConstantFPRangesImpl(TestFn, Exhaustive, /*MayBeQNaN=*/false,
134-
/*MayBeSNaN=*/true);
135-
EnumerateConstantFPRangesImpl(TestFn, Exhaustive, /*MayBeQNaN=*/true,
146+
EnumerateConstantFPRangesImpl(TestFn, Level, /*MayBeQNaN=*/true,
136147
/*MayBeSNaN=*/false);
137-
EnumerateConstantFPRangesImpl(TestFn, Exhaustive, /*MayBeQNaN=*/true,
148+
if (IgnoreSNaNs)
149+
return;
150+
EnumerateConstantFPRangesImpl(TestFn, Level, /*MayBeQNaN=*/false,
151+
/*MayBeSNaN=*/true);
152+
EnumerateConstantFPRangesImpl(TestFn, Level, /*MayBeQNaN=*/true,
138153
/*MayBeSNaN=*/true);
139154
}
140155

141156
template <typename Fn>
142157
static void EnumerateTwoInterestingConstantFPRanges(Fn TestFn,
143-
bool Exhaustive) {
158+
SparseLevel Level) {
144159
EnumerateConstantFPRanges(
145160
[&](const ConstantFPRange &CR1) {
146161
EnumerateConstantFPRanges(
147-
[&](const ConstantFPRange &CR2) { TestFn(CR1, CR2); }, Exhaustive);
162+
[&](const ConstantFPRange &CR2) { TestFn(CR1, CR2); }, Level,
163+
/*IgnoreSNaNs=*/true);
148164
},
149-
Exhaustive);
165+
Level, /*IgnoreSNaNs=*/true);
150166
}
151167

152168
template <typename Fn>
@@ -348,16 +364,25 @@ TEST_F(ConstantFPRangeTest, ExhaustivelyEnumerate) {
348364
constexpr unsigned Expected = 4 * ((NNaNValues + 1) * NNaNValues / 2 + 1);
349365
unsigned Count = 0;
350366
EnumerateConstantFPRanges([&](const ConstantFPRange &) { ++Count; },
351-
/*Exhaustive=*/true);
367+
SparseLevel::Dense);
352368
EXPECT_EQ(Expected, Count);
353369
}
354370

355371
TEST_F(ConstantFPRangeTest, Enumerate) {
356-
constexpr unsigned NNaNValues = 2 * ((1 << 4) - 2 + 4);
372+
constexpr unsigned NNaNValues = 2 * ((1 << 4) - 2 + 5);
357373
constexpr unsigned Expected = 4 * ((NNaNValues + 1) * NNaNValues / 2 + 1);
358374
unsigned Count = 0;
359375
EnumerateConstantFPRanges([&](const ConstantFPRange &) { ++Count; },
360-
/*Exhaustive=*/false);
376+
SparseLevel::SpecialValuesWithAllPowerOfTwos);
377+
EXPECT_EQ(Expected, Count);
378+
}
379+
380+
TEST_F(ConstantFPRangeTest, EnumerateWithSpecialValuesOnly) {
381+
constexpr unsigned NNaNValues = 2 * 5;
382+
constexpr unsigned Expected = 4 * ((NNaNValues + 1) * NNaNValues / 2 + 1);
383+
unsigned Count = 0;
384+
EnumerateConstantFPRanges([&](const ConstantFPRange &) { ++Count; },
385+
SparseLevel::SpecialValuesOnly);
361386
EXPECT_EQ(Expected, Count);
362387
}
363388

@@ -459,7 +484,7 @@ TEST_F(ConstantFPRangeTest, FPClassify) {
459484
EXPECT_EQ(SignBit, CR.getSignBit()) << CR;
460485
EXPECT_EQ(Mask, CR.classify()) << CR;
461486
},
462-
/*Exhaustive=*/true);
487+
SparseLevel::Dense);
463488
#endif
464489
}
465490

@@ -560,7 +585,7 @@ TEST_F(ConstantFPRangeTest, makeAllowedFCmpRegion) {
560585
<< "Suboptimal result for makeAllowedFCmpRegion(" << Pred << ", "
561586
<< CR << ")";
562587
},
563-
/*Exhaustive=*/false);
588+
SparseLevel::SpecialValuesWithAllPowerOfTwos);
564589
}
565590
#endif
566591
}
@@ -671,7 +696,7 @@ TEST_F(ConstantFPRangeTest, makeSatisfyingFCmpRegion) {
671696
<< ", " << CR << ")";
672697
}
673698
},
674-
/*Exhaustive=*/false);
699+
SparseLevel::SpecialValuesWithAllPowerOfTwos);
675700
}
676701
#endif
677702
}
@@ -804,13 +829,13 @@ TEST_F(ConstantFPRangeTest, negate) {
804829
}
805830

806831
TEST_F(ConstantFPRangeTest, getWithout) {
807-
EXPECT_EQ(Full.getWithoutNaN(), ConstantFPRange::getNonNaN(Sem));
832+
EXPECT_EQ(Full.getWithoutNaN(), NonNaN);
808833
EXPECT_EQ(NaN.getWithoutNaN(), Empty);
809834

810835
EXPECT_EQ(NaN.getWithoutInf(), NaN);
811836
EXPECT_EQ(PosInf.getWithoutInf(), Empty);
812837
EXPECT_EQ(NegInf.getWithoutInf(), Empty);
813-
EXPECT_EQ(ConstantFPRange::getNonNaN(Sem).getWithoutInf(), Finite);
838+
EXPECT_EQ(NonNaN.getWithoutInf(), Finite);
814839
EXPECT_EQ(Zero.getWithoutInf(), Zero);
815840
EXPECT_EQ(ConstantFPRange::getNonNaN(APFloat::getInf(Sem, /*Negative=*/true),
816841
APFloat(3.0))
@@ -925,4 +950,119 @@ TEST_F(ConstantFPRangeTest, cast) {
925950
/*IgnoreNaNPayload=*/true);
926951
}
927952

953+
TEST_F(ConstantFPRangeTest, add) {
954+
EXPECT_EQ(Full.add(Full), NonNaN.unionWith(QNaN));
955+
EXPECT_EQ(Full.add(Empty), Empty);
956+
EXPECT_EQ(Empty.add(Full), Empty);
957+
EXPECT_EQ(Empty.add(Empty), Empty);
958+
EXPECT_EQ(One.add(One), ConstantFPRange(APFloat(2.0)));
959+
EXPECT_EQ(Some.add(Some),
960+
ConstantFPRange::getNonNaN(APFloat(-6.0), APFloat(6.0)));
961+
EXPECT_EQ(SomePos.add(SomeNeg),
962+
ConstantFPRange::getNonNaN(APFloat(-3.0), APFloat(3.0)));
963+
EXPECT_EQ(PosInf.add(PosInf), PosInf);
964+
EXPECT_EQ(NegInf.add(NegInf), NegInf);
965+
EXPECT_EQ(PosInf.add(Finite.unionWith(PosInf)), PosInf);
966+
EXPECT_EQ(NegInf.add(Finite.unionWith(NegInf)), NegInf);
967+
EXPECT_EQ(PosInf.add(Finite.unionWith(NegInf)), PosInf.unionWith(QNaN));
968+
EXPECT_EQ(NegInf.add(Finite.unionWith(PosInf)), NegInf.unionWith(QNaN));
969+
EXPECT_EQ(PosInf.add(NegInf), QNaN);
970+
EXPECT_EQ(NegInf.add(PosInf), QNaN);
971+
EXPECT_EQ(PosZero.add(NegZero), PosZero);
972+
EXPECT_EQ(PosZero.add(Zero), PosZero);
973+
EXPECT_EQ(NegZero.add(NegZero), NegZero);
974+
EXPECT_EQ(NegZero.add(Zero), Zero);
975+
EXPECT_EQ(NaN.add(NaN), QNaN);
976+
EXPECT_EQ(NaN.add(Finite), QNaN);
977+
EXPECT_EQ(NonNaN.unionWith(NaN).add(NonNaN), NonNaN.unionWith(QNaN));
978+
EXPECT_EQ(PosInf.unionWith(QNaN).add(PosInf), PosInf.unionWith(QNaN));
979+
EXPECT_EQ(PosInf.unionWith(NaN).add(ConstantFPRange(APFloat(24.0))),
980+
PosInf.unionWith(QNaN));
981+
982+
#if defined(EXPENSIVE_CHECKS)
983+
EnumerateTwoInterestingConstantFPRanges(
984+
[](const ConstantFPRange &LHS, const ConstantFPRange &RHS) {
985+
ConstantFPRange Res = LHS.add(RHS);
986+
ConstantFPRange Expected =
987+
ConstantFPRange::getEmpty(LHS.getSemantics());
988+
EnumerateValuesInConstantFPRange(
989+
LHS,
990+
[&](const APFloat &LHSC) {
991+
EnumerateValuesInConstantFPRange(
992+
RHS,
993+
[&](const APFloat &RHSC) {
994+
APFloat Sum = LHSC + RHSC;
995+
EXPECT_TRUE(Res.contains(Sum))
996+
<< "Wrong result for " << LHS << " + " << RHS
997+
<< ". The result " << Res << " should contain " << Sum;
998+
if (!Expected.contains(Sum))
999+
Expected = Expected.unionWith(ConstantFPRange(Sum));
1000+
},
1001+
/*IgnoreNaNPayload=*/true);
1002+
},
1003+
/*IgnoreNaNPayload=*/true);
1004+
EXPECT_EQ(Res, Expected)
1005+
<< "Suboptimal result for " << LHS << " + " << RHS << ". Expected "
1006+
<< Expected << ", but got " << Res;
1007+
},
1008+
SparseLevel::SpecialValuesOnly);
1009+
#endif
1010+
}
1011+
1012+
TEST_F(ConstantFPRangeTest, sub) {
1013+
EXPECT_EQ(Full.sub(Full), NonNaN.unionWith(QNaN));
1014+
EXPECT_EQ(Full.sub(Empty), Empty);
1015+
EXPECT_EQ(Empty.sub(Full), Empty);
1016+
EXPECT_EQ(Empty.sub(Empty), Empty);
1017+
EXPECT_EQ(One.sub(One), ConstantFPRange(APFloat(0.0)));
1018+
EXPECT_EQ(Some.sub(Some),
1019+
ConstantFPRange::getNonNaN(APFloat(-6.0), APFloat(6.0)));
1020+
EXPECT_EQ(SomePos.sub(SomeNeg),
1021+
ConstantFPRange::getNonNaN(APFloat(0.0), APFloat(6.0)));
1022+
EXPECT_EQ(PosInf.sub(NegInf), PosInf);
1023+
EXPECT_EQ(NegInf.sub(PosInf), NegInf);
1024+
EXPECT_EQ(PosInf.sub(Finite.unionWith(NegInf)), PosInf);
1025+
EXPECT_EQ(NegInf.sub(Finite.unionWith(PosInf)), NegInf);
1026+
EXPECT_EQ(PosInf.sub(Finite.unionWith(PosInf)), PosInf.unionWith(QNaN));
1027+
EXPECT_EQ(NegInf.sub(Finite.unionWith(NegInf)), NegInf.unionWith(QNaN));
1028+
EXPECT_EQ(PosInf.sub(PosInf), QNaN);
1029+
EXPECT_EQ(NegInf.sub(NegInf), QNaN);
1030+
EXPECT_EQ(PosZero.sub(NegZero), PosZero);
1031+
EXPECT_EQ(PosZero.sub(Zero), PosZero);
1032+
EXPECT_EQ(NegZero.sub(NegZero), PosZero);
1033+
EXPECT_EQ(NegZero.sub(PosZero), NegZero);
1034+
EXPECT_EQ(NegZero.sub(Zero), Zero);
1035+
EXPECT_EQ(NaN.sub(NaN), QNaN);
1036+
EXPECT_EQ(NaN.add(Finite), QNaN);
1037+
1038+
#if defined(EXPENSIVE_CHECKS)
1039+
EnumerateTwoInterestingConstantFPRanges(
1040+
[](const ConstantFPRange &LHS, const ConstantFPRange &RHS) {
1041+
ConstantFPRange Res = LHS.sub(RHS);
1042+
ConstantFPRange Expected =
1043+
ConstantFPRange::getEmpty(LHS.getSemantics());
1044+
EnumerateValuesInConstantFPRange(
1045+
LHS,
1046+
[&](const APFloat &LHSC) {
1047+
EnumerateValuesInConstantFPRange(
1048+
RHS,
1049+
[&](const APFloat &RHSC) {
1050+
APFloat Diff = LHSC - RHSC;
1051+
EXPECT_TRUE(Res.contains(Diff))
1052+
<< "Wrong result for " << LHS << " - " << RHS
1053+
<< ". The result " << Res << " should contain " << Diff;
1054+
if (!Expected.contains(Diff))
1055+
Expected = Expected.unionWith(ConstantFPRange(Diff));
1056+
},
1057+
/*IgnoreNaNPayload=*/true);
1058+
},
1059+
/*IgnoreNaNPayload=*/true);
1060+
EXPECT_EQ(Res, Expected)
1061+
<< "Suboptimal result for " << LHS << " - " << RHS << ". Expected "
1062+
<< Expected << ", but got " << Res;
1063+
},
1064+
SparseLevel::SpecialValuesOnly);
1065+
#endif
1066+
}
1067+
9281068
} // anonymous namespace

0 commit comments

Comments
 (0)