//===- ConstantRangeTest.cpp - ConstantRange 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/IR/ConstantFPRange.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/Operator.h"
#include "gtest/gtest.h"

using namespace llvm;

namespace {

class ConstantFPRangeTest : public ::testing::Test {
protected:
  static const fltSemantics &Sem;
  static ConstantFPRange Full;
  static ConstantFPRange Empty;
  static ConstantFPRange Finite;
  static ConstantFPRange One;
  static ConstantFPRange PosZero;
  static ConstantFPRange NegZero;
  static ConstantFPRange Zero;
  static ConstantFPRange PosInf;
  static ConstantFPRange NegInf;
  static ConstantFPRange Denormal;
  static ConstantFPRange NaN;
  static ConstantFPRange SNaN;
  static ConstantFPRange QNaN;
  static ConstantFPRange Some;
  static ConstantFPRange SomePos;
  static ConstantFPRange SomeNeg;
};

const fltSemantics &ConstantFPRangeTest::Sem = APFloat::IEEEdouble();
ConstantFPRange ConstantFPRangeTest::Full =
    ConstantFPRange::getFull(APFloat::IEEEdouble());
ConstantFPRange ConstantFPRangeTest::Empty =
    ConstantFPRange::getEmpty(APFloat::IEEEdouble());
ConstantFPRange ConstantFPRangeTest::Finite =
    ConstantFPRange::getFinite(APFloat::IEEEdouble());
ConstantFPRange ConstantFPRangeTest::One = ConstantFPRange(APFloat(1.0));
ConstantFPRange ConstantFPRangeTest::PosZero = ConstantFPRange(
    APFloat::getZero(APFloat::IEEEdouble(), /*Negative=*/false));
ConstantFPRange ConstantFPRangeTest::NegZero =
    ConstantFPRange(APFloat::getZero(APFloat::IEEEdouble(), /*Negative=*/true));
ConstantFPRange ConstantFPRangeTest::Zero = ConstantFPRange::getNonNaN(
    APFloat::getZero(APFloat::IEEEdouble(), /*Negative=*/true),
    APFloat::getZero(APFloat::IEEEdouble(), /*Negative=*/false));
ConstantFPRange ConstantFPRangeTest::Denormal =
    ConstantFPRange(APFloat::getSmallest(APFloat::IEEEdouble()));
ConstantFPRange ConstantFPRangeTest::PosInf =
    ConstantFPRange(APFloat::getInf(APFloat::IEEEdouble(), /*Negative=*/false));
ConstantFPRange ConstantFPRangeTest::NegInf =
    ConstantFPRange(APFloat::getInf(APFloat::IEEEdouble(), /*Negative=*/true));
ConstantFPRange ConstantFPRangeTest::NaN = ConstantFPRange::getNaNOnly(
    APFloat::IEEEdouble(), /*MayBeQNaN=*/true, /*MayBeSNaN=*/true);
ConstantFPRange ConstantFPRangeTest::SNaN =
    ConstantFPRange(APFloat::getSNaN(APFloat::IEEEdouble()));
ConstantFPRange ConstantFPRangeTest::QNaN =
    ConstantFPRange(APFloat::getQNaN(APFloat::IEEEdouble()));
ConstantFPRange ConstantFPRangeTest::Some =
    ConstantFPRange::getNonNaN(APFloat(-3.0), APFloat(3.0));
ConstantFPRange ConstantFPRangeTest::SomePos = ConstantFPRange::getNonNaN(
    APFloat::getZero(APFloat::IEEEdouble(), /*Negative=*/false), APFloat(3.0));
ConstantFPRange ConstantFPRangeTest::SomeNeg = ConstantFPRange::getNonNaN(
    APFloat(-3.0), APFloat::getZero(APFloat::IEEEdouble(), /*Negative=*/true));

static void strictNext(APFloat &V) {
  // Note: nextUp(+/-0) is smallest.
  if (V.isNegZero())
    V = APFloat::getZero(V.getSemantics(), /*Negative=*/false);
  else
    V.next(/*nextDown=*/false);
}

template <typename Fn>
static void EnumerateConstantFPRangesImpl(Fn TestFn, bool Exhaustive,
                                          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) {
    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)));
    Values.push_back(APFloat::getSmallest(Sem, /*Negative=*/true));
    Values.push_back(APFloat::getZero(Sem, /*Negative=*/true));
    size_t E = Values.size();
    for (size_t I = 1; I <= E; ++I)
      Values.push_back(-Values[E - I]);
    for (size_t I = 0; I != Values.size(); ++I)
      for (size_t J = I; J != Values.size(); ++J)
        TestFn(ConstantFPRange(Values[I], Values[J], MayBeQNaN, MayBeSNaN));
    return;
  }

  auto Next = [&](APFloat &V) {
    if (V.isPosInfinity())
      return false;
    strictNext(V);
    return true;
  };

  APFloat Lower = NegInf;
  do {
    APFloat Upper = Lower;
    do {
      TestFn(ConstantFPRange(Lower, Upper, MayBeQNaN, MayBeSNaN));
    } while (Next(Upper));
  } while (Next(Lower));
}

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

