Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions llvm/include/llvm/IR/ConstantFPRange.h
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,14 @@ class [[nodiscard]] ConstantFPRange {
LLVM_ABI ConstantFPRange
cast(const fltSemantics &DstSem,
APFloat::roundingMode RM = APFloat::rmNearestTiesToEven) const;

/// Return a new range representing the possible values resulting
/// from an addition of a value in this range and a value in \p Other.
LLVM_ABI ConstantFPRange add(const ConstantFPRange &Other) const;

/// Return a new range representing the possible values resulting
/// from a subtraction of a value in this range and a value in \p Other.
LLVM_ABI ConstantFPRange sub(const ConstantFPRange &Other) const;
};

inline raw_ostream &operator<<(raw_ostream &OS, const ConstantFPRange &CR) {
Expand Down
70 changes: 66 additions & 4 deletions llvm/lib/IR/ConstantFPRange.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -414,15 +414,31 @@ ConstantFPRange ConstantFPRange::negate() const {
return ConstantFPRange(-Upper, -Lower, MayBeQNaN, MayBeSNaN);
}

/// Return true if the finite part is not empty after removing infinities.
static bool removeInf(APFloat &Lower, APFloat &Upper, bool &HasPosInf,
bool &HasNegInf) {
assert(strictCompare(Lower, Upper) != APFloat::cmpGreaterThan &&
"Non-NaN part is empty.");
auto &Sem = Lower.getSemantics();
if (Lower.isNegInfinity()) {
Lower = APFloat::getLargest(Sem, /*Negative=*/true);
HasNegInf = true;
}
if (Upper.isPosInfinity()) {
Upper = APFloat::getLargest(Sem, /*Negative=*/false);
HasPosInf = true;
}
return strictCompare(Lower, Upper) != APFloat::cmpGreaterThan;
}

ConstantFPRange ConstantFPRange::getWithoutInf() const {
if (isNaNOnly())
return *this;
APFloat NewLower = Lower;
APFloat NewUpper = Upper;
if (Lower.isNegInfinity())
NewLower = APFloat::getLargest(getSemantics(), /*Negative=*/true);
if (Upper.isPosInfinity())
NewUpper = APFloat::getLargest(getSemantics(), /*Negative=*/false);
bool UnusedFlag;
removeInf(NewLower, NewUpper, /*HasPosInf=*/UnusedFlag,
/*HasNegInf=*/UnusedFlag);
canonicalizeRange(NewLower, NewUpper);
return ConstantFPRange(std::move(NewLower), std::move(NewUpper), MayBeQNaN,
MayBeSNaN);
Expand All @@ -444,3 +460,49 @@ ConstantFPRange ConstantFPRange::cast(const fltSemantics &DstSem,
/*MayBeQNaNVal=*/MayBeQNaN || MayBeSNaN,
/*MayBeSNaNVal=*/false);
}

ConstantFPRange ConstantFPRange::add(const ConstantFPRange &Other) const {
bool ResMayBeQNaN = ((MayBeQNaN || MayBeSNaN) && !Other.isEmptySet()) ||
((Other.MayBeQNaN || Other.MayBeSNaN) && !isEmptySet());
if (isNaNOnly() || Other.isNaNOnly())
return getNaNOnly(getSemantics(), /*MayBeQNaN=*/ResMayBeQNaN,
/*MayBeSNaN=*/false);
bool LHSHasNegInf = false, LHSHasPosInf = false;
APFloat LHSLower = Lower, LHSUpper = Upper;
bool LHSFiniteIsNonEmpty =
removeInf(LHSLower, LHSUpper, LHSHasPosInf, LHSHasNegInf);
bool RHSHasNegInf = false, RHSHasPosInf = false;
APFloat RHSLower = Other.Lower, RHSUpper = Other.Upper;
bool RHSFiniteIsNonEmpty =
removeInf(RHSLower, RHSUpper, RHSHasPosInf, RHSHasNegInf);
// -inf + +inf = QNaN
ResMayBeQNaN |=
(LHSHasNegInf && RHSHasPosInf) || (LHSHasPosInf && RHSHasNegInf);
// +inf + finite/+inf = +inf, -inf + finite/-inf = -inf
bool HasNegInf = (LHSHasNegInf && (RHSFiniteIsNonEmpty || RHSHasNegInf)) ||
(RHSHasNegInf && (LHSFiniteIsNonEmpty || LHSHasNegInf));
bool HasPosInf = (LHSHasPosInf && (RHSFiniteIsNonEmpty || RHSHasPosInf)) ||
(RHSHasPosInf && (LHSFiniteIsNonEmpty || LHSHasPosInf));
if (LHSFiniteIsNonEmpty && RHSFiniteIsNonEmpty) {
APFloat NewLower =
HasNegInf ? APFloat::getInf(LHSLower.getSemantics(), /*Negative=*/true)
: LHSLower + RHSLower;
APFloat NewUpper =
HasPosInf ? APFloat::getInf(LHSUpper.getSemantics(), /*Negative=*/false)
: LHSUpper + RHSUpper;
return ConstantFPRange(NewLower, NewUpper, ResMayBeQNaN,
/*MayBeSNaN=*/false);
}
// If both HasNegInf and HasPosInf are false, the non-NaN part is empty.
// We just return the canonical form [+inf, -inf] for the empty non-NaN set.
return ConstantFPRange(
APFloat::getInf(Lower.getSemantics(), /*Negative=*/HasNegInf),
APFloat::getInf(Upper.getSemantics(), /*Negative=*/!HasPosInf),
ResMayBeQNaN,
/*MayBeSNaN=*/false);
}

ConstantFPRange ConstantFPRange::sub(const ConstantFPRange &Other) const {
// fsub X, Y = fadd X, (fneg Y)
return add(Other.negate());
}
186 changes: 163 additions & 23 deletions llvm/unittests/IR/ConstantFPRangeTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class ConstantFPRangeTest : public ::testing::Test {
static ConstantFPRange Full;
static ConstantFPRange Empty;
static ConstantFPRange Finite;
static ConstantFPRange NonNaN;
static ConstantFPRange One;
static ConstantFPRange PosZero;
static ConstantFPRange NegZero;
Expand All @@ -44,6 +45,8 @@ ConstantFPRange ConstantFPRangeTest::Empty =
ConstantFPRange::getEmpty(APFloat::IEEEdouble());
ConstantFPRange ConstantFPRangeTest::Finite =
ConstantFPRange::getFinite(APFloat::IEEEdouble());
ConstantFPRange ConstantFPRangeTest::NonNaN =
ConstantFPRange::getNonNaN(APFloat::IEEEdouble());
ConstantFPRange ConstantFPRangeTest::One = ConstantFPRange(APFloat(1.0));
ConstantFPRange ConstantFPRangeTest::PosZero = ConstantFPRange(
APFloat::getZero(APFloat::IEEEdouble(), /*Negative=*/false));
Expand Down Expand Up @@ -79,26 +82,35 @@ static void strictNext(APFloat &V) {
V.next(/*nextDown=*/false);
}

enum class SparseLevel {
Dense,
SpecialValuesWithAllPowerOfTwos,
SpecialValuesOnly,
};

template <typename Fn>
static void EnumerateConstantFPRangesImpl(Fn TestFn, bool Exhaustive,
static void EnumerateConstantFPRangesImpl(Fn TestFn, SparseLevel Level,
bool MayBeQNaN, bool MayBeSNaN) {
const fltSemantics &Sem = APFloat::Float8E4M3();
APFloat PosInf = APFloat::getInf(Sem, /*Negative=*/false);
APFloat NegInf = APFloat::getInf(Sem, /*Negative=*/true);
TestFn(ConstantFPRange(PosInf, NegInf, MayBeQNaN, MayBeSNaN));

if (!Exhaustive) {
if (Level != SparseLevel::Dense) {
SmallVector<APFloat, 36> Values;
Values.push_back(APFloat::getInf(Sem, /*Negative=*/true));
Values.push_back(APFloat::getLargest(Sem, /*Negative=*/true));
unsigned BitWidth = APFloat::semanticsSizeInBits(Sem);
unsigned Exponents = APFloat::semanticsMaxExponent(Sem) -
APFloat::semanticsMinExponent(Sem) + 3;
unsigned MantissaBits = APFloat::semanticsPrecision(Sem) - 1;
// Add -2^(max exponent), -2^(max exponent-1), ..., -2^(min exponent)
for (unsigned M = Exponents - 2; M != 0; --M)
Values.push_back(
APFloat(Sem, APInt(BitWidth, (M + Exponents) << MantissaBits)));
if (Level == SparseLevel::SpecialValuesWithAllPowerOfTwos) {
// Add -2^(max exponent), -2^(max exponent-1), ..., -2^(min exponent)
for (unsigned M = Exponents - 2; M != 0; --M)
Values.push_back(
APFloat(Sem, APInt(BitWidth, (M + Exponents) << MantissaBits)));
}
Values.push_back(APFloat::getSmallestNormalized(Sem, /*Negative=*/true));
Values.push_back(APFloat::getSmallest(Sem, /*Negative=*/true));
Values.push_back(APFloat::getZero(Sem, /*Negative=*/true));
size_t E = Values.size();
Expand Down Expand Up @@ -127,26 +139,30 @@ static void EnumerateConstantFPRangesImpl(Fn TestFn, bool Exhaustive,
}

template <typename Fn>
static void EnumerateConstantFPRanges(Fn TestFn, bool Exhaustive) {
EnumerateConstantFPRangesImpl(TestFn, Exhaustive, /*MayBeQNaN=*/false,
static void EnumerateConstantFPRanges(Fn TestFn, SparseLevel Level,
bool IgnoreSNaNs = false) {
EnumerateConstantFPRangesImpl(TestFn, Level, /*MayBeQNaN=*/false,
/*MayBeSNaN=*/false);
EnumerateConstantFPRangesImpl(TestFn, Exhaustive, /*MayBeQNaN=*/false,
/*MayBeSNaN=*/true);
EnumerateConstantFPRangesImpl(TestFn, Exhaustive, /*MayBeQNaN=*/true,
EnumerateConstantFPRangesImpl(TestFn, Level, /*MayBeQNaN=*/true,
/*MayBeSNaN=*/false);
EnumerateConstantFPRangesImpl(TestFn, Exhaustive, /*MayBeQNaN=*/true,
if (IgnoreSNaNs)
return;
EnumerateConstantFPRangesImpl(TestFn, Level, /*MayBeQNaN=*/false,
/*MayBeSNaN=*/true);
EnumerateConstantFPRangesImpl(TestFn, Level, /*MayBeQNaN=*/true,
/*MayBeSNaN=*/true);
}

template <typename Fn>
static void EnumerateTwoInterestingConstantFPRanges(Fn TestFn,
bool Exhaustive) {
SparseLevel Level) {
EnumerateConstantFPRanges(
[&](const ConstantFPRange &CR1) {
EnumerateConstantFPRanges(
[&](const ConstantFPRange &CR2) { TestFn(CR1, CR2); }, Exhaustive);
[&](const ConstantFPRange &CR2) { TestFn(CR1, CR2); }, Level,
/*IgnoreSNaNs=*/true);
},
Exhaustive);
Level, /*IgnoreSNaNs=*/true);
}

template <typename Fn>
Expand Down Expand Up @@ -348,16 +364,25 @@ TEST_F(ConstantFPRangeTest, ExhaustivelyEnumerate) {
constexpr unsigned Expected = 4 * ((NNaNValues + 1) * NNaNValues / 2 + 1);
unsigned Count = 0;
EnumerateConstantFPRanges([&](const ConstantFPRange &) { ++Count; },
/*Exhaustive=*/true);
SparseLevel::Dense);
EXPECT_EQ(Expected, Count);
}

TEST_F(ConstantFPRangeTest, Enumerate) {
constexpr unsigned NNaNValues = 2 * ((1 << 4) - 2 + 4);
constexpr unsigned NNaNValues = 2 * ((1 << 4) - 2 + 5);
constexpr unsigned Expected = 4 * ((NNaNValues + 1) * NNaNValues / 2 + 1);
unsigned Count = 0;
EnumerateConstantFPRanges([&](const ConstantFPRange &) { ++Count; },
/*Exhaustive=*/false);
SparseLevel::SpecialValuesWithAllPowerOfTwos);
EXPECT_EQ(Expected, Count);
}

TEST_F(ConstantFPRangeTest, EnumerateWithSpecialValuesOnly) {
constexpr unsigned NNaNValues = 2 * 5;
constexpr unsigned Expected = 4 * ((NNaNValues + 1) * NNaNValues / 2 + 1);
unsigned Count = 0;
EnumerateConstantFPRanges([&](const ConstantFPRange &) { ++Count; },
SparseLevel::SpecialValuesOnly);
EXPECT_EQ(Expected, Count);
}

Expand Down Expand Up @@ -459,7 +484,7 @@ TEST_F(ConstantFPRangeTest, FPClassify) {
EXPECT_EQ(SignBit, CR.getSignBit()) << CR;
EXPECT_EQ(Mask, CR.classify()) << CR;
},
/*Exhaustive=*/true);
SparseLevel::Dense);
#endif
}

Expand Down Expand Up @@ -560,7 +585,7 @@ TEST_F(ConstantFPRangeTest, makeAllowedFCmpRegion) {
<< "Suboptimal result for makeAllowedFCmpRegion(" << Pred << ", "
<< CR << ")";
},
/*Exhaustive=*/false);
SparseLevel::SpecialValuesWithAllPowerOfTwos);
}
#endif
}
Expand Down Expand Up @@ -671,7 +696,7 @@ TEST_F(ConstantFPRangeTest, makeSatisfyingFCmpRegion) {
<< ", " << CR << ")";
}
},
/*Exhaustive=*/false);
SparseLevel::SpecialValuesWithAllPowerOfTwos);
}
#endif
}
Expand Down Expand Up @@ -804,13 +829,13 @@ TEST_F(ConstantFPRangeTest, negate) {
}

