[llvm] [ConstraintElim] use condition from latch in addInfoForInductions. (PR #210984)
Florian Hahn via llvm-commits
llvm-commits at lists.llvm.org
Fri Jul 24 08:43:48 PDT 2026
https://github.com/fhahn updated https://github.com/llvm/llvm-project/pull/210984
>From 59092069f5d950f2838d9dcfdb687147f133ae93 Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Thu, 9 Jul 2026 17:51:57 +0100
Subject: [PATCH 1/3] [ConstraintElim] use condition from latch in
addInfoForInductions.
Also use condition in latch to inject %iv <= %B and co into the header,
if the latch condition compares a post-inc IV like %iv + %step != %B.
This allows reasoning about loops that have been rotated before
ConstraintElimination, e.g. during the LTO phase or Swift where the
frontend already rotates loops before generating LLVM IR.
Alive2 Proof for injecting %iv <= B from %iv + %step != %B in latch:
https://alive2.llvm.org/ce/z/pH8Czb
Depends on https://github.com/llvm/llvm-project/pull/210079 (included in
R)
---
.../Scalar/ConstraintElimination.cpp | 20 ++++++++++++++-----
...on-condition-in-loop-exit-latch-counted.ll | 12 ++++-------
.../loops-bottom-tested-pointer-cmps.ll | 15 ++++++--------
3 files changed, 25 insertions(+), 22 deletions(-)
diff --git a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
index da92f978cc08c..aab17e149f088 100644
--- a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
@@ -944,7 +944,12 @@ static void dumpConstraint(ArrayRef<int64_t> C,
void State::addInfoForInductions(BasicBlock &BB) {
auto *L = LI.getLoopFor(&BB);
- if (!L || L->getHeader() != &BB)
+ if (!L)
+ return;
+
+ BasicBlock *Header = L->getHeader();
+ BasicBlock *Latch = L->getLoopLatch();
+ if (Header != &BB && Latch != &BB)
return;
// A is either a phi or a post-increment PN + C with constant step. For the
@@ -960,10 +965,15 @@ void State::addInfoForInductions(BasicBlock &BB) {
if (!match(BB.getTerminator(),
m_Br(m_c_ICmp(Pred, IndValue, m_Value(B)), m_Value(), m_Value())))
return;
- if (PN->getParent() != &BB || PN->getNumIncomingValues() != 2 ||
+ if (PN->getParent() != Header || PN->getNumIncomingValues() != 2 ||
!SE.isSCEVable(PN->getType()))
return;
+ // Only use the condition in the latch to inject facts in the header when
+ // comparing a post-inc IV.
+ if (&BB == Latch && !IncStep)
+ return;
+
BasicBlock *InLoopSucc = nullptr;
if (Pred == CmpInst::ICMP_NE)
InLoopSucc = cast<CondBrInst>(BB.getTerminator())->getSuccessor(0);
@@ -1109,9 +1119,9 @@ void State::addInfoForInductions(BasicBlock &BB) {
WorkList.push_back(FactOrCheck::getConditionFact(DTN, CmpInst::ICMP_ULT, PN,
B, StartBeforeBoundULE));
- // 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
- // be u<= B, as other exits may only exit earlier.
+ // Try to add condition from the header or latch to the dedicated exit
+ // blocks. When exiting either with EQ or NE, we know that the induction value
+ // must be u<= B, as other exits may only exit earlier.
assert(!StepOffset->isNegative() && "induction must be increasing");
assert((Pred == CmpInst::ICMP_EQ || Pred == CmpInst::ICMP_NE) &&
"unsupported predicate");
diff --git a/llvm/test/Transforms/ConstraintElimination/induction-condition-in-loop-exit-latch-counted.ll b/llvm/test/Transforms/ConstraintElimination/induction-condition-in-loop-exit-latch-counted.ll
index 412e07809e0c4..1ebe1754d4a0e 100644
--- a/llvm/test/Transforms/ConstraintElimination/induction-condition-in-loop-exit-latch-counted.ll
+++ b/llvm/test/Transforms/ConstraintElimination/induction-condition-in-loop-exit-latch-counted.ll
@@ -15,8 +15,7 @@ define i1 @latch_counted_header_check_removable(ptr %p, i64 %n, i64 %lim) {
; CHECK: [[LOOP_HEADER]]:
; CHECK-NEXT: [[IV:%.*]] = phi i64 [ 0, %[[PH]] ], [ [[IV_NEXT:%.*]], %[[LOOP_LATCH:.*]] ]
; CHECK-NEXT: [[OFF:%.*]] = shl nuw nsw i64 [[IV]], 2
-; CHECK-NEXT: [[RC:%.*]] = icmp ult i64 [[OFF]], [[LIM]]
-; CHECK-NEXT: br i1 [[RC]], label %[[LOOP_LATCH]], label %[[EXIT_1]]
+; CHECK-NEXT: br i1 true, label %[[LOOP_LATCH]], label %[[EXIT_1]]
; CHECK: [[LOOP_LATCH]]:
; CHECK-NEXT: [[GEP:%.*]] = getelementptr i8, ptr [[P]], i64 [[OFF]]
; CHECK-NEXT: store i8 0, ptr [[GEP]], align 1
@@ -131,8 +130,7 @@ define i1 @latch_counted_bound_consumer_folded(ptr %s, i64 %n) {
; CHECK-NEXT: br i1 [[EXITCOND_NOT]], label %[[EXIT:.*]], label %[[LOOP_HEADER]]
; CHECK: [[EXIT]]:
; CHECK-NEXT: [[SUB:%.*]] = add i64 [[N]], -1
-; CHECK-NEXT: [[CMP_NOT:%.*]] = icmp eq i64 [[SUB]], 0
-; CHECK-NEXT: ret i1 [[CMP_NOT]]
+; CHECK-NEXT: ret i1 false
;
entry:
br label %loop.header
@@ -163,8 +161,7 @@ define i64 @both_header_and_latch_guards(ptr %p, i64 %n) {
; CHECK-NEXT: br i1 [[POS]], label %[[LOOP_HEADER:.*]], label %[[EXIT_EARLY:.*]]
; CHECK: [[LOOP_HEADER]]:
; CHECK-NEXT: [[IV:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP_LATCH:.*]] ]
-; CHECK-NEXT: [[HC:%.*]] = icmp eq i64 [[IV]], [[N]]
-; CHECK-NEXT: br i1 [[HC]], label %[[EXIT_HDR:.*]], label %[[LOOP_BODY:.*]]
+; CHECK-NEXT: br i1 false, label %[[EXIT_HDR:.*]], label %[[LOOP_BODY:.*]]
; CHECK: [[LOOP_BODY]]:
; CHECK-NEXT: [[GEP:%.*]] = getelementptr i8, ptr [[P]], i64 [[IV]]
; CHECK-NEXT: store i8 0, ptr [[GEP]], align 1
@@ -257,8 +254,7 @@ define i1 @latch_counted_signed_header_check_removable(ptr %p, i64 %n) {
; CHECK-NEXT: br label %[[LOOP_HEADER:.*]]
; CHECK: [[LOOP_HEADER]]:
; CHECK-NEXT: [[IV:%.*]] = phi i64 [ -10, %[[PH]] ], [ [[IV_NEXT:%.*]], %[[LOOP_LATCH:.*]] ]
-; CHECK-NEXT: [[C:%.*]] = icmp slt i64 [[IV]], [[N]]
-; CHECK-NEXT: br i1 [[C]], label %[[LOOP_LATCH]], label %[[EXIT_1:.*]]
+; CHECK-NEXT: br i1 true, label %[[LOOP_LATCH]], label %[[EXIT_1:.*]]
; CHECK: [[LOOP_LATCH]]:
; CHECK-NEXT: [[GEP:%.*]] = getelementptr i8, ptr [[P]], i64 [[IV]]
; CHECK-NEXT: store i8 0, ptr [[GEP]], align 1
diff --git a/llvm/test/Transforms/ConstraintElimination/loops-bottom-tested-pointer-cmps.ll b/llvm/test/Transforms/ConstraintElimination/loops-bottom-tested-pointer-cmps.ll
index 91546d4abf438..6491f46e4f124 100644
--- a/llvm/test/Transforms/ConstraintElimination/loops-bottom-tested-pointer-cmps.ll
+++ b/llvm/test/Transforms/ConstraintElimination/loops-bottom-tested-pointer-cmps.ll
@@ -21,8 +21,7 @@ define void @checks_in_loops_removable(ptr %ptr, ptr %lower, ptr %upper, i8 %n)
; CHECK: loop.header:
; CHECK-NEXT: [[IV:%.*]] = phi i16 [ 0, [[PRE_2]] ], [ [[IV_NEXT:%.*]], [[LOOP_LATCH:%.*]] ]
; CHECK-NEXT: [[PTR_IV:%.*]] = getelementptr inbounds i8, ptr [[PTR]], i16 [[IV]]
-; CHECK-NEXT: [[CMP_PTR_IV_LOWER:%.*]] = icmp ugt ptr [[LOWER]], [[PTR_IV]]
-; CHECK-NEXT: [[CMP_PTR_IV_UPPER:%.*]] = icmp ule ptr [[UPPER]], [[PTR_IV]]
+; CHECK-NEXT: [[CMP_PTR_IV_UPPER:%.*]] = or i1 false, false
; CHECK-NEXT: br i1 [[CMP_PTR_IV_UPPER]], label [[TRAP]], label [[LOOP_LATCH]]
; CHECK: loop.latch:
; CHECK-NEXT: store i8 0, ptr [[PTR_IV]], align 4
@@ -85,14 +84,12 @@ define void @some_checks_in_loops_removable(ptr %ptr, ptr %lower, ptr %upper, i8
; CHECK: loop.header:
; CHECK-NEXT: [[IV:%.*]] = phi i16 [ 0, [[PRE_2]] ], [ [[IV_NEXT:%.*]], [[LOOP_LATCH:%.*]] ]
; CHECK-NEXT: [[PTR_IV:%.*]] = getelementptr inbounds i8, ptr [[PTR]], i16 [[IV]]
-; CHECK-NEXT: [[CMP_PTR_IV_LOWER:%.*]] = icmp ugt ptr [[LOWER]], [[PTR_IV]]
-; CHECK-NEXT: [[CMP_PTR_IV_UPPER:%.*]] = icmp ule ptr [[UPPER]], [[PTR_IV]]
+; CHECK-NEXT: [[CMP_PTR_IV_UPPER:%.*]] = or i1 false, false
; CHECK-NEXT: br i1 [[CMP_PTR_IV_UPPER]], label [[TRAP]], label [[LOOP_BODY:%.*]]
; CHECK: loop.body:
; CHECK-NEXT: [[IV_1:%.*]] = add nuw nsw i16 [[IV]], 1
; CHECK-NEXT: [[PTR_IV_1:%.*]] = getelementptr inbounds i8, ptr [[PTR]], i16 [[IV_1]]
-; CHECK-NEXT: [[CMP_PTR_IV_1_UPPER:%.*]] = icmp ule ptr [[UPPER]], [[PTR_IV_1]]
-; CHECK-NEXT: [[OR_1:%.*]] = or i1 false, [[CMP_PTR_IV_1_UPPER]]
+; CHECK-NEXT: [[OR_1:%.*]] = or i1 false, false
; CHECK-NEXT: br i1 [[CMP_PTR_IV_UPPER]], label [[TRAP]], label [[LOOP_LATCH]]
; CHECK: loop.latch:
; CHECK-NEXT: store i8 0, ptr [[PTR_IV]], align 4
@@ -161,15 +158,15 @@ define void @no_checks_in_loops_removable(ptr %ptr, ptr %lower, ptr %upper, i8 %
; CHECK: loop.header:
; CHECK-NEXT: [[IV:%.*]] = phi i16 [ 0, [[PRE_1]] ], [ [[IV_NEXT:%.*]], [[LOOP_LATCH:%.*]] ]
; CHECK-NEXT: [[PTR_IV:%.*]] = getelementptr inbounds i8, ptr [[PTR]], i16 [[IV]]
-; CHECK-NEXT: [[CMP_PTR_IV_LOWER:%.*]] = icmp ugt ptr [[LOWER]], [[PTR_IV]]
; CHECK-NEXT: [[CMP_PTR_IV_UPPER:%.*]] = icmp ule ptr [[UPPER]], [[PTR_IV]]
-; CHECK-NEXT: br i1 [[CMP_PTR_IV_UPPER]], label [[TRAP]], label [[LOOP_BODY:%.*]]
+; CHECK-NEXT: [[OR:%.*]] = or i1 false, [[CMP_PTR_IV_UPPER]]
+; CHECK-NEXT: br i1 [[OR]], label [[TRAP]], label [[LOOP_BODY:%.*]]
; CHECK: loop.body:
; CHECK-NEXT: [[IV_1:%.*]] = add nuw nsw i16 [[IV]], 1
; CHECK-NEXT: [[PTR_IV_1:%.*]] = getelementptr inbounds i8, ptr [[PTR]], i16 [[IV_1]]
; CHECK-NEXT: [[CMP_PTR_IV_1_UPPER:%.*]] = icmp ule ptr [[UPPER]], [[PTR_IV_1]]
; CHECK-NEXT: [[OR_1:%.*]] = or i1 false, [[CMP_PTR_IV_1_UPPER]]
-; CHECK-NEXT: br i1 [[CMP_PTR_IV_UPPER]], label [[TRAP]], label [[LOOP_LATCH]]
+; CHECK-NEXT: br i1 [[OR]], label [[TRAP]], label [[LOOP_LATCH]]
; CHECK: loop.latch:
; CHECK-NEXT: store i8 0, ptr [[PTR_IV]], align 4
; CHECK-NEXT: [[IV_NEXT]] = add nuw nsw i16 [[IV]], 1
>From 6eb4bba320a83dce94a2f7cfdff94dd0f6af433f Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Wed, 22 Jul 2026 14:24:41 +0100
Subject: [PATCH 2/3] !fixup refine comment
---
llvm/lib/Transforms/Scalar/ConstraintElimination.cpp | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
index aab17e149f088..f29da3bbec800 100644
--- a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
@@ -969,8 +969,10 @@ void State::addInfoForInductions(BasicBlock &BB) {
!SE.isSCEVable(PN->getType()))
return;
- // Only use the condition in the latch to inject facts in the header when
- // comparing a post-inc IV.
+ // The condition in the latch allows injecting %PN - %step <= %B - %step
+ // in the header, but currently we can only add facts based on existing IR
+ // values. For now, only use the condition when comparing a post-inc IV, in
+ // which case the injected condition %PN <= %B is an over-approximation.
if (&BB == Latch && !IncStep)
return;
>From 29f2886509922b6ba72f5dab47c2ebf033d78ac5 Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Fri, 24 Jul 2026 16:43:02 +0100
Subject: [PATCH 3/3] !fixup update comment as suggested, thanks!
---
llvm/lib/Transforms/Scalar/ConstraintElimination.cpp | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
index f29da3bbec800..3a1edf7c4ff09 100644
--- a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
@@ -969,10 +969,12 @@ void State::addInfoForInductions(BasicBlock &BB) {
!SE.isSCEVable(PN->getType()))
return;
- // The condition in the latch allows injecting %PN - %step <= %B - %step
- // in the header, but currently we can only add facts based on existing IR
- // values. For now, only use the condition when comparing a post-inc IV, in
- // which case the injected condition %PN <= %B is an over-approximation.
+ // For latch conditions, we need to inject the condition that holds for the
+ // next iteration into the header. We limit to post-inc conditions, for which
+ // an original PN + Step != B condition results in a PN < B constraint in the
+ // header, which also holds for the next loop iteration. This would no longer
+ // be correct if the post-inc handling would inject a more precise PN + Step <
+ // B constraint instead.
if (&BB == Latch && !IncStep)
return;
More information about the llvm-commits
mailing list