[llvm] 37d488f - [ConstraintElim] Look through post-increment in header EQ/NE compare. (#209199)

via llvm-commits llvm-commits at lists.llvm.org
Tue Jul 14 02:17:17 PDT 2026


Author: Florian Hahn
Date: 2026-07-14T09:17:12Z
New Revision: 37d488f15991d8c65666834a7aed14000d1393fa

URL: https://github.com/llvm/llvm-project/commit/37d488f15991d8c65666834a7aed14000d1393fa
DIFF: https://github.com/llvm/llvm-project/commit/37d488f15991d8c65666834a7aed14000d1393fa.diff

LOG: [ConstraintElim] Look through post-increment in header EQ/NE compare. (#209199)

Extend the header-controlled induction handling to look through a
post-increment `PN + C` (constant C) on the compared value

For now, the new path is limited to deriving unsigned facts.

Alive2 Proof: https://alive2.llvm.org/ce/z/DCNeSr

PR: https://github.com/llvm/llvm-project/pull/209199

Added: 
    

Modified: 
    llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
    llvm/test/Transforms/ConstraintElimination/induction-condition-in-loop-exit.ll
    llvm/test/Transforms/ConstraintElimination/loops-header-tested-pointer-cmps.ll

Removed: 
    


################################################################################
diff  --git a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
index 448a5d648f0b8..5c0377c454dd5 100644
--- a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
@@ -941,12 +941,18 @@ void State::addInfoForInductions(BasicBlock &BB) {
   if (!L || L->getHeader() != &BB)
     return;
 
-  PHINode *PN;
+  // A is either a phi or a post-increment PN + C with constant step. For the
+  // latter, extract the constant IncStep.
+  Value *A;
   Value *B;
+  PHINode *PN = nullptr;
+  const APInt *IncStep = nullptr;
   CmpPredicate Pred;
+  auto IndValue =
+      m_Value(A, m_CombineOr(m_Phi(PN), m_c_Add(m_Phi(PN), m_APInt(IncStep))));
 
   if (!match(BB.getTerminator(),
-             m_Br(m_c_ICmp(Pred, m_Phi(PN), m_Value(B)), m_Value(), m_Value())))
+             m_Br(m_c_ICmp(Pred, IndValue, m_Value(B)), m_Value(), m_Value())))
     return;
   if (PN->getParent() != &BB || PN->getNumIncomingValues() != 2 ||
       !SE.isSCEVable(PN->getType()))
@@ -1002,6 +1008,11 @@ void State::addInfoForInductions(BasicBlock &BB) {
   else
     return;
 
+  // If we looked through `PN + C`, only derive facts when that add is
+  // really the induction's post-increment.
+  if (IncStep && (*IncStep != StepOffset || StepOffset.isNegative()))
+    return;
+
   // Handle negative steps.
   if (StepOffset.isNegative()) {
     // TODO: Extend to allow steps > -1.
@@ -1042,24 +1053,39 @@ void State::addInfoForInductions(BasicBlock &BB) {
       return;
   }
 
-  // AR may wrap. Add PN >= StartValue conditional on StartValue <= B which
+  Value *LowerBound = StartValue;
+  if (IncStep) {
+    // Adjust lower bound when dealing with a post-increment value.
+    auto *StartC = dyn_cast<ConstantInt>(StartValue);
+    if (!StartC)
+      return;
+    bool Overflow = false;
+    APInt Sum = StartC->getValue().uadd_ov(StepOffset, Overflow);
+    if (Overflow)
+      return;
+    LowerBound = ConstantInt::get(StartValue->getType(), Sum);
+  }
+
+  // AR may wrap. Add PN >= StartValue conditional on LowerBound <= B which
   // guarantees that the loop exits before wrapping in combination with the
   // restrictions on B and the step above.
   if (!MonotonicallyIncreasingUnsigned)
     WorkList.push_back(FactOrCheck::getConditionFact(
         DTN, CmpInst::ICMP_UGE, PN, StartValue,
-        ConditionTy(CmpInst::ICMP_ULE, StartValue, B)));
-  if (!MonotonicallyIncreasingSigned)
+        ConditionTy(CmpInst::ICMP_ULE, LowerBound, B)));
+  // Only unsigned facts are derived for the post-increment path.
+  if (!MonotonicallyIncreasingSigned && !IncStep)
     WorkList.push_back(FactOrCheck::getConditionFact(
         DTN, CmpInst::ICMP_SGE, PN, StartValue,
         ConditionTy(CmpInst::ICMP_SLE, StartValue, B)));
 
   WorkList.push_back(FactOrCheck::getConditionFact(
       DTN, CmpInst::ICMP_ULT, PN, B,
-      ConditionTy(CmpInst::ICMP_ULE, StartValue, B)));
-  WorkList.push_back(FactOrCheck::getConditionFact(
-      DTN, CmpInst::ICMP_SLT, PN, B,
-      ConditionTy(CmpInst::ICMP_SLE, StartValue, B)));
+      ConditionTy(CmpInst::ICMP_ULE, LowerBound, B)));
+  if (!IncStep)
+    WorkList.push_back(FactOrCheck::getConditionFact(
+        DTN, CmpInst::ICMP_SLT, PN, B,
+        ConditionTy(CmpInst::ICMP_SLE, StartValue, B)));
 
   // Try to add condition from header to the dedicated exit blocks. When exiting
   // either with EQ or NE in the header, we know that the induction value must
