[llvm] [LV] Avoid collapsing VF to zero when a loop has no memory ops or reductions (PR #216266)

Pengcheng Wang via llvm-commits llvm-commits at lists.llvm.org
Thu Aug 20 01:06:40 PDT 2026


https://github.com/wangpc-pp updated https://github.com/llvm/llvm-project/pull/216266

>From db2784dc515bb62a4075b486aed633e5b5a6776a Mon Sep 17 00:00:00 2001
From: Pengcheng Wang <wangpengcheng.pp at bytedance.com>
Date: Mon, 17 Aug 2026 11:57:14 +0800
Subject: [PATCH 1/2] [LV] Add test for VF selection in loops with no memory
 ops or reductions

Add a RISC-V early-exit search loop that has no loads, stores, or reductions.
getSmallestAndWidestTypes() cannot observe an element type for such a loop and
reports the smallest type as the -1U sentinel. This captures the current
behavior where -vectorizer-maximize-bandwidth leaves the loop scalar even
though it is vectorized by default.

Assisted-by: TRAE CLI (Opus 4.8)

Co-authored-by: TRAE CLI <noreply at bytedance.com>
---
 .../RISCV/smallest-and-widest-types.ll        | 49 +++++++++++++++++++
 1 file changed, 49 insertions(+)
 create mode 100644 llvm/test/Transforms/LoopVectorize/RISCV/smallest-and-widest-types.ll

diff --git a/llvm/test/Transforms/LoopVectorize/RISCV/smallest-and-widest-types.ll b/llvm/test/Transforms/LoopVectorize/RISCV/smallest-and-widest-types.ll
new file mode 100644
index 0000000000000..d60ea815d9760
--- /dev/null
+++ b/llvm/test/Transforms/LoopVectorize/RISCV/smallest-and-widest-types.ll
@@ -0,0 +1,49 @@
+; NOTE: Assertions have been autogenerated by utils/update_analyze_test_checks.py UTC_ARGS: --filter "The Smallest and Widest types" --filter "Selecting VF" --filter "Vectorization is possible but not beneficial" --version 6
+; REQUIRES: asserts
+; RUN: opt -passes=loop-vectorize -mtriple riscv64 -mattr=+v \
+; RUN:   -debug-only=loop-vectorize -disable-output -S < %s 2>&1 | \
+; RUN:   FileCheck %s --check-prefix=DEFAULT
+; RUN: opt -passes=loop-vectorize -mtriple riscv64 -mattr=+v \
+; RUN:   -vectorizer-maximize-bandwidth \
+; RUN:   -debug-only=loop-vectorize -disable-output -S < %s 2>&1 | \
+; RUN:   FileCheck %s --check-prefix=MAXBW
+
+; This is an early-exit search loop with no loads/stores and no reductions, so
+; getSmallestAndWidestTypes() cannot observe any element type and reports the
+; smallest type as the -1U sentinel (4294967295).
+;
+; FIXME: The sentinel makes the max-bandwidth VF computation divide the register
+; width by it, collapse the VF to zero, and disable vectorization, so with
+; -vectorizer-maximize-bandwidth this loop is left scalar even though it is
+; vectorized by default.
+
+define i32 @find_first_ge(i32 %n, i32 %target) {
+; DEFAULT-LABEL: 'find_first_ge'
+; DEFAULT:  LV: The Smallest and Widest types: 4294967295 / 8 bits.
+; DEFAULT:  LV: Selecting VF: vscale x 16.
+;
+; MAXBW-LABEL: 'find_first_ge'
+; MAXBW:  LV: The Smallest and Widest types: 4294967295 / 8 bits.
+; MAXBW:  LV: Vectorization is possible but not beneficial.
+;
+entry:
+  br label %loop.header
+
+loop.header:
+  %iv = phi i32 [ %iv.next, %loop.latch ], [ 0, %entry ]
+  %shl = shl i32 %iv, 1
+  %add = add i32 %shl, 512
+  %cmp = icmp ult i32 %add, %target
+  br i1 %cmp, label %loop.latch, label %exit.early
+
+loop.latch:
+  %iv.next = add nuw i32 %iv, 1
+  %exitcond = icmp eq i32 %iv.next, %n
+  br i1 %exitcond, label %exit, label %loop.header
+
+exit.early:
+  ret i32 %iv
+
+exit:
+  ret i32 %n
+}

>From 5c6f7f76f353bea17429fd7ea9dffbba2f884761 Mon Sep 17 00:00:00 2001
From: Pengcheng Wang <wangpengcheng.pp at bytedance.com>
Date: Mon, 17 Aug 2026 12:00:12 +0800
Subject: [PATCH 2/2] [LV] Avoid collapsing VF to zero when a loop has no
 memory ops or reductions

getSmallestAndWidestTypes() initializes MinWidth to the -1U sentinel and only
updates it from loads, stores, and reduction recurrences. A loop with none of
these (for example an early-exit search loop) therefore reports SmallestType as
the sentinel value.

The max-bandwidth VF computation in getMaximizedVFForTarget() divides the widest
register width by SmallestType. With the sentinel this underflows to zero, so
MaxVF collapses to an empty ElementCount and vectorization is disabled. As a
result -vectorizer-maximize-bandwidth, which is meant to allow a larger VF, can
instead turn a loop that vectorizes by default into a scalar loop.

Restore the SmallestType <= WidestType invariant by falling back to MaxWidth
when no element type was observed. This keeps the default path unchanged and
makes the max-bandwidth path pick the same VF for such loops.

Assisted-by: TRAE CLI (Opus 4.8)

Co-authored-by: TRAE CLI <noreply at bytedance.com>
---
 .../Vectorize/LoopVectorizationPlanner.cpp    |  9 ++++++
 .../RISCV/smallest-and-widest-types.ll        | 28 +++++++------------
 2 files changed, 19 insertions(+), 18 deletions(-)

diff --git a/llvm/lib/Transforms/Vectorize/LoopVectorizationPlanner.cpp b/llvm/lib/Transforms/Vectorize/LoopVectorizationPlanner.cpp
index fd7fd2e011a83..d9ce5b5e7ebf4 100644
--- a/llvm/lib/Transforms/Vectorize/LoopVectorizationPlanner.cpp
+++ b/llvm/lib/Transforms/Vectorize/LoopVectorizationPlanner.cpp
@@ -527,6 +527,15 @@ VFSelectionContext::getSmallestAndWidestTypes() const {
           MaxWidth, DL.getTypeSizeInBits(T->getScalarType()).getFixedValue());
     }
   }
