[llvm] [ConstraintElim] Handle single And/Or as exit condition in loop. (PR #221228)

Florian Hahn via llvm-commits llvm-commits at lists.llvm.org
Mon Sep 7 00:50:51 PDT 2026


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

>From d72d0d6773e07be5fb1208b07f54144738234c82 Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Fri, 4 Sep 2026 14:15:46 +0100
Subject: [PATCH 1/2] [ConstraintElim] Add tests for loops with and/or exit
 conditions.

---
 .../induction-exit-condition-or-and.ll        | 474 ++++++++++++++++++
 1 file changed, 474 insertions(+)
 create mode 100644 llvm/test/Transforms/ConstraintElimination/induction-exit-condition-or-and.ll

diff --git a/llvm/test/Transforms/ConstraintElimination/induction-exit-condition-or-and.ll b/llvm/test/Transforms/ConstraintElimination/induction-exit-condition-or-and.ll
new file mode 100644
index 0000000000000..5c4679e606894
--- /dev/null
+++ b/llvm/test/Transforms/ConstraintElimination/induction-exit-condition-or-and.ll
@@ -0,0 +1,474 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -passes=constraint-elimination -S %s | FileCheck %s
+
+declare void @use(i1)
+
+define void @stride2_or_latch(i64 %n, i1 %x, i1 %header.ec) {
+; CHECK-LABEL: define void @stride2_or_latch(
+; CHECK-SAME: i64 [[N:%.*]], i1 [[X:%.*]], i1 [[HEADER_EC:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    [[PRE_C:%.*]] = icmp sgt i64 [[N]], 0
+; CHECK-NEXT:    call void @llvm.assume(i1 [[PRE_C]])
+; CHECK-NEXT:    br label %[[LOOP_HEADER:.*]]
+; CHECK:       [[LOOP_HEADER]]:
+; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP_LATCH:.*]] ]
+; CHECK-NEXT:    [[C:%.*]] = icmp ult i64 [[IV]], [[N]]
+; CHECK-NEXT:    call void @use(i1 [[C]])
+; CHECK-NEXT:    [[N_MINUS_1:%.*]] = sub nuw i64 [[N]], 1
+; CHECK-NEXT:    [[C_TIGHT:%.*]] = icmp ult i64 [[IV]], [[N_MINUS_1]]
+; CHECK-NEXT:    call void @use(i1 [[C_TIGHT]])
+; CHECK-NEXT:    br i1 [[HEADER_EC]], label %[[EXIT:.*]], label %[[LOOP_LATCH]]
+; CHECK:       [[LOOP_LATCH]]:
+; CHECK-NEXT:    [[IV_NEXT]] = add nsw i64 [[IV]], 2
+; CHECK-NEXT:    [[EC:%.*]] = icmp sge i64 [[IV_NEXT]], [[N]]
+; CHECK-NEXT:    [[COND:%.*]] = or i1 [[X]], [[EC]]
+; CHECK-NEXT:    br i1 [[COND]], label %[[EXIT]], label %[[LOOP_HEADER]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret void
+;
+entry:
+  %pre.c = icmp sgt i64 %n, 0
+  call void @llvm.assume(i1 %pre.c)
+  br label %loop.header
+
+loop.header:
+  %iv = phi i64 [ 0, %entry ], [ %iv.next, %loop.latch ]
+  %c = icmp ult i64 %iv, %n
+  call void @use(i1 %c)
+  %n.minus.1 = sub nuw i64 %n, 1
+  %c.tight = icmp ult i64 %iv, %n.minus.1
+  call void @use(i1 %c.tight)
+  br i1 %header.ec, label %exit, label %loop.latch
+
+loop.latch:
+  %iv.next = add nsw i64 %iv, 2
+  %ec = icmp sge i64 %iv.next, %n
+  %cond = or i1 %x, %ec
+  br i1 %cond, label %exit, label %loop.header
+
+exit:
+  ret void
+}
+
+define void @stride2_or_latch_operands_swapped(i64 %n, i1 %x, i1 %header.ec) {
+; CHECK-LABEL: define void @stride2_or_latch_operands_swapped(
+; CHECK-SAME: i64 [[N:%.*]], i1 [[X:%.*]], i1 [[HEADER_EC:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    [[PRE_C:%.*]] = icmp sgt i64 [[N]], 0
+; CHECK-NEXT:    call void @llvm.assume(i1 [[PRE_C]])
+; CHECK-NEXT:    br label %[[LOOP_HEADER:.*]]
+; CHECK:       [[LOOP_HEADER]]:
+; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP_LATCH:.*]] ]
+; CHECK-NEXT:    [[C:%.*]] = icmp ult i64 [[IV]], [[N]]
+; CHECK-NEXT:    call void @use(i1 [[C]])
+; CHECK-NEXT:    [[N_MINUS_1:%.*]] = sub nuw i64 [[N]], 1
+; CHECK-NEXT:    [[C_TIGHT:%.*]] = icmp ult i64 [[IV]], [[N_MINUS_1]]
+; CHECK-NEXT:    call void @use(i1 [[C_TIGHT]])
+; CHECK-NEXT:    br i1 [[HEADER_EC]], label %[[EXIT:.*]], label %[[LOOP_LATCH]]
+; CHECK:       [[LOOP_LATCH]]:
+; CHECK-NEXT:    [[IV_NEXT]] = add nsw i64 [[IV]], 2
+; CHECK-NEXT:    [[EC:%.*]] = icmp sge i64 [[IV_NEXT]], [[N]]
+; CHECK-NEXT:    [[COND:%.*]] = or i1 [[EC]], [[X]]
+; CHECK-NEXT:    br i1 [[COND]], label %[[EXIT]], label %[[LOOP_HEADER]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret void
+;
+entry:
+  %pre.c = icmp sgt i64 %n, 0
+  call void @llvm.assume(i1 %pre.c)
+  br label %loop.header
+
+loop.header:
+  %iv = phi i64 [ 0, %entry ], [ %iv.next, %loop.latch ]
+  %c = icmp ult i64 %iv, %n
+  call void @use(i1 %c)
+  %n.minus.1 = sub nuw i64 %n, 1
+  %c.tight = icmp ult i64 %iv, %n.minus.1
+  call void @use(i1 %c.tight)
+  br i1 %header.ec, label %exit, label %loop.latch
+
+loop.latch:
+  %iv.next = add nsw i64 %iv, 2
+  %ec = icmp sge i64 %iv.next, %n
+  %cond = or i1 %ec, %x
+  br i1 %cond, label %exit, label %loop.header
+
+exit:
+  ret void
+}
+
+define void @neg_or_on_continue_edge(i64 %n, i1 %x, i1 %header.ec) {
+; CHECK-LABEL: define void @neg_or_on_continue_edge(
+; CHECK-SAME: i64 [[N:%.*]], i1 [[X:%.*]], i1 [[HEADER_EC:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    [[PRE_C:%.*]] = icmp ne i64 [[N]], 0
+; CHECK-NEXT:    call void @llvm.assume(i1 [[PRE_C]])
+; CHECK-NEXT:    br label %[[LOOP_HEADER:.*]]
+; CHECK:       [[LOOP_HEADER]]:
+; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP_LATCH:.*]] ]
+; CHECK-NEXT:    [[C:%.*]] = icmp ult i64 [[IV]], [[N]]
+; CHECK-NEXT:    call void @use(i1 [[C]])
+; CHECK-NEXT:    [[N_MINUS_1:%.*]] = sub nuw i64 [[N]], 1
+; CHECK-NEXT:    [[C_TIGHT:%.*]] = icmp ult i64 [[IV]], [[N_MINUS_1]]
+; CHECK-NEXT:    call void @use(i1 [[C_TIGHT]])
+; CHECK-NEXT:    br i1 [[HEADER_EC]], label %[[EXIT:.*]], label %[[LOOP_LATCH]]
+; CHECK:       [[LOOP_LATCH]]:
+; CHECK-NEXT:    [[IV_NEXT]] = add nsw i64 [[IV]], 2
+; CHECK-NEXT:    [[EC:%.*]] = icmp slt i64 [[IV_NEXT]], [[N]]
+; CHECK-NEXT:    [[COND:%.*]] = or i1 [[X]], [[EC]]
+; CHECK-NEXT:    br i1 [[COND]], label %[[LOOP_HEADER]], label %[[EXIT]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret void
+;
+entry:
+  %pre.c = icmp ne i64 %n, 0
+  call void @llvm.assume(i1 %pre.c)
+  br label %loop.header
+
+loop.header:
+  %iv = phi i64 [ 0, %entry ], [ %iv.next, %loop.latch ]
+  %c = icmp ult i64 %iv, %n
+  call void @use(i1 %c)
+  %n.minus.1 = sub nuw i64 %n, 1
+  %c.tight = icmp ult i64 %iv, %n.minus.1
+  call void @use(i1 %c.tight)
+  br i1 %header.ec, label %exit, label %loop.latch
+
+loop.latch:
+  %iv.next = add nsw i64 %iv, 2
+  %ec = icmp slt i64 %iv.next, %n
+  %cond = or i1 %x, %ec
+  br i1 %cond, label %loop.header, label %exit
+
+exit:
+  ret void
+}
+
+define void @neg_or_on_continue_edge_operands_swapped(i64 %n, i1 %x, i1 %header.ec) {
+; CHECK-LABEL: define void @neg_or_on_continue_edge_operands_swapped(
+; CHECK-SAME: i64 [[N:%.*]], i1 [[X:%.*]], i1 [[HEADER_EC:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    [[PRE_C:%.*]] = icmp ne i64 [[N]], 0
+; CHECK-NEXT:    call void @llvm.assume(i1 [[PRE_C]])
+; CHECK-NEXT:    br label %[[LOOP_HEADER:.*]]
+; CHECK:       [[LOOP_HEADER]]:
+; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP_LATCH:.*]] ]
+; CHECK-NEXT:    [[C:%.*]] = icmp ult i64 [[IV]], [[N]]
+; CHECK-NEXT:    call void @use(i1 [[C]])
+; CHECK-NEXT:    [[N_MINUS_1:%.*]] = sub nuw i64 [[N]], 1
+; CHECK-NEXT:    [[C_TIGHT:%.*]] = icmp ult i64 [[IV]], [[N_MINUS_1]]
+; CHECK-NEXT:    call void @use(i1 [[C_TIGHT]])
+; CHECK-NEXT:    br i1 [[HEADER_EC]], label %[[EXIT:.*]], label %[[LOOP_LATCH]]
+; CHECK:       [[LOOP_LATCH]]:
+; CHECK-NEXT:    [[IV_NEXT]] = add nsw i64 [[IV]], 2
+; CHECK-NEXT:    [[EC:%.*]] = icmp slt i64 [[IV_NEXT]], [[N]]
+; CHECK-NEXT:    [[COND:%.*]] = or i1 [[EC]], [[X]]
+; CHECK-NEXT:    br i1 [[COND]], label %[[LOOP_HEADER]], label %[[EXIT]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret void
+;
+entry:
+  %pre.c = icmp ne i64 %n, 0
+  call void @llvm.assume(i1 %pre.c)
+  br label %loop.header
+
+loop.header:
+  %iv = phi i64 [ 0, %entry ], [ %iv.next, %loop.latch ]
+  %c = icmp ult i64 %iv, %n
+  call void @use(i1 %c)
+  %n.minus.1 = sub nuw i64 %n, 1
+  %c.tight = icmp ult i64 %iv, %n.minus.1
+  call void @use(i1 %c.tight)
+  br i1 %header.ec, label %exit, label %loop.latch
+
+loop.latch:
+  %iv.next = add nsw i64 %iv, 2
+  %ec = icmp slt i64 %iv.next, %n
+  %cond = or i1 %ec, %x
+  br i1 %cond, label %loop.header, label %exit
+
+exit:
+  ret void
+}
+
+define void @sel_or_peel(i64 %n, i1 %x, i1 %header.ec) {
+; CHECK-LABEL: define void @sel_or_peel(
+; CHECK-SAME: i64 [[N:%.*]], i1 [[X:%.*]], i1 [[HEADER_EC:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    [[PRE_C:%.*]] = icmp sgt i64 [[N]], 0
+; CHECK-NEXT:    call void @llvm.assume(i1 [[PRE_C]])
+; CHECK-NEXT:    br label %[[LOOP_HEADER:.*]]
+; CHECK:       [[LOOP_HEADER]]:
+; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP_LATCH:.*]] ]
+; CHECK-NEXT:    [[C:%.*]] = icmp ult i64 [[IV]], [[N]]
+; CHECK-NEXT:    call void @use(i1 [[C]])
+; CHECK-NEXT:    [[N_MINUS_1:%.*]] = sub nuw i64 [[N]], 1
+; CHECK-NEXT:    [[C_TIGHT:%.*]] = icmp ult i64 [[IV]], [[N_MINUS_1]]
+; CHECK-NEXT:    call void @use(i1 [[C_TIGHT]])
+; CHECK-NEXT:    br i1 [[HEADER_EC]], label %[[EXIT:.*]], label %[[LOOP_LATCH]]
+; CHECK:       [[LOOP_LATCH]]:
+; CHECK-NEXT:    [[IV_NEXT]] = add nsw i64 [[IV]], 2
+; CHECK-NEXT:    [[EC:%.*]] = icmp sge i64 [[IV_NEXT]], [[N]]
+; CHECK-NEXT:    [[COND:%.*]] = select i1 [[X]], i1 true, i1 [[EC]]
+; CHECK-NEXT:    br i1 [[COND]], label %[[EXIT]], label %[[LOOP_HEADER]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret void
+;
+entry:
+  %pre.c = icmp sgt i64 %n, 0
+  call void @llvm.assume(i1 %pre.c)
+  br label %loop.header
+
+loop.header:
+  %iv = phi i64 [ 0, %entry ], [ %iv.next, %loop.latch ]
+  %c = icmp ult i64 %iv, %n
+  call void @use(i1 %c)
+  %n.minus.1 = sub nuw i64 %n, 1
+  %c.tight = icmp ult i64 %iv, %n.minus.1
+  call void @use(i1 %c.tight)
+  br i1 %header.ec, label %exit, label %loop.latch
+
+loop.latch:
+  %iv.next = add nsw i64 %iv, 2
+  %ec = icmp sge i64 %iv.next, %n
+  %cond = select i1 %x, i1 true, i1 %ec
+  br i1 %cond, label %exit, label %loop.header
+
+exit:
+  ret void
+}
+
+define void @stride2_and_latch(i64 %n, i1 %x, i1 %header.ec) {
+; CHECK-LABEL: define void @stride2_and_latch(
+; CHECK-SAME: i64 [[N:%.*]], i1 [[X:%.*]], i1 [[HEADER_EC:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    [[PRE_C:%.*]] = icmp sgt i64 [[N]], 0
+; CHECK-NEXT:    call void @llvm.assume(i1 [[PRE_C]])
+; CHECK-NEXT:    br label %[[LOOP_HEADER:.*]]
+; CHECK:       [[LOOP_HEADER]]:
+; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP_LATCH:.*]] ]
+; CHECK-NEXT:    [[C:%.*]] = icmp ult i64 [[IV]], [[N]]
+; CHECK-NEXT:    call void @use(i1 [[C]])
+; CHECK-NEXT:    [[N_MINUS_1:%.*]] = sub nuw i64 [[N]], 1
+; CHECK-NEXT:    [[C_TIGHT:%.*]] = icmp ult i64 [[IV]], [[N_MINUS_1]]
+; CHECK-NEXT:    call void @use(i1 [[C_TIGHT]])
+; CHECK-NEXT:    br i1 [[HEADER_EC]], label %[[EXIT:.*]], label %[[LOOP_LATCH]]
+; CHECK:       [[LOOP_LATCH]]:
+; CHECK-NEXT:    [[IV_NEXT]] = add nsw i64 [[IV]], 2
+; CHECK-NEXT:    [[EC:%.*]] = icmp slt i64 [[IV_NEXT]], [[N]]
+; CHECK-NEXT:    [[COND:%.*]] = and i1 [[X]], [[EC]]
+; CHECK-NEXT:    br i1 [[COND]], label %[[LOOP_HEADER]], label %[[EXIT]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret void
+;
+entry:
+  %pre.c = icmp sgt i64 %n, 0
+  call void @llvm.assume(i1 %pre.c)
+  br label %loop.header
+
+loop.header:
+  %iv = phi i64 [ 0, %entry ], [ %iv.next, %loop.latch ]
+  %c = icmp ult i64 %iv, %n
+  call void @use(i1 %c)
+  %n.minus.1 = sub nuw i64 %n, 1
+  %c.tight = icmp ult i64 %iv, %n.minus.1
+  call void @use(i1 %c.tight)
+  br i1 %header.ec, label %exit, label %loop.latch
+
+loop.latch:
+  %iv.next = add nsw i64 %iv, 2
+  %ec = icmp slt i64 %iv.next, %n
+  %cond = and i1 %x, %ec
+  br i1 %cond, label %loop.header, label %exit
+
+exit:
+  ret void
+}
+
+define void @stride2_and_latch_operands_swapped(i64 %n, i1 %x, i1 %header.ec) {
+; CHECK-LABEL: define void @stride2_and_latch_operands_swapped(
+; CHECK-SAME: i64 [[N:%.*]], i1 [[X:%.*]], i1 [[HEADER_EC:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    [[PRE_C:%.*]] = icmp sgt i64 [[N]], 0
+; CHECK-NEXT:    call void @llvm.assume(i1 [[PRE_C]])
+; CHECK-NEXT:    br label %[[LOOP_HEADER:.*]]
+; CHECK:       [[LOOP_HEADER]]:
+; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP_LATCH:.*]] ]
+; CHECK-NEXT:    [[C:%.*]] = icmp ult i64 [[IV]], [[N]]
+; CHECK-NEXT:    call void @use(i1 [[C]])
+; CHECK-NEXT:    [[N_MINUS_1:%.*]] = sub nuw i64 [[N]], 1
+; CHECK-NEXT:    [[C_TIGHT:%.*]] = icmp ult i64 [[IV]], [[N_MINUS_1]]
+; CHECK-NEXT:    call void @use(i1 [[C_TIGHT]])
+; CHECK-NEXT:    br i1 [[HEADER_EC]], label %[[EXIT:.*]], label %[[LOOP_LATCH]]
+; CHECK:       [[LOOP_LATCH]]:
+; CHECK-NEXT:    [[IV_NEXT]] = add nsw i64 [[IV]], 2
+; CHECK-NEXT:    [[EC:%.*]] = icmp slt i64 [[IV_NEXT]], [[N]]
+; CHECK-NEXT:    [[COND:%.*]] = and i1 [[EC]], [[X]]
+; CHECK-NEXT:    br i1 [[COND]], label %[[LOOP_HEADER]], label %[[EXIT]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret void
+;
+entry:
+  %pre.c = icmp sgt i64 %n, 0
+  call void @llvm.assume(i1 %pre.c)
+  br label %loop.header
+
+loop.header:
+  %iv = phi i64 [ 0, %entry ], [ %iv.next, %loop.latch ]
+  %c = icmp ult i64 %iv, %n
+  call void @use(i1 %c)
+  %n.minus.1 = sub nuw i64 %n, 1
+  %c.tight = icmp ult i64 %iv, %n.minus.1
+  call void @use(i1 %c.tight)
+  br i1 %header.ec, label %exit, label %loop.latch
+
+loop.latch:
+  %iv.next = add nsw i64 %iv, 2
+  %ec = icmp slt i64 %iv.next, %n
+  %cond = and i1 %ec, %x
+  br i1 %cond, label %loop.header, label %exit
+
+exit:
+  ret void
+}
+
+define void @neg_and_on_exit_edge(i64 %n, i1 %x, i1 %header.ec) {
+; CHECK-LABEL: define void @neg_and_on_exit_edge(
+; CHECK-SAME: i64 [[N:%.*]], i1 [[X:%.*]], i1 [[HEADER_EC:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    [[PRE_C:%.*]] = icmp sgt i64 [[N]], 0
+; CHECK-NEXT:    call void @llvm.assume(i1 [[PRE_C]])
+; CHECK-NEXT:    br label %[[LOOP_HEADER:.*]]
+; CHECK:       [[LOOP_HEADER]]:
+; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP_LATCH:.*]] ]
+; CHECK-NEXT:    [[C:%.*]] = icmp ult i64 [[IV]], [[N]]
+; CHECK-NEXT:    call void @use(i1 [[C]])
+; CHECK-NEXT:    [[N_MINUS_1:%.*]] = sub nuw i64 [[N]], 1
+; CHECK-NEXT:    [[C_TIGHT:%.*]] = icmp ult i64 [[IV]], [[N_MINUS_1]]
+; CHECK-NEXT:    call void @use(i1 [[C_TIGHT]])
+; CHECK-NEXT:    br i1 [[HEADER_EC]], label %[[EXIT:.*]], label %[[LOOP_LATCH]]
+; CHECK:       [[LOOP_LATCH]]:
+; CHECK-NEXT:    [[IV_NEXT]] = add nsw i64 [[IV]], 2
+; CHECK-NEXT:    [[EC:%.*]] = icmp sge i64 [[IV_NEXT]], [[N]]
+; CHECK-NEXT:    [[COND:%.*]] = and i1 [[X]], [[EC]]
+; CHECK-NEXT:    br i1 [[COND]], label %[[EXIT]], label %[[LOOP_HEADER]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret void
+;
+entry:
+  %pre.c = icmp sgt i64 %n, 0
+  call void @llvm.assume(i1 %pre.c)
+  br label %loop.header
+
+loop.header:
+  %iv = phi i64 [ 0, %entry ], [ %iv.next, %loop.latch ]
+  %c = icmp ult i64 %iv, %n
+  call void @use(i1 %c)
+  %n.minus.1 = sub nuw i64 %n, 1
+  %c.tight = icmp ult i64 %iv, %n.minus.1
+  call void @use(i1 %c.tight)
+  br i1 %header.ec, label %exit, label %loop.latch
+
+loop.latch:
+  %iv.next = add nsw i64 %iv, 2
+  %ec = icmp sge i64 %iv.next, %n
+  %cond = and i1 %x, %ec
+  br i1 %cond, label %exit, label %loop.header
+
+exit:
+  ret void
+}
+
+define void @neg_and_on_exit_edge_operands_swapped(i64 %n, i1 %x, i1 %header.ec) {
+; CHECK-LABEL: define void @neg_and_on_exit_edge_operands_swapped(
+; CHECK-SAME: i64 [[N:%.*]], i1 [[X:%.*]], i1 [[HEADER_EC:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    [[PRE_C:%.*]] = icmp sgt i64 [[N]], 0
+; CHECK-NEXT:    call void @llvm.assume(i1 [[PRE_C]])
+; CHECK-NEXT:    br label %[[LOOP_HEADER:.*]]
+; CHECK:       [[LOOP_HEADER]]:
+; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP_LATCH:.*]] ]
+; CHECK-NEXT:    [[C:%.*]] = icmp ult i64 [[IV]], [[N]]
+; CHECK-NEXT:    call void @use(i1 [[C]])
+; CHECK-NEXT:    [[N_MINUS_1:%.*]] = sub nuw i64 [[N]], 1
+; CHECK-NEXT:    [[C_TIGHT:%.*]] = icmp ult i64 [[IV]], [[N_MINUS_1]]
+; CHECK-NEXT:    call void @use(i1 [[C_TIGHT]])
+; CHECK-NEXT:    br i1 [[HEADER_EC]], label %[[EXIT:.*]], label %[[LOOP_LATCH]]
+; CHECK:       [[LOOP_LATCH]]:
+; CHECK-NEXT:    [[IV_NEXT]] = add nsw i64 [[IV]], 2
+; CHECK-NEXT:    [[EC:%.*]] = icmp sge i64 [[IV_NEXT]], [[N]]
+; CHECK-NEXT:    [[COND:%.*]] = and i1 [[EC]], [[X]]
+; CHECK-NEXT:    br i1 [[COND]], label %[[EXIT]], label %[[LOOP_HEADER]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret void
+;
+entry:
+  %pre.c = icmp sgt i64 %n, 0
+  call void @llvm.assume(i1 %pre.c)
+  br label %loop.header
+
+loop.header:
+  %iv = phi i64 [ 0, %entry ], [ %iv.next, %loop.latch ]
+  %c = icmp ult i64 %iv, %n
+  call void @use(i1 %c)
+  %n.minus.1 = sub nuw i64 %n, 1
+  %c.tight = icmp ult i64 %iv, %n.minus.1
+  call void @use(i1 %c.tight)
+  br i1 %header.ec, label %exit, label %loop.latch
+
+loop.latch:
+  %iv.next = add nsw i64 %iv, 2
+  %ec = icmp sge i64 %iv.next, %n
+  %cond = and i1 %ec, %x
+  br i1 %cond, label %exit, label %loop.header
+
+exit:
+  ret void
+}
+
+define void @sel_and_peel(i64 %n, i1 %x, i1 %header.ec) {
+; CHECK-LABEL: define void @sel_and_peel(
+; CHECK-SAME: i64 [[N:%.*]], i1 [[X:%.*]], i1 [[HEADER_EC:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    [[PRE_C:%.*]] = icmp sgt i64 [[N]], 0
+; CHECK-NEXT:    call void @llvm.assume(i1 [[PRE_C]])
+; CHECK-NEXT:    br label %[[LOOP_HEADER:.*]]
+; CHECK:       [[LOOP_HEADER]]:
+; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP_LATCH:.*]] ]
+; CHECK-NEXT:    [[C:%.*]] = icmp ult i64 [[IV]], [[N]]
+; CHECK-NEXT:    call void @use(i1 [[C]])
+; CHECK-NEXT:    [[N_MINUS_1:%.*]] = sub nuw i64 [[N]], 1
+; CHECK-NEXT:    [[C_TIGHT:%.*]] = icmp ult i64 [[IV]], [[N_MINUS_1]]
+; CHECK-NEXT:    call void @use(i1 [[C_TIGHT]])
+; CHECK-NEXT:    br i1 [[HEADER_EC]], label %[[EXIT:.*]], label %[[LOOP_LATCH]]
+; CHECK:       [[LOOP_LATCH]]:
+; CHECK-NEXT:    [[IV_NEXT]] = add nsw i64 [[IV]], 2
+; CHECK-NEXT:    [[EC:%.*]] = icmp slt i64 [[IV_NEXT]], [[N]]
+; CHECK-NEXT:    [[COND:%.*]] = select i1 [[X]], i1 [[EC]], i1 false
+; CHECK-NEXT:    br i1 [[COND]], label %[[LOOP_HEADER]], label %[[EXIT]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret void
+;
+entry:
+  %pre.c = icmp sgt i64 %n, 0
+  call void @llvm.assume(i1 %pre.c)
+  br label %loop.header
+
+loop.header:
+  %iv = phi i64 [ 0, %entry ], [ %iv.next, %loop.latch ]
+  %c = icmp ult i64 %iv, %n
+  call void @use(i1 %c)
+  %n.minus.1 = sub nuw i64 %n, 1
+  %c.tight = icmp ult i64 %iv, %n.minus.1
+  call void @use(i1 %c.tight)
+  br i1 %header.ec, label %exit, label %loop.latch
+
+loop.latch:
+  %iv.next = add nsw i64 %iv, 2
+  %ec = icmp slt i64 %iv.next, %n
+  %cond = select i1 %x, i1 %ec, i1 false
+  br i1 %cond, label %loop.header, label %exit
+
+exit:
+  ret void
+}

>From 97e7f03cc1f33dee071add4add749f30576fa067 Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Mon, 31 Aug 2026 16:44:06 +0100
Subject: [PATCH 2/2] [ConstraintElim] Handle single And/Or as exit condition
 in loop.

In some cases, exits conditions in the loop are And/Or of and IV-based
condition and another condition. If the loop continues only if all
conditions are true (AND + true successor is the header, OR + false
successor is the header), we can still use the IV based condition to
bound the IV.

For now, just peels a single AND/OR.

This enables folds in a few additional cases:
https://github.com/dtcxzyw/llvm-opt-benchmark-nightly/pull/1193.
---
 .../Scalar/ConstraintElimination.cpp          | 26 ++++++++++++++++---
 .../and-implied-by-operands.ll                |  3 +--
 .../induction-exit-condition-or-and.ll        | 18 +++++--------
 3 files changed, 29 insertions(+), 18 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
index c4bc584028e83..26b9ad4073dfe 100644
--- a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
@@ -1086,9 +1086,23 @@ void State::addInfoForInductions(BasicBlock &BB) {
   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, IndValue, m_Value(B)), m_Value(), m_Value())))
+  auto *Br = dyn_cast<CondBrInst>(BB.getTerminator());
+  if (!Br)
     return;
+
+  auto CountingCmp = m_c_ICmp(Pred, IndValue, m_Value(B));
+  std::optional<bool> PeeledOnEdge;
+  if (!match(Br->getCondition(), CountingCmp)) {
+    // Look through AND/OR, and remember which edge requires all operands to be
+    // true.
+    if (match(Br->getCondition(), m_c_LogicalAnd(CountingCmp, m_Value())))
+      PeeledOnEdge = true;
+    else if (match(Br->getCondition(), m_c_LogicalOr(CountingCmp, m_Value())))
+      PeeledOnEdge = false;
+    else
+      return;
+  }
+
   if (PN->getParent() != Header || PN->getNumIncomingValues() != 2 ||
       !SE.isSCEVable(PN->getType()))
     return;
@@ -1106,8 +1120,12 @@ void State::addInfoForInductions(BasicBlock &BB) {
       Pred == CmpInst::ICMP_NE || ICmpInst::isLT(Pred) || ICmpInst::isLE(Pred);
   CmpInst::Predicate ContinuePred =
       ContinueOnTrue ? Pred.dropSameSign() : CmpInst::getInversePredicate(Pred);
-  BasicBlock *InLoopSucc = cast<CondBrInst>(BB.getTerminator())
-                               ->getSuccessor(ContinueOnTrue ? 0 : 1);
+  BasicBlock *InLoopSucc = Br->getSuccessor(ContinueOnTrue ? 0 : 1);
+
+  // The peeled condition only implies the compare on the edge where the
+  // combined condition forces its operands, which must be the in-loop edge.
+  if (PeeledOnEdge && *PeeledOnEdge != ContinueOnTrue)
+    return;
 
   if (!L->contains(InLoopSucc) || !L->isLoopExiting(&BB) || InLoopSucc == &BB)
     return;
diff --git a/llvm/test/Transforms/ConstraintElimination/and-implied-by-operands.ll b/llvm/test/Transforms/ConstraintElimination/and-implied-by-operands.ll
index 78a91b56a59cf..820010e28cf21 100644
--- a/llvm/test/Transforms/ConstraintElimination/and-implied-by-operands.ll
+++ b/llvm/test/Transforms/ConstraintElimination/and-implied-by-operands.ll
@@ -321,8 +321,7 @@ define void @test_monotonic_ptr_iv_inc_1_eq_to_uge(ptr %start, i16 %len) {
 ; CHECK-NEXT:    [[AND_0:%.*]] = and i1 [[LEN_NEG]], [[C]]
 ; CHECK-NEXT:    br i1 [[AND_0]], label [[FOR_BODY:%.*]], label [[EXIT:%.*]]
 ; CHECK:       for.body:
-; CHECK-NEXT:    [[T_2:%.*]] = icmp ult ptr [[PTR_IV]], [[UPPER]]
-; CHECK-NEXT:    [[AND:%.*]] = and i1 true, [[T_2]]
+; CHECK-NEXT:    [[AND:%.*]] = and i1 true, true
 ; CHECK-NEXT:    br i1 [[AND]], label [[LOOP_LATCH]], label [[EXIT]]
 ; CHECK:       loop.latch:
 ; CHECK-NEXT:    call void @use(ptr [[PTR_IV]])
diff --git a/llvm/test/Transforms/ConstraintElimination/induction-exit-condition-or-and.ll b/llvm/test/Transforms/ConstraintElimination/induction-exit-condition-or-and.ll
index 5c4679e606894..a333b7c7369a2 100644
--- a/llvm/test/Transforms/ConstraintElimination/induction-exit-condition-or-and.ll
+++ b/llvm/test/Transforms/ConstraintElimination/induction-exit-condition-or-and.ll
@@ -12,8 +12,7 @@ define void @stride2_or_latch(i64 %n, i1 %x, i1 %header.ec) {
 ; CHECK-NEXT:    br label %[[LOOP_HEADER:.*]]
 ; CHECK:       [[LOOP_HEADER]]:
 ; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP_LATCH:.*]] ]
-; CHECK-NEXT:    [[C:%.*]] = icmp ult i64 [[IV]], [[N]]
-; CHECK-NEXT:    call void @use(i1 [[C]])
+; CHECK-NEXT:    call void @use(i1 true)
 ; CHECK-NEXT:    [[N_MINUS_1:%.*]] = sub nuw i64 [[N]], 1
 ; CHECK-NEXT:    [[C_TIGHT:%.*]] = icmp ult i64 [[IV]], [[N_MINUS_1]]
 ; CHECK-NEXT:    call void @use(i1 [[C_TIGHT]])
@@ -59,8 +58,7 @@ define void @stride2_or_latch_operands_swapped(i64 %n, i1 %x, i1 %header.ec) {
 ; CHECK-NEXT:    br label %[[LOOP_HEADER:.*]]
 ; CHECK:       [[LOOP_HEADER]]:
 ; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP_LATCH:.*]] ]
-; CHECK-NEXT:    [[C:%.*]] = icmp ult i64 [[IV]], [[N]]
-; CHECK-NEXT:    call void @use(i1 [[C]])
+; CHECK-NEXT:    call void @use(i1 true)
 ; CHECK-NEXT:    [[N_MINUS_1:%.*]] = sub nuw i64 [[N]], 1
 ; CHECK-NEXT:    [[C_TIGHT:%.*]] = icmp ult i64 [[IV]], [[N_MINUS_1]]
 ; CHECK-NEXT:    call void @use(i1 [[C_TIGHT]])
@@ -200,8 +198,7 @@ define void @sel_or_peel(i64 %n, i1 %x, i1 %header.ec) {
 ; CHECK-NEXT:    br label %[[LOOP_HEADER:.*]]
 ; CHECK:       [[LOOP_HEADER]]:
 ; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP_LATCH:.*]] ]
-; CHECK-NEXT:    [[C:%.*]] = icmp ult i64 [[IV]], [[N]]
-; CHECK-NEXT:    call void @use(i1 [[C]])
+; CHECK-NEXT:    call void @use(i1 true)
 ; CHECK-NEXT:    [[N_MINUS_1:%.*]] = sub nuw i64 [[N]], 1
 ; CHECK-NEXT:    [[C_TIGHT:%.*]] = icmp ult i64 [[IV]], [[N_MINUS_1]]
 ; CHECK-NEXT:    call void @use(i1 [[C_TIGHT]])
@@ -247,8 +244,7 @@ define void @stride2_and_latch(i64 %n, i1 %x, i1 %header.ec) {
 ; CHECK-NEXT:    br label %[[LOOP_HEADER:.*]]
 ; CHECK:       [[LOOP_HEADER]]:
 ; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP_LATCH:.*]] ]
-; CHECK-NEXT:    [[C:%.*]] = icmp ult i64 [[IV]], [[N]]
-; CHECK-NEXT:    call void @use(i1 [[C]])
+; CHECK-NEXT:    call void @use(i1 true)
 ; CHECK-NEXT:    [[N_MINUS_1:%.*]] = sub nuw i64 [[N]], 1
 ; CHECK-NEXT:    [[C_TIGHT:%.*]] = icmp ult i64 [[IV]], [[N_MINUS_1]]
 ; CHECK-NEXT:    call void @use(i1 [[C_TIGHT]])
@@ -294,8 +290,7 @@ define void @stride2_and_latch_operands_swapped(i64 %n, i1 %x, i1 %header.ec) {
 ; CHECK-NEXT:    br label %[[LOOP_HEADER:.*]]
 ; CHECK:       [[LOOP_HEADER]]:
 ; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP_LATCH:.*]] ]