template <typename Fn>
static void EnumerateTwoInterestingConstantFPRanges(Fn TestFn,
                                                    bool Exhaustive) {
  EnumerateConstantFPRanges(
      [&](const ConstantFPRange &CR1) {
        EnumerateConstantFPRanges(
            [&](const ConstantFPRange &CR2) { TestFn(CR1, CR2); }, Exhaustive);
      },
      Exhaustive);
}

template <typename Fn>
static void EnumerateValuesInConstantFPRange(const ConstantFPRange &CR,
                                             Fn TestFn, bool IgnoreNaNPayload) {
  const fltSemantics &Sem = CR.getSemantics();
  if (IgnoreNaNPayload) {
    if (CR.containsSNaN()) {
      TestFn(APFloat::getSNaN(Sem, false));
      TestFn(APFloat::getSNaN(Sem, true));
    }
    if (CR.containsQNaN()) {
      TestFn(APFloat::getQNaN(Sem, false));
      TestFn(APFloat::getQNaN(Sem, true));
    }
    if (CR.isNaNOnly())
      return;
    APFloat Lower = CR.getLower();
    const APFloat &Upper = CR.getUpper();
    auto Next = [&](APFloat &V) {
      if (V.bitwiseIsEqual(Upper))
        return false;
      strictNext(V);
      return true;
    };
    do
      TestFn(Lower);
    while (Next(Lower));
  } else {
    unsigned Bits = APFloat::semanticsSizeInBits(Sem);
    assert(Bits < 32 && "Too many bits");
    for (unsigned I = 0, E = (1U << Bits) - 1; I != E; ++I) {
      APFloat V(Sem, APInt(Bits, I));
      if (CR.contains(V))
        TestFn(V);
    }
  }
}

template <typename Fn>
static bool AnyOfValueInConstantFPRange(const ConstantFPRange &CR, Fn TestFn,
                                        bool IgnoreNaNPayload) {
  const fltSemantics &Sem = CR.getSemantics();
  if (IgnoreNaNPayload) {
    if (CR.containsSNaN()) {
      if (TestFn(APFloat::getSNaN(Sem, false)))
        return true;
      if (TestFn(APFloat::getSNaN(Sem, true)))
        return true;
    }
    if (CR.containsQNaN()) {
      if (TestFn(APFloat::getQNaN(Sem, false)))
        return true;
      if (TestFn(APFloat::getQNaN(Sem, true)))
        return true;
    }
    if (CR.isNaNOnly())
      return false;
    APFloat Lower = CR.getLower();
    const APFloat &Upper = CR.getUpper();
    auto Next = [&](APFloat &V) {
      if (V.bitwiseIsEqual(Upper))
        return false;
      strictNext(V);
      return true;
    };
    do {
      if (TestFn(Lower))
        return true;
    } while (Next(Lower));
  } else {
    unsigned Bits = APFloat::semanticsSizeInBits(Sem);
    assert(Bits < 32 && "Too many bits");
    for (unsigned I = 0, E = (1U << Bits) - 1; I != E; ++I) {
      APFloat V(Sem, APInt(Bits, I));
      if (CR.contains(V) && TestFn(V))
        return true;
    }
  }
  return false;
}