+
+  // If the loop has no loads/stores or reductions (e.g. a search loop with an
+  // early exit), MinWidth is never updated and is left at its sentinel value.
+  // Fall back to MaxWidth to keep the SmallestType <= WidestType invariant, so
+  // callers such as the max-bandwidth VF computation don't divide by the
+  // sentinel and collapse the VF to zero.
+  if (MinWidth == -1U)
+    MinWidth = MaxWidth;
+
   return {MinWidth, MaxWidth};
 }
 
diff --git a/llvm/test/Transforms/LoopVectorize/RISCV/smallest-and-widest-types.ll b/llvm/test/Transforms/LoopVectorize/RISCV/smallest-and-widest-types.ll
index d60ea815d9760..2c0f4a051136d 100644
--- a/llvm/test/Transforms/LoopVectorize/RISCV/smallest-and-widest-types.ll
+++ b/llvm/test/Transforms/LoopVectorize/RISCV/smallest-and-widest-types.ll
@@ -1,30 +1,22 @@
 ; NOTE: Assertions have been autogenerated by utils/update_analyze_test_checks.py UTC_ARGS: --filter "The Smallest and Widest types" --filter "Selecting VF" --filter "Vectorization is possible but not beneficial" --version 6
 ; REQUIRES: asserts
 ; RUN: opt -passes=loop-vectorize -mtriple riscv64 -mattr=+v \
-; RUN:   -debug-only=loop-vectorize -disable-output -S < %s 2>&1 | \
-; RUN:   FileCheck %s --check-prefix=DEFAULT
+; RUN:   -debug-only=loop-vectorize -disable-output -S < %s 2>&1 | FileCheck %s
 ; RUN: opt -passes=loop-vectorize -mtriple riscv64 -mattr=+v \
 ; RUN:   -vectorizer-maximize-bandwidth \
-; RUN:   -debug-only=loop-vectorize -disable-output -S < %s 2>&1 | \
-; RUN:   FileCheck %s --check-prefix=MAXBW
+; RUN:   -debug-only=loop-vectorize -disable-output -S < %s 2>&1 | FileCheck %s
 
 ; This is an early-exit search loop with no loads/stores and no reductions, so
-; getSmallestAndWidestTypes() cannot observe any element type and reports the
-; smallest type as the -1U sentinel (4294967295).
-;
-; FIXME: The sentinel makes the max-bandwidth VF computation divide the register
-; width by it, collapse the VF to zero, and disable vectorization, so with
-; -vectorizer-maximize-bandwidth this loop is left scalar even though it is
-; vectorized by default.
+; getSmallestAndWidestTypes() cannot observe any element type. The smallest type
+; is reported as equal to the widest type instead of the -1U sentinel, so the
+; max-bandwidth VF computation does not divide the register width by the
+; sentinel and collapse the VF to zero. With and without
+; -vectorizer-maximize-bandwidth we pick the same VF.
 
 define i32 @find_first_ge(i32 %n, i32 %target) {
-; DEFAULT-LABEL: 'find_first_ge'
-; DEFAULT:  LV: The Smallest and Widest types: 4294967295 / 8 bits.
-; DEFAULT:  LV: Selecting VF: vscale x 16.
-;
-; MAXBW-LABEL: 'find_first_ge'
-; MAXBW:  LV: The Smallest and Widest types: 4294967295 / 8 bits.
-; MAXBW:  LV: Vectorization is possible but not beneficial.
+; CHECK-LABEL: 'find_first_ge'
+; CHECK:  LV: The Smallest and Widest types: 8 / 8 bits.
+; CHECK:  LV: Selecting VF: vscale x 16.
 ;
 entry:
   br label %loop.header



More information about the llvm-commits mailing list