[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