TEST_F(ConstantFPRangeTest, Basics) {
  EXPECT_TRUE(Full.isFullSet());
  EXPECT_FALSE(Full.isEmptySet());
  EXPECT_TRUE(Full.contains(APFloat::getNaN(Sem)));
  EXPECT_TRUE(Full.contains(APFloat::getInf(Sem, /*Negative=*/false)));
  EXPECT_TRUE(Full.contains(APFloat::getInf(Sem, /*Negative=*/true)));
  EXPECT_TRUE(Full.contains(APFloat::getZero(Sem, /*Negative=*/false)));
  EXPECT_TRUE(Full.contains(APFloat::getZero(Sem, /*Negative=*/true)));
  EXPECT_TRUE(Full.contains(APFloat::getSmallest(Sem)));
  EXPECT_TRUE(Full.contains(APFloat(2.0)));
  EXPECT_TRUE(Full.contains(Full));
  EXPECT_TRUE(Full.contains(Empty));
  EXPECT_TRUE(Full.contains(Finite));
  EXPECT_TRUE(Full.contains(Zero));
  EXPECT_TRUE(Full.contains(Some));

  EXPECT_FALSE(Empty.isFullSet());
  EXPECT_TRUE(Empty.isEmptySet());
  EXPECT_FALSE(Empty.contains(APFloat::getNaN(Sem)));
  EXPECT_FALSE(Empty.contains(APFloat::getInf(Sem, /*Negative=*/false)));
  EXPECT_FALSE(Empty.contains(APFloat::getZero(Sem, /*Negative=*/true)));
  EXPECT_FALSE(Empty.contains(APFloat(2.0)));
  EXPECT_TRUE(Empty.contains(Empty));

  EXPECT_FALSE(Finite.isFullSet());
  EXPECT_FALSE(Finite.isEmptySet());
  EXPECT_FALSE(Finite.contains(APFloat::getNaN(Sem)));
  EXPECT_FALSE(Finite.contains(APFloat::getInf(Sem, /*Negative=*/false)));
  EXPECT_FALSE(Finite.contains(APFloat::getInf(Sem, /*Negative=*/true)));
  EXPECT_TRUE(Finite.contains(APFloat::getLargest(Sem, /*Negative=*/false)));
  EXPECT_TRUE(Finite.contains(APFloat::getLargest(Sem, /*Negative=*/true)));
  EXPECT_TRUE(Finite.contains(Finite));
  EXPECT_TRUE(Finite.contains(Some));
  EXPECT_TRUE(Finite.contains(Denormal));
  EXPECT_TRUE(Finite.contains(Zero));
  EXPECT_FALSE(Finite.contains(PosInf));
  EXPECT_FALSE(Finite.contains(NaN));

  EXPECT_TRUE(One.contains(APFloat(1.0)));
  EXPECT_FALSE(One.contains(APFloat(1.1)));

  EXPECT_TRUE(PosZero.contains(APFloat::getZero(Sem, /*Negative=*/false)));
  EXPECT_FALSE(PosZero.contains(APFloat::getZero(Sem, /*Negative=*/true)));
  EXPECT_TRUE(NegZero.contains(APFloat::getZero(Sem, /*Negative=*/true)));
  EXPECT_FALSE(NegZero.contains(APFloat::getZero(Sem, /*Negative=*/false)));
  EXPECT_TRUE(Zero.contains(PosZero));
  EXPECT_TRUE(Zero.contains(NegZero));
  EXPECT_TRUE(Denormal.contains(APFloat::getSmallest(Sem)));
  EXPECT_FALSE(Denormal.contains(APFloat::getSmallestNormalized(Sem)));
  EXPECT_TRUE(PosInf.contains(APFloat::getInf(Sem, /*Negative=*/false)));
  EXPECT_TRUE(NegInf.contains(APFloat::getInf(Sem, /*Negative=*/true)));
  EXPECT_TRUE(NaN.contains(APFloat::getQNaN(Sem)));
  EXPECT_TRUE(NaN.contains(APFloat::getSNaN(Sem)));
  EXPECT_TRUE(NaN.contains(SNaN));
  EXPECT_TRUE(NaN.contains(QNaN));

  EXPECT_TRUE(Some.contains(APFloat(3.0)));
  EXPECT_TRUE(Some.contains(APFloat(-3.0)));
  EXPECT_FALSE(Some.contains(APFloat(4.0)));
  APFloat Next1(3.0);
  Next1.next(/*nextDown=*/true);
  EXPECT_TRUE(Some.contains(Next1));
  APFloat Next2(3.0);
  Next2.next(/*nextDown=*/false);
  EXPECT_FALSE(Some.contains(Next2));
  EXPECT_TRUE(Some.contains(Zero));
  EXPECT_TRUE(Some.contains(Some));
  EXPECT_TRUE(Some.contains(One));
  EXPECT_FALSE(Some.contains(NaN));
  EXPECT_FALSE(Some.contains(PosInf));
  EXPECT_TRUE(SomePos.contains(APFloat(3.0)));
  EXPECT_FALSE(SomeNeg.contains(APFloat(3.0)));
  EXPECT_TRUE(SomeNeg.contains(APFloat(-3.0)));
  EXPECT_FALSE(SomePos.contains(APFloat(-3.0)));
  EXPECT_TRUE(Some.contains(SomePos));
  EXPECT_TRUE(Some.contains(SomeNeg));
}

TEST_F(ConstantFPRangeTest, Equality) {
  EXPECT_EQ(Full, Full);
  EXPECT_EQ(Empty, Empty);
  EXPECT_EQ(One, One);
  EXPECT_EQ(Some, Some);
  EXPECT_NE(Full, Empty);
  EXPECT_NE(Zero, PosZero);
  EXPECT_NE(One, NaN);
  EXPECT_NE(Some, One);
  EXPECT_NE(SNaN, QNaN);
}