-; CHECK-NEXT:    [[C:%.*]] = icmp ult i64 [[IV]], [[N]]
-; CHECK-NEXT:    call void @use(i1 [[C]])
+; CHECK-NEXT:    call void @use(i1 true)
 ; CHECK-NEXT:    [[N_MINUS_1:%.*]] = sub nuw i64 [[N]], 1
 ; CHECK-NEXT:    [[C_TIGHT:%.*]] = icmp ult i64 [[IV]], [[N_MINUS_1]]
 ; CHECK-NEXT:    call void @use(i1 [[C_TIGHT]])
@@ -435,8 +430,7 @@ define void @sel_and_peel(i64 %n, i1 %x, i1 %header.ec) {
 ; CHECK-NEXT:    br label %[[LOOP_HEADER:.*]]
 ; CHECK:       [[LOOP_HEADER]]:
 ; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP_LATCH:.*]] ]
-; CHECK-NEXT:    [[C:%.*]] = icmp ult i64 [[IV]], [[N]]
-; CHECK-NEXT:    call void @use(i1 [[C]])
+; CHECK-NEXT:    call void @use(i1 true)
 ; CHECK-NEXT:    [[N_MINUS_1:%.*]] = sub nuw i64 [[N]], 1
 ; CHECK-NEXT:    [[C_TIGHT:%.*]] = icmp ult i64 [[IV]], [[N_MINUS_1]]
 ; CHECK-NEXT:    call void @use(i1 [[C_TIGHT]])



More information about the llvm-commits mailing list