[llvm] [ConstraintElim] Bound non-increasing header IVs by start value. (PR #217921)

Florian Hahn via llvm-commits llvm-commits at lists.llvm.org
Sat Aug 22 06:43:43 PDT 2026


https://github.com/fhahn updated https://github.com/llvm/llvm-project/pull/217921

>From d956708ec13a4cd57ae25771a759451957b91a9a Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Fri, 21 Aug 2026 14:45:23 +0100
Subject: [PATCH 1/3] [ConstraintElim] Add more tests

---
 .../monotonic-int-phis-decrement.ll           | 214 ++++++++++++++++++
 .../constraint-eliminiation-interactions.ll   |  60 +++++
 2 files changed, 274 insertions(+)

diff --git a/llvm/test/Transforms/ConstraintElimination/monotonic-int-phis-decrement.ll b/llvm/test/Transforms/ConstraintElimination/monotonic-int-phis-decrement.ll
index b1895c0881df8..06bd70da4335a 100644
--- a/llvm/test/Transforms/ConstraintElimination/monotonic-int-phis-decrement.ll
+++ b/llvm/test/Transforms/ConstraintElimination/monotonic-int-phis-decrement.ll
@@ -419,3 +419,217 @@ loop.latch:
 exit:
   ret void
 }
+
+; The upper bound does not depend on the step being exactly -1.
+define void @add_rec_decreasing_nsw_step_minus_2(i8 %start, i8 %b) {
+; CHECK-LABEL: define void @add_rec_decreasing_nsw_step_minus_2(
+; CHECK-SAME: i8 [[START:%.*]], i8 [[B:%.*]]) {
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br label [[LOOP_HEADER:%.*]]
+; CHECK:       loop.header:
+; CHECK-NEXT:    [[K_0:%.*]] = phi i8 [ [[START]], [[ENTRY:%.*]] ], [ [[K_DEC:%.*]], [[LOOP_LATCH:%.*]] ]
+; CHECK-NEXT:    [[CMP2_NOT:%.*]] = icmp eq i8 [[K_0]], [[B]]
+; CHECK-NEXT:    br i1 [[CMP2_NOT]], label [[EXIT:%.*]], label [[LOOP_LATCH]]
+; CHECK:       loop.latch:
+; CHECK-NEXT:    [[S:%.*]] = icmp sle i8 [[K_0]], [[START]]
+; CHECK-NEXT:    call void @use(i1 [[S]])
+; CHECK-NEXT:    [[K_DEC]] = add nsw i8 [[K_0]], -2
+; CHECK-NEXT:    br label [[LOOP_HEADER]]
+; CHECK:       exit:
+; CHECK-NEXT:    ret void
+;
+entry:
+  br label %loop.header
+
+loop.header:
+  %k.0 = phi i8 [ %start, %entry ], [ %k.dec, %loop.latch ]
+  %cmp2.not = icmp eq i8 %k.0, %b
+  br i1 %cmp2.not, label %exit, label %loop.latch
+
+loop.latch:
+  %s = icmp sle i8 %k.0, %start
+  call void @use(i1 %s)
+  %k.dec = add nsw i8 %k.0, -2
+  br label %loop.header
+
+exit:
+  ret void
+}
+
+define void @add_rec_decreasing_nsw_compare_in_header(i8 %start, i8 %b) {
+; CHECK-LABEL: define void @add_rec_decreasing_nsw_compare_in_header(
+; CHECK-SAME: i8 [[START:%.*]], i8 [[B:%.*]]) {
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br label [[LOOP_HEADER:%.*]]
+; CHECK:       loop.header:
+; CHECK-NEXT:    [[K_0:%.*]] = phi i8 [ [[START]], [[ENTRY:%.*]] ], [ [[K_DEC:%.*]], [[LOOP_LATCH:%.*]] ]
+; CHECK-NEXT:    [[S:%.*]] = icmp sle i8 [[K_0]], [[START]]
+; CHECK-NEXT:    call void @use(i1 [[S]])
+; CHECK-NEXT:    [[CMP2_NOT:%.*]] = icmp eq i8 [[K_0]], [[B]]
+; CHECK-NEXT:    br i1 [[CMP2_NOT]], label [[EXIT:%.*]], label [[LOOP_LATCH]]
+; CHECK:       loop.latch:
+; CHECK-NEXT:    [[K_DEC]] = add nsw i8 [[K_0]], -1
+; CHECK-NEXT:    br label [[LOOP_HEADER]]
+; CHECK:       exit:
+; CHECK-NEXT:    ret void
+;
+entry:
+  br label %loop.header
+
+loop.header:
+  %k.0 = phi i8 [ %start, %entry ], [ %k.dec, %loop.latch ]
+  %s = icmp sle i8 %k.0, %start
+  call void @use(i1 %s)
+  %cmp2.not = icmp eq i8 %k.0, %b
+  br i1 %cmp2.not, label %exit, label %loop.latch
+
+loop.latch:
+  %k.dec = add nsw i8 %k.0, -1
+  br label %loop.header
+
+exit:
+  ret void
+}
+
+define void @add_rec_decreasing_nsw_second_phi(i8 %start, i8 %start2, i8 %b) {
+; CHECK-LABEL: define void @add_rec_decreasing_nsw_second_phi(
+; CHECK-SAME: i8 [[START:%.*]], i8 [[START2:%.*]], i8 [[B:%.*]]) {
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br label [[LOOP_HEADER:%.*]]
+; CHECK:       loop.header:
+; CHECK-NEXT:    [[K_0:%.*]] = phi i8 [ [[START]], [[ENTRY:%.*]] ], [ [[K_DEC:%.*]], [[LOOP_LATCH:%.*]] ]
+; CHECK-NEXT:    [[J_0:%.*]] = phi i8 [ [[START2]], [[ENTRY]] ], [ [[J_DEC:%.*]], [[LOOP_LATCH]] ]
+; CHECK-NEXT:    [[CMP2_NOT:%.*]] = icmp eq i8 [[K_0]], [[B]]
+; CHECK-NEXT:    br i1 [[CMP2_NOT]], label [[EXIT:%.*]], label [[LOOP_LATCH]]
+; CHECK:       loop.latch:
+; CHECK-NEXT:    [[S:%.*]] = icmp sle i8 [[J_0]], [[START2]]
+; CHECK-NEXT:    call void @use(i1 [[S]])
+; CHECK-NEXT:    [[K_DEC]] = add nsw i8 [[K_0]], -1
+; CHECK-NEXT:    [[J_DEC]] = add nsw i8 [[J_0]], -3
+; CHECK-NEXT:    br label [[LOOP_HEADER]]
+; CHECK:       exit:
+; CHECK-NEXT:    ret void
+;
+entry:
+  br label %loop.header
+
+loop.header:
+  %k.0 = phi i8 [ %start, %entry ], [ %k.dec, %loop.latch ]
+  %j.0 = phi i8 [ %start2, %entry ], [ %j.dec, %loop.latch ]
+  %cmp2.not = icmp eq i8 %k.0, %b
+  br i1 %cmp2.not, label %exit, label %loop.latch
+
+loop.latch:
+  %s = icmp sle i8 %j.0, %start2
+  call void @use(i1 %s)
+  %k.dec = add nsw i8 %k.0, -1
+  %j.dec = add nsw i8 %j.0, -3
+  br label %loop.header
+
+exit:
+  ret void
+}
+
+define void @add_rec_decreasing_nsw_slt_exit(i8 %start, i8 %b) {
+; CHECK-LABEL: define void @add_rec_decreasing_nsw_slt_exit(
+; CHECK-SAME: i8 [[START:%.*]], i8 [[B:%.*]]) {
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br label [[LOOP_HEADER:%.*]]
+; CHECK:       loop.header:
+; CHECK-NEXT:    [[K_0:%.*]] = phi i8 [ [[START]], [[ENTRY:%.*]] ], [ [[K_DEC:%.*]], [[LOOP_LATCH:%.*]] ]
+; CHECK-NEXT:    [[EC:%.*]] = icmp slt i8 [[K_0]], [[B]]
+; CHECK-NEXT:    br i1 [[EC]], label [[EXIT:%.*]], label [[LOOP_LATCH]]
+; CHECK:       loop.latch:
+; CHECK-NEXT:    [[S:%.*]] = icmp sle i8 [[K_0]], [[START]]
+; CHECK-NEXT:    call void @use(i1 [[S]])
+; CHECK-NEXT:    [[K_DEC]] = add nsw i8 [[K_0]], -1
+; CHECK-NEXT:    br label [[LOOP_HEADER]]
+; CHECK:       exit:
+; CHECK-NEXT:    ret void
+;
+entry:
+  br label %loop.header
+
+loop.header:
+  %k.0 = phi i8 [ %start, %entry ], [ %k.dec, %loop.latch ]
+  %ec = icmp slt i8 %k.0, %b
+  br i1 %ec, label %exit, label %loop.latch
+
+loop.latch:
+  %s = icmp sle i8 %k.0, %start
+  call void @use(i1 %s)
+  %k.dec = add nsw i8 %k.0, -1
+  br label %loop.header
+
+exit:
+  ret void
+}
+
+define void @add_rec_decreasing_no_wrap_flags_wraps_past_start() {
+; CHECK-LABEL: define void @add_rec_decreasing_no_wrap_flags_wraps_past_start() {
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br label [[LOOP_HEADER:%.*]]
+; CHECK:       loop.header:
+; CHECK-NEXT:    [[K_0:%.*]] = phi i8 [ 5, [[ENTRY:%.*]] ], [ [[K_DEC:%.*]], [[LOOP_LATCH:%.*]] ]
+; CHECK-NEXT:    [[S:%.*]] = icmp sle i8 [[K_0]], 5
+; CHECK-NEXT:    call void @use(i1 [[S]])
+; CHECK-NEXT:    br label [[LOOP_LATCH]]
+; CHECK:       loop.latch:
+; CHECK-NEXT:    [[K_DEC]] = add i8 [[K_0]], -1
+; CHECK-NEXT:    [[EC:%.*]] = icmp eq i8 [[K_DEC]], 5
+; CHECK-NEXT:    br i1 [[EC]], label [[EXIT:%.*]], label [[LOOP_HEADER]]
+; CHECK:       exit:
+; CHECK-NEXT:    ret void
+;
+entry:
+  br label %loop.header
+
+loop.header:
+  %k.0 = phi i8 [ 5, %entry ], [ %k.dec, %loop.latch ]
+  %s = icmp sle i8 %k.0, 5
+  call void @use(i1 %s)
+  br label %loop.latch
+
+loop.latch:
+  %k.dec = add i8 %k.0, -1
+  %ec = icmp eq i8 %k.dec, 5
+  br i1 %ec, label %exit, label %loop.header
+
+exit:
+  ret void
+}
+
+define void @zero_start_nuw_no_upper_bound(i8 %b) {
+; CHECK-LABEL: define void @zero_start_nuw_no_upper_bound(
+; CHECK-SAME: i8 [[B:%.*]]) {
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br label [[LOOP_HEADER:%.*]]
+; CHECK:       loop.header:
+; CHECK-NEXT:    [[IV:%.*]] = phi i8 [ 0, [[ENTRY:%.*]] ], [ [[IV_NEXT:%.*]], [[LOOP_LATCH:%.*]] ]
+; CHECK-NEXT:    [[S:%.*]] = icmp sle i8 [[IV]], 0
+; CHECK-NEXT:    call void @use(i1 [[S]])
+; CHECK-NEXT:    [[EC:%.*]] = icmp eq i8 [[IV]], [[B]]
+; CHECK-NEXT:    br i1 [[EC]], label [[EXIT:%.*]], label [[LOOP_LATCH]]
+; CHECK:       loop.latch:
+; CHECK-NEXT:    [[IV_NEXT]] = add nuw i8 [[IV]], 1
+; CHECK-NEXT:    br label [[LOOP_HEADER]]
+; CHECK:       exit:
+; CHECK-NEXT:    ret void
+;
+entry:
+  br label %loop.header
+
+loop.header:
+  %iv = phi i8 [ 0, %entry ], [ %iv.next, %loop.latch ]
+  %s = icmp sle i8 %iv, 0
+  call void @use(i1 %s)
+  %ec = icmp eq i8 %iv, %b
+  br i1 %ec, label %exit, label %loop.latch
+
+loop.latch:
+  %iv.next = add nuw i8 %iv, 1
+  br label %loop.header
+
+exit:
+  ret void
+}
diff --git a/llvm/test/Transforms/PhaseOrdering/constraint-eliminiation-interactions.ll b/llvm/test/Transforms/PhaseOrdering/constraint-eliminiation-interactions.ll
index ce7485ba98cfb..27ec5f662d3a7 100644
--- a/llvm/test/Transforms/PhaseOrdering/constraint-eliminiation-interactions.ll
+++ b/llvm/test/Transforms/PhaseOrdering/constraint-eliminiation-interactions.ll
@@ -1,6 +1,8 @@
 ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
 ; RUN: opt -O3 -S %s | FileCheck %s
 
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+
 ; SCEV can infer tighter wrap flags for %iv, which in turn can be used by
 ; ConstraintElimination.
 define void @sum_of_induction_and_guard(i8 %n, i8 %j) mustprogress {
@@ -159,3 +161,61 @@ else:
   %res = phi i64 [ %v, %cont ], [ 0, %entry ]
   ret i64 %res
 }
+
+define void @shl_flags(i32 %n) {
+; CHECK-LABEL: define void @shl_flags(
+; CHECK-SAME: i32 [[N:%.*]]) local_unnamed_addr {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    [[START:%.*]] = lshr i32 [[N]], 1
+; CHECK-NEXT:    [[TMP0:%.*]] = lshr i32 [[N]], 1
+; CHECK-NEXT:    [[TMP2:%.*]] = zext nneg i32 [[TMP0]] to i64
+; CHECK-NEXT:    [[TMP1:%.*]] = zext nneg i32 [[START]] to i64
+; CHECK-NEXT:    br label %[[LOOP_HEADER:.*]]
+; CHECK:       [[LOOP_HEADER]]:
+; CHECK-NEXT:    [[EXT:%.*]] = phi i64 [ [[INDVARS_IV_NEXT:%.*]], %[[LOOP_LATCH:.*]] ], [ [[TMP2]], %[[ENTRY]] ]
+; CHECK-NEXT:    [[GUARD:%.*]] = icmp sgt i64 [[EXT]], [[TMP1]]
+; CHECK-NEXT:    br i1 [[GUARD]], label %[[LOOP_LATCH]], label %[[BODY:.*]]
+; CHECK:       [[BODY]]:
+; CHECK-NEXT:    [[TMP3:%.*]] = trunc nuw i64 [[EXT]] to i32
+; CHECK-NEXT:    [[SHL:%.*]] = shl nuw i32 [[TMP3]], 1
+; CHECK-NEXT:    [[POS:%.*]] = icmp sgt i32 [[SHL]], 0
+; CHECK-NEXT:    br i1 [[POS]], label %[[CALL_BB:.*]], label %[[EXIT:.*]]
+; CHECK:       [[CALL_BB]]:
+; CHECK-NEXT:    [[V:%.*]] = tail call i32 @load(i64 [[EXT]])
+; CHECK-NEXT:    br label %[[LOOP_LATCH]]
+; CHECK:       [[LOOP_LATCH]]:
+; CHECK-NEXT:    [[DONE:%.*]] = icmp eq i64 [[EXT]], 0
+; CHECK-NEXT:    [[INDVARS_IV_NEXT]] = add nsw i64 [[EXT]], -1
+; CHECK-NEXT:    br i1 [[DONE]], label %[[EXIT]], label %[[LOOP_HEADER]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret void
+;
+entry:
+  %start = lshr i32 %n, 1
+  br label %loop.header
+
+loop.header:
+  %iv = phi i32 [ %start, %entry ], [ %iv.next, %loop.latch ]
+  %guard = icmp slt i32 %start, %iv
+  br i1 %guard, label %loop.latch, label %body
+
+body:
+  %shl = shl i32 %iv, 1
+  %pos = icmp sgt i32 %shl, 0
+  br i1 %pos, label %call.bb, label %exit
+
+call.bb:
+  %ext = zext i32 %iv to i64
+  %v = call i32 @load(i64 %ext)
+  br label %loop.latch
+
+loop.latch:
+  %iv.next = add i32 %iv, -1
+  %done = icmp eq i32 %iv, 0
+  br i1 %done, label %exit, label %loop.header
+
+exit:
+  ret void
+}
+
+declare i32 @load(i64)

>From 4eb6708bfb5ba81e7593ef06a1c0038725bea2c3 Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Thu, 20 Aug 2026 16:32:18 +0100
Subject: [PATCH 2/3] [ConstraintElim] Bound non-increasing header IVs by start
 value.

Generalize addLowerBoundsForHeaderInductions to also support bounding
decrementing IVs: if the IV gets decremented w/o wapping in the signed
sense, it will never exceed the start value and we can add PN s>=
StartValue.

This helps to remove a number of branches in real-world workloads:
https://github.com/dtcxzyw/llvm-opt-benchmark-nightly/pull/984

Note that in a few instances (found 2), the removed branches result in
flags dropped. We should be able to recover those (and more) by using
info from ConstrantElimination to strengthen flags.

Alive2 Proof: https://alive2.llvm.org/ce/z/7t_xHX
---
 .../Scalar/ConstraintElimination.cpp          | 100 +++++++++++-------
 ...nduction-condition-in-loop-exit-postinc.ll |   6 +-
 .../monotonic-int-phis-decrement.ll           |  18 ++--
 .../constraint-eliminiation-interactions.ll   |  15 +--
 4 files changed, 73 insertions(+), 66 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
index 6e8552b04b4eb..ccdc51d67ac60 100644
--- a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
@@ -172,6 +172,17 @@ struct FactOrCheck {
   bool isConditionFact() const { return Ty == EntryTy::ConditionFact; }
 };
 
+/// The senses in which an induction phi is monotonic, together with the
+/// direction it moves in.
+struct MonotonicInfo {
+  /// True if the phi steps by a negative constant.
+  bool Decreasing = false;
+  /// True if the phi is monotonic in the unsigned sense.
+  bool Unsigned = false;
+  /// True if the phi is monotonic in the signed sense.
+  bool Signed = false;
+};
+
 /// Keep state required to build worklist.
 struct State {
   DominatorTree &DT;
@@ -188,16 +199,16 @@ struct State {
   void addInfoFor(BasicBlock &BB);
 
   /// If \p BB is a loop header, bound each induction phi in it by its start
-  /// value, if it is non-decreasing.
-  void addLowerBoundsForHeaderInductions(BasicBlock &BB);
+  /// value.
+  void addBoundsForHeaderInductions(BasicBlock &BB);
 
   /// Try to add facts for loop inductions (AddRecs) in EQ/NE compares
   /// controlling the loop header.
   void addInfoForInductions(BasicBlock &BB);
 
-  /// Returns the senses, {unsigned, signed}, in which stepping the induction
-  /// phi \p PN, starting at \p Start, to \p Step cannot decrease it.
-  std::pair<bool, bool> getNonDecreasingInfo(PHINode &PN, Value *Step);
+  /// Returns the direction the induction phi \p PN with backedge value \p Step
+  /// moves in, and the senses in which it is monotonic in that direction.
+  MonotonicInfo getMonotonicityInfo(PHINode &PN, Value *Step);
 
   /// Returns true if we can add a known condition from BB to its successor
   /// block Succ.
@@ -986,40 +997,46 @@ getStartAndBackedgeValue(const PHINode &PN, const BasicBlock *LoopPred) {
   return {PN.getIncomingValue(StartIdx), PN.getIncomingValue(1 - StartIdx)};
 }
 
-std::pair<bool, bool> State::getNonDecreasingInfo(PHINode &PN, Value *Step) {
-  bool Unsigned = false, Signed = false;
+MonotonicInfo State::getMonotonicityInfo(PHINode &PN, Value *Step) {
+  MonotonicInfo Info;
   const APInt *StepOffset = nullptr;
   if (match(Step, m_c_Add(m_Specific(&PN), m_APInt(StepOffset)))) {
-    if (StepOffset->isNegative())
-      return {false, false};
+    Info.Decreasing = StepOffset->isNegative();
     const auto *Add = cast<OverflowingBinaryOperator>(Step);
-    Unsigned = Add->hasNoUnsignedWrap();
-    Signed = Add->hasNoSignedWrap();
+    Info.Unsigned = !Info.Decreasing && Add->hasNoUnsignedWrap();
+    Info.Signed = Add->hasNoSignedWrap();
   } else if (const auto *GEP = dyn_cast<GEPOperator>(Step)) {
+    // TODO: Handle the non-increasing direction, which needs a nusw GEP with a
+    // negative constant offset.
     const DataLayout &DL = PN.getDataLayout();
     APInt GEPOffset(DL.getIndexTypeSizeInBits(GEP->getType()), 0);
-    Unsigned = GEP->getPointerOperand() == &PN &&
-               (GEP->hasNoUnsignedWrap() ||
-                ((GEP->hasNoUnsignedSignedWrap() &&
-                  GEP->accumulateConstantOffset(DL, GEPOffset) &&
-                  !GEPOffset.isNegative())));
+    Info.Unsigned = GEP->getPointerOperand() == &PN &&
+                    (GEP->hasNoUnsignedWrap() ||
+                     ((GEP->hasNoUnsignedSignedWrap() &&
+                       GEP->accumulateConstantOffset(DL, GEPOffset) &&
+                       !GEPOffset.isNegative())));
   }
 
   // Forming the SCEV of a phi is expensive, so only consult it for a PN + C
   // step whose no-wrap flags prove nothing.
-  if (Unsigned || Signed || !StepOffset)
-    return {Unsigned, Signed};
+  if (Info.Unsigned || Info.Signed || !StepOffset)
+    return Info;
 
   const auto *AR = dyn_cast<SCEVAddRecExpr>(SE.getSCEV(&PN));
   if (!AR)
-    return {false, false};
-  return {SE.getMonotonicPredicateType(AR, CmpInst::ICMP_UGT) ==
-              ScalarEvolution::MonotonicallyIncreasing,
-          SE.getMonotonicPredicateType(AR, CmpInst::ICMP_SGT) ==
-              ScalarEvolution::MonotonicallyIncreasing};
+    return Info;
+  ScalarEvolution::MonotonicPredicateType Expected =
+      Info.Decreasing ? ScalarEvolution::MonotonicallyDecreasing
+                      : ScalarEvolution::MonotonicallyIncreasing;
+  auto IsMonotonic = [&](CmpInst::Predicate Pred) {
+    return SE.getMonotonicPredicateType(AR, Pred) == Expected;
+  };
+  Info.Signed = IsMonotonic(CmpInst::ICMP_SGT);
+  Info.Unsigned = !Info.Decreasing && IsMonotonic(CmpInst::ICMP_UGT);
+  return Info;
 }
 
-void State::addLowerBoundsForHeaderInductions(BasicBlock &BB) {
+void State::addBoundsForHeaderInductions(BasicBlock &BB) {
   Loop *L = LI.getLoopFor(&BB);
   if (!L || L->getHeader() != &BB)
     return;
@@ -1036,17 +1053,22 @@ void State::addLowerBoundsForHeaderInductions(BasicBlock &BB) {
     if (!Start)
       continue;
 
-    auto [Unsigned, Signed] = getNonDecreasingInfo(PN, Step);
+    MonotonicInfo Info = getMonotonicityInfo(PN, Step);
     // Every variable in the unsigned system already has a `V >= 0` row, so a
     // zero start value would just duplicate it.
     if (match(Start, m_Zero()))
-      Unsigned = false;
-    if (!Unsigned && !Signed)
+      Info.Unsigned = false;
+    if (!Info.Unsigned && !Info.Signed)
       continue;
 
-    CmpPredicate Pred(Unsigned ? CmpInst::ICMP_UGE : CmpInst::ICMP_SGE,
-                      /*HasSameSign=*/Unsigned && Signed);
-    WorkList.push_back(FactOrCheck::getConditionFact(DTN, Pred, &PN, Start));
+    // A non-decreasing induction cannot step below its start value, and a
+    // non-increasing one cannot step above it.
+    Value *LHS = &PN, *RHS = Start;
+    if (Info.Decreasing)
+      std::swap(LHS, RHS);
+    CmpPredicate Pred(Info.Unsigned ? CmpInst::ICMP_UGE : CmpInst::ICMP_SGE,
+                      /*HasSameSign=*/Info.Unsigned && Info.Signed);
+    WorkList.push_back(FactOrCheck::getConditionFact(DTN, Pred, LHS, RHS));
   }
 }
 
@@ -1122,6 +1144,8 @@ void State::addInfoForInductions(BasicBlock &BB) {
   if (IncStep && (*IncStep != *StepOffset || StepOffset->isNegative()))
     return;
 
+  MonotonicInfo Info = getMonotonicityInfo(*PN, Backedge);
+
   // Handle negative steps.
   if (StepOffset->isNegative()) {
     // TODO: Extend to allow steps > -1.
@@ -1134,9 +1158,10 @@ void State::addInfoForInductions(BasicBlock &BB) {
     WorkList.push_back(FactOrCheck::getConditionFact(
         DTN, CmpInst::ICMP_UGE, StartValue, PN,
         ConditionTy(CmpInst::ICMP_ULE, B, StartValue)));
-    WorkList.push_back(FactOrCheck::getConditionFact(
-        DTN, CmpInst::ICMP_SGE, StartValue, PN,
-        ConditionTy(CmpInst::ICMP_SLE, B, StartValue)));
+    if (!(Info.Decreasing && Info.Signed))
+      WorkList.push_back(FactOrCheck::getConditionFact(
+          DTN, CmpInst::ICMP_SGE, StartValue, PN,
+          ConditionTy(CmpInst::ICMP_SLE, B, StartValue)));
     // Add PN > B conditional on B <= StartValue which guarantees that the loop
     // exits when reaching B with a step of -1.
     WorkList.push_back(FactOrCheck::getConditionFact(
@@ -1148,9 +1173,6 @@ void State::addInfoForInductions(BasicBlock &BB) {
     return;
   }
 
-  auto [MonotonicallyIncreasingUnsigned, MonotonicallyIncreasingSigned] =
-      getNonDecreasingInfo(*PN, Backedge);
-
   // Make sure AR either steps by 1 or that the value we compare against is a
   // GEP based on the same start value and all offsets are a multiple of the
   // step size, to guarantee that the induction will reach the value.
@@ -1186,10 +1208,10 @@ void State::addInfoForInductions(BasicBlock &BB) {
   // restrictions on B and the step above.
   ConditionTy StartBeforeBoundULE = {CmpInst::ICMP_ULE, LowerBound, B};
   ConditionTy StartBeforeBoundSLE = {CmpInst::ICMP_SLE, LowerBound, B};
-  if (!MonotonicallyIncreasingUnsigned && LowerBoundNUW)
+  if (!Info.Unsigned && LowerBoundNUW)
     WorkList.push_back(FactOrCheck::getConditionFact(
         DTN, CmpInst::ICMP_UGE, PN, StartValue, StartBeforeBoundULE));
-  if (!MonotonicallyIncreasingSigned && LowerBoundNSW)
+  if (!Info.Signed && LowerBoundNSW)
     WorkList.push_back(FactOrCheck::getConditionFact(
         DTN, CmpInst::ICMP_SGE, PN, StartValue, StartBeforeBoundSLE));
 
@@ -1264,7 +1286,7 @@ static bool getConstraintFromMemoryAccess(GetElementPtrInst &GEP,
 }
 
 void State::addInfoFor(BasicBlock &BB) {
-  addLowerBoundsForHeaderInductions(BB);
+  addBoundsForHeaderInductions(BB);
   addInfoForInductions(BB);
   auto &DL = BB.getDataLayout();
 
diff --git a/llvm/test/Transforms/ConstraintElimination/induction-condition-in-loop-exit-postinc.ll b/llvm/test/Transforms/ConstraintElimination/induction-condition-in-loop-exit-postinc.ll
index 06336f5f3218a..e5e4dab8fcec9 100644
--- a/llvm/test/Transforms/ConstraintElimination/induction-condition-in-loop-exit-postinc.ll
+++ b/llvm/test/Transforms/ConstraintElimination/induction-condition-in-loop-exit-postinc.ll
@@ -640,8 +640,7 @@ define void @header_postdec_negative_step_folds() {
 ; CHECK-NEXT:    call void @use(i1 [[ULE]])
 ; CHECK-NEXT:    [[SGT:%.*]] = icmp sgt i8 [[IV]], 2
 ; CHECK-NEXT:    call void @use(i1 [[SGT]])
-; CHECK-NEXT:    [[SLE:%.*]] = icmp sle i8 [[IV]], 5
-; CHECK-NEXT:    call void @use(i1 [[SLE]])
+; CHECK-NEXT:    call void @use(i1 true)
 ; CHECK-NEXT:    br label %[[LOOP]]
 ; CHECK:       [[EXIT]]:
 ; CHECK-NEXT:    ret void
@@ -685,8 +684,7 @@ define void @latch_postdec_negative_step_folds() {
 ; CHECK-NEXT:    call void @use(i1 [[ULE]])
 ; CHECK-NEXT:    [[SGT:%.*]] = icmp sgt i8 [[IV]], 2
 ; CHECK-NEXT:    call void @use(i1 [[SGT]])
-; CHECK-NEXT:    [[SLE:%.*]] = icmp sle i8 [[IV]], 5
-; CHECK-NEXT:    call void @use(i1 [[SLE]])
+; CHECK-NEXT:    call void @use(i1 true)
 ; CHECK-NEXT:    br label %[[LOOP_LATCH]]
 ; CHECK:       [[LOOP_LATCH]]:
 ; CHECK-NEXT:    [[IV_NEXT]] = add i8 [[IV]], -1
diff --git a/llvm/test/Transforms/ConstraintElimination/monotonic-int-phis-decrement.ll b/llvm/test/Transforms/ConstraintElimination/monotonic-int-phis-decrement.ll
index 06bd70da4335a..f2e4b42fc477a 100644
--- a/llvm/test/Transforms/ConstraintElimination/monotonic-int-phis-decrement.ll
+++ b/llvm/test/Transforms/ConstraintElimination/monotonic-int-phis-decrement.ll
@@ -269,8 +269,7 @@ define void @add_rec_decreasing_nsw_signed_monotonic(i8 %start, i8 %b) {
 ; CHECK-NEXT:    [[CMP2_NOT:%.*]] = icmp eq i8 [[K_0]], [[B]]
 ; CHECK-NEXT:    br i1 [[CMP2_NOT]], label [[EXIT:%.*]], label [[LOOP_LATCH]]
 ; CHECK:       loop.latch:
-; CHECK-NEXT:    [[S:%.*]] = icmp sle i8 [[K_0]], [[START]]
-; CHECK-NEXT:    call void @use(i1 [[S]])
+; CHECK-NEXT:    call void @use(i1 true)
 ; CHECK-NEXT:    [[U:%.*]] = icmp ule i8 [[K_0]], [[START]]
 ; CHECK-NEXT:    call void @use(i1 [[U]])
 ; CHECK-NEXT:    [[K_DEC]] = add nsw i8 [[K_0]], -1
@@ -388,8 +387,7 @@ define void @add_rec_decreasing_nsw_from_scev(i8 %b) {
 ; CHECK-NEXT:    [[CMP2_NOT:%.*]] = icmp eq i8 [[K_0]], [[B]]
 ; CHECK-NEXT:    br i1 [[CMP2_NOT]], label [[EXIT:%.*]], label [[LOOP_BODY:%.*]]
 ; CHECK:       loop.body:
-; CHECK-NEXT:    [[S:%.*]] = icmp sle i8 [[K_0]], 100
-; CHECK-NEXT:    call void @use(i1 [[S]])
+; CHECK-NEXT:    call void @use(i1 true)
 ; CHECK-NEXT:    [[POSITIVE:%.*]] = icmp sgt i8 [[K_0]], 0
 ; CHECK-NEXT:    br i1 [[POSITIVE]], label [[LOOP_LATCH]], label [[EXIT]]
 ; CHECK:       loop.latch:
@@ -431,8 +429,7 @@ define void @add_rec_decreasing_nsw_step_minus_2(i8 %start, i8 %b) {
 ; CHECK-NEXT:    [[CMP2_NOT:%.*]] = icmp eq i8 [[K_0]], [[B]]
 ; CHECK-NEXT:    br i1 [[CMP2_NOT]], label [[EXIT:%.*]], label [[LOOP_LATCH]]
 ; CHECK:       loop.latch:
-; CHECK-NEXT:    [[S:%.*]] = icmp sle i8 [[K_0]], [[START]]
-; CHECK-NEXT:    call void @use(i1 [[S]])
+; CHECK-NEXT:    call void @use(i1 true)
 ; CHECK-NEXT:    [[K_DEC]] = add nsw i8 [[K_0]], -2
 ; CHECK-NEXT:    br label [[LOOP_HEADER]]
 ; CHECK:       exit:
@@ -463,8 +460,7 @@ define void @add_rec_decreasing_nsw_compare_in_header(i8 %start, i8 %b) {
 ; CHECK-NEXT:    br label [[LOOP_HEADER:%.*]]
 ; CHECK:       loop.header:
 ; CHECK-NEXT:    [[K_0:%.*]] = phi i8 [ [[START]], [[ENTRY:%.*]] ], [ [[K_DEC:%.*]], [[LOOP_LATCH:%.*]] ]
-; CHECK-NEXT:    [[S:%.*]] = icmp sle i8 [[K_0]], [[START]]
-; CHECK-NEXT:    call void @use(i1 [[S]])
+; CHECK-NEXT:    call void @use(i1 true)
 ; CHECK-NEXT:    [[CMP2_NOT:%.*]] = icmp eq i8 [[K_0]], [[B]]
 ; CHECK-NEXT:    br i1 [[CMP2_NOT]], label [[EXIT:%.*]], label [[LOOP_LATCH]]
 ; CHECK:       loop.latch:
@@ -502,8 +498,7 @@ define void @add_rec_decreasing_nsw_second_phi(i8 %start, i8 %start2, i8 %b) {
 ; CHECK-NEXT:    [[CMP2_NOT:%.*]] = icmp eq i8 [[K_0]], [[B]]
 ; CHECK-NEXT:    br i1 [[CMP2_NOT]], label [[EXIT:%.*]], label [[LOOP_LATCH]]
 ; CHECK:       loop.latch:
-; CHECK-NEXT:    [[S:%.*]] = icmp sle i8 [[J_0]], [[START2]]
-; CHECK-NEXT:    call void @use(i1 [[S]])
+; CHECK-NEXT:    call void @use(i1 true)
 ; CHECK-NEXT:    [[K_DEC]] = add nsw i8 [[K_0]], -1
 ; CHECK-NEXT:    [[J_DEC]] = add nsw i8 [[J_0]], -3
 ; CHECK-NEXT:    br label [[LOOP_HEADER]]
@@ -540,8 +535,7 @@ define void @add_rec_decreasing_nsw_slt_exit(i8 %start, i8 %b) {
 ; CHECK-NEXT:    [[EC:%.*]] = icmp slt i8 [[K_0]], [[B]]
 ; CHECK-NEXT:    br i1 [[EC]], label [[EXIT:%.*]], label [[LOOP_LATCH]]
 ; CHECK:       loop.latch:
-; CHECK-NEXT:    [[S:%.*]] = icmp sle i8 [[K_0]], [[START]]
-; CHECK-NEXT:    call void @use(i1 [[S]])
+; CHECK-NEXT:    call void @use(i1 true)
 ; CHECK-NEXT:    [[K_DEC]] = add nsw i8 [[K_0]], -1
 ; CHECK-NEXT:    br label [[LOOP_HEADER]]
 ; CHECK:       exit:
diff --git a/llvm/test/Transforms/PhaseOrdering/constraint-eliminiation-interactions.ll b/llvm/test/Transforms/PhaseOrdering/constraint-eliminiation-interactions.ll
index 27ec5f662d3a7..f710f66ae413f 100644
--- a/llvm/test/Transforms/PhaseOrdering/constraint-eliminiation-interactions.ll
+++ b/llvm/test/Transforms/PhaseOrdering/constraint-eliminiation-interactions.ll
@@ -166,24 +166,17 @@ define void @shl_flags(i32 %n) {
 ; CHECK-LABEL: define void @shl_flags(
 ; CHECK-SAME: i32 [[N:%.*]]) local_unnamed_addr {
 ; CHECK-NEXT:  [[ENTRY:.*]]:
-; CHECK-NEXT:    [[START:%.*]] = lshr i32 [[N]], 1
 ; CHECK-NEXT:    [[TMP0:%.*]] = lshr i32 [[N]], 1
 ; CHECK-NEXT:    [[TMP2:%.*]] = zext nneg i32 [[TMP0]] to i64
-; CHECK-NEXT:    [[TMP1:%.*]] = zext nneg i32 [[START]] to i64
 ; CHECK-NEXT:    br label %[[LOOP_HEADER:.*]]
 ; CHECK:       [[LOOP_HEADER]]:
-; CHECK-NEXT:    [[EXT:%.*]] = phi i64 [ [[INDVARS_IV_NEXT:%.*]], %[[LOOP_LATCH:.*]] ], [ [[TMP2]], %[[ENTRY]] ]
-; CHECK-NEXT:    [[GUARD:%.*]] = icmp sgt i64 [[EXT]], [[TMP1]]
-; CHECK-NEXT:    br i1 [[GUARD]], label %[[LOOP_LATCH]], label %[[BODY:.*]]
-; CHECK:       [[BODY]]:
-; CHECK-NEXT:    [[TMP3:%.*]] = trunc nuw i64 [[EXT]] to i32
-; CHECK-NEXT:    [[SHL:%.*]] = shl nuw i32 [[TMP3]], 1
+; CHECK-NEXT:    [[EXT:%.*]] = phi i64 [ [[INDVARS_IV_NEXT:%.*]], %[[CALL_BB:.*]] ], [ [[TMP2]], %[[ENTRY]] ]
+; CHECK-NEXT:    [[IV:%.*]] = trunc nuw i64 [[EXT]] to i32
+; CHECK-NEXT:    [[SHL:%.*]] = shl i32 [[IV]], 1
 ; CHECK-NEXT:    [[POS:%.*]] = icmp sgt i32 [[SHL]], 0
-; CHECK-NEXT:    br i1 [[POS]], label %[[CALL_BB:.*]], label %[[EXIT:.*]]
+; CHECK-NEXT:    br i1 [[POS]], label %[[CALL_BB]], label %[[EXIT:.*]]
 ; CHECK:       [[CALL_BB]]:
 ; CHECK-NEXT:    [[V:%.*]] = tail call i32 @load(i64 [[EXT]])
-; CHECK-NEXT:    br label %[[LOOP_LATCH]]
-; CHECK:       [[LOOP_LATCH]]:
 ; CHECK-NEXT:    [[DONE:%.*]] = icmp eq i64 [[EXT]], 0
 ; CHECK-NEXT:    [[INDVARS_IV_NEXT]] = add nsw i64 [[EXT]], -1
 ; CHECK-NEXT:    br i1 [[DONE]], label %[[EXIT]], label %[[LOOP_HEADER]]

>From e736d57797649e0f45e7556727b20be0121862fc Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Sat, 22 Aug 2026 14:39:55 +0100
Subject: [PATCH 3/3] !fixup add TODO + tests for more negative code paths

---
 .../monotonic-int-phis-decrement.ll           | 80 +++++++++++++++++++
 .../constraint-eliminiation-interactions.ll   |  1 +
 2 files changed, 81 insertions(+)

diff --git a/llvm/test/Transforms/ConstraintElimination/monotonic-int-phis-decrement.ll b/llvm/test/Transforms/ConstraintElimination/monotonic-int-phis-decrement.ll
index f2e4b42fc477a..32d38acb8bac9 100644
--- a/llvm/test/Transforms/ConstraintElimination/monotonic-int-phis-decrement.ll
+++ b/llvm/test/Transforms/ConstraintElimination/monotonic-int-phis-decrement.ll
@@ -375,6 +375,86 @@ exit:
   ret void
 }
 
+; Same as above, but the loop is guarded by %b <=s %start, which guarantees the
+; loop exits before the decrement wraps in the signed sense.
+define void @add_rec_decreasing_no_wrap_flags_signed_precond(i8 %start, i8 %b) {
+; CHECK-LABEL: define void @add_rec_decreasing_no_wrap_flags_signed_precond(
+; CHECK-SAME: i8 [[START:%.*]], i8 [[B:%.*]]) {
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[PRECOND:%.*]] = icmp sle i8 [[B]], [[START]]
+; CHECK-NEXT:    br i1 [[PRECOND]], label [[LOOP_HEADER:%.*]], label [[EXIT:%.*]]
+; CHECK:       loop.header:
+; CHECK-NEXT:    [[K_0:%.*]] = phi i8 [ [[START]], [[ENTRY:%.*]] ], [ [[K_DEC:%.*]], [[LOOP_LATCH:%.*]] ]
+; CHECK-NEXT:    [[CMP2_NOT:%.*]] = icmp eq i8 [[K_0]], [[B]]
+; CHECK-NEXT:    br i1 [[CMP2_NOT]], label [[EXIT]], label [[LOOP_LATCH]]
+; CHECK:       loop.latch:
+; CHECK-NEXT:    call void @use(i1 true)
+; CHECK-NEXT:    [[U:%.*]] = icmp ule i8 [[K_0]], [[START]]
+; CHECK-NEXT:    call void @use(i1 [[U]])
+; CHECK-NEXT:    [[K_DEC]] = add i8 [[K_0]], -1
+; CHECK-NEXT:    br label [[LOOP_HEADER]]
+; CHECK:       exit:
+; CHECK-NEXT:    ret void
+;
+entry:
+  %precond = icmp sle i8 %b, %start
+  br i1 %precond, label %loop.header, label %exit
+
+loop.header:
+  %k.0 = phi i8 [ %start, %entry ], [ %k.dec, %loop.latch ]
+  %cmp2.not = icmp eq i8 %k.0, %b
+  br i1 %cmp2.not, label %exit, label %loop.latch
+
+loop.latch:
+  %s = icmp sle i8 %k.0, %start
+  call void @use(i1 %s)
+  %u = icmp ule i8 %k.0, %start
+  call void @use(i1 %u)
+  %k.dec = add i8 %k.0, -1
+  br label %loop.header
+
+exit:
+  ret void
+}
+
+; Same as above, but the step is a `sub` and thus not visible as `add %k.0, C`
+; in the IR; the direction of the induction comes from SCEV only.
+define void @add_rec_decreasing_sub_step_signed_precond(i8 %start, i8 %b) {
+; CHECK-LABEL: define void @add_rec_decreasing_sub_step_signed_precond(
+; CHECK-SAME: i8 [[START:%.*]], i8 [[B:%.*]]) {
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[PRECOND:%.*]] = icmp sle i8 [[B]], [[START]]
+; CHECK-NEXT:    br i1 [[PRECOND]], label [[LOOP_HEADER:%.*]], label [[EXIT:%.*]]
+; CHECK:       loop.header:
+; CHECK-NEXT:    [[K_0:%.*]] = phi i8 [ [[START]], [[ENTRY:%.*]] ], [ [[K_DEC:%.*]], [[LOOP_LATCH:%.*]] ]
+; CHECK-NEXT:    [[CMP2_NOT:%.*]] = icmp eq i8 [[K_0]], [[B]]
+; CHECK-NEXT:    br i1 [[CMP2_NOT]], label [[EXIT]], label [[LOOP_LATCH]]
+; CHECK:       loop.latch:
+; CHECK-NEXT:    call void @use(i1 true)
+; CHECK-NEXT:    [[K_DEC]] = sub i8 [[K_0]], 1
+; CHECK-NEXT:    br label [[LOOP_HEADER]]
+; CHECK:       exit:
+; CHECK-NEXT:    ret void
+;
+entry:
+  %precond = icmp sle i8 %b, %start
+  br i1 %precond, label %loop.header, label %exit
+
+loop.header:
+  %k.0 = phi i8 [ %start, %entry ], [ %k.dec, %loop.latch ]
+  %cmp2.not = icmp eq i8 %k.0, %b
+  br i1 %cmp2.not, label %exit, label %loop.latch
+
+loop.latch:
+  %s = icmp sle i8 %k.0, %start
+  call void @use(i1 %s)
+  %k.dec = sub i8 %k.0, 1
+  br label %loop.header
+
+exit:
+  ret void
+}
+
 ; The decrement has no wrap flags in the IR, but SCEV can infer nsw for the
 ; induction from the guarded backedge.
 define void @add_rec_decreasing_nsw_from_scev(i8 %b) {
diff --git a/llvm/test/Transforms/PhaseOrdering/constraint-eliminiation-interactions.ll b/llvm/test/Transforms/PhaseOrdering/constraint-eliminiation-interactions.ll
index f710f66ae413f..4b5e0b278f350 100644
--- a/llvm/test/Transforms/PhaseOrdering/constraint-eliminiation-interactions.ll
+++ b/llvm/test/Transforms/PhaseOrdering/constraint-eliminiation-interactions.ll
@@ -162,6 +162,7 @@ else:
   ret i64 %res
 }
 
+; TODO: We should be able to preserve shl nuw.
 define void @shl_flags(i32 %n) {
 ; CHECK-LABEL: define void @shl_flags(
 ; CHECK-SAME: i32 [[N:%.*]]) local_unnamed_addr {



More information about the llvm-commits mailing list