TEST_F(ConstantFPRangeTest, SingleElement) {
  EXPECT_EQ(Full.getSingleElement(), static_cast<APFloat *>(nullptr));
  EXPECT_EQ(Empty.getSingleElement(), static_cast<APFloat *>(nullptr));
  EXPECT_EQ(Finite.getSingleElement(), static_cast<APFloat *>(nullptr));
  EXPECT_EQ(Zero.getSingleElement(), static_cast<APFloat *>(nullptr));
  EXPECT_EQ(NaN.getSingleElement(), static_cast<APFloat *>(nullptr));
  EXPECT_EQ(SNaN.getSingleElement(), static_cast<APFloat *>(nullptr));
  EXPECT_EQ(QNaN.getSingleElement(), static_cast<APFloat *>(nullptr));

  EXPECT_EQ(*One.getSingleElement(), APFloat(1.0));
  EXPECT_EQ(*PosZero.getSingleElement(), APFloat::getZero(Sem));
  EXPECT_EQ(*PosInf.getSingleElement(), APFloat::getInf(Sem));
  ConstantFPRange PosZeroOrNaN = PosZero.unionWith(NaN);
  EXPECT_EQ(*PosZeroOrNaN.getSingleElement(/*ExcludesNaN=*/true),
            APFloat::getZero(Sem));

  EXPECT_FALSE(Full.isSingleElement());
  EXPECT_FALSE(Empty.isSingleElement());
  EXPECT_TRUE(One.isSingleElement());
  EXPECT_FALSE(Some.isSingleElement());
  EXPECT_FALSE(Zero.isSingleElement());
  EXPECT_TRUE(PosZeroOrNaN.isSingleElement(/*ExcludesNaN=*/true));
}

TEST_F(ConstantFPRangeTest, ExhaustivelyEnumerate) {
  constexpr unsigned NNaNValues = (1 << 8) - 2 * ((1 << 3) - 1);
  constexpr unsigned Expected = 4 * ((NNaNValues + 1) * NNaNValues / 2 + 1);
  unsigned Count = 0;
  EnumerateConstantFPRanges([&](const ConstantFPRange &) { ++Count; },
                            /*Exhaustive=*/true);
  EXPECT_EQ(Expected, Count);
}

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

TEST_F(ConstantFPRangeTest, IntersectWith) {
  EXPECT_EQ(Empty.intersectWith(Full), Empty);
  EXPECT_EQ(Empty.intersectWith(Empty), Empty);
  EXPECT_EQ(Empty.intersectWith(One), Empty);
  EXPECT_EQ(Empty.intersectWith(Some), Empty);
  EXPECT_EQ(Full.intersectWith(Full), Full);
  EXPECT_EQ(Some.intersectWith(Some), Some);
  EXPECT_EQ(Some.intersectWith(One), One);
  EXPECT_EQ(Full.intersectWith(One), One);
  EXPECT_EQ(Full.intersectWith(Some), Some);
  EXPECT_EQ(Some.intersectWith(SomePos), SomePos);
  EXPECT_EQ(Some.intersectWith(SomeNeg), SomeNeg);
  EXPECT_EQ(NaN.intersectWith(Finite), Empty);
  EXPECT_EQ(NaN.intersectWith(SNaN), SNaN);
  EXPECT_EQ(NaN.intersectWith(QNaN), QNaN);
  EXPECT_EQ(Finite.intersectWith(One), One);
  EXPECT_EQ(Some.intersectWith(Zero), Zero);
  EXPECT_EQ(ConstantFPRange::getNonNaN(APFloat(1.0), APFloat(4.0))
                .intersectWith(
                    ConstantFPRange::getNonNaN(APFloat(3.0), APFloat(6.0))),
            ConstantFPRange::getNonNaN(APFloat(3.0), APFloat(4.0)));
  EXPECT_EQ(ConstantFPRange::getNonNaN(APFloat(1.0), APFloat(2.0))
                .intersectWith(
                    ConstantFPRange::getNonNaN(APFloat(5.0), APFloat(6.0))),
            Empty);
}