@@ -1067,14 +1093,14 @@ void State::addInfoForInductions(BasicBlock &BB) {
   assert(!StepOffset.isNegative() && "induction must be increasing");
   assert((Pred == CmpInst::ICMP_EQ || Pred == CmpInst::ICMP_NE) &&
          "unsupported predicate");
-  ConditionTy Precond = {CmpInst::ICMP_ULE, StartValue, B};
+  ConditionTy Precond = {CmpInst::ICMP_ULE, LowerBound, B};
   SmallVector<BasicBlock *> ExitBBs;
   L->getExitBlocks(ExitBBs);
   for (BasicBlock *EB : ExitBBs) {
     // Bail out on non-dedicated exits.
     if (DT.dominates(&BB, EB)) {
       WorkList.emplace_back(FactOrCheck::getConditionFact(
-          DT.getNode(EB), CmpInst::ICMP_ULE, PN, B, Precond));
+          DT.getNode(EB), CmpInst::ICMP_ULE, A, B, Precond));
     }
   }
 }

diff  --git a/llvm/test/Transforms/ConstraintElimination/induction-condition-in-loop-exit.ll b/llvm/test/Transforms/ConstraintElimination/induction-condition-in-loop-exit.ll
index d9f0055e0e83f..0f7dce25f18da 100644
--- a/llvm/test/Transforms/ConstraintElimination/induction-condition-in-loop-exit.ll
+++ b/llvm/test/Transforms/ConstraintElimination/induction-condition-in-loop-exit.ll
@@ -1079,3 +1079,314 @@ exit:
   %cmp.not = icmp eq i64 %sub, 0
   ret i1 %cmp.not
 }
