[llvm] [SLP] De-duplicate costing of perfect-diamond gather spills (PR #220569)
Hari Limaye via llvm-commits
llvm-commits at lists.llvm.org
Mon Sep 7 01:56:56 PDT 2026
https://github.com/hazzlim updated https://github.com/llvm/llvm-project/pull/220569
>From 796c7b69f161cdbc6fe799736ed0c2a226a60e13 Mon Sep 17 00:00:00 2001
From: Hari Limaye <hari.limaye at arm.com>
Date: Wed, 2 Sep 2026 10:11:56 +0000
Subject: [PATCH 1/7] [SLP] De-duplicate costing of perfect-diamond gather
spills
Refactor the logic in `getSpillCost` to avoid double counting the cost
of spilling a loop-invariant perfect-diamond gather entry across a loop
containing a call.
---
.../Transforms/Vectorize/SLPVectorizer.cpp | 70 +++++++++++++++----
.../spillcost-shared-loop-invariant.ll | 44 ++++--------
2 files changed, 72 insertions(+), 42 deletions(-)
diff --git a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
index 3d1c305660102..30c31b84c1ff8 100644
--- a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
+++ b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
@@ -18936,26 +18936,33 @@ InstructionCost BoUpSLP::getSpillCost() {
LoopBodyHasNonVecCall.try_emplace(L, false);
return false;
};
- auto CheckPredecessors = [&](BasicBlock *Root, BasicBlock *Pred,
- BasicBlock *OpParent) {
- auto Key = std::make_pair(Root, OpParent);
- if (auto It = ParentOpParentToPreds.find(Key);
- It != ParentOpParentToPreds.end())
- return It->second;
- uint64_t Res = 0;
- scope_exit Cleanup([&]() { ParentOpParentToPreds.try_emplace(Key, Res); });
+ auto GetLoopInvariantSpillRegion = [&](BasicBlock *UseBB,
+ BasicBlock *DefBB) -> const Loop * {
// If Op is loop-invariant, a call anywhere in the loop body forces a spill,
// even when a call-free forward path from Root back to OpParent exists on
// the first iteration. Find the outermost such enclosing loop and reject if
// its body contains a non-vec call.
- const Loop *L = LI->getLoopFor(Root);
+ const Loop *L = LI->getLoopFor(UseBB);
const Loop *Outermost = nullptr;
- while (L && !L->contains(OpParent)) {
+ while (L && !L->contains(DefBB)) {
Outermost = L;
L = L->getParentLoop();
}
if (Outermost && LoopBodyHasCall(Outermost)) {
- Res = getLoopNestScale(Outermost);
+ return Outermost;
+ }
+ return nullptr;
+ };
+ auto CheckPredecessors = [&](BasicBlock *Root, BasicBlock *Pred,
+ BasicBlock *OpParent) {
+ auto Key = std::make_pair(Root, OpParent);
+ if (auto It = ParentOpParentToPreds.find(Key);
+ It != ParentOpParentToPreds.end())
+ return It->second;
+ uint64_t Res = 0;
+ scope_exit Cleanup([&]() { ParentOpParentToPreds.try_emplace(Key, Res); });
+ if (const auto *L = GetLoopInvariantSpillRegion(Root, OpParent)) {
+ Res = getLoopNestScale(L);
return Res;
}
SmallVector<BasicBlock *> Worklist;
@@ -19040,6 +19047,44 @@ InstructionCost BoUpSLP::getSpillCost() {
}
return nullptr;
};
+ auto IsCoveredByMatchingVectorEntry = [&](const TreeEntry *Gather,
+ const Loop *SpillLoop) -> bool {
+ assert(Gather->isGather() && Gather->hasState());
+
+ // Find the real vector entry reused by this perfect-diamond gather.
+ const TreeEntry *SameTE = getSameValuesTreeEntry(
+ Gather->getMainOp(), Gather->Scalars, /*SameVF=*/true);
+ if (!SameTE || SameTE == Gather || SameTE->State != TreeEntry::Vectorize ||
+ ScalarOrPseudoEntries.contains(SameTE) || !SameTE->UserTreeIndex)
+ return false;
+
+ // Only permit an ordinary vectorized user.
+ const TreeEntry *UserTE = SameTE->UserTreeIndex.UserTE;
+ if (!UserTE || UserTE->State != TreeEntry::Vectorize ||
+ ScalarOrPseudoEntries.contains(UserTE) ||
+ UserTE->getOpcode() == Instruction::PHI)
+ return false;
+
+ Instruction *Def = EntriesToLastInstruction.lookup(SameTE);
+ Instruction *Use = EntriesToLastInstruction.lookup(UserTE);
+ if (!Def || !Use)
+ return false;
+
+ // Require the matching entry to be defined outside the loop and its use to
+ // execute directly in the same loop as the gather user.
+ if (SpillLoop->contains(Def->getParent()) ||
+ LI->getLoopFor(Use->getParent()) != SpillLoop)
+ return false;
+
+ // Ensure that the matching entry is also loop invariant.
+ if (!all_of(SameTE->Scalars, [&](Value *V) {
+ return !isa<Instruction>(V) || SpillLoop->isLoopInvariant(V);
+ }))
+ return false;
+
+ return GetLoopInvariantSpillRegion(Use->getParent(), Def->getParent()) ==
+ SpillLoop;
+ };
while (!LiveEntries.empty()) {
const TreeEntry *Entry = LiveEntries.pop_back_val();
const auto OpIt = EntriesToOperands.find(Entry);
@@ -19079,7 +19124,8 @@ InstructionCost BoUpSLP::getSpillCost() {
all_of(Op->Scalars, [&](Value *V) {
return !isa<Instruction>(V) || L->isLoopInvariant(V);
}))
- AddCosts(Op, GetSpillScale(Parent));
+ if (!IsCoveredByMatchingVectorEntry(Op, L))
+ AddCosts(Op, GetSpillScale(Parent));
continue;
}
Budget = 0;
diff --git a/llvm/test/Transforms/SLPVectorizer/AArch64/spillcost-shared-loop-invariant.ll b/llvm/test/Transforms/SLPVectorizer/AArch64/spillcost-shared-loop-invariant.ll
index 0ce0fba110477..706b6afd565d6 100644
--- a/llvm/test/Transforms/SLPVectorizer/AArch64/spillcost-shared-loop-invariant.ll
+++ b/llvm/test/Transforms/SLPVectorizer/AArch64/spillcost-shared-loop-invariant.ll
@@ -9,27 +9,20 @@ define void @shared_loop_invariant(ptr %params, i1 %cond, i64 %n) {
; CHECK-SAME: ptr [[PARAMS:%.*]], i1 [[COND:%.*]], i64 [[N:%.*]]) {
; CHECK-NEXT: [[ENTRY:.*]]:
; CHECK-NEXT: [[OFFSET:%.*]] = getelementptr i8, ptr [[PARAMS]], i64 88
-; CHECK-NEXT: [[X:%.*]] = load double, ptr [[OFFSET]], align 8
-; CHECK-NEXT: [[Y_PTR:%.*]] = getelementptr i8, ptr [[PARAMS]], i64 96
-; CHECK-NEXT: [[Y:%.*]] = load double, ptr [[Y_PTR]], align 8
+; CHECK-NEXT: [[TMP0:%.*]] = load <2 x double>, ptr [[OFFSET]], align 8
; CHECK-NEXT: br label %[[LOOP:.*]]
; CHECK: [[LOOP]]:
; CHECK-NEXT: [[I:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[NEXT:%.*]], %[[MERGE:.*]] ]
; CHECK-NEXT: br i1 [[COND]], label %[[RIGHT:.*]], label %[[LEFT:.*]]
; CHECK: [[LEFT]]:
-; CHECK-NEXT: [[LEFT_X:%.*]] = fadd double 0.000000e+00, [[X]]
-; CHECK-NEXT: [[LEFT_Y:%.*]] = fadd double 0.000000e+00, [[Y]]
+; CHECK-NEXT: [[TMP1:%.*]] = fadd <2 x double> zeroinitializer, [[TMP0]]
; CHECK-NEXT: br label %[[MERGE]]
; CHECK: [[RIGHT]]:
-; CHECK-NEXT: [[RIGHT_X:%.*]] = fadd double 0.000000e+00, [[X]]
-; CHECK-NEXT: [[RIGHT_Y:%.*]] = fadd double 0.000000e+00, [[Y]]
+; CHECK-NEXT: [[TMP2:%.*]] = fadd <2 x double> zeroinitializer, [[TMP0]]
; CHECK-NEXT: br label %[[MERGE]]
; CHECK: [[MERGE]]:
-; CHECK-NEXT: [[PHI_Y:%.*]] = phi double [ [[RIGHT_Y]], %[[RIGHT]] ], [ [[LEFT_Y]], %[[LEFT]] ]
-; CHECK-NEXT: [[PHI_X:%.*]] = phi double [ [[RIGHT_X]], %[[RIGHT]] ], [ [[LEFT_X]], %[[LEFT]] ]
-; CHECK-NEXT: store double [[PHI_X]], ptr [[PARAMS]], align 8
-; CHECK-NEXT: [[OUT_Y:%.*]] = getelementptr i8, ptr [[PARAMS]], i64 8
-; CHECK-NEXT: store double [[PHI_Y]], ptr [[OUT_Y]], align 8
+; CHECK-NEXT: [[TMP3:%.*]] = phi <2 x double> [ [[TMP2]], %[[RIGHT]] ], [ [[TMP1]], %[[LEFT]] ]
+; CHECK-NEXT: store <2 x double> [[TMP3]], ptr [[PARAMS]], align 8
; CHECK-NEXT: call void @external()
; CHECK-NEXT: [[NEXT]] = add nuw i64 [[I]], 1
; CHECK-NEXT: [[CONTINUE:%.*]] = icmp ult i64 [[NEXT]], [[N]]
@@ -78,33 +71,24 @@ define void @shared_coordinate_offsets(ptr %params, ptr %left.coords, ptr %right
; CHECK-SAME: ptr [[PARAMS:%.*]], ptr [[LEFT_COORDS:%.*]], ptr [[RIGHT_COORDS:%.*]], ptr [[OUT:%.*]], i1 [[COND:%.*]], i64 [[N:%.*]]) {
; CHECK-NEXT: [[ENTRY:.*]]:
; CHECK-NEXT: [[OFFSET_PTR:%.*]] = getelementptr i8, ptr [[PARAMS]], i64 88
-; CHECK-NEXT: [[OFFSET_X:%.*]] = load double, ptr [[OFFSET_PTR]], align 8
-; CHECK-NEXT: [[OFFSET_Y_PTR:%.*]] = getelementptr i8, ptr [[PARAMS]], i64 96
-; CHECK-NEXT: [[OFFSET_Y:%.*]] = load double, ptr [[OFFSET_Y_PTR]], align 8
+; CHECK-NEXT: [[TMP0:%.*]] = load <2 x double>, ptr [[OFFSET_PTR]], align 8
; CHECK-NEXT: br label %[[LOOP:.*]]
; CHECK: [[LOOP]]:
; CHECK-NEXT: [[I:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[NEXT:%.*]], %[[MERGE:.*]] ]
; CHECK-NEXT: br i1 [[COND]], label %[[RIGHT:.*]], label %[[LEFT:.*]]
; CHECK: [[LEFT]]:
-; CHECK-NEXT: [[LEFT_X:%.*]] = load double, ptr [[LEFT_COORDS]], align 8
-; CHECK-NEXT: [[LEFT_Y_PTR:%.*]] = getelementptr i8, ptr [[LEFT_COORDS]], i64 8
-; CHECK-NEXT: [[LEFT_Y:%.*]] = load double, ptr [[LEFT_Y_PTR]], align 8
-; CHECK-NEXT: [[LEFT_ADJUSTED_X:%.*]] = fadd fast double [[LEFT_X]], [[OFFSET_X]]
-; CHECK-NEXT: [[LEFT_ADJUSTED_Y:%.*]] = fadd fast double [[LEFT_Y]], [[OFFSET_Y]]
+; CHECK-NEXT: [[TMP1:%.*]] = load <2 x double>, ptr [[LEFT_COORDS]], align 8
+; CHECK-NEXT: [[TMP2:%.*]] = fadd fast <2 x double> [[TMP1]], [[TMP0]]
; CHECK-NEXT: br label %[[MERGE]]
; CHECK: [[RIGHT]]:
-; CHECK-NEXT: [[RIGHT_X:%.*]] = load double, ptr [[RIGHT_COORDS]], align 8
-; CHECK-NEXT: [[RIGHT_Y_PTR:%.*]] = getelementptr i8, ptr [[RIGHT_COORDS]], i64 8
-; CHECK-NEXT: [[RIGHT_Y:%.*]] = load double, ptr [[RIGHT_Y_PTR]], align 8
-; CHECK-NEXT: [[RIGHT_ADJUSTED_X:%.*]] = fadd fast double [[RIGHT_X]], [[OFFSET_X]]
-; CHECK-NEXT: [[RIGHT_ADJUSTED_Y:%.*]] = fadd fast double [[RIGHT_Y]], [[OFFSET_Y]]
+; CHECK-NEXT: [[TMP3:%.*]] = load <2 x double>, ptr [[RIGHT_COORDS]], align 8
+; CHECK-NEXT: [[TMP4:%.*]] = fadd fast <2 x double> [[TMP3]], [[TMP0]]
; CHECK-NEXT: br label %[[MERGE]]
; CHECK: [[MERGE]]:
-; CHECK-NEXT: [[ADJUSTED_X:%.*]] = phi double [ [[RIGHT_ADJUSTED_X]], %[[RIGHT]] ], [ [[LEFT_ADJUSTED_X]], %[[LEFT]] ]
-; CHECK-NEXT: [[ADJUSTED_Y:%.*]] = phi double [ [[RIGHT_ADJUSTED_Y]], %[[RIGHT]] ], [ [[LEFT_ADJUSTED_Y]], %[[LEFT]] ]
-; CHECK-NEXT: store double [[ADJUSTED_X]], ptr [[OUT]], align 8
-; CHECK-NEXT: [[OUT_Y:%.*]] = getelementptr i8, ptr [[OUT]], i64 8
-; CHECK-NEXT: store double [[ADJUSTED_Y]], ptr [[OUT_Y]], align 8
+; CHECK-NEXT: [[TMP5:%.*]] = phi <2 x double> [ [[TMP4]], %[[RIGHT]] ], [ [[TMP2]], %[[LEFT]] ]
+; CHECK-NEXT: store <2 x double> [[TMP5]], ptr [[OUT]], align 8
+; CHECK-NEXT: [[ADJUSTED_X:%.*]] = extractelement <2 x double> [[TMP5]], i64 0
+; CHECK-NEXT: [[ADJUSTED_Y:%.*]] = extractelement <2 x double> [[TMP5]], i64 1
; CHECK-NEXT: call void @use_coordinates(double [[ADJUSTED_X]], double [[ADJUSTED_Y]])
; CHECK-NEXT: [[NEXT]] = add nuw i64 [[I]], 1
; CHECK-NEXT: [[CONTINUE:%.*]] = icmp ult i64 [[NEXT]], [[N]]
>From a287182e84f9010520b08731388f961f5aef48ba Mon Sep 17 00:00:00 2001
From: Hari Limaye <hari.limaye at arm.com>
Date: Wed, 2 Sep 2026 14:14:59 +0000
Subject: [PATCH 2/7] Fixup
---
llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
index 30c31b84c1ff8..e92e17a61b825 100644
--- a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
+++ b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
@@ -19049,7 +19049,9 @@ InstructionCost BoUpSLP::getSpillCost() {
};
auto IsCoveredByMatchingVectorEntry = [&](const TreeEntry *Gather,
const Loop *SpillLoop) -> bool {
- assert(Gather->isGather() && Gather->hasState());
+ assert(Gather->isGather());
+ if (!Gather->hasState())
+ return false;
// Find the real vector entry reused by this perfect-diamond gather.
const TreeEntry *SameTE = getSameValuesTreeEntry(
>From 893edd6a018e0cf2457d1f5e375c09695aedc012 Mon Sep 17 00:00:00 2001
From: Hari Limaye <hari.limaye at arm.com>
Date: Wed, 2 Sep 2026 21:46:45 +0000
Subject: [PATCH 3/7] Review suggested changes
---
.../Transforms/Vectorize/SLPVectorizer.cpp | 26 ++++++++++++++++---
1 file changed, 22 insertions(+), 4 deletions(-)
diff --git a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
index e92e17a61b825..66c6ccaba77f9 100644
--- a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
+++ b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
@@ -18938,10 +18938,11 @@ InstructionCost BoUpSLP::getSpillCost() {
};
auto GetLoopInvariantSpillRegion = [&](BasicBlock *UseBB,
BasicBlock *DefBB) -> const Loop * {
- // If Op is loop-invariant, a call anywhere in the loop body forces a spill,
- // even when a call-free forward path from Root back to OpParent exists on
- // the first iteration. Find the outermost such enclosing loop and reject if
- // its body contains a non-vec call.
+ // If DefBB is outside a loop containing UseBB, the value is live across
+ // iterations of that loop. A non-vectorized call in the loop may therefore
+ // require a spill even when the entry path from DefBB to UseBB is
+ // call-free. Return the outermost such loop containing a relevant call so
+ // its execution scale can be used to cost the spill.
const Loop *L = LI->getLoopFor(UseBB);
const Loop *Outermost = nullptr;
while (L && !L->contains(DefBB)) {
@@ -19067,6 +19068,17 @@ InstructionCost BoUpSLP::getSpillCost() {
UserTE->getOpcode() == Instruction::PHI)
return false;
+ // The spill walk does not descend through gather entries; if any ancestor
+ // of the matching entry's user is a gather, the matching edge is never
+ // charged and de-duplicating would lose the spill cost entirely.
+ for (const TreeEntry *E = UserTE; E != Root;) {
+ if (!E->UserTreeIndex)
+ return false;
+ E = E->UserTreeIndex.UserTE;
+ if (E->isGather())
+ return false;
+ }
+
Instruction *Def = EntriesToLastInstruction.lookup(SameTE);
Instruction *Use = EntriesToLastInstruction.lookup(UserTE);
if (!Def || !Use)
@@ -19084,6 +19096,12 @@ InstructionCost BoUpSLP::getSpillCost() {
}))
return false;
+ // A non-vec call between Def and the end of its block preempts the
+ // loop-invariant charge for the matching entry's edge with a smaller
+ // scale, so de-duplicating would drop the in-loop spill cost.
+ if (!CheckForNonVecCallsInSameBlock(Def, Def->getParent()->getTerminator()))
+ return false;
+
return GetLoopInvariantSpillRegion(Use->getParent(), Def->getParent()) ==
SpillLoop;
};
>From eafa8116eb4d16b1e944090a612350b4bcb34350 Mon Sep 17 00:00:00 2001
From: Hari Limaye <hari.limaye at arm.com>
Date: Thu, 3 Sep 2026 10:46:49 +0000
Subject: [PATCH 4/7] Don't require Gather to have state
---
llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
index 66c6ccaba77f9..6b338d4699b78 100644
--- a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
+++ b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
@@ -19051,12 +19051,10 @@ InstructionCost BoUpSLP::getSpillCost() {
auto IsCoveredByMatchingVectorEntry = [&](const TreeEntry *Gather,
const Loop *SpillLoop) -> bool {
assert(Gather->isGather());
- if (!Gather->hasState())
- return false;
// Find the real vector entry reused by this perfect-diamond gather.
const TreeEntry *SameTE = getSameValuesTreeEntry(
- Gather->getMainOp(), Gather->Scalars, /*SameVF=*/true);
+ Gather->Scalars.front(), Gather->Scalars, /*SameVF=*/true);
if (!SameTE || SameTE == Gather || SameTE->State != TreeEntry::Vectorize ||
ScalarOrPseudoEntries.contains(SameTE) || !SameTE->UserTreeIndex)
return false;
>From 1c234d6dd758f48e6ad3300256b68337fd6caab1 Mon Sep 17 00:00:00 2001
From: Hari Limaye <hari.limaye at arm.com>
Date: Thu, 3 Sep 2026 15:51:43 +0000
Subject: [PATCH 5/7] Use MainOp or first non-poison/non-undef
---
llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp | 16 ++++++++++++++--
1 file changed, 14 insertions(+), 2 deletions(-)
diff --git a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
index 6b338d4699b78..05dd4e2a6c96a 100644
--- a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
+++ b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
@@ -19052,9 +19052,21 @@ InstructionCost BoUpSLP::getSpillCost() {
const Loop *SpillLoop) -> bool {
assert(Gather->isGather());
+ Value *LookupValue = nullptr;
+ if (Gather->hasState()) {
+ LookupValue = Gather->getMainOp();
+ } else {
+ auto *It = find_if(Gather->Scalars, [](Value *V) {
+ return !isa<PoisonValue, UndefValue>(V);
+ });
+ if (It == Gather->Scalars.end())
+ return false;
+ LookupValue = *It;
+ }
+
// Find the real vector entry reused by this perfect-diamond gather.
- const TreeEntry *SameTE = getSameValuesTreeEntry(
- Gather->Scalars.front(), Gather->Scalars, /*SameVF=*/true);
+ const TreeEntry *SameTE =
+ getSameValuesTreeEntry(LookupValue, Gather->Scalars, /*SameVF=*/true);
if (!SameTE || SameTE == Gather || SameTE->State != TreeEntry::Vectorize ||
ScalarOrPseudoEntries.contains(SameTE) || !SameTE->UserTreeIndex)
return false;
>From 6fc9ab4d81332c409910ce2cc6decea997cdb58e Mon Sep 17 00:00:00 2001
From: Hari Limaye <hari.limaye at arm.com>
Date: Fri, 4 Sep 2026 09:35:18 +0000
Subject: [PATCH 6/7] Review updates
---
llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
index 05dd4e2a6c96a..37393f11a6f07 100644
--- a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
+++ b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
@@ -18949,10 +18949,7 @@ InstructionCost BoUpSLP::getSpillCost() {
Outermost = L;
L = L->getParentLoop();
}
- if (Outermost && LoopBodyHasCall(Outermost)) {
- return Outermost;
- }
- return nullptr;
+ return (Outermost && LoopBodyHasCall(Outermost)) ? Outermost : nullptr;
};
auto CheckPredecessors = [&](BasicBlock *Root, BasicBlock *Pred,
BasicBlock *OpParent) {
@@ -19073,11 +19070,16 @@ InstructionCost BoUpSLP::getSpillCost() {
// Only permit an ordinary vectorized user.
const TreeEntry *UserTE = SameTE->UserTreeIndex.UserTE;
- if (!UserTE || UserTE->State != TreeEntry::Vectorize ||
+ assert(UserTE && "Expected a user tree entry.");
+ if (UserTE->State != TreeEntry::Vectorize ||
ScalarOrPseudoEntries.contains(UserTE) ||
UserTE->getOpcode() == Instruction::PHI)
return false;
+ // Different demotion state would make the two edge costs unequal.
+ if (MinBWs.contains(SameTE) != MinBWs.contains(Gather))
+ return false;
+
// The spill walk does not descend through gather entries; if any ancestor
// of the matching entry's user is a gather, the matching edge is never
// charged and de-duplicating would lose the spill cost entirely.
>From 14b5737a8e1a277f8968979b3e3c23aa4caa8eac Mon Sep 17 00:00:00 2001
From: Hari Limaye <hari.limaye at arm.com>
Date: Mon, 7 Sep 2026 08:42:30 +0000
Subject: [PATCH 7/7] Move the budget reset before we do any analysis
---
llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
index 37393f11a6f07..48cd8443fa9c8 100644
--- a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
+++ b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
@@ -19145,6 +19145,10 @@ InstructionCost BoUpSLP::getSpillCost() {
if (Entry->State == TreeEntry::SplitVectorize ||
(Op->isGather() && allConstant(Op->Scalars)))
continue;
+
+ // Reset the scan budget for analsysis of each edge.
+ Budget = 0;
+
// A gather with all loop-invariant lanes is hoisted to the loop
// preheader by optimizeGatherSequence, so its vector value becomes live
// across any non-vectorized call in the loop body. Charge it like any
@@ -19160,7 +19164,6 @@ InstructionCost BoUpSLP::getSpillCost() {
AddCosts(Op, GetSpillScale(Parent));
continue;
}
- Budget = 0;
BasicBlock *Pred = nullptr;
if (auto *Phi = dyn_cast<PHINode>(Entry->getMainOp()))
Pred = Phi->getIncomingBlock(Op->UserTreeIndex.EdgeIdx);
More information about the llvm-commits
mailing list