TEST_F(ConstantFPRangeTest, UnionWith) {
  EXPECT_EQ(Empty.unionWith(Full), Full);
  EXPECT_EQ(Empty.unionWith(Empty), Empty);
  EXPECT_EQ(Empty.unionWith(One), One);
  EXPECT_EQ(Empty.unionWith(Some), Some);
  EXPECT_EQ(Full.unionWith(Full), Full);
  EXPECT_EQ(Some.unionWith(Some), Some);
  EXPECT_EQ(Some.unionWith(One), Some);
  EXPECT_EQ(Full.unionWith(Some), Full);
  EXPECT_EQ(Some.unionWith(SomePos), Some);
  EXPECT_EQ(Some.unionWith(SomeNeg), Some);
  EXPECT_EQ(Finite.unionWith(One), Finite);
  EXPECT_EQ(Some.unionWith(Zero), Some);
  EXPECT_EQ(Finite.unionWith(PosInf).unionWith(NegInf).unionWith(NaN), Full);
  EXPECT_EQ(PosZero.unionWith(NegZero), Zero);
  EXPECT_EQ(NaN.unionWith(SNaN), NaN);
  EXPECT_EQ(NaN.unionWith(QNaN), NaN);
  EXPECT_EQ(SNaN.unionWith(QNaN), NaN);
  EXPECT_EQ(
      ConstantFPRange::getNonNaN(APFloat(1.0), APFloat(4.0))
          .unionWith(ConstantFPRange::getNonNaN(APFloat(3.0), APFloat(6.0))),
      ConstantFPRange::getNonNaN(APFloat(1.0), APFloat(6.0)));
  EXPECT_EQ(
      ConstantFPRange::getNonNaN(APFloat(1.0), APFloat(2.0))
          .unionWith(ConstantFPRange::getNonNaN(APFloat(5.0), APFloat(6.0))),
      ConstantFPRange::getNonNaN(APFloat(1.0), APFloat(6.0)));
}

TEST_F(ConstantFPRangeTest, FPClassify) {
  EXPECT_EQ(Empty.classify(), fcNone);
  EXPECT_EQ(Full.classify(), fcAllFlags);
  EXPECT_EQ(Finite.classify(), fcFinite);
  EXPECT_EQ(Zero.classify(), fcZero);
  EXPECT_EQ(NaN.classify(), fcNan);
  EXPECT_EQ(SNaN.classify(), fcSNan);
  EXPECT_EQ(QNaN.classify(), fcQNan);
  EXPECT_EQ(One.classify(), fcPosNormal);
  EXPECT_EQ(Some.classify(), fcFinite);
  EXPECT_EQ(SomePos.classify(), fcPosFinite);
  EXPECT_EQ(SomeNeg.classify(), fcNegFinite);
  EXPECT_EQ(PosInf.classify(), fcPosInf);
  EXPECT_EQ(NegInf.classify(), fcNegInf);
  EXPECT_EQ(Finite.getSignBit(), std::nullopt);
  EXPECT_EQ(PosZero.getSignBit(), false);
  EXPECT_EQ(NegZero.getSignBit(), true);
  EXPECT_EQ(SomePos.getSignBit(), false);
  EXPECT_EQ(SomeNeg.getSignBit(), true);

#if defined(EXPENSIVE_CHECKS)
  EnumerateConstantFPRanges(
      [](const ConstantFPRange &CR) {
        unsigned Mask = fcNone;
        bool HasPos = false, HasNeg = false;
        EnumerateValuesInConstantFPRange(
            CR,
            [&](const APFloat &V) {
              Mask |= V.classify();
              if (V.isNegative())
                HasNeg = true;
              else
                HasPos = true;
            },
            /*IgnoreNaNPayload=*/true);

        std::optional<bool> SignBit = std::nullopt;
        if (HasPos != HasNeg)
          SignBit = HasNeg;

        EXPECT_EQ(SignBit, CR.getSignBit()) << CR;
        EXPECT_EQ(Mask, CR.classify()) << CR;
      },
      /*Exhaustive=*/true);
#endif
}

TEST_F(ConstantFPRangeTest, Print) {
  auto ToString = [](const ConstantFPRange &CR) {
    std::string Str;
    raw_string_ostream OS(Str);
    CR.print(OS);
    return Str;
  };

  EXPECT_EQ(ToString(Full), "full-set");
  EXPECT_EQ(ToString(Empty), "empty-set");
  EXPECT_EQ(ToString(NaN), "NaN");
  EXPECT_EQ(ToString(SNaN), "SNaN");
  EXPECT_EQ(ToString(QNaN), "QNaN");
  EXPECT_EQ(ToString(One), "[1, 1]");
  EXPECT_EQ(ToString(Some.unionWith(SNaN)), "[-3, 3] with SNaN");
}