+
+; The loop counts up from 0 with the header exit test on the post-increment
+; (iv.next == 100). The unsigned exit fact iv.next u<= 100 is derived, so
+; `iv.next u> 100` in the dedicated exit folds to false.
+define i1 @postinc_header_eq_unsigned_exit_fact(i1 %c) {
+; CHECK-LABEL: define i1 @postinc_header_eq_unsigned_exit_fact(
+; CHECK-SAME: i1 [[C:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    br label %[[LOOP_HEADER:.*]]
+; CHECK:       [[LOOP_HEADER]]:
+; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LATCH:.*]] ]
+; CHECK-NEXT:    [[IV_NEXT]] = add i64 [[IV]], 1
+; CHECK-NEXT:    [[DONE:%.*]] = icmp eq i64 [[IV_NEXT]], 100
+; CHECK-NEXT:    br i1 [[DONE]], label %[[EXIT:.*]], label %[[BODY:.*]]
+; CHECK:       [[BODY]]:
+; CHECK-NEXT:    br i1 [[C]], label %[[EXIT_BODY:.*]], label %[[LATCH]]
+; CHECK:       [[LATCH]]:
+; CHECK-NEXT:    br label %[[LOOP_HEADER]]
+; CHECK:       [[EXIT_BODY]]:
+; CHECK-NEXT:    ret i1 false
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret i1 false
+;
+entry:
+  br label %loop.header
+
+loop.header:
+  %iv = phi i64 [ 0, %entry ], [ %iv.next, %latch ]
+  %iv.next = add i64 %iv, 1
+  %done = icmp eq i64 %iv.next, 100
+  br i1 %done, label %exit, label %body
+
+body:
+  br i1 %c, label %exit_body, label %latch
+
+latch:
+  br label %loop.header
+
+exit_body:
+  %chk = icmp ugt i64 %iv.next, 100
+  ret i1 %chk
+
+exit:
+  ret i1 false
+}
+
+; Same as above but with a NE header test. The unsigned facts are still derived
+; and the body `iv u< 100` folds to true.
+define i1 @postinc_header_ne_unsigned_body_fact(i1 %c) {
+; CHECK-LABEL: define i1 @postinc_header_ne_unsigned_body_fact(
+; CHECK-SAME: i1 [[C:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    br label %[[LOOP_HEADER:.*]]
+; CHECK:       [[LOOP_HEADER]]:
+; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LATCH:.*]] ]
+; CHECK-NEXT:    [[IV_NEXT]] = add i64 [[IV]], 1
+; CHECK-NEXT:    [[DONE:%.*]] = icmp ne i64 [[IV_NEXT]], 100
+; CHECK-NEXT:    br i1 [[DONE]], label %[[BODY:.*]], label %[[EXIT:.*]]
+; CHECK:       [[BODY]]:
+; CHECK-NEXT:    br i1 [[C]], label %[[EXIT_BODY:.*]], label %[[LATCH]]
+; CHECK:       [[LATCH]]:
+; CHECK-NEXT:    br label %[[LOOP_HEADER]]
+; CHECK:       [[EXIT_BODY]]:
+; CHECK-NEXT:    ret i1 true
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret i1 false
+;
+entry:
+  br label %loop.header
+
+loop.header:
+  %iv = phi i64 [ 0, %entry ], [ %iv.next, %latch ]
+  %iv.next = add i64 %iv, 1
+  %done = icmp ne i64 %iv.next, 100
+  br i1 %done, label %body, label %exit
+
+body:
+  %chk = icmp ult i64 %iv, 100
+  br i1 %c, label %exit_body, label %latch
+
+latch:
+  br label %loop.header
+
+exit_body:
+  ret i1 %chk
+
+exit:
+  ret i1 false
+}
+
+; The compared post-increment `iv + 2` does not match the induction step of 1.
+define i1 @postinc_incstep_ne_step_not_folded(i1 %c) {
+; CHECK-LABEL: define i1 @postinc_incstep_ne_step_not_folded(
+; CHECK-SAME: i1 [[C:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    br label %[[LOOP_HEADER:.*]]
+; CHECK:       [[LOOP_HEADER]]:
+; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LATCH:.*]] ]
+; CHECK-NEXT:    [[IV_PLUS2:%.*]] = add i64 [[IV]], 2
+; CHECK-NEXT:    [[DONE:%.*]] = icmp eq i64 [[IV_PLUS2]], 100
+; CHECK-NEXT:    br i1 [[DONE]], label %[[EXIT:.*]], label %[[BODY:.*]]
+; CHECK:       [[BODY]]:
+; CHECK-NEXT:    [[CHK:%.*]] = icmp ult i64 [[IV_PLUS2]], 100
+; CHECK-NEXT:    br i1 [[C]], label %[[EXIT_BODY:.*]], label %[[LATCH]]
+; CHECK:       [[LATCH]]:
+; CHECK-NEXT:    [[IV_NEXT]] = add i64 [[IV]], 1
+; CHECK-NEXT:    br label %[[LOOP_HEADER]]
+; CHECK:       [[EXIT_BODY]]:
+; CHECK-NEXT:    ret i1 [[CHK]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret i1 false
+;
+entry:
+  br label %loop.header
+
+loop.header:
+  %iv = phi i64 [ 0, %entry ], [ %iv.next, %latch ]
+  %iv.plus2 = add i64 %iv, 2
+  %done = icmp eq i64 %iv.plus2, 100
+  br i1 %done, label %exit, label %body
+
+body:
+  %chk = icmp ult i64 %iv.plus2, 100
+  br i1 %c, label %exit_body, label %latch
+
+latch:
+  %iv.next = add i64 %iv, 1
+  br label %loop.header
+
+exit_body:
+  ret i1 %chk
+
+exit:
+  ret i1 false
+}
+
+; Post-increment with negative step.
+define i1 @postinc_negative_step_not_folded(i1 %c) {
+; CHECK-LABEL: define i1 @postinc_negative_step_not_folded(
+; CHECK-SAME: i1 [[C:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    br label %[[LOOP_HEADER:.*]]
+; CHECK:       [[LOOP_HEADER]]:
+; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ 100, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LATCH:.*]] ]
+; CHECK-NEXT:    [[IV_NEXT]] = add i64 [[IV]], -1
+; CHECK-NEXT:    [[DONE:%.*]] = icmp eq i64 [[IV_NEXT]], 0
+; CHECK-NEXT:    br i1 [[DONE]], label %[[EXIT:.*]], label %[[BODY:.*]]
+; CHECK:       [[BODY]]:
+; CHECK-NEXT:    [[CHK:%.*]] = icmp ugt i64 [[IV_NEXT]], 0
+; CHECK-NEXT:    br i1 [[C]], label %[[EXIT_BODY:.*]], label %[[LATCH]]
+; CHECK:       [[LATCH]]:
+; CHECK-NEXT:    br label %[[LOOP_HEADER]]
+; CHECK:       [[EXIT_BODY]]:
+; CHECK-NEXT:    ret i1 [[CHK]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret i1 false
+;
+entry:
+  br label %loop.header
+
+loop.header:
+  %iv = phi i64 [ 100, %entry ], [ %iv.next, %latch ]
+  %iv.next = add i64 %iv, -1
+  %done = icmp eq i64 %iv.next, 0
+  br i1 %done, label %exit, label %body
+
+body:
+  %chk = icmp ugt i64 %iv.next, 0
+  br i1 %c, label %exit_body, label %latch
+
+latch:
+  br label %loop.header
+
+exit_body:
+  ret i1 %chk
+
+exit:
+  ret i1 false
+}
+
+; Induction start value is not constant.
+define i1 @postinc_non_constant_start_not_folded(i64 %start, i1 %c) {
+; CHECK-LABEL: define i1 @postinc_non_constant_start_not_folded(
+; CHECK-SAME: i64 [[START:%.*]], i1 [[C:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    br label %[[LOOP_HEADER:.*]]
+; CHECK:       [[LOOP_HEADER]]:
+; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ [[START]], %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LATCH:.*]] ]
+; CHECK-NEXT:    [[IV_NEXT]] = add i64 [[IV]], 1
+; CHECK-NEXT:    [[DONE:%.*]] = icmp eq i64 [[IV_NEXT]], 100
+; CHECK-NEXT:    br i1 [[DONE]], label %[[EXIT:.*]], label %[[BODY:.*]]
+; CHECK:       [[BODY]]:
+; CHECK-NEXT:    br i1 [[C]], label %[[EXIT_BODY:.*]], label %[[LATCH]]
+; CHECK:       [[LATCH]]:
+; CHECK-NEXT:    br label %[[LOOP_HEADER]]
+; CHECK:       [[EXIT_BODY]]:
+; CHECK-NEXT:    [[CHK:%.*]] = icmp ule i64 [[IV_NEXT]], 100
+; CHECK-NEXT:    ret i1 [[CHK]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret i1 false
+;
+entry:
+  br label %loop.header
+
+loop.header:
+  %iv = phi i64 [ %start, %entry ], [ %iv.next, %latch ]
+  %iv.next = add i64 %iv, 1
+  %done = icmp eq i64 %iv.next, 100
+  br i1 %done, label %exit, label %body
+
+body:
+  br i1 %c, label %exit_body, label %latch
+
+latch:
+  br label %loop.header
+
+exit_body:
+  %chk = icmp ule i64 %iv.next, 100
+  ret i1 %chk
+
+exit:
+  ret i1 false
+}
+
+; Adjusted lower bound `StartValue + StepOffset` overflows unsigned.
+define i1 @postinc_start_plus_step_overflow_not_folded(i1 %c) {
+; CHECK-LABEL: define i1 @postinc_start_plus_step_overflow_not_folded(
+; CHECK-SAME: i1 [[C:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    br label %[[LOOP_HEADER:.*]]
+; CHECK:       [[LOOP_HEADER]]:
+; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ -1, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LATCH:.*]] ]
+; CHECK-NEXT:    [[IV_NEXT]] = add i64 [[IV]], 1
+; CHECK-NEXT:    [[DONE:%.*]] = icmp eq i64 [[IV_NEXT]], 100
+; CHECK-NEXT:    br i1 [[DONE]], label %[[EXIT:.*]], label %[[BODY:.*]]
+; CHECK:       [[BODY]]:
+; CHECK-NEXT:    br i1 [[C]], label %[[EXIT_BODY:.*]], label %[[LATCH]]
+; CHECK:       [[LATCH]]:
+; CHECK-NEXT:    br label %[[LOOP_HEADER]]
+; CHECK:       [[EXIT_BODY]]:
+; CHECK-NEXT:    [[CHK:%.*]] = icmp ule i64 [[IV_NEXT]], 100
+; CHECK-NEXT:    ret i1 [[CHK]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret i1 false
+;
+entry:
+  br label %loop.header
+
+loop.header:
+  %iv = phi i64 [ -1, %entry ], [ %iv.next, %latch ]
+  %iv.next = add i64 %iv, 1
+  %done = icmp eq i64 %iv.next, 100
+  br i1 %done, label %exit, label %body
+
+body:
+  br i1 %c, label %exit_body, label %latch
+
+latch:
+  br label %loop.header
+
+exit_body:
+  %chk = icmp ule i64 %iv.next, 100
+  ret i1 %chk
+
+exit:
+  ret i1 false
+}
+
+; Post-increment condition with signed predicates in body.
+define i1 @postinc_negative_start_signed_body_not_folded(i1 %c) {
+; CHECK-LABEL: define i1 @postinc_negative_start_signed_body_not_folded(
+; CHECK-SAME: i1 [[C:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    br label %[[LOOP_HEADER:.*]]
+; CHECK:       [[LOOP_HEADER]]:
+; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ -10, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LATCH:.*]] ]
+; CHECK-NEXT:    [[IV_NEXT]] = add i64 [[IV]], 1
+; CHECK-NEXT:    [[DONE:%.*]] = icmp eq i64 [[IV_NEXT]], 10
+; CHECK-NEXT:    br i1 [[DONE]], label %[[EXIT:.*]], label %[[BODY:.*]]
+; CHECK:       [[BODY]]:
+; CHECK-NEXT:    [[CHK:%.*]] = icmp slt i64 [[IV]], 10
+; CHECK-NEXT:    br i1 [[C]], label %[[EXIT_BODY:.*]], label %[[LATCH]]
+; CHECK:       [[LATCH]]:
+; CHECK-NEXT:    br label %[[LOOP_HEADER]]
+; CHECK:       [[EXIT_BODY]]:
+; CHECK-NEXT:    ret i1 [[CHK]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret i1 false
+;
+entry:
+  br label %loop.header
+
+loop.header:
+  %iv = phi i64 [ -10, %entry ], [ %iv.next, %latch ]
+  %iv.next = add i64 %iv, 1
+  %done = icmp eq i64 %iv.next, 10
+  br i1 %done, label %exit, label %body
+
+body:
+  %chk = icmp slt i64 %iv, 10
+  br i1 %c, label %exit_body, label %latch
+
+latch:
+  br label %loop.header
+
+exit_body:
+  ret i1 %chk
+
+exit:
+  ret i1 false
+}

diff  --git a/llvm/test/Transforms/ConstraintElimination/loops-header-tested-pointer-cmps.ll b/llvm/test/Transforms/ConstraintElimination/loops-header-tested-pointer-cmps.ll
index df0cb40965430..66aeb85bf0a93 100644
--- a/llvm/test/Transforms/ConstraintElimination/loops-header-tested-pointer-cmps.ll
+++ b/llvm/test/Transforms/ConstraintElimination/loops-header-tested-pointer-cmps.ll
@@ -210,9 +210,9 @@ define void @test2_with_ne(ptr %src, ptr %lower, ptr %upper, i8 %N) {
 ; CHECK-NEXT:    br i1 [[EC]], label [[EXIT:%.*]], label [[LOOP_BODY:%.*]]
 ; CHECK:       loop.body:
 ; CHECK-NEXT:    [[SRC_IV:%.*]] = getelementptr inbounds i8, ptr [[SRC]], i8 [[IV]]
-; CHECK-NEXT:    [[CMP_IV_START:%.*]] = icmp ult ptr [[SRC_IV]], [[LOWER]]
 ; CHECK-NEXT:    [[CMP_IV_END:%.*]] = icmp uge ptr [[SRC_IV]], [[UPPER]]
-; CHECK-NEXT:    br i1 [[CMP_IV_END]], label [[TRAP_BB]], label [[LOOP_BODY_1:%.*]]
+; CHECK-NEXT:    [[OR_1:%.*]] = or i1 false, [[CMP_IV_END]]
+; CHECK-NEXT:    br i1 [[OR_1]], label [[TRAP_BB]], label [[LOOP_BODY_1:%.*]]
 ; CHECK:       loop.body.1:
 ; CHECK-NEXT:    [[ADD_1:%.*]] = add nuw nsw i8 [[IV]], 1
 ; CHECK-NEXT:    [[SRC_IV_1:%.*]] = getelementptr inbounds i8, ptr [[SRC]], i8 [[ADD_1]]


        


More information about the llvm-commits mailing list