diff --git a/llvm/include/llvm/IR/ConstantFPRange.h b/llvm/include/llvm/IR/ConstantFPRange.h index d47f6c02c883d..39dc7c100d6f7 100644 --- a/llvm/include/llvm/IR/ConstantFPRange.h +++ b/llvm/include/llvm/IR/ConstantFPRange.h @@ -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) { diff --git a/llvm/lib/IR/ConstantFPRange.cpp b/llvm/lib/IR/ConstantFPRange.cpp index 070e833f4d1c0..51d2e215c980c 100644 --- a/llvm/lib/IR/ConstantFPRange.cpp +++ b/llvm/lib/IR/ConstantFPRange.cpp @@ -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); @@ -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()); +} diff --git a/llvm/unittests/IR/ConstantFPRangeTest.cpp b/llvm/unittests/IR/ConstantFPRangeTest.cpp index 58a65b9a96ab8..cf9b31cf88480 100644 --- a/llvm/unittests/IR/ConstantFPRangeTest.cpp +++ b/llvm/unittests/IR/ConstantFPRangeTest.cpp @@ -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; @@ -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)); @@ -79,15 +82,21 @@ static void strictNext(APFloat &V) { V.next(/*nextDown=*/false); } +enum class SparseLevel { + Dense, + SpecialValuesWithAllPowerOfTwos, + SpecialValuesOnly, +}; + template -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 Values; Values.push_back(APFloat::getInf(Sem, /*Negative=*/true)); Values.push_back(APFloat::getLargest(Sem, /*Negative=*/true)); @@ -95,10 +104,13 @@ static void EnumerateConstantFPRangesImpl(Fn TestFn, bool Exhaustive, 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(); @@ -127,26 +139,30 @@ static void EnumerateConstantFPRangesImpl(Fn TestFn, bool Exhaustive, } template -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 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 @@ -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); } @@ -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 } @@ -560,7 +585,7 @@ TEST_F(ConstantFPRangeTest, makeAllowedFCmpRegion) { << "Suboptimal result for makeAllowedFCmpRegion(" << Pred << ", " << CR << ")"; }, - /*Exhaustive=*/false); + SparseLevel::SpecialValuesWithAllPowerOfTwos); } #endif } @@ -671,7 +696,7 @@ TEST_F(ConstantFPRangeTest, makeSatisfyingFCmpRegion) { << ", " << CR << ")"; } }, - /*Exhaustive=*/false); + SparseLevel::SpecialValuesWithAllPowerOfTwos); } #endif } @@ -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)) @@ -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