#ifdef GTEST_HAS_DEATH_TEST
#ifndef NDEBUG
TEST_F(ConstantFPRangeTest, NonCanonicalEmptySet) {
  EXPECT_DEATH((void)(ConstantFPRange::getNonNaN(APFloat(1.0), APFloat(0.0))),
               "Non-canonical form");
}
TEST_F(ConstantFPRangeTest, MismatchedSemantics) {
  EXPECT_DEATH((void)(ConstantFPRange::getNonNaN(APFloat(0.0), APFloat(1.0f))),
               "Should only use the same semantics");
  EXPECT_DEATH((void)(One.contains(APFloat(1.0f))),
               "Should only use the same semantics");
  ConstantFPRange OneF32 = ConstantFPRange(APFloat(1.0f));
  EXPECT_DEATH((void)(One.contains(OneF32)),
               "Should only use the same semantics");
  EXPECT_DEATH((void)(One.intersectWith(OneF32)),
               "Should only use the same semantics");
  EXPECT_DEATH((void)(One.unionWith(OneF32)),
               "Should only use the same semantics");
}
#endif
#endif

TEST_F(ConstantFPRangeTest, makeAllowedFCmpRegion) {
  EXPECT_EQ(ConstantFPRange::makeAllowedFCmpRegion(
                FCmpInst::FCMP_OLE,
                ConstantFPRange::getNonNaN(APFloat(1.0), APFloat(2.0))),
            ConstantFPRange::getNonNaN(APFloat::getInf(Sem, /*Negative=*/true),
                                       APFloat(2.0)));
  EXPECT_EQ(
      ConstantFPRange::makeAllowedFCmpRegion(
          FCmpInst::FCMP_OLT,
          ConstantFPRange::getNonNaN(APFloat(1.0),
                                     APFloat::getInf(Sem, /*Negative=*/false))),
      ConstantFPRange::getNonNaN(APFloat::getInf(Sem, /*Negative=*/true),
                                 APFloat::getLargest(Sem, /*Negative=*/false)));
  EXPECT_EQ(
      ConstantFPRange::makeAllowedFCmpRegion(
          FCmpInst::FCMP_OGT,
          ConstantFPRange::getNonNaN(APFloat::getZero(Sem, /*Negative=*/true),
                                     APFloat(2.0))),
      ConstantFPRange::getNonNaN(APFloat::getSmallest(Sem, /*Negative=*/false),
                                 APFloat::getInf(Sem, /*Negative=*/false)));
  EXPECT_EQ(ConstantFPRange::makeAllowedFCmpRegion(
                FCmpInst::FCMP_OGE,
                ConstantFPRange::getNonNaN(APFloat(1.0), APFloat(2.0))),
            ConstantFPRange::getNonNaN(
                APFloat(1.0), APFloat::getInf(Sem, /*Negative=*/false)));
  EXPECT_EQ(ConstantFPRange::makeAllowedFCmpRegion(
                FCmpInst::FCMP_OEQ,
                ConstantFPRange::getNonNaN(APFloat(1.0), APFloat(2.0))),
            ConstantFPRange::getNonNaN(APFloat(1.0), APFloat(2.0)));

#if defined(EXPENSIVE_CHECKS)
  for (auto Pred : FCmpInst::predicates()) {
    EnumerateConstantFPRanges(
        [Pred](const ConstantFPRange &CR) {
          ConstantFPRange Res =
              ConstantFPRange::makeAllowedFCmpRegion(Pred, CR);
          ConstantFPRange Optimal =
              ConstantFPRange::getEmpty(CR.getSemantics());
          EnumerateValuesInConstantFPRange(
              ConstantFPRange::getFull(CR.getSemantics()),
              [&](const APFloat &V) {
                if (AnyOfValueInConstantFPRange(
                        CR,
                        [&](const APFloat &U) {
                          return FCmpInst::compare(V, U, Pred);
                        },
                        /*IgnoreNaNPayload=*/true))
                  Optimal = Optimal.unionWith(ConstantFPRange(V));
              },
              /*IgnoreNaNPayload=*/true);

          EXPECT_TRUE(Res.contains(Optimal))
              << "Wrong result for makeAllowedFCmpRegion(" << Pred << ", " << CR
              << "). Expected " << Optimal << ", but got " << Res;
          EXPECT_EQ(Res, Optimal)
              << "Suboptimal result for makeAllowedFCmpRegion(" << Pred << ", "
              << CR << ")";
        },
        /*Exhaustive=*/false);
  }
#endif
}

