[llvm] [LV] Add tests for reverse early-exit loops in deref regions (PR #228062)

Aleksandr Popov via llvm-commits llvm-commits at lists.llvm.org
Fri Oct 2 06:35:22 PDT 2026


https://github.com/aleks-tmb updated https://github.com/llvm/llvm-project/pull/228062

>From 78cfe4082f70ec580b8de17d2cd55846a7b79e27 Mon Sep 17 00:00:00 2001
From: Aleksandr Popov <apopov at azul.com>
Date: Mon, 10 Aug 2026 21:19:15 +0000
Subject: [PATCH 1/3] [LoopVectorize] Add tests for reverse early-exit loops in
 deref regions

Related to #211962
---
 .../LoopVectorize/reverse-loop-length.ll      | 97 +++++++++++++++++++
 1 file changed, 97 insertions(+)
 create mode 100644 llvm/test/Transforms/LoopVectorize/reverse-loop-length.ll

diff --git a/llvm/test/Transforms/LoopVectorize/reverse-loop-length.ll b/llvm/test/Transforms/LoopVectorize/reverse-loop-length.ll
new file mode 100644
index 0000000000000..ec9c3ffea70e5
--- /dev/null
+++ b/llvm/test/Transforms/LoopVectorize/reverse-loop-length.ll
@@ -0,0 +1,97 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-globals none --filter-out-after "scalar.ph:" --version 6
+; RUN: opt -p loop-vectorize -force-vector-width=4 -S %s | FileCheck %s
+
+; The loops below walk a dereferenceable region backwards, so every load is
+; safe.
+
+; TODO: The AR runs over [0, %length), which is exactly the region the assume
+; marks dereferenceable, so the early-exit loop should be vectorized.
+define ptr @reverse_reaches_base_dynamic_length(i64 %length, ptr %ptr) {
+; CHECK-LABEL: define ptr @reverse_reaches_base_dynamic_length(
+; CHECK-SAME: i64 [[LENGTH:%.*]], ptr [[PTR:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[NULL_CHECK:%.*]] = icmp eq i64 [[LENGTH]], 0
+; CHECK-NEXT:    br i1 [[NULL_CHECK]], label %[[EXIT:.*]], label %[[PREHEADER:.*]]
+; CHECK:       [[PREHEADER]]:
+; CHECK-NEXT:    call void @llvm.assume(i1 true) [ "dereferenceable"(ptr [[PTR]], i64 [[LENGTH]]) ]
+; CHECK-NEXT:    [[START:%.*]] = sub i64 [[LENGTH]], 1
+; CHECK-NEXT:    br label %[[LOOP:.*]]
+; CHECK:       [[LOOP]]:
+; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ [[IV_NEXT:%.*]], %[[LATCH:.*]] ], [ [[START]], %[[PREHEADER]] ]
+; CHECK-NEXT:    [[ELEMENT_GEP:%.*]] = getelementptr i8, ptr [[PTR]], i64 [[IV]]
+; CHECK-NEXT:    [[ELEMENT:%.*]] = load i8, ptr [[ELEMENT_GEP]], align 1
+; CHECK-NEXT:    [[FOUND_CHECK:%.*]] = icmp eq i8 [[ELEMENT]], 0
+; CHECK-NEXT:    br i1 [[FOUND_CHECK]], label %[[EXIT_LOOPEXIT:.*]], label %[[LATCH]]
+; CHECK:       [[LATCH]]:
+; CHECK-NEXT:    [[IV_NEXT]] = add i64 [[IV]], -1
+; CHECK-NEXT:    [[RANGE_CHECK:%.*]] = icmp ne i64 [[IV]], 0
+; CHECK-NEXT:    br i1 [[RANGE_CHECK]], label %[[LOOP]], label %[[EXIT_LOOPEXIT]]
+; CHECK:       [[EXIT_LOOPEXIT]]:
+; CHECK-NEXT:    br label %[[EXIT]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret ptr null
+;
+entry:
+  %null_check = icmp eq i64 %length, 0
+  br i1 %null_check, label %exit, label %preheader
+
+preheader:
+  call void @llvm.assume(i1 true) [ "dereferenceable"(ptr %ptr, i64 %length) ]
+  %start = sub i64 %length, 1
+  br label %loop
+
+loop:
+  %iv = phi i64 [ %iv.next, %latch ], [ %start, %preheader ]
+  %element_gep = getelementptr i8, ptr %ptr, i64 %iv
+  %element = load i8, ptr %element_gep, align 1
+  %found_check = icmp eq i8 %element, 0
+  br i1 %found_check, label %exit, label %latch
+
+latch:
+  %iv.next = add i64 %iv, -1
+  %range_check = icmp ne i64 %iv, 0
+  br i1 %range_check, label %loop, label %exit
+
+exit:
+  ret ptr null
+}
+
+; TODO: The last iteration lands exactly on %ptr, so the accessed range [0, 16)
+; exactly fills the dereferenceable region, and the early-exit loop should be
+; vectorized.
+define ptr @reverse_reaches_base_static_length(ptr dereferenceable(16) %ptr) {
+; CHECK-LABEL: define ptr @reverse_reaches_base_static_length(
+; CHECK-SAME: ptr dereferenceable(16) [[PTR:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    br label %[[LOOP:.*]]
+; CHECK:       [[LOOP]]:
+; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ [[IV_NEXT:%.*]], %[[LATCH:.*]] ], [ 15, %[[ENTRY]] ]
+; CHECK-NEXT:    [[ELEMENT_GEP:%.*]] = getelementptr i8, ptr [[PTR]], i64 [[IV]]
+; CHECK-NEXT:    [[ELEMENT:%.*]] = load i8, ptr [[ELEMENT_GEP]], align 1
+; CHECK-NEXT:    [[FOUND_CHECK:%.*]] = icmp eq i8 [[ELEMENT]], 0
+; CHECK-NEXT:    br i1 [[FOUND_CHECK]], label %[[EXIT:.*]], label %[[LATCH]]
+; CHECK:       [[LATCH]]:
+; CHECK-NEXT:    [[IV_NEXT]] = add i64 [[IV]], -1
+; CHECK-NEXT:    [[RANGE_CHECK:%.*]] = icmp ne i64 [[IV]], 0
+; CHECK-NEXT:    br i1 [[RANGE_CHECK]], label %[[LOOP]], label %[[EXIT]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret ptr null
+;
+entry:
+  br label %loop
+
+loop:
+  %iv = phi i64 [ %iv.next, %latch ], [ 15, %entry ]
+  %element_gep = getelementptr i8, ptr %ptr, i64 %iv
+  %element = load i8, ptr %element_gep, align 1
+  %found_check = icmp eq i8 %element, 0
+  br i1 %found_check, label %exit, label %latch
+
+latch:
+  %iv.next = add i64 %iv, -1
+  %range_check = icmp ne i64 %iv, 0
+  br i1 %range_check, label %loop, label %exit
+
+exit:
+  ret ptr null
+}

>From 3018408c90fa9856a897bd9e37e7602e078686bc Mon Sep 17 00:00:00 2001
From: Aleksandr Popov <apopov at azul.com>
Date: Thu, 1 Oct 2026 15:15:55 +0000
Subject: [PATCH 2/3] Apply review suggestions

---
 .../LoopVectorize/reverse-loop-length.ll      | 67 ++++++++++---------
 1 file changed, 36 insertions(+), 31 deletions(-)

diff --git a/llvm/test/Transforms/LoopVectorize/reverse-loop-length.ll b/llvm/test/Transforms/LoopVectorize/reverse-loop-length.ll
index ec9c3ffea70e5..dbaaeadb6a890 100644
--- a/llvm/test/Transforms/LoopVectorize/reverse-loop-length.ll
+++ b/llvm/test/Transforms/LoopVectorize/reverse-loop-length.ll
@@ -4,32 +4,34 @@
 ; The loops below walk a dereferenceable region backwards, so every load is
 ; safe.
 
-; TODO: The AR runs over [0, %length), which is exactly the region the assume
-; marks dereferenceable, so the early-exit loop should be vectorized.
+; TODO: The counting-down IV accesses [0, %length), which is exactly the region
+; the assume marks dereferenceable, so the early-exit loop should be vectorized.
 define ptr @reverse_reaches_base_dynamic_length(i64 %length, ptr %ptr) {
 ; CHECK-LABEL: define ptr @reverse_reaches_base_dynamic_length(
 ; CHECK-SAME: i64 [[LENGTH:%.*]], ptr [[PTR:%.*]]) {
-; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:  [[ENTRY:.*]]:
 ; CHECK-NEXT:    [[NULL_CHECK:%.*]] = icmp eq i64 [[LENGTH]], 0
 ; CHECK-NEXT:    br i1 [[NULL_CHECK]], label %[[EXIT:.*]], label %[[PREHEADER:.*]]
 ; CHECK:       [[PREHEADER]]:
 ; CHECK-NEXT:    call void @llvm.assume(i1 true) [ "dereferenceable"(ptr [[PTR]], i64 [[LENGTH]]) ]
 ; CHECK-NEXT:    [[START:%.*]] = sub i64 [[LENGTH]], 1
-; CHECK-NEXT:    br label %[[LOOP:.*]]
-; CHECK:       [[LOOP]]:
-; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ [[IV_NEXT:%.*]], %[[LATCH:.*]] ], [ [[START]], %[[PREHEADER]] ]
+; CHECK-NEXT:    br label %[[LOOP_HEADER:.*]]
+; CHECK:       [[LOOP_HEADER]]:
+; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ [[START]], %[[PREHEADER]] ], [ [[IV_NEXT:%.*]], %[[LOOP_LATCH:.*]] ]
 ; CHECK-NEXT:    [[ELEMENT_GEP:%.*]] = getelementptr i8, ptr [[PTR]], i64 [[IV]]
 ; CHECK-NEXT:    [[ELEMENT:%.*]] = load i8, ptr [[ELEMENT_GEP]], align 1
 ; CHECK-NEXT:    [[FOUND_CHECK:%.*]] = icmp eq i8 [[ELEMENT]], 0
-; CHECK-NEXT:    br i1 [[FOUND_CHECK]], label %[[EXIT_LOOPEXIT:.*]], label %[[LATCH]]
-; CHECK:       [[LATCH]]:
+; CHECK-NEXT:    br i1 [[FOUND_CHECK]], label %[[EXIT_LOOPEXIT:.*]], label %[[LOOP_LATCH]]
+; CHECK:       [[LOOP_LATCH]]:
 ; CHECK-NEXT:    [[IV_NEXT]] = add i64 [[IV]], -1
 ; CHECK-NEXT:    [[RANGE_CHECK:%.*]] = icmp ne i64 [[IV]], 0
-; CHECK-NEXT:    br i1 [[RANGE_CHECK]], label %[[LOOP]], label %[[EXIT_LOOPEXIT]]
+; CHECK-NEXT:    br i1 [[RANGE_CHECK]], label %[[LOOP_HEADER]], label %[[EXIT_LOOPEXIT]]
 ; CHECK:       [[EXIT_LOOPEXIT]]:
+; CHECK-NEXT:    [[RES_PH:%.*]] = phi ptr [ null, %[[LOOP_LATCH]] ], [ [[ELEMENT_GEP]], %[[LOOP_HEADER]] ]
 ; CHECK-NEXT:    br label %[[EXIT]]
 ; CHECK:       [[EXIT]]:
-; CHECK-NEXT:    ret ptr null
+; CHECK-NEXT:    [[RES:%.*]] = phi ptr [ null, %[[ENTRY]] ], [ [[RES_PH]], %[[EXIT_LOOPEXIT]] ]
+; CHECK-NEXT:    ret ptr [[RES]]
 ;
 entry:
   %null_check = icmp eq i64 %length, 0
@@ -38,22 +40,23 @@ entry:
 preheader:
   call void @llvm.assume(i1 true) [ "dereferenceable"(ptr %ptr, i64 %length) ]
   %start = sub i64 %length, 1
-  br label %loop
+  br label %loop.header
 
-loop:
-  %iv = phi i64 [ %iv.next, %latch ], [ %start, %preheader ]
+loop.header:
+  %iv = phi i64 [ %start, %preheader ], [ %iv.next, %loop.latch ]
   %element_gep = getelementptr i8, ptr %ptr, i64 %iv
   %element = load i8, ptr %element_gep, align 1
   %found_check = icmp eq i8 %element, 0
-  br i1 %found_check, label %exit, label %latch
+  br i1 %found_check, label %exit, label %loop.latch
 
-latch:
+loop.latch:
   %iv.next = add i64 %iv, -1
   %range_check = icmp ne i64 %iv, 0
-  br i1 %range_check, label %loop, label %exit
+  br i1 %range_check, label %loop.header, label %exit
 
 exit:
-  ret ptr null
+  %res = phi ptr [ null, %entry ], [ %element_gep, %loop.header ], [ null, %loop.latch ]
+  ret ptr %res
 }
 
 ; TODO: The last iteration lands exactly on %ptr, so the accessed range [0, 16)
@@ -63,35 +66,37 @@ define ptr @reverse_reaches_base_static_length(ptr dereferenceable(16) %ptr) {
 ; CHECK-LABEL: define ptr @reverse_reaches_base_static_length(
 ; CHECK-SAME: ptr dereferenceable(16) [[PTR:%.*]]) {
 ; CHECK-NEXT:  [[ENTRY:.*]]:
-; CHECK-NEXT:    br label %[[LOOP:.*]]
-; CHECK:       [[LOOP]]:
-; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ [[IV_NEXT:%.*]], %[[LATCH:.*]] ], [ 15, %[[ENTRY]] ]
+; CHECK-NEXT:    br label %[[LOOP_HEADER:.*]]
+; CHECK:       [[LOOP_HEADER]]:
+; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ 15, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP_LATCH:.*]] ]
 ; CHECK-NEXT:    [[ELEMENT_GEP:%.*]] = getelementptr i8, ptr [[PTR]], i64 [[IV]]
 ; CHECK-NEXT:    [[ELEMENT:%.*]] = load i8, ptr [[ELEMENT_GEP]], align 1
 ; CHECK-NEXT:    [[FOUND_CHECK:%.*]] = icmp eq i8 [[ELEMENT]], 0
-; CHECK-NEXT:    br i1 [[FOUND_CHECK]], label %[[EXIT:.*]], label %[[LATCH]]
-; CHECK:       [[LATCH]]:
+; CHECK-NEXT:    br i1 [[FOUND_CHECK]], label %[[EXIT:.*]], label %[[LOOP_LATCH]]
+; CHECK:       [[LOOP_LATCH]]:
 ; CHECK-NEXT:    [[IV_NEXT]] = add i64 [[IV]], -1
 ; CHECK-NEXT:    [[RANGE_CHECK:%.*]] = icmp ne i64 [[IV]], 0
-; CHECK-NEXT:    br i1 [[RANGE_CHECK]], label %[[LOOP]], label %[[EXIT]]
+; CHECK-NEXT:    br i1 [[RANGE_CHECK]], label %[[LOOP_HEADER]], label %[[EXIT]]
 ; CHECK:       [[EXIT]]:
-; CHECK-NEXT:    ret ptr null
+; CHECK-NEXT:    [[RES:%.*]] = phi ptr [ [[ELEMENT_GEP]], %[[LOOP_HEADER]] ], [ null, %[[LOOP_LATCH]] ]
+; CHECK-NEXT:    ret ptr [[RES]]
 ;
 entry:
-  br label %loop
+  br label %loop.header
 
-loop:
-  %iv = phi i64 [ %iv.next, %latch ], [ 15, %entry ]
+loop.header:
+  %iv = phi i64 [ 15, %entry ], [ %iv.next, %loop.latch ]
   %element_gep = getelementptr i8, ptr %ptr, i64 %iv
   %element = load i8, ptr %element_gep, align 1
   %found_check = icmp eq i8 %element, 0
-  br i1 %found_check, label %exit, label %latch
+  br i1 %found_check, label %exit, label %loop.latch
 
-latch:
+loop.latch:
   %iv.next = add i64 %iv, -1
   %range_check = icmp ne i64 %iv, 0
-  br i1 %range_check, label %loop, label %exit
+  br i1 %range_check, label %loop.header, label %exit
 
 exit:
-  ret ptr null
+  %res = phi ptr [ %element_gep, %loop.header ], [ null, %loop.latch ]
+  ret ptr %res
 }

>From 8f24449cadaf8a64d2efd8e73717b47c1a22809b Mon Sep 17 00:00:00 2001
From: Aleksandr Popov <42888396+aleks-tmb at users.noreply.github.com>
Date: Fri, 2 Oct 2026 14:18:24 +0200
Subject: [PATCH 3/3] Update
 llvm/test/Transforms/LoopVectorize/reverse-loop-length.ll

Co-authored-by: Florian Hahn <flo at fhahn.com>
---
 llvm/test/Transforms/LoopVectorize/reverse-loop-length.ll | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/test/Transforms/LoopVectorize/reverse-loop-length.ll b/llvm/test/Transforms/LoopVectorize/reverse-loop-length.ll
index dbaaeadb6a890..1cfcfd42cf3fb 100644
--- a/llvm/test/Transforms/LoopVectorize/reverse-loop-length.ll
+++ b/llvm/test/Transforms/LoopVectorize/reverse-loop-length.ll
@@ -4,7 +4,7 @@
 ; The loops below walk a dereferenceable region backwards, so every load is
 ; safe.
 
-; TODO: The counting-down IV accesses [0, %length), which is exactly the region
+; TODO: The counting-down IV is in the range [0, %length), which is exactly the region
 ; the assume marks dereferenceable, so the early-exit loop should be vectorized.
 define ptr @reverse_reaches_base_dynamic_length(i64 %length, ptr %ptr) {
 ; CHECK-LABEL: define ptr @reverse_reaches_base_dynamic_length(



More information about the llvm-commits mailing list