This handles the case where this combine:
icmp sgt (ashr X, ShAmtC), C --> icmp sgt X, ((C + 1) << ShAmtC) - 1
wasn't performed by instcombine.
This is my first patch to LVI and I'm not really sure this is right.
Proof of the original combine: https://alive2.llvm.org/ce/z/SfpsvX
I think the better way to explain this is icmp slt (ashr X, ShAmtC), C --> icmp slt X, C << ShAmtC (https://alive2.llvm.org/ce/z/ww5u4i). In this case the only precondition is ((C << ShAmtC) >> ShAmtC) == C. Same for sge.
All the +1/-1 get introduced by using sgt/sle because we're effectively trying to reduce them to that base case. This also introduces the extra preconditions to avoid overflow on +/-1.
With this in mind, I think we can introduce an abstraction like this to transparently handle all the signed predicates while only specifying the fold for slt (without knowledge of anything about ashr in particular):
static std::optional<ConstantRange> getRangeViaSLT( CmpInst::Predicate Pred, APInt RHS, function_ref<std::optional<ConstantRange>(const APInt &)> Fn) { bool Invert = false; if (Pred == ICmpInst::SGT || Pred == ICmpInst::SGE) { Pred = ICmpInst::getInversePredicate(Pred); Invert = true; } if (Pred == ICmpInst::SLE) { Pred = ICmpInst::SLT; if (RHS.isSignedMax()) return std::nullopt; // Could also return full/empty here, if we wanted. ++RHS; } assert(Pred == ICmpInst::SLT && "Must be signed predicate"); if (auto CR = Fn(RHS)) return Invert ? CR->inverse() : CR; return std::nullopt; }And then do something like this:
const APInt *ShAmtC; if (CmpInst::isSigned(EdgePred) && match(LHS, m_AShr(m_Specific(Val), m_APInt(ShAmtC))) && match(RHS, m_APInt(C))) { return getRangeViaSLT(EdgePred, *C, [&](const APInt &RHS) { APInt New = RHS << *ShAmtC; if ((New.ashr(*ShAmtC)) != New) return std::nullopt; return ConstantRange::getNonEmpty(APInt:.getSignedMinValue(BW), New + 1); }); }Code is untested, but I think the general idea should work.