TEST_F(ConstantFPRangeTest, makeSatisfyingFCmpRegion) {
  EXPECT_EQ(ConstantFPRange::makeSatisfyingFCmpRegion(
                FCmpInst::FCMP_OLE,
                ConstantFPRange::getNonNaN(APFloat(1.0), APFloat(2.0))),
            ConstantFPRange::getNonNaN(APFloat::getInf(Sem, /*Negative=*/true),
                                       APFloat(1.0)));
  EXPECT_EQ(
      ConstantFPRange::makeSatisfyingFCmpRegion(
          FCmpInst::FCMP_OLT, ConstantFPRange::getNonNaN(
                                  APFloat::getSmallest(Sem, /*Negative=*/false),
                                  APFloat::getInf(Sem, /*Negative=*/false))),
      ConstantFPRange::getNonNaN(APFloat::getInf(Sem, /*Negative=*/true),
                                 APFloat::getZero(Sem, /*Negative=*/false)));
  EXPECT_EQ(
      ConstantFPRange::makeSatisfyingFCmpRegion(
          FCmpInst::FCMP_OGT, ConstantFPRange::getNonNaN(
                                  APFloat::getZero(Sem, /*Negative=*/true),
                                  APFloat::getZero(Sem, /*Negative=*/false))),
      ConstantFPRange::getNonNaN(APFloat::getSmallest(Sem, /*Negative=*/false),
                                 APFloat::getInf(Sem, /*Negative=*/false)));
  EXPECT_EQ(ConstantFPRange::makeSatisfyingFCmpRegion(
                FCmpInst::FCMP_OGE,
                ConstantFPRange::getNonNaN(APFloat(1.0), APFloat(2.0))),
            ConstantFPRange::getNonNaN(
                APFloat(2.0), APFloat::getInf(Sem, /*Negative=*/false)));
  EXPECT_EQ(ConstantFPRange::makeSatisfyingFCmpRegion(
                FCmpInst::FCMP_OEQ,
                ConstantFPRange::getNonNaN(APFloat(1.0), APFloat(2.0))),
            ConstantFPRange::getEmpty(Sem));
  EXPECT_EQ(ConstantFPRange::makeSatisfyingFCmpRegion(
                FCmpInst::FCMP_OEQ,
                ConstantFPRange::getNonNaN(APFloat(1.0), APFloat(1.0))),
            ConstantFPRange::getNonNaN(APFloat(1.0), APFloat(1.0)));

#if defined(EXPENSIVE_CHECKS)
  for (auto Pred : FCmpInst::predicates()) {
    EnumerateConstantFPRanges(
        [Pred](const ConstantFPRange &CR) {
          ConstantFPRange Res =
              ConstantFPRange::makeSatisfyingFCmpRegion(Pred, CR);
          // Super set of the optimal set excluding NaNs
          ConstantFPRange SuperSet(CR.getSemantics());
          bool ContainsSNaN = false;
          bool ContainsQNaN = false;
          unsigned NonNaNValsInOptimalSet = 0;
          EnumerateValuesInConstantFPRange(
              ConstantFPRange::getFull(CR.getSemantics()),
              [&](const APFloat &V) {
                if (AnyOfValueInConstantFPRange(
                        CR,
                        [&](const APFloat &U) {
                          return !FCmpInst::compare(V, U, Pred);
                        },
                        /*IgnoreNaNPayload=*/true)) {
                  EXPECT_FALSE(Res.contains(V))
                      << "Wrong result for makeSatisfyingFCmpRegion(" << Pred
                      << ", " << CR << "). The result " << Res
                      << " should not contain " << V;
                } else {
                  if (V.isNaN()) {
                    if (V.isSignaling())
                      ContainsSNaN = true;
                    else
                      ContainsQNaN = true;
                  } else {
                    SuperSet = SuperSet.unionWith(ConstantFPRange(V));
                    ++NonNaNValsInOptimalSet;
                  }
                }
              },
              /*IgnoreNaNPayload=*/true);

          // Check optimality

          // The usefullness of making the result optimal for one/une is
          // questionable.
          if (Pred == FCmpInst::FCMP_ONE || Pred == FCmpInst::FCMP_UNE)
            return;

          EXPECT_FALSE(ContainsSNaN && !Res.containsSNaN())
              << "Suboptimal result for makeSatisfyingFCmpRegion(" << Pred
              << ", " << CR << "), should contain SNaN, but got " << Res;
          EXPECT_FALSE(ContainsQNaN && !Res.containsQNaN())
              << "Suboptimal result for makeSatisfyingFCmpRegion(" << Pred
              << ", " << CR << "), should contain QNaN, but got " << Res;

          // We only care about the cases where the result is representable by
          // ConstantFPRange.
          unsigned NonNaNValsInSuperSet = 0;
          EnumerateValuesInConstantFPRange(
              SuperSet,
              [&](const APFloat &V) {
                if (!V.isNaN())
                  ++NonNaNValsInSuperSet;
              },
              /*IgnoreNaNPayload=*/true);

          if (NonNaNValsInSuperSet == NonNaNValsInOptimalSet) {
            ConstantFPRange Optimal =
                ConstantFPRange(SuperSet.getLower(), SuperSet.getUpper(),
                                ContainsQNaN, ContainsSNaN);
            EXPECT_EQ(Res, Optimal)
                << "Suboptimal result for makeSatisfyingFCmpRegion(" << Pred
                << ", " << CR << ")";
          }
        },
        /*Exhaustive=*/false);
  }
#endif
}

