[llvm] [SLP] Limit second pass to register VF in vectorizeNonVectorizableInsts. (PR #222755)

Florian Hahn via llvm-commits llvm-commits at lists.llvm.org
Thu Sep 10 12:55:50 PDT 2026


https://github.com/fhahn created https://github.com/llvm/llvm-project/pull/222755

The second tryToVectorizeList pass in vectorizeNonVectorizableInsts
would try all roots for each possible VF between 1 and the number of
entries in the list.

This can cause super-linear compile-time, for example when there are
basic blocks with calls taking a large number of loads as arguments.

For example, running SLPVectorizer on a block with 1024 loads passed to
calls (https://llvm.godbolt.org/z/8M53G6WzW) will take a large amount of
time (timeout on godbolt, locally on Apple M1 it takes ~30s). With the
fix, it only takes 0.01s.

On large IR corpus, I did not see any difference in vectorization
decisions on AArch64.

>From 67eff6a3f55a05b5e300ff4467efecdb3c2750f5 Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Thu, 10 Sep 2026 20:54:09 +0100
Subject: [PATCH 1/2] [SLP] Precommit tests

---
 .../AArch64/operand-chains-max-reg-vf.ll      | 86 +++++++++++++++++++
 1 file changed, 86 insertions(+)
 create mode 100644 llvm/test/Transforms/SLPVectorizer/AArch64/operand-chains-max-reg-vf.ll

diff --git a/llvm/test/Transforms/SLPVectorizer/AArch64/operand-chains-max-reg-vf.ll b/llvm/test/Transforms/SLPVectorizer/AArch64/operand-chains-max-reg-vf.ll
new file mode 100644
index 0000000000000..2cccd063a8631
--- /dev/null
+++ b/llvm/test/Transforms/SLPVectorizer/AArch64/operand-chains-max-reg-vf.ll
@@ -0,0 +1,86 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -passes=slp-vectorizer -mtriple=aarch64 -S %s | FileCheck %s
+
+define void @call_operands_wider_than_reg(ptr %p, ptr %q) {
+; CHECK-LABEL: define void @call_operands_wider_than_reg(
+; CHECK-SAME: ptr [[P:%.*]], ptr [[Q:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[P_0:%.*]] = getelementptr inbounds i32, ptr [[P]], i64 0
+; CHECK-NEXT:    [[Q_0:%.*]] = getelementptr inbounds i32, ptr [[Q]], i64 0
+; CHECK-NEXT:    [[TMP0:%.*]] = load <8 x i32>, ptr [[P_0]], align 4
+; CHECK-NEXT:    [[TMP1:%.*]] = load <8 x i32>, ptr [[Q_0]], align 4
+; CHECK-NEXT:    [[TMP2:%.*]] = add <8 x i32> [[TMP0]], [[TMP1]]
+; CHECK-NEXT:    [[TMP6:%.*]] = extractelement <8 x i32> [[TMP2]], i64 0
+; CHECK-NEXT:    call void @f(i32 [[TMP6]])
+; CHECK-NEXT:    [[TMP7:%.*]] = extractelement <8 x i32> [[TMP2]], i64 1
+; CHECK-NEXT:    call void @f(i32 [[TMP7]])
+; CHECK-NEXT:    [[TMP8:%.*]] = extractelement <8 x i32> [[TMP2]], i64 2
+; CHECK-NEXT:    call void @g(i32 [[TMP8]])
+; CHECK-NEXT:    [[TMP9:%.*]] = extractelement <8 x i32> [[TMP2]], i64 3
+; CHECK-NEXT:    call void @g(i32 [[TMP9]])
+; CHECK-NEXT:    [[TMP10:%.*]] = extractelement <8 x i32> [[TMP2]], i64 4
+; CHECK-NEXT:    call void @h(i32 [[TMP10]])
+; CHECK-NEXT:    [[TMP11:%.*]] = extractelement <8 x i32> [[TMP2]], i64 5
+; CHECK-NEXT:    call void @h(i32 [[TMP11]])
+; CHECK-NEXT:    [[TMP12:%.*]] = extractelement <8 x i32> [[TMP2]], i64 6
+; CHECK-NEXT:    call void @k(i32 [[TMP12]])
+; CHECK-NEXT:    [[TMP13:%.*]] = extractelement <8 x i32> [[TMP2]], i64 7
+; CHECK-NEXT:    call void @k(i32 [[TMP13]])
+; CHECK-NEXT:    ret void
+;
+entry:
+  %p.0 = getelementptr inbounds i32, ptr %p, i64 0
+  %p.1 = getelementptr inbounds i32, ptr %p, i64 1
+  %p.2 = getelementptr inbounds i32, ptr %p, i64 2
+  %p.3 = getelementptr inbounds i32, ptr %p, i64 3
+  %p.4 = getelementptr inbounds i32, ptr %p, i64 4
+  %p.5 = getelementptr inbounds i32, ptr %p, i64 5
+  %p.6 = getelementptr inbounds i32, ptr %p, i64 6
+  %p.7 = getelementptr inbounds i32, ptr %p, i64 7
+  %q.0 = getelementptr inbounds i32, ptr %q, i64 0
+  %q.1 = getelementptr inbounds i32, ptr %q, i64 1
+  %q.2 = getelementptr inbounds i32, ptr %q, i64 2
+  %q.3 = getelementptr inbounds i32, ptr %q, i64 3
+  %q.4 = getelementptr inbounds i32, ptr %q, i64 4
+  %q.5 = getelementptr inbounds i32, ptr %q, i64 5
+  %q.6 = getelementptr inbounds i32, ptr %q, i64 6
+  %q.7 = getelementptr inbounds i32, ptr %q, i64 7
+  %l.0 = load i32, ptr %p.0
+  %r.0 = load i32, ptr %q.0
+  %add.0 = add i32 %l.0, %r.0
+  %l.1 = load i32, ptr %p.1
+  %r.1 = load i32, ptr %q.1
+  %add.1 = add i32 %l.1, %r.1
+  %l.2 = load i32, ptr %p.2
+  %r.2 = load i32, ptr %q.2
+  %add.2 = add i32 %l.2, %r.2
+  %l.3 = load i32, ptr %p.3
+  %r.3 = load i32, ptr %q.3
+  %add.3 = add i32 %l.3, %r.3
+  %l.4 = load i32, ptr %p.4
+  %r.4 = load i32, ptr %q.4
+  %add.4 = add i32 %l.4, %r.4
+  %l.5 = load i32, ptr %p.5
+  %r.5 = load i32, ptr %q.5
+  %add.5 = add i32 %l.5, %r.5
+  %l.6 = load i32, ptr %p.6
+  %r.6 = load i32, ptr %q.6
+  %add.6 = add i32 %l.6, %r.6
+  %l.7 = load i32, ptr %p.7
+  %r.7 = load i32, ptr %q.7
+  %add.7 = add i32 %l.7, %r.7
+  call void @f(i32 %add.0)
+  call void @f(i32 %add.1)
+  call void @g(i32 %add.2)
+  call void @g(i32 %add.3)
+  call void @h(i32 %add.4)
+  call void @h(i32 %add.5)
+  call void @k(i32 %add.6)
+  call void @k(i32 %add.7)
+  ret void
+}
+
+declare void @f(i32)
+declare void @g(i32)
+declare void @h(i32)
+declare void @k(i32)

>From 86a395c036dbc8f8e46719bbd2a4169377564943 Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Thu, 10 Sep 2026 18:14:20 +0100
Subject: [PATCH 2/2] [SLP] Limit second pass to register VF in
 vectorizeNonVectorizableInsts.

The second tryToVectorizeList pass in vectorizeNonVectorizableInsts
would try all roots for each possible VF between 1 and the number of
entries in the list.

This can cause super-linear compile-time, for example when there are
basic blocks with calls taking a large number of loads as arguments.

For example, running SLPVectorizer on a block with 1024 loads passed to
calls (https://llvm.godbolt.org/z/8M53G6WzW) will take a large amount of
time (timeout on godbolt, locally on Apple M1 it takes ~30s). With the
fix, it only takes 0.01s.

On large IR corpus, I did not see any difference in vectorization
decisions on AArch64.
---
 .../Transforms/Vectorize/SLPVectorizer.cpp    |  5 +++-
 .../AArch64/operand-chains-max-reg-vf.ll      | 27 +++++++++++--------
 2 files changed, 20 insertions(+), 12 deletions(-)

diff --git a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
index 28e665c4c07cf..f92fa516630de 100644
--- a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
+++ b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
@@ -33365,7 +33365,10 @@ bool SLPVectorizerPass::vectorizeNonVectorizableInsts(
   Changed |= tryToVectorizeSequence<Value>(
       Operands, OperandSorter, AreCompatibleOperands,
       [this, &R](ArrayRef<Value *> Candidates, bool MaxVFOnly) {
-        return tryToVectorizeList(Candidates, R, MaxVFOnly);
+        // Limit to StandaloneSeeds if !MaxVFOnly to avoid quadratic scan for
+        // large set of candidates.
+        return tryToVectorizeList(Candidates, R, MaxVFOnly,
+                                  /*StandaloneSeeds=*/!MaxVFOnly);
       },
       /*MaxVFOnly=*/true, R);
   return Changed;
diff --git a/llvm/test/Transforms/SLPVectorizer/AArch64/operand-chains-max-reg-vf.ll b/llvm/test/Transforms/SLPVectorizer/AArch64/operand-chains-max-reg-vf.ll
index 2cccd063a8631..5a08a24001cfc 100644
--- a/llvm/test/Transforms/SLPVectorizer/AArch64/operand-chains-max-reg-vf.ll
+++ b/llvm/test/Transforms/SLPVectorizer/AArch64/operand-chains-max-reg-vf.ll
@@ -6,25 +6,30 @@ define void @call_operands_wider_than_reg(ptr %p, ptr %q) {
 ; CHECK-SAME: ptr [[P:%.*]], ptr [[Q:%.*]]) {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
 ; CHECK-NEXT:    [[P_0:%.*]] = getelementptr inbounds i32, ptr [[P]], i64 0
+; CHECK-NEXT:    [[P_4:%.*]] = getelementptr inbounds i32, ptr [[P]], i64 4
 ; CHECK-NEXT:    [[Q_0:%.*]] = getelementptr inbounds i32, ptr [[Q]], i64 0
-; CHECK-NEXT:    [[TMP0:%.*]] = load <8 x i32>, ptr [[P_0]], align 4
-; CHECK-NEXT:    [[TMP1:%.*]] = load <8 x i32>, ptr [[Q_0]], align 4
-; CHECK-NEXT:    [[TMP2:%.*]] = add <8 x i32> [[TMP0]], [[TMP1]]
-; CHECK-NEXT:    [[TMP6:%.*]] = extractelement <8 x i32> [[TMP2]], i64 0
+; CHECK-NEXT:    [[Q_4:%.*]] = getelementptr inbounds i32, ptr [[Q]], i64 4
+; CHECK-NEXT:    [[TMP0:%.*]] = load <4 x i32>, ptr [[P_0]], align 4
+; CHECK-NEXT:    [[TMP1:%.*]] = load <4 x i32>, ptr [[Q_0]], align 4
+; CHECK-NEXT:    [[TMP2:%.*]] = add <4 x i32> [[TMP0]], [[TMP1]]
+; CHECK-NEXT:    [[TMP3:%.*]] = load <4 x i32>, ptr [[P_4]], align 4
+; CHECK-NEXT:    [[TMP4:%.*]] = load <4 x i32>, ptr [[Q_4]], align 4
+; CHECK-NEXT:    [[TMP5:%.*]] = add <4 x i32> [[TMP3]], [[TMP4]]
+; CHECK-NEXT:    [[TMP6:%.*]] = extractelement <4 x i32> [[TMP2]], i64 0
 ; CHECK-NEXT:    call void @f(i32 [[TMP6]])
-; CHECK-NEXT:    [[TMP7:%.*]] = extractelement <8 x i32> [[TMP2]], i64 1
+; CHECK-NEXT:    [[TMP7:%.*]] = extractelement <4 x i32> [[TMP2]], i64 1
 ; CHECK-NEXT:    call void @f(i32 [[TMP7]])
-; CHECK-NEXT:    [[TMP8:%.*]] = extractelement <8 x i32> [[TMP2]], i64 2
+; CHECK-NEXT:    [[TMP8:%.*]] = extractelement <4 x i32> [[TMP2]], i64 2
 ; CHECK-NEXT:    call void @g(i32 [[TMP8]])
-; CHECK-NEXT:    [[TMP9:%.*]] = extractelement <8 x i32> [[TMP2]], i64 3
+; CHECK-NEXT:    [[TMP9:%.*]] = extractelement <4 x i32> [[TMP2]], i64 3
 ; CHECK-NEXT:    call void @g(i32 [[TMP9]])
-; CHECK-NEXT:    [[TMP10:%.*]] = extractelement <8 x i32> [[TMP2]], i64 4
+; CHECK-NEXT:    [[TMP10:%.*]] = extractelement <4 x i32> [[TMP5]], i64 0
 ; CHECK-NEXT:    call void @h(i32 [[TMP10]])
-; CHECK-NEXT:    [[TMP11:%.*]] = extractelement <8 x i32> [[TMP2]], i64 5
+; CHECK-NEXT:    [[TMP11:%.*]] = extractelement <4 x i32> [[TMP5]], i64 1
 ; CHECK-NEXT:    call void @h(i32 [[TMP11]])
-; CHECK-NEXT:    [[TMP12:%.*]] = extractelement <8 x i32> [[TMP2]], i64 6
+; CHECK-NEXT:    [[TMP12:%.*]] = extractelement <4 x i32> [[TMP5]], i64 2
 ; CHECK-NEXT:    call void @k(i32 [[TMP12]])
-; CHECK-NEXT:    [[TMP13:%.*]] = extractelement <8 x i32> [[TMP2]], i64 7
+; CHECK-NEXT:    [[TMP13:%.*]] = extractelement <4 x i32> [[TMP5]], i64 3
 ; CHECK-NEXT:    call void @k(i32 [[TMP13]])
 ; CHECK-NEXT:    ret void
 ;



More information about the llvm-commits mailing list