diff --git a/llvm/include/llvm/IR/ConstantRange.h b/llvm/include/llvm/IR/ConstantRange.h --- a/llvm/include/llvm/IR/ConstantRange.h +++ b/llvm/include/llvm/IR/ConstantRange.h @@ -128,6 +128,26 @@ /// NOTE: false does not mean that inverse predicate holds! bool icmp(CmpInst::Predicate Pred, const ConstantRange &Other) const; + /// Return true iff CR1 ult CR2 is equivalent to CR1 slt CR2. + /// Does not depend on strictness/direction of the predicate. + static bool + areInsensitiveToSignednessOfICmpPredicate(const ConstantRange &CR1, + const ConstantRange &CR2); + + /// Return true iff CR1 ult CR2 is equivalent to CR1 sgt CR2. + /// Does not depend on strictness/direction of the predicate. + static bool + areInsensitiveToSignednessOfSwappedICmpPredicate(const ConstantRange &CR1, + const ConstantRange &CR2); + + /// If the comparison between constant ranges this and Other + /// is insensitive to the signedness of the comparison predicate, + /// return a predicate equivalent to \p Pred, with flipped signedness + /// (i.e. unsigned instead of signed or vice versa), and maybe swapped. + CmpInst::Predicate + getEquivalentPredWithFlippedSignedness(CmpInst::Predicate Pred, + const ConstantRange &Other) const; + /// Produce the largest range containing all X such that "X BinOp Y" is /// guaranteed not to wrap (overflow) for *all* Y in Other. However, there may /// be *some* Y in Other for which additional X not contained in the result diff --git a/llvm/lib/IR/ConstantRange.cpp b/llvm/lib/IR/ConstantRange.cpp --- a/llvm/lib/IR/ConstantRange.cpp +++ b/llvm/lib/IR/ConstantRange.cpp @@ -147,6 +147,65 @@ return makeAllowedICmpRegion(Pred, C); } +bool ConstantRange::areInsensitiveToSignednessOfICmpPredicate( + const ConstantRange &CR1, const ConstantRange &CR2) { + if (CR1.isEmptySet() || CR2.isEmptySet()) + return true; + + if (CR1.isFullSet() || CR2.isFullSet()) + return false; + if (CR1.isWrappedSet() || CR2.isWrappedSet()) + return false; + if (CR1.isSignWrappedSet() || CR2.isSignWrappedSet()) + return false; + + if (CR1.contains(CR2) || CR2.contains(CR1)) + return true; + + if (CR1.isAllNonNegative() && CR2.isAllNonNegative()) + return true; + + if (CR1.isAllNegative() && CR2.isAllNegative()) + return !CR1.isUpperWrapped() || !CR2.isUpperWrapped(); + + return false; +} + +bool ConstantRange::areInsensitiveToSignednessOfSwappedICmpPredicate( + const ConstantRange &CR1, const ConstantRange &CR2) { + if (CR1.isEmptySet() || CR2.isEmptySet()) + return true; + if (CR1.isFullSet() || CR2.isFullSet()) + return false; + + bool TrulyEquivalent = true; + + for (auto N1 : {CR1.getUnsignedMin(), CR1.getUnsignedMax(), + CR1.getSignedMin(), CR1.getSignedMax()}) + for (auto N2 : {CR2.getUnsignedMin(), CR2.getUnsignedMax(), + CR2.getSignedMin(), CR2.getSignedMax()}) + TrulyEquivalent &= N1.slt(N2) == N1.ugt(N2); + + return TrulyEquivalent; +} + +CmpInst::Predicate ConstantRange::getEquivalentPredWithFlippedSignedness( + CmpInst::Predicate Pred, const ConstantRange &Other) const { + assert(CmpInst::isIntPredicate(Pred) && CmpInst::isRelational(Pred) && + "Only for relational integer predicates!"); + + CmpInst::Predicate FlippedSignednessPred = + CmpInst::getFlippedSignednessPredicate(Pred); + + if (areInsensitiveToSignednessOfICmpPredicate(*this, Other)) + return FlippedSignednessPred; + + if (areInsensitiveToSignednessOfSwappedICmpPredicate(*this, Other)) + return CmpInst::getSwappedPredicate(FlippedSignednessPred); + + return CmpInst::Predicate::BAD_ICMP_PREDICATE; +} + bool ConstantRange::getEquivalentICmp(CmpInst::Predicate &Pred, APInt &RHS) const { bool Success = false; diff --git a/llvm/unittests/IR/ConstantRangeTest.cpp b/llvm/unittests/IR/ConstantRangeTest.cpp --- a/llvm/unittests/IR/ConstantRangeTest.cpp +++ b/llvm/unittests/IR/ConstantRangeTest.cpp @@ -2531,4 +2531,113 @@ [](const APInt &N) { return ~N; }, PreferSmallest); } -} // anonymous namespace +bool compare(const APInt &LHS, const APInt &RHS, ICmpInst::Predicate Pred) { + assert(ICmpInst::isIntPredicate(Pred) && "Only for integer predicates!"); + switch (Pred) { + case ICmpInst::Predicate::ICMP_EQ: + return LHS.eq(RHS); + case ICmpInst::Predicate::ICMP_NE: + return LHS.ne(RHS); + case ICmpInst::Predicate::ICMP_UGT: + return LHS.ugt(RHS); + case ICmpInst::Predicate::ICMP_UGE: + return LHS.uge(RHS); + case ICmpInst::Predicate::ICMP_ULT: + return LHS.ult(RHS); + case ICmpInst::Predicate::ICMP_ULE: + return LHS.ule(RHS); + case ICmpInst::Predicate::ICMP_SGT: + return LHS.sgt(RHS); + case ICmpInst::Predicate::ICMP_SGE: + return LHS.sge(RHS); + case ICmpInst::Predicate::ICMP_SLT: + return LHS.slt(RHS); + case ICmpInst::Predicate::ICMP_SLE: + return LHS.sle(RHS); + default: + llvm_unreachable("Unexpected non-integer predicate."); + }; +} + +template +void testConstantRangeICmpPredEquivalence(ICmpInst::Predicate SrcPred, T Func) { + unsigned Bits = 4; + EnumerateTwoConstantRanges(Bits, [&](const ConstantRange &CR1, + const ConstantRange &CR2) { + ICmpInst::Predicate TgtPred; + bool ExpectedEquivalent; + std::tie(TgtPred, ExpectedEquivalent) = Func(CR1, CR2, SrcPred); + if (TgtPred == CmpInst::Predicate::BAD_ICMP_PREDICATE) + return; + bool TrulyEquivalent = true; + ForeachNumInConstantRange(CR1, [&](const APInt &N1) { + if (!TrulyEquivalent) + return; + ForeachNumInConstantRange(CR2, [&](const APInt &N2) { + if (!TrulyEquivalent) + return; + TrulyEquivalent &= compare(N1, N2, SrcPred) == compare(N1, N2, TgtPred); + }); + }); + ASSERT_EQ(TrulyEquivalent, ExpectedEquivalent); + }); +} + +TEST_F(ConstantRangeTest, areInsensitiveToSignednessOfICmpPredicate) { + for (auto Pred : seq((unsigned)ICmpInst::Predicate::FIRST_ICMP_PREDICATE, + ICmpInst::Predicate::LAST_ICMP_PREDICATE + 1)) { + if (ICmpInst::isEquality((ICmpInst::Predicate)Pred)) + continue; + ICmpInst::Predicate FlippedSignednessPred = + ICmpInst::getFlippedSignednessPredicate((ICmpInst::Predicate)Pred); + testConstantRangeICmpPredEquivalence( + (ICmpInst::Predicate)Pred, + [FlippedSignednessPred](const ConstantRange &CR1, + const ConstantRange &CR2, + ICmpInst::Predicate SrcPred) { + return std::make_pair( + FlippedSignednessPred, + ConstantRange::areInsensitiveToSignednessOfICmpPredicate(CR1, + CR2)); + }); + } +} + +TEST_F(ConstantRangeTest, areInsensitiveToSignednessOfSwappedICmpPredicate) { + for (auto Pred : seq((unsigned)ICmpInst::Predicate::FIRST_ICMP_PREDICATE, + ICmpInst::Predicate::LAST_ICMP_PREDICATE + 1)) { + if (ICmpInst::isEquality((ICmpInst::Predicate)Pred)) + continue; + ICmpInst::Predicate FlippedSignednessPred = + ICmpInst::getFlippedSignednessPredicate((ICmpInst::Predicate)Pred); + ICmpInst::Predicate SwappedFlippedSignednessPred = + ICmpInst::getSwappedPredicate(FlippedSignednessPred); + testConstantRangeICmpPredEquivalence( + (ICmpInst::Predicate)Pred, + [SwappedFlippedSignednessPred](const ConstantRange &CR1, + const ConstantRange &CR2, + ICmpInst::Predicate SrcPred) { + return std::make_pair( + SwappedFlippedSignednessPred, + ConstantRange::areInsensitiveToSignednessOfSwappedICmpPredicate( + CR1, CR2)); + }); + } +} + +TEST_F(ConstantRangeTest, getEquivalentPredWithFlippedSignedness) { + for (auto Pred : seq((unsigned)ICmpInst::Predicate::FIRST_ICMP_PREDICATE, + ICmpInst::Predicate::LAST_ICMP_PREDICATE + 1)) { + if (ICmpInst::isEquality((ICmpInst::Predicate)Pred)) + continue; + testConstantRangeICmpPredEquivalence( + (ICmpInst::Predicate)Pred, + [](const ConstantRange &CR1, const ConstantRange &CR2, + ICmpInst::Predicate SrcPred) { + return std::make_pair( + CR1.getEquivalentPredWithFlippedSignedness(SrcPred, CR2), true); + }); + } +} + +} // anonymous namespace