TEST_F(ConstantFPRangeTest, fcmp) {
  std::vector<ConstantFPRange> InterestingRanges;
  const fltSemantics &Sem = APFloat::Float8E4M3();
  auto FpImm = [&](double V) {
    bool ignored;
    APFloat APF(V);
    APF.convert(Sem, APFloat::rmNearestTiesToEven, &ignored);
    return APF;
  };

  InterestingRanges.push_back(ConstantFPRange::getEmpty(Sem));
  InterestingRanges.push_back(ConstantFPRange::getFull(Sem));
  InterestingRanges.push_back(ConstantFPRange::getFinite(Sem));
  InterestingRanges.push_back(ConstantFPRange(FpImm(1.0)));
  InterestingRanges.push_back(
      ConstantFPRange(APFloat::getZero(Sem, /*Negative=*/false)));
  InterestingRanges.push_back(
      ConstantFPRange(APFloat::getZero(Sem, /*Negative=*/true)));
  InterestingRanges.push_back(
      ConstantFPRange(APFloat::getInf(Sem, /*Negative=*/false)));
  InterestingRanges.push_back(
      ConstantFPRange(APFloat::getInf(Sem, /*Negative=*/true)));
  InterestingRanges.push_back(
      ConstantFPRange(APFloat::getSmallest(Sem, /*Negative=*/false)));
  InterestingRanges.push_back(
      ConstantFPRange(APFloat::getSmallest(Sem, /*Negative=*/true)));
  InterestingRanges.push_back(
      ConstantFPRange(APFloat::getLargest(Sem, /*Negative=*/false)));
  InterestingRanges.push_back(
      ConstantFPRange(APFloat::getLargest(Sem, /*Negative=*/true)));
  InterestingRanges.push_back(
      ConstantFPRange::getNaNOnly(Sem, /*MayBeQNaN=*/true, /*MayBeSNaN=*/true));
  InterestingRanges.push_back(
      ConstantFPRange::getNonNaN(FpImm(0.0), FpImm(1.0)));
  InterestingRanges.push_back(
      ConstantFPRange::getNonNaN(FpImm(2.0), FpImm(3.0)));
  InterestingRanges.push_back(
      ConstantFPRange::getNonNaN(FpImm(-1.0), FpImm(1.0)));
  InterestingRanges.push_back(
      ConstantFPRange::getNonNaN(FpImm(-1.0), FpImm(-0.0)));
  InterestingRanges.push_back(ConstantFPRange::getNonNaN(
      APFloat::getInf(Sem, /*Negative=*/true), FpImm(-1.0)));
  InterestingRanges.push_back(ConstantFPRange::getNonNaN(
      FpImm(1.0), APFloat::getInf(Sem, /*Negative=*/false)));

  for (auto &LHS : InterestingRanges) {
    for (auto &RHS : InterestingRanges) {
      for (auto Pred : FCmpInst::predicates()) {
        if (LHS.fcmp(Pred, RHS)) {
          EnumerateValuesInConstantFPRange(
              LHS,
              [&](const APFloat &LHSC) {
                EnumerateValuesInConstantFPRange(
                    RHS,
                    [&](const APFloat &RHSC) {
                      EXPECT_TRUE(FCmpInst::compare(LHSC, RHSC, Pred))
                          << LHS << " " << Pred << " " << RHS
                          << " doesn't hold";
                    },
                    /*IgnoreNaNPayload=*/true);
              },
              /*IgnoreNaNPayload=*/true);
        }
      }
    }
  }
}

TEST_F(ConstantFPRangeTest, makeExactFCmpRegion) {
  for (auto Pred : FCmpInst::predicates()) {
    EnumerateValuesInConstantFPRange(
        ConstantFPRange::getFull(APFloat::Float8E4M3()),
        [Pred](const APFloat &V) {
          std::optional<ConstantFPRange> Res =
              ConstantFPRange::makeExactFCmpRegion(Pred, V);
          ConstantFPRange Allowed =
              ConstantFPRange::makeAllowedFCmpRegion(Pred, ConstantFPRange(V));
          ConstantFPRange Satisfying =
              ConstantFPRange::makeSatisfyingFCmpRegion(Pred,
                                                        ConstantFPRange(V));
          if (Allowed == Satisfying)
            EXPECT_EQ(Res, Allowed) << "Wrong result for makeExactFCmpRegion("
                                    << Pred << ", " << V << ").";
          else
            EXPECT_FALSE(Res.has_value())
                << "Wrong result for makeExactFCmpRegion(" << Pred << ", " << V
                << ").";
        },
        /*IgnoreNaNPayload=*/true);
  }
}

} // anonymous namespace
