[llvm] [ConstraintElim] Add more tests for facts from loop start values (NFC) (PR #215896)
Florian Hahn via llvm-commits
llvm-commits at lists.llvm.org
Wed Aug 12 13:45:59 PDT 2026
https://github.com/fhahn created https://github.com/llvm/llvm-project/pull/215896
None
>From 705d0c806b82c193e93c794a3fc144cb12c17183 Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Wed, 12 Aug 2026 21:44:41 +0100
Subject: [PATCH] [ConstraintElim] Add more tests for facts from loop start
values (NFC)
---
.../header-induction-start-bounds.ll | 699 ++++++++++++++++++
1 file changed, 699 insertions(+)
create mode 100644 llvm/test/Transforms/ConstraintElimination/header-induction-start-bounds.ll
diff --git a/llvm/test/Transforms/ConstraintElimination/header-induction-start-bounds.ll b/llvm/test/Transforms/ConstraintElimination/header-induction-start-bounds.ll
new file mode 100644
index 0000000000000..12252ab36ca42
--- /dev/null
+++ b/llvm/test/Transforms/ConstraintElimination/header-induction-start-bounds.ll
@@ -0,0 +1,699 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -p constraint-elimination -S %s | FileCheck %s
+
+declare void @use(i1)
+
+define void @relational_exit_cond(i32 %n) {
+; CHECK-LABEL: define void @relational_exit_cond(
+; CHECK-SAME: i32 [[N:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IV:%.*]] = phi i32 [ 5, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[T_1:%.*]] = icmp uge i32 [[IV]], 5
+; CHECK-NEXT: call void @use(i1 [[T_1]])
+; CHECK-NEXT: [[T_2:%.*]] = icmp sge i32 [[IV]], 5
+; CHECK-NEXT: call void @use(i1 [[T_2]])
+; CHECK-NEXT: [[F_1:%.*]] = icmp ult i32 [[IV]], 5
+; CHECK-NEXT: call void @use(i1 [[F_1]])
+; CHECK-NEXT: [[IV_NEXT]] = add nuw nsw i32 [[IV]], 1
+; CHECK-NEXT: [[EC:%.*]] = icmp ult i32 [[IV_NEXT]], [[N]]
+; CHECK-NEXT: br i1 [[EC]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %iv = phi i32 [ 5, %entry ], [ %iv.next, %loop ]
+ %t.1 = icmp uge i32 %iv, 5
+ call void @use(i1 %t.1)
+ %t.2 = icmp sge i32 %iv, 5
+ call void @use(i1 %t.2)
+ %f.1 = icmp ult i32 %iv, 5
+ call void @use(i1 %f.1)
+ %iv.next = add nuw nsw i32 %iv, 1
+ %ec = icmp ult i32 %iv.next, %n
+ br i1 %ec, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+define void @second_induction(i32 %n, i32 %s) {
+; CHECK-LABEL: define void @second_induction(
+; CHECK-SAME: i32 [[N:%.*]], i32 [[S:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IV:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[IV_2:%.*]] = phi i32 [ [[S]], %[[ENTRY]] ], [ [[IV_2_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[T_1:%.*]] = icmp uge i32 [[IV_2]], [[S]]
+; CHECK-NEXT: call void @use(i1 [[T_1]])
+; CHECK-NEXT: [[IV_NEXT]] = add nuw nsw i32 [[IV]], 1
+; CHECK-NEXT: [[IV_2_NEXT]] = add nuw i32 [[IV_2]], 3
+; CHECK-NEXT: [[EC:%.*]] = icmp ult i32 [[IV_NEXT]], [[N]]
+; CHECK-NEXT: br i1 [[EC]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %iv = phi i32 [ 0, %entry ], [ %iv.next, %loop ]
+ %iv.2 = phi i32 [ %s, %entry ], [ %iv.2.next, %loop ]
+ %t.1 = icmp uge i32 %iv.2, %s
+ call void @use(i1 %t.1)
+ %iv.next = add nuw nsw i32 %iv, 1
+ %iv.2.next = add nuw i32 %iv.2, 3
+ %ec = icmp ult i32 %iv.next, %n
+ br i1 %ec, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+; A decreasing induction is not bounded from below by its start value.
+define void @decreasing(i32 %n) {
+; CHECK-LABEL: define void @decreasing(
+; CHECK-SAME: i32 [[N:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IV:%.*]] = phi i32 [ 100, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[C_1:%.*]] = icmp uge i32 [[IV]], 100
+; CHECK-NEXT: call void @use(i1 [[C_1]])
+; CHECK-NEXT: [[IV_NEXT]] = add nsw i32 [[IV]], -1
+; CHECK-NEXT: [[EC:%.*]] = icmp ugt i32 [[IV_NEXT]], [[N]]
+; CHECK-NEXT: br i1 [[EC]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %iv = phi i32 [ 100, %entry ], [ %iv.next, %loop ]
+ %c.1 = icmp uge i32 %iv, 100
+ call void @use(i1 %c.1)
+ %iv.next = add nsw i32 %iv, -1
+ %ec = icmp ugt i32 %iv.next, %n
+ br i1 %ec, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+; The induction may wrap, so it may become smaller than its start value.
+define void @may_wrap(ptr %p) {
+; CHECK-LABEL: define void @may_wrap(
+; CHECK-SAME: ptr [[P:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IV:%.*]] = phi i8 [ 3, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[C_1:%.*]] = icmp uge i8 [[IV]], 3
+; CHECK-NEXT: call void @use(i1 [[C_1]])
+; CHECK-NEXT: [[IV_NEXT]] = add i8 [[IV]], 1
+; CHECK-NEXT: [[EC:%.*]] = load i1, ptr [[P]], align 1
+; CHECK-NEXT: br i1 [[EC]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %iv = phi i8 [ 3, %entry ], [ %iv.next, %loop ]
+ %c.1 = icmp uge i8 %iv, 3
+ call void @use(i1 %c.1)
+ %iv.next = add i8 %iv, 1
+ %ec = load i1, ptr %p, align 1
+ br i1 %ec, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+define void @start_value_is_expression(i32 %a, i32 %n) {
+; CHECK-LABEL: define void @start_value_is_expression(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[N:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[START:%.*]] = add nuw i32 [[A]], 3
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IV:%.*]] = phi i32 [ [[START]], %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[T_1:%.*]] = icmp uge i32 [[IV]], [[START]]
+; CHECK-NEXT: call void @use(i1 [[T_1]])
+; CHECK-NEXT: [[IV_NEXT]] = add nuw nsw i32 [[IV]], 1
+; CHECK-NEXT: [[EC:%.*]] = icmp ult i32 [[IV_NEXT]], [[N]]
+; CHECK-NEXT: br i1 [[EC]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ %start = add nuw i32 %a, 3
+ br label %loop
+
+loop:
+ %iv = phi i32 [ %start, %entry ], [ %iv.next, %loop ]
+ %t.1 = icmp uge i32 %iv, %start
+ call void @use(i1 %t.1)
+ %iv.next = add nuw nsw i32 %iv, 1
+ %ec = icmp ult i32 %iv.next, %n
+ br i1 %ec, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+; Only the unsigned bound follows from `add nuw`.
+define void @nuw_only(i32 %s, i32 %n) {
+; CHECK-LABEL: define void @nuw_only(
+; CHECK-SAME: i32 [[S:%.*]], i32 [[N:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IV:%.*]] = phi i32 [ [[S]], %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[T_1:%.*]] = icmp uge i32 [[IV]], [[S]]
+; CHECK-NEXT: call void @use(i1 [[T_1]])
+; CHECK-NEXT: [[C_1:%.*]] = icmp sge i32 [[IV]], [[S]]
+; CHECK-NEXT: call void @use(i1 [[C_1]])
+; CHECK-NEXT: [[IV_NEXT]] = add nuw i32 [[IV]], 1
+; CHECK-NEXT: [[EC:%.*]] = icmp ult i32 [[IV_NEXT]], [[N]]
+; CHECK-NEXT: br i1 [[EC]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %iv = phi i32 [ %s, %entry ], [ %iv.next, %loop ]
+ %t.1 = icmp uge i32 %iv, %s
+ call void @use(i1 %t.1)
+ %c.1 = icmp sge i32 %iv, %s
+ call void @use(i1 %c.1)
+ %iv.next = add nuw i32 %iv, 1
+ %ec = icmp ult i32 %iv.next, %n
+ br i1 %ec, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+; Only the signed bound follows from `add nsw`.
+define void @nsw_only(i32 %s, i32 %n) {
+; CHECK-LABEL: define void @nsw_only(
+; CHECK-SAME: i32 [[S:%.*]], i32 [[N:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IV:%.*]] = phi i32 [ [[S]], %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[T_1:%.*]] = icmp sge i32 [[IV]], [[S]]
+; CHECK-NEXT: call void @use(i1 [[T_1]])
+; CHECK-NEXT: [[C_1:%.*]] = icmp uge i32 [[IV]], [[S]]
+; CHECK-NEXT: call void @use(i1 [[C_1]])
+; CHECK-NEXT: [[IV_NEXT]] = add nsw i32 [[IV]], 1
+; CHECK-NEXT: [[EC:%.*]] = icmp slt i32 [[IV_NEXT]], [[N]]
+; CHECK-NEXT: br i1 [[EC]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %iv = phi i32 [ %s, %entry ], [ %iv.next, %loop ]
+ %t.1 = icmp sge i32 %iv, %s
+ call void @use(i1 %t.1)
+ %c.1 = icmp uge i32 %iv, %s
+ call void @use(i1 %c.1)
+ %iv.next = add nsw i32 %iv, 1
+ %ec = icmp slt i32 %iv.next, %n
+ br i1 %ec, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+define void @pointer_induction_gep_nuw(ptr %p, i32 %n) {
+; CHECK-LABEL: define void @pointer_induction_gep_nuw(
+; CHECK-SAME: ptr [[P:%.*]], i32 [[N:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IV:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[PTR_IV:%.*]] = phi ptr [ [[P]], %[[ENTRY]] ], [ [[PTR_IV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[T_1:%.*]] = icmp uge ptr [[PTR_IV]], [[P]]
+; CHECK-NEXT: call void @use(i1 [[T_1]])
+; CHECK-NEXT: [[PTR_IV_NEXT]] = getelementptr nuw i32, ptr [[PTR_IV]], i64 1
+; CHECK-NEXT: [[IV_NEXT]] = add nuw nsw i32 [[IV]], 1
+; CHECK-NEXT: [[EC:%.*]] = icmp ult i32 [[IV_NEXT]], [[N]]
+; CHECK-NEXT: br i1 [[EC]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %iv = phi i32 [ 0, %entry ], [ %iv.next, %loop ]
+ %ptr.iv = phi ptr [ %p, %entry ], [ %ptr.iv.next, %loop ]
+ %t.1 = icmp uge ptr %ptr.iv, %p
+ call void @use(i1 %t.1)
+ %ptr.iv.next = getelementptr nuw i32, ptr %ptr.iv, i64 1
+ %iv.next = add nuw nsw i32 %iv, 1
+ %ec = icmp ult i32 %iv.next, %n
+ br i1 %ec, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+; `getelementptr nusw` with a non-negative offset implies nuw.
+define void @pointer_induction_gep_nusw(ptr %p, i32 %n) {
+; CHECK-LABEL: define void @pointer_induction_gep_nusw(
+; CHECK-SAME: ptr [[P:%.*]], i32 [[N:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IV:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[PTR_IV:%.*]] = phi ptr [ [[P]], %[[ENTRY]] ], [ [[PTR_IV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[T_1:%.*]] = icmp uge ptr [[PTR_IV]], [[P]]
+; CHECK-NEXT: call void @use(i1 [[T_1]])
+; CHECK-NEXT: [[PTR_IV_NEXT]] = getelementptr nusw i32, ptr [[PTR_IV]], i64 1
+; CHECK-NEXT: [[IV_NEXT]] = add nuw nsw i32 [[IV]], 1
+; CHECK-NEXT: [[EC:%.*]] = icmp ult i32 [[IV_NEXT]], [[N]]
+; CHECK-NEXT: br i1 [[EC]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %iv = phi i32 [ 0, %entry ], [ %iv.next, %loop ]
+ %ptr.iv = phi ptr [ %p, %entry ], [ %ptr.iv.next, %loop ]
+ %t.1 = icmp uge ptr %ptr.iv, %p
+ call void @use(i1 %t.1)
+ %ptr.iv.next = getelementptr nusw i32, ptr %ptr.iv, i64 1
+ %iv.next = add nuw nsw i32 %iv, 1
+ %ec = icmp ult i32 %iv.next, %n
+ br i1 %ec, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+; A plain getelementptr may wrap, so no bound can be derived.
+define void @pointer_induction_gep_no_flags(ptr %p, i32 %n) {
+; CHECK-LABEL: define void @pointer_induction_gep_no_flags(
+; CHECK-SAME: ptr [[P:%.*]], i32 [[N:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IV:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[PTR_IV:%.*]] = phi ptr [ [[P]], %[[ENTRY]] ], [ [[PTR_IV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[C_1:%.*]] = icmp uge ptr [[PTR_IV]], [[P]]
+; CHECK-NEXT: call void @use(i1 [[C_1]])
+; CHECK-NEXT: [[PTR_IV_NEXT]] = getelementptr i32, ptr [[PTR_IV]], i64 1
+; CHECK-NEXT: [[IV_NEXT]] = add nuw nsw i32 [[IV]], 1
+; CHECK-NEXT: [[EC:%.*]] = icmp ult i32 [[IV_NEXT]], [[N]]
+; CHECK-NEXT: br i1 [[EC]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %iv = phi i32 [ 0, %entry ], [ %iv.next, %loop ]
+ %ptr.iv = phi ptr [ %p, %entry ], [ %ptr.iv.next, %loop ]
+ %c.1 = icmp uge ptr %ptr.iv, %p
+ call void @use(i1 %c.1)
+ %ptr.iv.next = getelementptr i32, ptr %ptr.iv, i64 1
+ %iv.next = add nuw nsw i32 %iv, 1
+ %ec = icmp ult i32 %iv.next, %n
+ br i1 %ec, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+define void @pointer_induction_gep_negative_offset(ptr %p, i32 %n) {
+; CHECK-LABEL: define void @pointer_induction_gep_negative_offset(
+; CHECK-SAME: ptr [[P:%.*]], i32 [[N:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IV:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[PTR_IV:%.*]] = phi ptr [ [[P]], %[[ENTRY]] ], [ [[PTR_IV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[C_1:%.*]] = icmp uge ptr [[PTR_IV]], [[P]]
+; CHECK-NEXT: call void @use(i1 [[C_1]])
+; CHECK-NEXT: [[PTR_IV_NEXT]] = getelementptr nuw i32, ptr [[PTR_IV]], i64 -1
+; CHECK-NEXT: [[IV_NEXT]] = add nuw nsw i32 [[IV]], 1
+; CHECK-NEXT: [[EC:%.*]] = icmp ult i32 [[IV_NEXT]], [[N]]
+; CHECK-NEXT: br i1 [[EC]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %iv = phi i32 [ 0, %entry ], [ %iv.next, %loop ]
+ %ptr.iv = phi ptr [ %p, %entry ], [ %ptr.iv.next, %loop ]
+ %c.1 = icmp uge ptr %ptr.iv, %p
+ call void @use(i1 %c.1)
+ %ptr.iv.next = getelementptr nuw i32, ptr %ptr.iv, i64 -1
+ %iv.next = add nuw nsw i32 %iv, 1
+ %ec = icmp ult i32 %iv.next, %n
+ br i1 %ec, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+declare i1 @cond()
+
+; nuw only: iv goes 127, -128, ... so it is non-decreasing UNSIGNED but wraps
+; signed. `%iv sge 127` must NOT be folded.
+define void @nuw_only_signed_must_not_fold() {
+; CHECK-LABEL: define void @nuw_only_signed_must_not_fold() {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IV:%.*]] = phi i8 [ 127, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[MUST_NOT_FOLD:%.*]] = icmp sge i8 [[IV]], 127
+; CHECK-NEXT: call void @use(i1 [[MUST_NOT_FOLD]])
+; CHECK-NEXT: [[MAY_FOLD:%.*]] = icmp uge i8 [[IV]], 127
+; CHECK-NEXT: call void @use(i1 [[MAY_FOLD]])
+; CHECK-NEXT: [[IV_NEXT]] = add nuw i8 [[IV]], 1
+; CHECK-NEXT: [[EC:%.*]] = call i1 @cond()
+; CHECK-NEXT: br i1 [[EC]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %iv = phi i8 [ 127, %entry ], [ %iv.next, %loop ]
+ %must.not.fold = icmp sge i8 %iv, 127
+ call void @use(i1 %must.not.fold)
+ %may.fold = icmp uge i8 %iv, 127
+ call void @use(i1 %may.fold)
+ %iv.next = add nuw i8 %iv, 1
+ %ec = call i1 @cond()
+ br i1 %ec, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+; nsw only: iv goes -1, 0, 1, ... so it is non-decreasing SIGNED but wraps
+; unsigned (255 -> 0). `%iv uge -1` must NOT be folded.
+define void @nsw_only_unsigned_must_not_fold() {
+; CHECK-LABEL: define void @nsw_only_unsigned_must_not_fold() {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IV:%.*]] = phi i8 [ -1, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[MUST_NOT_FOLD:%.*]] = icmp uge i8 [[IV]], -1
+; CHECK-NEXT: call void @use(i1 [[MUST_NOT_FOLD]])
+; CHECK-NEXT: [[MAY_FOLD:%.*]] = icmp sge i8 [[IV]], -1
+; CHECK-NEXT: call void @use(i1 [[MAY_FOLD]])
+; CHECK-NEXT: [[IV_NEXT]] = add nsw i8 [[IV]], 1
+; CHECK-NEXT: [[EC:%.*]] = call i1 @cond()
+; CHECK-NEXT: br i1 [[EC]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %iv = phi i8 [ -1, %entry ], [ %iv.next, %loop ]
+ %must.not.fold = icmp uge i8 %iv, -1
+ call void @use(i1 %must.not.fold)
+ %may.fold = icmp sge i8 %iv, -1
+ call void @use(i1 %may.fold)
+ %iv.next = add nsw i8 %iv, 1
+ %ec = call i1 @cond()
+ br i1 %ec, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+; A negative step must give no lower bound in either sense.
+define void @negative_step_must_not_fold() {
+; CHECK-LABEL: define void @negative_step_must_not_fold() {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IV:%.*]] = phi i8 [ 10, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[MUST_NOT_FOLD_U:%.*]] = icmp uge i8 [[IV]], 10
+; CHECK-NEXT: call void @use(i1 [[MUST_NOT_FOLD_U]])
+; CHECK-NEXT: [[MUST_NOT_FOLD_S:%.*]] = icmp sge i8 [[IV]], 10
+; CHECK-NEXT: call void @use(i1 [[MUST_NOT_FOLD_S]])
+; CHECK-NEXT: [[IV_NEXT]] = add nuw nsw i8 [[IV]], -1
+; CHECK-NEXT: [[EC:%.*]] = call i1 @cond()
+; CHECK-NEXT: br i1 [[EC]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %iv = phi i8 [ 10, %entry ], [ %iv.next, %loop ]
+ %must.not.fold.u = icmp uge i8 %iv, 10
+ call void @use(i1 %must.not.fold.u)
+ %must.not.fold.s = icmp sge i8 %iv, 10
+ call void @use(i1 %must.not.fold.s)
+ %iv.next = add nuw nsw i8 %iv, -1
+ %ec = call i1 @cond()
+ br i1 %ec, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+define void @gep_negative_offset_must_not_fold(ptr %p) {
+; CHECK-LABEL: define void @gep_negative_offset_must_not_fold(
+; CHECK-SAME: ptr [[P:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[PV:%.*]] = phi ptr [ [[P]], %[[ENTRY]] ], [ [[PV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[MUST_NOT_FOLD:%.*]] = icmp uge ptr [[PV]], [[P]]
+; CHECK-NEXT: call void @use(i1 [[MUST_NOT_FOLD]])
+; CHECK-NEXT: [[PV_NEXT]] = getelementptr nuw i8, ptr [[PV]], i64 -1
+; CHECK-NEXT: [[EC:%.*]] = call i1 @cond()
+; CHECK-NEXT: br i1 [[EC]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %pv = phi ptr [ %p, %entry ], [ %pv.next, %loop ]
+ %must.not.fold = icmp uge ptr %pv, %p
+ call void @use(i1 %must.not.fold)
+ %pv.next = getelementptr nuw i8, ptr %pv, i64 -1
+ %ec = call i1 @cond()
+ br i1 %ec, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+; gep with only nusw and a negative offset: nusw does not imply nuw here.
+define void @gep_nusw_negative_offset_must_not_fold(ptr %p) {
+; CHECK-LABEL: define void @gep_nusw_negative_offset_must_not_fold(
+; CHECK-SAME: ptr [[P:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[PV:%.*]] = phi ptr [ [[P]], %[[ENTRY]] ], [ [[PV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[MUST_NOT_FOLD:%.*]] = icmp uge ptr [[PV]], [[P]]
+; CHECK-NEXT: call void @use(i1 [[MUST_NOT_FOLD]])
+; CHECK-NEXT: [[PV_NEXT]] = getelementptr nusw i8, ptr [[PV]], i64 -1
+; CHECK-NEXT: [[EC:%.*]] = call i1 @cond()
+; CHECK-NEXT: br i1 [[EC]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %pv = phi ptr [ %p, %entry ], [ %pv.next, %loop ]
+ %must.not.fold = icmp uge ptr %pv, %p
+ call void @use(i1 %must.not.fold)
+ %pv.next = getelementptr nusw i8, ptr %pv, i64 -1
+ %ec = call i1 @cond()
+ br i1 %ec, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+define void @gep_no_signed_bound(ptr %p) {
+; CHECK-LABEL: define void @gep_no_signed_bound(
+; CHECK-SAME: ptr [[P:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[PV:%.*]] = phi ptr [ [[P]], %[[ENTRY]] ], [ [[PV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[MUST_NOT_FOLD:%.*]] = icmp sge ptr [[PV]], [[P]]
+; CHECK-NEXT: call void @use(i1 [[MUST_NOT_FOLD]])
+; CHECK-NEXT: [[PV_NEXT]] = getelementptr nuw i8, ptr [[PV]], i64 1
+; CHECK-NEXT: [[EC:%.*]] = call i1 @cond()
+; CHECK-NEXT: br i1 [[EC]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %pv = phi ptr [ %p, %entry ], [ %pv.next, %loop ]
+ %must.not.fold = icmp sge ptr %pv, %p
+ call void @use(i1 %must.not.fold)
+ %pv.next = getelementptr nuw i8, ptr %pv, i64 1
+ %ec = call i1 @cond()
+ br i1 %ec, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+; A gep with a non-constant offset may step backwards; no lower bound.
+define void @gep_variable_offset_must_not_fold(ptr %p, i64 %off) {
+; CHECK-LABEL: define void @gep_variable_offset_must_not_fold(
+; CHECK-SAME: ptr [[P:%.*]], i64 [[OFF:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[PV:%.*]] = phi ptr [ [[P]], %[[ENTRY]] ], [ [[PV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[MUST_NOT_FOLD:%.*]] = icmp uge ptr [[PV]], [[P]]
+; CHECK-NEXT: call void @use(i1 [[MUST_NOT_FOLD]])
+; CHECK-NEXT: [[PV_NEXT]] = getelementptr nusw i8, ptr [[PV]], i64 [[OFF]]
+; CHECK-NEXT: [[EC:%.*]] = call i1 @cond()
+; CHECK-NEXT: br i1 [[EC]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %pv = phi ptr [ %p, %entry ], [ %pv.next, %loop ]
+ %must.not.fold = icmp uge ptr %pv, %p
+ call void @use(i1 %must.not.fold)
+ %pv.next = getelementptr nusw i8, ptr %pv, i64 %off
+ %ec = call i1 @cond()
+ br i1 %ec, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+; The loop is entered from 2 different blocks, so there is no unique start
+; value to bound the induction by.
+define void @multiple_loop_predecessors_must_not_fold(i32 %s, i1 %c) {
+; CHECK-LABEL: define void @multiple_loop_predecessors_must_not_fold(
+; CHECK-SAME: i32 [[S:%.*]], i1 [[C:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: br i1 [[C]], label %[[PH_1:.*]], label %[[PH_2:.*]]
+; CHECK: [[PH_1]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[PH_2]]:
+; CHECK-NEXT: br label %[[LOOP]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IV:%.*]] = phi i32 [ [[S]], %[[PH_1]] ], [ 10, %[[PH_2]] ], [ [[IV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[MUST_NOT_FOLD:%.*]] = icmp uge i32 [[IV]], [[S]]
+; CHECK-NEXT: call void @use(i1 [[MUST_NOT_FOLD]])
+; CHECK-NEXT: [[IV_NEXT]] = add nuw nsw i32 [[IV]], 1
+; CHECK-NEXT: [[EC:%.*]] = call i1 @cond()
+; CHECK-NEXT: br i1 [[EC]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br i1 %c, label %ph.1, label %ph.2
+
+ph.1:
+ br label %loop
+
+ph.2:
+ br label %loop
+
+loop:
+ %iv = phi i32 [ %s, %ph.1 ], [ 10, %ph.2 ], [ %iv.next, %loop ]
+ %must.not.fold = icmp uge i32 %iv, %s
+ call void @use(i1 %must.not.fold)
+ %iv.next = add nuw nsw i32 %iv, 1
+ %ec = call i1 @cond()
+ br i1 %ec, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+; With multiple latches the phi has more than 2 incoming values and the steps
+; may go in different directions.
+define void @multiple_latches_must_not_fold(i32 %s) {
+; CHECK-LABEL: define void @multiple_latches_must_not_fold(
+; CHECK-SAME: i32 [[S:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IV:%.*]] = phi i32 [ [[S]], %[[ENTRY]] ], [ [[IV_INC:%.*]], %[[LATCH_1:.*]] ], [ [[IV_DEC:%.*]], %[[LATCH_2:.*]] ]
+; CHECK-NEXT: [[MUST_NOT_FOLD:%.*]] = icmp uge i32 [[IV]], [[S]]
+; CHECK-NEXT: call void @use(i1 [[MUST_NOT_FOLD]])
+; CHECK-NEXT: [[C_1:%.*]] = call i1 @cond()
+; CHECK-NEXT: br i1 [[C_1]], label %[[LATCH_1]], label %[[MID:.*]]
+; CHECK: [[MID]]:
+; CHECK-NEXT: [[C_2:%.*]] = call i1 @cond()
+; CHECK-NEXT: br i1 [[C_2]], label %[[LATCH_2]], label %[[EXIT:.*]]
+; CHECK: [[LATCH_1]]:
+; CHECK-NEXT: [[IV_INC]] = add nuw nsw i32 [[IV]], 1
+; CHECK-NEXT: br label %[[LOOP]]
+; CHECK: [[LATCH_2]]:
+; CHECK-NEXT: [[IV_DEC]] = add nsw i32 [[IV]], -1
+; CHECK-NEXT: br label %[[LOOP]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %iv = phi i32 [ %s, %entry ], [ %iv.inc, %latch.1 ], [ %iv.dec, %latch.2 ]
+ %must.not.fold = icmp uge i32 %iv, %s
+ call void @use(i1 %must.not.fold)
+ %c.1 = call i1 @cond()
+ br i1 %c.1, label %latch.1, label %mid
+
+mid:
+ %c.2 = call i1 @cond()
+ br i1 %c.2, label %latch.2, label %exit
+
+latch.1:
+ %iv.inc = add nuw nsw i32 %iv, 1
+ br label %loop
+
+latch.2:
+ %iv.dec = add nsw i32 %iv, -1
+ br label %loop
+
+exit:
+ ret void
+}
More information about the llvm-commits
mailing list