[llvm] [ConstraintElim] Support loops where header is also latch. (PR #221494)
Florian Hahn via llvm-commits
llvm-commits at lists.llvm.org
Mon Sep 7 04:11:40 PDT 2026
https://github.com/fhahn updated https://github.com/llvm/llvm-project/pull/221494
>From a5c993e3640b1fdac011c1a14d3afb3f01289a8b Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Sat, 5 Sep 2026 21:51:59 +0100
Subject: [PATCH 1/2] [ConstraintElim] Precommit latch == header tests.
---
...nduction-condition-in-loop-exit-postinc.ll | 41 +++++++++++++++++++
.../induction-symbolic-start-postinc.ll | 39 +++++++++++++++++-
2 files changed, 79 insertions(+), 1 deletion(-)
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 1fa5344c9911d..967dde589fbdd 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
@@ -711,6 +711,47 @@ exit:
ret void
}
+; Same as @latch_postdec_negative_step_folds, but the header is also to latch.
+define void @latch_postdec_negative_step_header_is_latch() {
+; CHECK-LABEL: define void @latch_postdec_negative_step_header_is_latch() {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IV:%.*]] = phi i8 [ 5, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[UGT:%.*]] = icmp ugt i8 [[IV]], 2
+; CHECK-NEXT: call void @use(i1 [[UGT]])
+; CHECK-NEXT: [[ULE:%.*]] = icmp ule i8 [[IV]], 5
+; CHECK-NEXT: call void @use(i1 [[ULE]])
+; CHECK-NEXT: [[SGT:%.*]] = icmp sgt i8 [[IV]], 2
+; CHECK-NEXT: call void @use(i1 [[SGT]])
+; CHECK-NEXT: call void @use(i1 true)
+; CHECK-NEXT: [[IV_NEXT]] = add i8 [[IV]], -1
+; CHECK-NEXT: [[EC:%.*]] = icmp eq i8 [[IV_NEXT]], 2
+; CHECK-NEXT: br i1 [[EC]], label %[[EXIT:.*]], label %[[LOOP]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %iv = phi i8 [ 5, %entry ], [ %iv.next, %loop ]
+ %ugt = icmp ugt i8 %iv, 2
+ call void @use(i1 %ugt)
+ %ule = icmp ule i8 %iv, 5
+ call void @use(i1 %ule)
+ %sgt = icmp sgt i8 %iv, 2
+ call void @use(i1 %sgt)
+ %sle = icmp sle i8 %iv, 5
+ call void @use(i1 %sle)
+ %iv.next = add i8 %iv, -1
+ %ec = icmp eq i8 %iv.next, 2
+ br i1 %ec, label %exit, label %loop
+
+exit:
+ ret void
+}
+
; B == StartValue for a latch post-decrement compare: %iv never reaches
; B + 1 == 6 before wrapping around the whole range, so nothing must be folded.
; The first iteration alone already has %iv == B.
diff --git a/llvm/test/Transforms/ConstraintElimination/induction-symbolic-start-postinc.ll b/llvm/test/Transforms/ConstraintElimination/induction-symbolic-start-postinc.ll
index d509e95fe8bcb..afb3620af4c1e 100644
--- a/llvm/test/Transforms/ConstraintElimination/induction-symbolic-start-postinc.ll
+++ b/llvm/test/Transforms/ConstraintElimination/induction-symbolic-start-postinc.ll
@@ -544,7 +544,6 @@ exit:
ret void
}
-; TODO: Header == Latch form not supported yet.
define void @postinc_symbolic_start_header_is_latch(i32 %start, i32 %n) {
; CHECK-LABEL: define void @postinc_symbolic_start_header_is_latch(
; CHECK-SAME: i32 [[START:%.*]], i32 [[N:%.*]]) {
@@ -624,3 +623,41 @@ loop:
exit:
ret void
}
+
+; The facts derived from the header/latch also hold in the exit block.
+define void @postinc_symbolic_start_header_is_latch_exit_block(i32 %start, i32 %n) {
+; CHECK-LABEL: define void @postinc_symbolic_start_header_is_latch_exit_block(
+; CHECK-SAME: i32 [[START:%.*]], i32 [[N:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[PRE:%.*]] = icmp ult i32 [[START]], [[N]]
+; CHECK-NEXT: call void @llvm.assume(i1 [[PRE]])
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IV:%.*]] = phi i32 [ [[START]], %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[IV_NEXT]] = add i32 [[IV]], 1
+; CHECK-NEXT: [[EC:%.*]] = icmp eq i32 [[IV_NEXT]], [[N]]
+; CHECK-NEXT: br i1 [[EC]], label %[[EXIT:.*]], label %[[LOOP]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: [[T:%.*]] = icmp ult i32 [[IV]], [[N]]
+; CHECK-NEXT: call void @use(i1 [[T]])
+; CHECK-NEXT: call void @use(i1 true)
+; CHECK-NEXT: ret void
+;
+entry:
+ %pre = icmp ult i32 %start, %n
+ call void @llvm.assume(i1 %pre)
+ br label %loop
+
+loop:
+ %iv = phi i32 [ %start, %entry ], [ %iv.next, %loop ]
+ %iv.next = add i32 %iv, 1
+ %ec = icmp eq i32 %iv.next, %n
+ br i1 %ec, label %exit, label %loop
+
+exit:
+ %t = icmp ult i32 %iv, %n
+ call void @use(i1 %t)
+ %c.1 = icmp ule i32 %iv.next, %n
+ call void @use(i1 %c.1)
+ ret void
+}
>From 4234fff39d6ee2aebaf689ee289ddb4a87046aa0 Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Sat, 5 Sep 2026 10:35:14 +0100
Subject: [PATCH 2/2] [ConstraintElim] Support loops where header is also
latch.
Remove the bail out for header == latch. We now support deriving facts
from the latch condition and the restriction is no longer necessary.
This enables inferring additional flags in a number of cases, and a few
additional folds in practice:
https://github.com/dtcxzyw/llvm-opt-benchmark-nightly/pull/1200.
It also removes a surprising limitation when writing manual tests.
---
llvm/lib/Transforms/Scalar/ConstraintElimination.cpp | 2 +-
.../induction-condition-in-loop-exit-postinc.ll | 9 +++------
.../induction-relational-predicate-latch.ll | 3 +--
.../induction-symbolic-start-postinc.ll | 6 ++----
4 files changed, 7 insertions(+), 13 deletions(-)
diff --git a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
index c4bc584028e83..4b4cb1f207508 100644
--- a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
@@ -1109,7 +1109,7 @@ void State::addInfoForInductions(BasicBlock &BB) {
BasicBlock *InLoopSucc = cast<CondBrInst>(BB.getTerminator())
->getSuccessor(ContinueOnTrue ? 0 : 1);
- if (!L->contains(InLoopSucc) || !L->isLoopExiting(&BB) || InLoopSucc == &BB)
+ if (!L->contains(InLoopSucc) || !L->isLoopExiting(&BB))
return;
BasicBlock *LoopPred = L->getLoopPredecessor();
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 967dde589fbdd..44741db16af6d 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
@@ -718,12 +718,9 @@ define void @latch_postdec_negative_step_header_is_latch() {
; CHECK-NEXT: br label %[[LOOP:.*]]
; CHECK: [[LOOP]]:
; CHECK-NEXT: [[IV:%.*]] = phi i8 [ 5, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP]] ]
-; CHECK-NEXT: [[UGT:%.*]] = icmp ugt i8 [[IV]], 2
-; CHECK-NEXT: call void @use(i1 [[UGT]])
-; CHECK-NEXT: [[ULE:%.*]] = icmp ule i8 [[IV]], 5
-; CHECK-NEXT: call void @use(i1 [[ULE]])
-; CHECK-NEXT: [[SGT:%.*]] = icmp sgt i8 [[IV]], 2
-; CHECK-NEXT: call void @use(i1 [[SGT]])
+; CHECK-NEXT: call void @use(i1 true)
+; CHECK-NEXT: call void @use(i1 true)
+; CHECK-NEXT: call void @use(i1 true)
; CHECK-NEXT: call void @use(i1 true)
; CHECK-NEXT: [[IV_NEXT]] = add i8 [[IV]], -1
; CHECK-NEXT: [[EC:%.*]] = icmp eq i8 [[IV_NEXT]], 2
diff --git a/llvm/test/Transforms/ConstraintElimination/induction-relational-predicate-latch.ll b/llvm/test/Transforms/ConstraintElimination/induction-relational-predicate-latch.ll
index abb3648b34ed6..803679831b988 100644
--- a/llvm/test/Transforms/ConstraintElimination/induction-relational-predicate-latch.ll
+++ b/llvm/test/Transforms/ConstraintElimination/induction-relational-predicate-latch.ll
@@ -128,8 +128,7 @@ define void @stride2_slt_header_is_latch(i64 %n) {
; CHECK-NEXT: br label %[[LOOP:.*]]
; CHECK: [[LOOP]]:
; CHECK-NEXT: [[IV:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP]] ]
-; CHECK-NEXT: [[C:%.*]] = icmp slt i64 [[IV]], [[N]]
-; CHECK-NEXT: call void @use(i1 [[C]])
+; CHECK-NEXT: call void @use(i1 true)
; CHECK-NEXT: [[IV_NEXT]] = add nsw i64 [[IV]], 2
; CHECK-NEXT: [[EC:%.*]] = icmp slt i64 [[IV_NEXT]], [[N]]
; CHECK-NEXT: br i1 [[EC]], label %[[LOOP]], label %[[EXIT:.*]]
diff --git a/llvm/test/Transforms/ConstraintElimination/induction-symbolic-start-postinc.ll b/llvm/test/Transforms/ConstraintElimination/induction-symbolic-start-postinc.ll
index afb3620af4c1e..6fbf5942a4d5c 100644
--- a/llvm/test/Transforms/ConstraintElimination/induction-symbolic-start-postinc.ll
+++ b/llvm/test/Transforms/ConstraintElimination/induction-symbolic-start-postinc.ll
@@ -553,8 +553,7 @@ define void @postinc_symbolic_start_header_is_latch(i32 %start, i32 %n) {
; CHECK-NEXT: br label %[[LOOP:.*]]
; CHECK: [[LOOP]]:
; CHECK-NEXT: [[IV:%.*]] = phi i32 [ [[START]], %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP]] ]
-; CHECK-NEXT: [[T:%.*]] = icmp ult i32 [[IV]], [[N]]
-; CHECK-NEXT: call void @use(i1 [[T]])
+; CHECK-NEXT: call void @use(i1 true)
; CHECK-NEXT: [[IV_NEXT]] = add i32 [[IV]], 1
; CHECK-NEXT: [[EC:%.*]] = icmp eq i32 [[IV_NEXT]], [[N]]
; CHECK-NEXT: br i1 [[EC]], label %[[EXIT:.*]], label %[[LOOP]]
@@ -638,8 +637,7 @@ define void @postinc_symbolic_start_header_is_latch_exit_block(i32 %start, i32 %
; CHECK-NEXT: [[EC:%.*]] = icmp eq i32 [[IV_NEXT]], [[N]]
; CHECK-NEXT: br i1 [[EC]], label %[[EXIT:.*]], label %[[LOOP]]
; CHECK: [[EXIT]]:
-; CHECK-NEXT: [[T:%.*]] = icmp ult i32 [[IV]], [[N]]
-; CHECK-NEXT: call void @use(i1 [[T]])
+; CHECK-NEXT: call void @use(i1 true)
; CHECK-NEXT: call void @use(i1 true)
; CHECK-NEXT: ret void
;
More information about the llvm-commits
mailing list