TEST_F(ConstantFPRangeTest, getWithout) {
EXPECT_EQ(Full.getWithoutNaN(), ConstantFPRange::getNonNaN(Sem));
EXPECT_EQ(Full.getWithoutNaN(), NonNaN);
EXPECT_EQ(NaN.getWithoutNaN(), Empty);

EXPECT_EQ(NaN.getWithoutInf(), NaN);
EXPECT_EQ(PosInf.getWithoutInf(), Empty);
EXPECT_EQ(NegInf.getWithoutInf(), Empty);
EXPECT_EQ(ConstantFPRange::getNonNaN(Sem).getWithoutInf(), Finite);
EXPECT_EQ(NonNaN.getWithoutInf(), Finite);
EXPECT_EQ(Zero.getWithoutInf(), Zero);
EXPECT_EQ(ConstantFPRange::getNonNaN(APFloat::getInf(Sem, /*Negative=*/true),
APFloat(3.0))
Expand Down Expand Up @@ -925,4 +950,119 @@ TEST_F(ConstantFPRangeTest, cast) {
/*IgnoreNaNPayload=*/true);
}

TEST_F(ConstantFPRangeTest, add) {
EXPECT_EQ(Full.add(Full), NonNaN.unionWith(QNaN));
EXPECT_EQ(Full.add(Empty), Empty);
EXPECT_EQ(Empty.add(Full), Empty);
EXPECT_EQ(Empty.add(Empty), Empty);
EXPECT_EQ(One.add(One), ConstantFPRange(APFloat(2.0)));
EXPECT_EQ(Some.add(Some),
ConstantFPRange::getNonNaN(APFloat(-6.0), APFloat(6.0)));
EXPECT_EQ(SomePos.add(SomeNeg),
ConstantFPRange::getNonNaN(APFloat(-3.0), APFloat(3.0)));
EXPECT_EQ(PosInf.add(PosInf), PosInf);
EXPECT_EQ(NegInf.add(NegInf), NegInf);
EXPECT_EQ(PosInf.add(Finite.unionWith(PosInf)), PosInf);
EXPECT_EQ(NegInf.add(Finite.unionWith(NegInf)), NegInf);
EXPECT_EQ(PosInf.add(Finite.unionWith(NegInf)), PosInf.unionWith(QNaN));
EXPECT_EQ(NegInf.add(Finite.unionWith(PosInf)), NegInf.unionWith(QNaN));
EXPECT_EQ(PosInf.add(NegInf), QNaN);
EXPECT_EQ(NegInf.add(PosInf), QNaN);
EXPECT_EQ(PosZero.add(NegZero), PosZero);
EXPECT_EQ(PosZero.add(Zero), PosZero);
EXPECT_EQ(NegZero.add(NegZero), NegZero);
EXPECT_EQ(NegZero.add(Zero), Zero);
EXPECT_EQ(NaN.add(NaN), QNaN);
EXPECT_EQ(NaN.add(Finite), QNaN);
EXPECT_EQ(NonNaN.unionWith(NaN).add(NonNaN), NonNaN.unionWith(QNaN));
EXPECT_EQ(PosInf.unionWith(QNaN).add(PosInf), PosInf.unionWith(QNaN));
EXPECT_EQ(PosInf.unionWith(NaN).add(ConstantFPRange(APFloat(24.0))),
PosInf.unionWith(QNaN));

#if defined(EXPENSIVE_CHECKS)
EnumerateTwoInterestingConstantFPRanges(
[](const ConstantFPRange &LHS, const ConstantFPRange &RHS) {
ConstantFPRange Res = LHS.add(RHS);
ConstantFPRange Expected =
ConstantFPRange::getEmpty(LHS.getSemantics());
EnumerateValuesInConstantFPRange(
LHS,
[&](const APFloat &LHSC) {
EnumerateValuesInConstantFPRange(
RHS,
[&](const APFloat &RHSC) {
APFloat Sum = LHSC + RHSC;
EXPECT_TRUE(Res.contains(Sum))
<< "Wrong result for " << LHS << " + " << RHS
<< ". The result " << Res << " should contain " << Sum;
if (!Expected.contains(Sum))
Expected = Expected.unionWith(ConstantFPRange(Sum));
},
/*IgnoreNaNPayload=*/true);
},
/*IgnoreNaNPayload=*/true);
EXPECT_EQ(Res, Expected)
<< "Suboptimal result for " << LHS << " + " << RHS << ". Expected "
<< Expected << ", but got " << Res;
},
SparseLevel::SpecialValuesOnly);
#endif
}

TEST_F(ConstantFPRangeTest, sub) {
EXPECT_EQ(Full.sub(Full), NonNaN.unionWith(QNaN));
EXPECT_EQ(Full.sub(Empty), Empty);
EXPECT_EQ(Empty.sub(Full), Empty);
EXPECT_EQ(Empty.sub(Empty), Empty);
EXPECT_EQ(One.sub(One), ConstantFPRange(APFloat(0.0)));
EXPECT_EQ(Some.sub(Some),
ConstantFPRange::getNonNaN(APFloat(-6.0), APFloat(6.0)));
EXPECT_EQ(SomePos.sub(SomeNeg),
ConstantFPRange::getNonNaN(APFloat(0.0), APFloat(6.0)));
EXPECT_EQ(PosInf.sub(NegInf), PosInf);
EXPECT_EQ(NegInf.sub(PosInf), NegInf);
EXPECT_EQ(PosInf.sub(Finite.unionWith(NegInf)), PosInf);
EXPECT_EQ(NegInf.sub(Finite.unionWith(PosInf)), NegInf);
EXPECT_EQ(PosInf.sub(Finite.unionWith(PosInf)), PosInf.unionWith(QNaN));
EXPECT_EQ(NegInf.sub(Finite.unionWith(NegInf)), NegInf.unionWith(QNaN));
EXPECT_EQ(PosInf.sub(PosInf), QNaN);
EXPECT_EQ(NegInf.sub(NegInf), QNaN);
EXPECT_EQ(PosZero.sub(NegZero), PosZero);
EXPECT_EQ(PosZero.sub(Zero), PosZero);
EXPECT_EQ(NegZero.sub(NegZero), PosZero);
EXPECT_EQ(NegZero.sub(PosZero), NegZero);
EXPECT_EQ(NegZero.sub(Zero), Zero);
EXPECT_EQ(NaN.sub(NaN), QNaN);
EXPECT_EQ(NaN.add(Finite), QNaN);

#if defined(EXPENSIVE_CHECKS)
EnumerateTwoInterestingConstantFPRanges(
[](const ConstantFPRange &LHS, const ConstantFPRange &RHS) {
ConstantFPRange Res = LHS.sub(RHS);
ConstantFPRange Expected =
ConstantFPRange::getEmpty(LHS.getSemantics());
EnumerateValuesInConstantFPRange(
LHS,
[&](const APFloat &LHSC) {
EnumerateValuesInConstantFPRange(
RHS,
[&](const APFloat &RHSC) {
APFloat Diff = LHSC - RHSC;
EXPECT_TRUE(Res.contains(Diff))
<< "Wrong result for " << LHS << " - " << RHS
<< ". The result " << Res << " should contain " << Diff;
if (!Expected.contains(Diff))
Expected = Expected.unionWith(ConstantFPRange(Diff));
},
/*IgnoreNaNPayload=*/true);
},
/*IgnoreNaNPayload=*/true);
EXPECT_EQ(Res, Expected)
<< "Suboptimal result for " << LHS << " - " << RHS << ". Expected "
<< Expected << ", but got " << Res;
},
SparseLevel::SpecialValuesOnly);
#endif
}

} // anonymous namespace