Skip to content

[SLP] Fix extract-cost scale using NCD of all external-user sites - #199962

Merged
alexey-bataev merged 3 commits into
mainfrom
users/alexey-bataev/spr/slp-fix-extract-cost-scale-using-ncd-of-all-external-user-sites
May 30, 2026
Merged

alexey-bataev merged 3 commits into
mainfrom
users/alexey-bataev/spr/slp-fix-extract-cost-scale-using-ncd-of-all-external-user-sites

Conversation

@alexey-bataev

Copy link
Copy Markdown
Member

ExtractCostCalculated deduplicates by scalar so only the first
ExternalUser determines the scale, making the cost depend on IR block
ordering via LLVM's reverse-insertion use-list order.
Add a pre-pass computing ScalarToExtractBlock - the nearest common
dominator of all effective extract sites per scalar. For PHI users inside
a loop the effective site is the incoming block; for PHI users outside
all loops it is the PHI's own block (scale = 1). The extract cost is
then scaled by getLoopNestScale of the NCD block, which is fully
order-independent.

Fixes #199548

Created using spr 1.3.7
Created using spr 1.3.7
@llvmorg-github-actions

Copy link
Copy Markdown

@llvm/pr-subscribers-vectorizers

Author: Alexey Bataev (alexey-bataev)

Changes

ExtractCostCalculated deduplicates by scalar so only the first
ExternalUser determines the scale, making the cost depend on IR block
ordering via LLVM's reverse-insertion use-list order.
Add a pre-pass computing ScalarToExtractBlock - the nearest common
dominator of all effective extract sites per scalar. For PHI users inside
a loop the effective site is the incoming block; for PHI users outside
all loops it is the PHI's own block (scale = 1). The extract cost is
then scaled by getLoopNestScale of the NCD block, which is fully
order-independent.

Fixes #199548


Patch is 26.44 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/199962.diff

3 Files Affected:

  • (modified) llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp (+71-2)
  • (modified) llvm/test/Transforms/SLPVectorizer/AArch64/lcssa-phi-extract-scale.ll (+66-44)
  • (modified) llvm/test/Transforms/SLPVectorizer/AArch64/lcssa-phi-inner-loop-scale.ll (+22-16)
diff --git a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
index 9d7821d2760f2..f72207fa148ae 100644
--- a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
+++ b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
@@ -19551,6 +19551,55 @@ InstructionCost BoUpSLP::getTreeCost(InstructionCost TreeCost,
     }
   }
   AllUsersGEPSWithStoresLoads &= UsedLanes.all();
+
+  // Pre-pass: for each externally-used scalar, find the basic block at which
+  // the extractelement will be placed by codegen. This mirrors what
+  // vectorizeTree does: the extract is placed at the nearest common dominator
+  // of all effective use sites. For a non-PHI user the effective site is the
+  // user's own block; for a PHI user it is the incoming block for the scalar
+  // operand (the predecessor of the PHI on the edge that carries the scalar).
+  // Using the NCD of all effective sites rather than the first-encountered
+  // user's block makes the extract-cost scale order-independent and correct
+  // even when users live in different loop nests.
+  SmallDenseMap<Value *, BasicBlock *> ScalarToExtractBlock;
+  for (const ExternalUser &EU : ExternalUses) {
+    if (!EU.User || isa<InsertElementInst>(EU.User))
+      continue;
+    if (EphValues.count(EU.User))
+      continue;
+    BasicBlock *UserParent = cast<Instruction>(EU.User)->getParent();
+    if (!DT->isReachableFromEntry(UserParent) || UserParent->isEHPad() ||
+        isa_and_present<UnreachableInst>(UserParent->getTerminator()))
+      continue;
+    BasicBlock *UseBlock = nullptr;
+    if (auto *PHI = dyn_cast<PHINode>(EU.User)) {
+      // When the PHI itself is inside a loop, the extractelement is placed
+      // in the incoming block for the scalar operand (the predecessor edge),
+      // not in the PHI's own block.  This applies to LCSSA phis at an inner-
+      // loop exit that are still inside an outer loop: the incoming block is
+      // in the inner loop while the PHI block is in the outer loop.
+      // When the PHI is outside all loops (a true loop-exit phi), codegen
+      // uses a vector phi at the exit block and the extract stays there
+      // (scale = 1), so we keep the PHI's own block as the effective site.
+      if (LI->getLoopFor(PHI->getParent())) {
+        for (unsigned Idx : seq<unsigned>(PHI->getNumIncomingValues())) {
+          if (PHI->getIncomingValue(Idx) != EU.Scalar)
+            continue;
+          BasicBlock *InBB = PHI->getIncomingBlock(Idx);
+          UseBlock =
+              UseBlock ? DT->findNearestCommonDominator(UseBlock, InBB) : InBB;
+        }
+      }
+      if (!UseBlock)
+        UseBlock = cast<Instruction>(EU.User)->getParent();
+    } else {
+      UseBlock = cast<Instruction>(EU.User)->getParent();
+    }
+    auto [It, Inserted] = ScalarToExtractBlock.try_emplace(EU.Scalar, UseBlock);
+    if (!Inserted && It->second && UseBlock)
+      It->second = DT->findNearestCommonDominator(It->second, UseBlock);
+  }
+
   SmallDenseSet<std::pair<Value *, Value *>, 8> CheckedScalarUser;
   for (ExternalUser &EU : ExternalUses) {
     LLVM_DEBUG(dbgs() << "SLP: Computing cost for external use of TreeEntry "
@@ -19836,8 +19885,28 @@ InstructionCost BoUpSLP::getTreeCost(InstructionCost TreeCost,
       }
     }
 
-    ExtraCost = ScaleCost(ExtraCost, *Entry, EU.Scalar,
-                          cast_or_null<Instruction>(EU.User));
+    // Scale the extract cost by the execution frequency of the block where
+    // codegen will place the extractelement. That block is the nearest common
+    // dominator of all effective use sites (precomputed in ScalarToExtractBlock
+    // above), which is order-independent. For scalars kept as originals the
+    // existing ScaleCost path (user-block based) remains correct, since the
+    // scalar instruction executes at its definition site's frequency.
+    if (!ExternalUsesAsOriginalScalar.contains(EU.Scalar)) {
+      if (ExtraCost.isValid() && ExtraCost != 0) {
+        BasicBlock *ExtractBB = ScalarToExtractBlock.lookup(EU.Scalar);
+        if (const Loop *L = ExtractBB ? LI->getLoopFor(ExtractBB) : nullptr) {
+          uint64_t Scale = getLoopNestScale(
+              findInnermostNonInvariantLoop(L, ArrayRef<Value *>(EU.Scalar)));
+          LLVM_DEBUG(dbgs()
+                     << "SLP: Extract scale " << Scale << " (NCD block) for "
+                     << EU.Scalar->getNameOrAsOperand() << "\n");
+          ExtraCost *= Scale;
+        }
+      }
+    } else {
+      ExtraCost = ScaleCost(ExtraCost, *Entry, EU.Scalar,
+                            cast_or_null<Instruction>(EU.User));
+    }
 
     ExtractCost += ExtraCost;
   }
diff --git a/llvm/test/Transforms/SLPVectorizer/AArch64/lcssa-phi-extract-scale.ll b/llvm/test/Transforms/SLPVectorizer/AArch64/lcssa-phi-extract-scale.ll
index 4ad0e6934d7a5..9b8520c122066 100644
--- a/llvm/test/Transforms/SLPVectorizer/AArch64/lcssa-phi-extract-scale.ll
+++ b/llvm/test/Transforms/SLPVectorizer/AArch64/lcssa-phi-extract-scale.ll
@@ -9,35 +9,43 @@ define double @test(ptr %obj, ptr %arr, i32 %n) {
 ; CHECK-SAME: ptr [[OBJ:%.*]], ptr [[ARR:%.*]], i32 [[N:%.*]]) #[[ATTR0:[0-9]+]] {
 ; CHECK-NEXT:  [[ENTRY:.*]]:
 ; CHECK-NEXT:    [[POS1:%.*]] = getelementptr inbounds i8, ptr [[OBJ]], i64 44
+; CHECK-NEXT:    [[POS2:%.*]] = getelementptr inbounds i8, ptr [[OBJ]], i64 48
 ; CHECK-NEXT:    [[COND_ENTER:%.*]] = icmp sgt i32 [[N]], 0
 ; CHECK-NEXT:    br i1 [[COND_ENTER]], label %[[OUTER_PREHEADER:.*]], label %[[EXIT:.*]]
 ; CHECK:       [[OUTER_PREHEADER]]:
 ; CHECK-NEXT:    br label %[[OUTER_HEADER:.*]]
 ; CHECK:       [[OUTER_HEADER]]:
-; CHECK-NEXT:    [[LOCAL_4_128:%.*]] = phi i32 [ 0, %[[OUTER_PREHEADER]] ], [ [[LCSSA_IV:%.*]], %[[OUTER_LATCH:.*]] ]
+; CHECK-NEXT:    %"$vphi.1" = phi i32 [ 16, %[[OUTER_PREHEADER]] ], [ [[DOTSINK6_I_LCSSA:%.*]], %[[OUTER_LATCH:.*]] ]
+; CHECK-NEXT:    %"$vphi.0" = phi i32 [ 4, %[[OUTER_PREHEADER]] ], [ [[DOTSINK_I_LCSSA:%.*]], %[[OUTER_LATCH]] ]
+; CHECK-NEXT:    [[LOCAL_4_128:%.*]] = phi i32 [ 0, %[[OUTER_PREHEADER]] ], [ [[LCSSA_IV:%.*]], %[[OUTER_LATCH]] ]
 ; CHECK-NEXT:    [[LOCAL_3_127:%.*]] = phi i32 [ 0, %[[OUTER_PREHEADER]] ], [ [[LOCAL_3_16_LCSSA:%.*]], %[[OUTER_LATCH]] ]
-; CHECK-NEXT:    [[TMP0:%.*]] = phi <2 x i32> [ <i32 4, i32 16>, %[[OUTER_PREHEADER]] ], [ [[TMP15:%.*]], %[[OUTER_LATCH]] ]
 ; CHECK-NEXT:    br label %[[INNER_HEADER:.*]]
 ; CHECK:       [[INNER_HEADER]]:
-; CHECK-NEXT:    [[LOCAL_4_128420:%.*]] = phi i32 [ [[LOCAL_4_128]], %[[OUTER_HEADER]] ], [ [[INC:%.*]], %[[INNER_BODY:.*]] ]
+; CHECK-NEXT:    %"$vphi.1418" = phi i32 [ %"$vphi.1", %[[OUTER_HEADER]] ], [ [[DOTSINK6_I:%.*]], %[[INNER_BODY:.*]] ]
+; CHECK-NEXT:    %"$vphi.0419" = phi i32 [ %"$vphi.0", %[[OUTER_HEADER]] ], [ [[DOTSINK_I:%.*]], %[[INNER_BODY]] ]
+; CHECK-NEXT:    [[LOCAL_4_128420:%.*]] = phi i32 [ [[LOCAL_4_128]], %[[OUTER_HEADER]] ], [ [[INC:%.*]], %[[INNER_BODY]] ]
 ; CHECK-NEXT:    [[LOCAL_3_127421:%.*]] = phi i32 [ [[LOCAL_3_127]], %[[OUTER_HEADER]] ], [ [[LOCAL_3_16:%.*]], %[[INNER_BODY]] ]
 ; CHECK-NEXT:    [[CHUNK_IV:%.*]] = phi i64 [ 0, %[[OUTER_HEADER]] ], [ [[CHUNK_IV_NEXT:%.*]], %[[INNER_BODY]] ]
-; CHECK-NEXT:    [[TMP1:%.*]] = phi <2 x i32> [ [[TMP0]], %[[OUTER_HEADER]] ], [ [[TMP14:%.*]], %[[INNER_BODY]] ]
-; CHECK-NEXT:    [[TMP2:%.*]] = extractelement <2 x i32> [[TMP1]], i32 0
-; CHECK-NEXT:    [[TMP3:%.*]] = icmp eq <2 x i32> [[TMP1]], zeroinitializer
-; CHECK-NEXT:    [[TMP4:%.*]] = add <2 x i32> [[TMP1]], splat (i32 -1)
-; CHECK-NEXT:    [[TMP5:%.*]] = select <2 x i1> [[TMP3]], <2 x i32> splat (i32 16), <2 x i32> [[TMP4]]
-; CHECK-NEXT:    [[TMP6:%.*]] = shufflevector <2 x i32> [[TMP1]], <2 x i32> [[TMP5]], <4 x i32> <i32 1, i32 0, i32 2, i32 3>
-; CHECK-NEXT:    [[TMP7:%.*]] = icmp ult <4 x i32> [[TMP6]], splat (i32 17)
-; CHECK-NEXT:    [[TMP8:%.*]] = call i1 @llvm.vector.reduce.and.v4i1(<4 x i1> [[TMP7]])
+; CHECK-NEXT:    [[C27:%.*]] = icmp ult i32 %"$vphi.0419", 17
+; CHECK-NEXT:    [[C28:%.*]] = icmp ult i32 %"$vphi.1418", 17
+; CHECK-NEXT:    [[WIDE_CHK:%.*]] = and i1 [[C28]], [[C27]]
+; CHECK-NEXT:    [[DOTNOT3_I17:%.*]] = icmp eq i32 %"$vphi.0419", 0
+; CHECK-NEXT:    [[V29:%.*]] = add i32 %"$vphi.0419", -1
+; CHECK-NEXT:    [[TMP10:%.*]] = select i1 [[DOTNOT3_I17]], i32 16, i32 [[V29]]
+; CHECK-NEXT:    [[C30:%.*]] = icmp ult i32 [[TMP10]], 17
+; CHECK-NEXT:    [[WIDE_CHK354:%.*]] = and i1 [[WIDE_CHK]], [[C30]]
+; CHECK-NEXT:    [[DOTNOT4_I19:%.*]] = icmp eq i32 %"$vphi.1418", 0
+; CHECK-NEXT:    [[V31:%.*]] = add i32 %"$vphi.1418", -1
+; CHECK-NEXT:    [[TMP11:%.*]] = select i1 [[DOTNOT4_I19]], i32 16, i32 [[V31]]
+; CHECK-NEXT:    [[C32:%.*]] = icmp ult i32 [[TMP11]], 17
+; CHECK-NEXT:    [[TMP8:%.*]] = and i1 [[C32]], [[WIDE_CHK354]]
 ; CHECK-NEXT:    br i1 [[TMP8]], label %[[INNER_BODY]], label %[[DEOPT:.*]]
 ; CHECK:       [[INNER_BODY]]:
 ; CHECK-NEXT:    [[CHUNK_IV_NEXT]] = add nuw nsw i64 [[CHUNK_IV]], 1
-; CHECK-NEXT:    [[V34:%.*]] = zext nneg i32 [[TMP2]] to i64
+; CHECK-NEXT:    [[V34:%.*]] = zext nneg i32 %"$vphi.0419" to i64
 ; CHECK-NEXT:    [[V35:%.*]] = getelementptr inbounds nuw [4 x i8], ptr [[ARR]], i64 [[V34]]
 ; CHECK-NEXT:    [[V36:%.*]] = load i32, ptr [[V35]], align 4
-; CHECK-NEXT:    [[TMP9:%.*]] = extractelement <2 x i32> [[TMP1]], i32 1
-; CHECK-NEXT:    [[V37:%.*]] = zext nneg i32 [[TMP9]] to i64
+; CHECK-NEXT:    [[V37:%.*]] = zext nneg i32 %"$vphi.1418" to i64
 ; CHECK-NEXT:    [[V38:%.*]] = getelementptr inbounds nuw [4 x i8], ptr [[ARR]], i64 [[V37]]
 ; CHECK-NEXT:    [[V39:%.*]] = load i32, ptr [[V38]], align 4
 ; CHECK-NEXT:    [[V40:%.*]] = sub i32 [[V36]], [[V39]]
@@ -47,11 +55,9 @@ define double @test(ptr %obj, ptr %arr, i32 %n) {
 ; CHECK-NEXT:    store i32 [[SPEC_SELECT_I13]], ptr [[V38]], align 4
 ; CHECK-NEXT:    [[V43:%.*]] = sitofp i32 [[SPEC_SELECT_I13]] to double
 ; CHECK-NEXT:    [[V44:%.*]] = fmul nnan double [[V43]], f0x3E00000000200000
-; CHECK-NEXT:    [[TMP10:%.*]] = extractelement <2 x i32> [[TMP5]], i32 0
 ; CHECK-NEXT:    [[V45:%.*]] = zext nneg i32 [[TMP10]] to i64
 ; CHECK-NEXT:    [[V46:%.*]] = getelementptr inbounds nuw [4 x i8], ptr [[ARR]], i64 [[V45]]
 ; CHECK-NEXT:    [[V47:%.*]] = load i32, ptr [[V46]], align 4
-; CHECK-NEXT:    [[TMP11:%.*]] = extractelement <2 x i32> [[TMP5]], i32 1
 ; CHECK-NEXT:    [[V48:%.*]] = zext nneg i32 [[TMP11]] to i64
 ; CHECK-NEXT:    [[V49:%.*]] = getelementptr inbounds nuw [4 x i8], ptr [[ARR]], i64 [[V48]]
 ; CHECK-NEXT:    [[V50:%.*]] = load i32, ptr [[V49]], align 4
@@ -60,10 +66,14 @@ define double @test(ptr %obj, ptr %arr, i32 %n) {
 ; CHECK-NEXT:    [[V53:%.*]] = icmp slt i32 [[V51]], 0
 ; CHECK-NEXT:    [[SPEC_SELECT_I:%.*]] = select i1 [[V53]], i32 [[V52]], i32 [[V51]]
 ; CHECK-NEXT:    store i32 [[SPEC_SELECT_I]], ptr [[V49]], align 4
-; CHECK-NEXT:    [[TMP12:%.*]] = icmp eq <2 x i32> [[TMP5]], zeroinitializer
-; CHECK-NEXT:    [[TMP13:%.*]] = add nsw <2 x i32> [[TMP5]], splat (i32 -1)
-; CHECK-NEXT:    [[TMP14]] = select <2 x i1> [[TMP12]], <2 x i32> splat (i32 16), <2 x i32> [[TMP13]]
-; CHECK-NEXT:    store <2 x i32> [[TMP14]], ptr [[POS1]], align 4
+; CHECK-NEXT:    [[DOTNOT3_I:%.*]] = icmp eq i32 [[TMP10]], 0
+; CHECK-NEXT:    [[V54:%.*]] = add nsw i32 [[TMP10]], -1
+; CHECK-NEXT:    [[DOTSINK_I]] = select i1 [[DOTNOT3_I]], i32 16, i32 [[V54]]
+; CHECK-NEXT:    store i32 [[DOTSINK_I]], ptr [[POS1]], align 4
+; CHECK-NEXT:    [[DOTNOT4_I:%.*]] = icmp eq i32 [[TMP11]], 0
+; CHECK-NEXT:    [[V55:%.*]] = add nsw i32 [[TMP11]], -1
+; CHECK-NEXT:    [[DOTSINK6_I]] = select i1 [[DOTNOT4_I]], i32 16, i32 [[V55]]
+; CHECK-NEXT:    store i32 [[DOTSINK6_I]], ptr [[POS2]], align 8
 ; CHECK-NEXT:    [[V56:%.*]] = sitofp i32 [[SPEC_SELECT_I]] to double
 ; CHECK-NEXT:    [[V57:%.*]] = fmul nnan double [[V56]], f0x3E00000000200000
 ; CHECK-NEXT:    [[V58:%.*]] = fmul nnan double [[V44]], [[V44]]
@@ -78,16 +88,17 @@ define double @test(ptr %obj, ptr %arr, i32 %n) {
 ; CHECK-NEXT:    [[V64:%.*]] = and i1 [[DOTNOT]], [[CHUNK_COND]]
 ; CHECK-NEXT:    br i1 [[V64]], label %[[INNER_HEADER]], label %[[OUTER_LATCH]]
 ; CHECK:       [[DEOPT]]:
-; CHECK-NEXT:    %"$vphi.0419.lcssa" = phi i32 [ [[TMP2]], %[[INNER_HEADER]] ]
+; CHECK-NEXT:    %"$vphi.0419.lcssa" = phi i32 [ %"$vphi.0419", %[[INNER_HEADER]] ]
 ; CHECK-NEXT:    [[LOCAL_4_128420_LCSSA:%.*]] = phi i32 [ [[LOCAL_4_128420]], %[[INNER_HEADER]] ]
 ; CHECK-NEXT:    [[LOCAL_3_127421_LCSSA:%.*]] = phi i32 [ [[LOCAL_3_127421]], %[[INNER_HEADER]] ]
 ; CHECK-NEXT:    call void @sink_use(i32 %"$vphi.0419.lcssa", i32 [[LOCAL_4_128420_LCSSA]], i32 [[LOCAL_3_127421_LCSSA]])
 ; CHECK-NEXT:    ret double 0.000000e+00
 ; CHECK:       [[OUTER_LATCH]]:
+; CHECK-NEXT:    [[DOTSINK_I_LCSSA]] = phi i32 [ [[DOTSINK_I]], %[[INNER_BODY]] ]
+; CHECK-NEXT:    [[DOTSINK6_I_LCSSA]] = phi i32 [ [[DOTSINK6_I]], %[[INNER_BODY]] ]
 ; CHECK-NEXT:    [[LOCAL_3_16_LCSSA]] = phi i32 [ [[LOCAL_3_16]], %[[INNER_BODY]] ]
 ; CHECK-NEXT:    [[LCSSA_IV]] = phi i32 [ [[INC]], %[[INNER_BODY]] ]
 ; CHECK-NEXT:    [[DOTNOT_LCSSA:%.*]] = phi i1 [ [[DOTNOT]], %[[INNER_BODY]] ]
-; CHECK-NEXT:    [[TMP15]] = phi <2 x i32> [ [[TMP14]], %[[INNER_BODY]] ]
 ; CHECK-NEXT:    br i1 [[DOTNOT_LCSSA]], label %[[OUTER_HEADER]], label %[[EXIT_LOOP:.*]]
 ; CHECK:       [[EXIT_LOOP]]:
 ; CHECK-NEXT:    [[FINAL:%.*]] = phi i32 [ [[LOCAL_3_16_LCSSA]], %[[OUTER_LATCH]] ]
@@ -218,33 +229,43 @@ define double @test_inloop_first(ptr %obj, ptr %arr, i32 %n) {
 ; CHECK-SAME: ptr [[OBJ:%.*]], ptr [[ARR:%.*]], i32 [[N:%.*]]) #[[ATTR0]] {
 ; CHECK-NEXT:  [[ENTRY:.*]]:
 ; CHECK-NEXT:    [[POS1:%.*]] = getelementptr inbounds i8, ptr [[OBJ]], i64 44
+; CHECK-NEXT:    [[POS2:%.*]] = getelementptr inbounds i8, ptr [[OBJ]], i64 48
 ; CHECK-NEXT:    [[COND_ENTER:%.*]] = icmp sgt i32 [[N]], 0
 ; CHECK-NEXT:    br i1 [[COND_ENTER]], label %[[OUTER_PREHEADER:.*]], label %[[EXIT:.*]]
 ; CHECK:       [[OUTER_PREHEADER]]:
 ; CHECK-NEXT:    br label %[[OUTER_HEADER:.*]]
 ; CHECK:       [[OUTER_HEADER]]:
-; CHECK-NEXT:    [[LOCAL_4_128:%.*]] = phi i32 [ 0, %[[OUTER_PREHEADER]] ], [ [[LCSSA_IV:%.*]], %[[OUTER_LATCH:.*]] ]
+; CHECK-NEXT:    %"$vphi.1" = phi i32 [ 16, %[[OUTER_PREHEADER]] ], [ [[DOTSINK6_I_LCSSA:%.*]], %[[OUTER_LATCH:.*]] ]
+; CHECK-NEXT:    %"$vphi.0" = phi i32 [ 4, %[[OUTER_PREHEADER]] ], [ [[DOTSINK_I_LCSSA:%.*]], %[[OUTER_LATCH]] ]
+; CHECK-NEXT:    [[LOCAL_4_128:%.*]] = phi i32 [ 0, %[[OUTER_PREHEADER]] ], [ [[LCSSA_IV:%.*]], %[[OUTER_LATCH]] ]
 ; CHECK-NEXT:    [[LOCAL_3_127:%.*]] = phi i32 [ 0, %[[OUTER_PREHEADER]] ], [ [[LOCAL_3_16_LCSSA:%.*]], %[[OUTER_LATCH]] ]
-; CHECK-NEXT:    [[TMP0:%.*]] = phi <2 x i32> [ <i32 4, i32 16>, %[[OUTER_PREHEADER]] ], [ [[TMP9:%.*]], %[[OUTER_LATCH]] ]
 ; CHECK-NEXT:    br label %[[INNER_HEADER:.*]]
 ; CHECK:       [[INNER_HEADER]]:
-; CHECK-NEXT:    [[LOCAL_4_128420:%.*]] = phi i32 [ [[LOCAL_4_128]], %[[OUTER_HEADER]] ], [ [[INC:%.*]], %[[INNER_BODY:.*]] ]
+; CHECK-NEXT:    %"$vphi.1418" = phi i32 [ %"$vphi.1", %[[OUTER_HEADER]] ], [ [[DOTSINK6_I:%.*]], %[[INNER_BODY:.*]] ]
+; CHECK-NEXT:    %"$vphi.0419" = phi i32 [ %"$vphi.0", %[[OUTER_HEADER]] ], [ [[DOTSINK_I:%.*]], %[[INNER_BODY]] ]
+; CHECK-NEXT:    [[LOCAL_4_128420:%.*]] = phi i32 [ [[LOCAL_4_128]], %[[OUTER_HEADER]] ], [ [[INC:%.*]], %[[INNER_BODY]] ]
 ; CHECK-NEXT:    [[LOCAL_3_127421:%.*]] = phi i32 [ [[LOCAL_3_127]], %[[OUTER_HEADER]] ], [ [[LOCAL_3_16:%.*]], %[[INNER_BODY]] ]
 ; CHECK-NEXT:    [[CHUNK_IV:%.*]] = phi i64 [ 0, %[[OUTER_HEADER]] ], [ [[CHUNK_IV_NEXT:%.*]], %[[INNER_BODY]] ]
-; CHECK-NEXT:    [[TMP1:%.*]] = phi <2 x i32> [ [[TMP0]], %[[OUTER_HEADER]] ], [ [[TMP15:%.*]], %[[INNER_BODY]] ]
-; CHECK-NEXT:    [[TMP2:%.*]] = extractelement <2 x i32> [[TMP1]], i32 0
-; CHECK-NEXT:    [[TMP3:%.*]] = icmp eq <2 x i32> [[TMP1]], zeroinitializer
-; CHECK-NEXT:    [[TMP4:%.*]] = add <2 x i32> [[TMP1]], splat (i32 -1)
-; CHECK-NEXT:    [[TMP5:%.*]] = select <2 x i1> [[TMP3]], <2 x i32> splat (i32 16), <2 x i32> [[TMP4]]
-; CHECK-NEXT:    [[TMP6:%.*]] = shufflevector <2 x i32> [[TMP1]], <2 x i32> [[TMP5]], <4 x i32> <i32 1, i32 0, i32 2, i32 3>
-; CHECK-NEXT:    [[TMP7:%.*]] = icmp ult <4 x i32> [[TMP6]], splat (i32 17)
-; CHECK-NEXT:    [[TMP8:%.*]] = call i1 @llvm.vector.reduce.and.v4i1(<4 x i1> [[TMP7]])
+; CHECK-NEXT:    [[C27:%.*]] = icmp ult i32 %"$vphi.0419", 17
+; CHECK-NEXT:    [[C28:%.*]] = icmp ult i32 %"$vphi.1418", 17
+; CHECK-NEXT:    [[WIDE_CHK:%.*]] = and i1 [[C28]], [[C27]]
+; CHECK-NEXT:    [[DOTNOT3_I17:%.*]] = icmp eq i32 %"$vphi.0419", 0
+; CHECK-NEXT:    [[V29:%.*]] = add i32 %"$vphi.0419", -1
+; CHECK-NEXT:    [[TMP11:%.*]] = select i1 [[DOTNOT3_I17]], i32 16, i32 [[V29]]
+; CHECK-NEXT:    [[C30:%.*]] = icmp ult i32 [[TMP11]], 17
+; CHECK-NEXT:    [[WIDE_CHK354:%.*]] = and i1 [[WIDE_CHK]], [[C30]]
+; CHECK-NEXT:    [[DOTNOT4_I19:%.*]] = icmp eq i32 %"$vphi.1418", 0
+; CHECK-NEXT:    [[V31:%.*]] = add i32 %"$vphi.1418", -1
+; CHECK-NEXT:    [[TMP12:%.*]] = select i1 [[DOTNOT4_I19]], i32 16, i32 [[V31]]
+; CHECK-NEXT:    [[C32:%.*]] = icmp ult i32 [[TMP12]], 17
+; CHECK-NEXT:    [[TMP8:%.*]] = and i1 [[C32]], [[WIDE_CHK354]]
 ; CHECK-NEXT:    br i1 [[TMP8]], label %[[INNER_BODY]], label %[[DEOPT:.*]]
 ; CHECK:       [[OUTER_LATCH]]:
+; CHECK-NEXT:    [[DOTSINK_I_LCSSA]] = phi i32 [ [[DOTSINK_I]], %[[INNER_BODY]] ]
+; CHECK-NEXT:    [[DOTSINK6_I_LCSSA]] = phi i32 [ [[DOTSINK6_I]], %[[INNER_BODY]] ]
 ; CHECK-NEXT:    [[LOCAL_3_16_LCSSA]] = phi i32 [ [[LOCAL_3_16]], %[[INNER_BODY]] ]
 ; CHECK-NEXT:    [[LCSSA_IV]] = phi i32 [ [[INC]], %[[INNER_BODY]] ]
 ; CHECK-NEXT:    [[DOTNOT_LCSSA:%.*]] = phi i1 [ [[DOTNOT:%.*]], %[[INNER_BODY]] ]
-; CHECK-NEXT:    [[TMP9]] = phi <2 x i32> [ [[TMP15]], %[[INNER_BODY]] ]
 ; CHECK-NEXT:    br i1 [[DOTNOT_LCSSA]], label %[[OUTER_HEADER]], label %[[EXIT_LOOP:.*]]
 ; CHECK:       [[EXIT_LOOP]]:
 ; CHECK-NEXT:    [[FINAL:%.*]] = phi i32 [ [[LOCAL_3_16_LCSSA]], %[[OUTER_LATCH]] ]
@@ -252,11 +273,10 @@ define double @test_inloop_first(ptr %obj, ptr %arr, i32 %n) {
 ; CHECK-NEXT:    br label %[[EXIT]]
 ; CHECK:       [[INNER_BODY]]:
 ; CHECK-NEXT:    [[CHUNK_IV_NEXT]] = add nuw nsw i64 [[CHUNK_IV]], 1
-; CHECK-NEXT:    [[V34:%.*]] = zext nneg i32 [[TMP2]] to i64
+; CHECK-NEXT:    [[V34:%.*]] = zext nneg i32 %"$vphi.0419" to i64
 ; CHECK-NEXT:    [[V35:%.*]] = getelementptr inbounds nuw [4 x i8], ptr [[ARR]], i64 [[V34]]
 ; CHECK-NEXT:    [[V36:%.*]] = load i32, ptr [[V35]], align 4
-; CHECK-NEXT:    [[TMP10:%.*]] = extractelement <2 x i32> [[TMP1]], i32 1
-; CHECK-NEXT:    [[V37:%.*]] = zext nneg i32 [[TMP10]] to i64
+; CHECK-NEXT:    [[V37:%.*]] = zext nneg i32 %"$vphi.1418" to i64
 ; CHECK-NEXT:    [[V38:%.*]] = getelementptr inbounds nuw [4 x i8], ptr [[ARR]], i64 [[V37]]
 ; CHECK-NEXT:    [[V39:%.*]] = load i32, ptr [[V38]], align 4
 ; CHECK-NEXT:    [[V40:%.*]] = sub i32 [[V36]], [[V39]]
@@ -266,11 +286,9 @@ define double @test_inloop_first(ptr %obj, ptr %arr, i32 %n) {
 ; CHECK-NEXT:    store i32 [[SPEC_SELECT_I13]], ptr [[V38]], align 4
 ; CHECK-NEXT:    [[V43:%.*]] = sitofp i32 [[SPEC_SELECT_I13]] to double
 ; CHECK-NEXT:    [[V44:%.*]] = fmul nnan double [[V43]], f0x3E00000000200000
-; CHECK-NEXT:    [[TMP11:%.*]] = extractelement <2 x i32> [[TMP5]], i32 0
 ; CHECK-NEXT:    [[V45:%.*]] = zext nneg i32 [[TMP11]] to i64
 ; CHECK-NEXT:    [[V46:%.*]] = getelementptr inbounds nuw [4 x i8], ptr [[ARR]], i64 [[V45]]
 ; CHECK-NEXT:    [[V47:%.*]] = load i32, ptr [[V46]], align 4
-; CHECK-NEXT:    [[TMP12:%.*]] = extractelement <2 x i32> [[TMP5]], i32 1
 ; CHECK-NEXT:    [[V48:%.*]] = zext nneg i32 [[TMP12]] to i64
 ; CHECK-NEXT:    [[V49:%.*]] = getelementptr inbounds nuw [4 x i8], ptr [[ARR]], i64 [[V48]]
 ; CHECK-NEXT:    [[V50:%.*]] = load i32, ptr [[V49]], align 4
@@ -279,10 +297,14 @@ define double @test_inloop_first(ptr %obj, ptr %arr, i32 %n) {
 ; CHECK-NEXT:    [[V53:%.*]] = icmp slt i32 [[V51]], 0
 ; CHECK-NEXT:    [[SPEC_SELECT_I:%.*]] = select i1 [[V53]], i32 [[V52]], i32 [[V51]]
 ; CHECK-NEXT:    store i32 [[SPEC_SELECT_I]], ptr [[V49]], align 4
-; CHECK-NEXT:    [[TMP13:%.*]] = icmp eq <2 x i32> [[TMP5]], zeroinitializer
-; CHECK-NEXT:    [[TMP14:%.*]] = add nsw <2 x i32> [[TMP5]], splat (i32 -1)
-; CHECK-NEXT:    [[TMP15]] = select <2 x i1> [[TMP13]], <2 x i32> splat (i32 16), <2 x i32> [[TMP14]]
-; CHECK-NEXT:    store <2 x i32> [[TMP15]], ptr [[POS1]], align 4
+; CHECK-NEXT:    [[DOTNOT3_I:%.*]] = icmp eq i32 [[TMP11]], 0
+; CHECK-NEXT:    [[V54:%.*]] = add nsw i32 [[TMP1...
[truncated]

@llvmorg-github-actions

Copy link
Copy Markdown

@llvm/pr-subscribers-llvm-transforms

Author: Alexey Bataev (alexey-bataev)

Changes

ExtractCostCalculated deduplicates by scalar so only the first
ExternalUser determines the scale, making the cost depend on IR block
ordering via LLVM's reverse-insertion use-list order.
Add a pre-pass computing ScalarToExtractBlock - the nearest common
dominator of all effective extract sites per scalar. For PHI users inside
a loop the effective site is the incoming block; for PHI users outside
all loops it is the PHI's own block (scale = 1). The extract cost is
then scaled by getLoopNestScale of the NCD block, which is fully
order-independent.

Fixes #199548


Patch is 26.44 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/199962.diff

3 Files Affected:

  • (modified) llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp (+71-2)
  • (modified) llvm/test/Transforms/SLPVectorizer/AArch64/lcssa-phi-extract-scale.ll (+66-44)
  • (modified) llvm/test/Transforms/SLPVectorizer/AArch64/lcssa-phi-inner-loop-scale.ll (+22-16)
diff --git a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
index 9d7821d2760f2..f72207fa148ae 100644
--- a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
+++ b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
@@ -19551,6 +19551,55 @@ InstructionCost BoUpSLP::getTreeCost(InstructionCost TreeCost,
     }
   }
   AllUsersGEPSWithStoresLoads &= UsedLanes.all();
+
+  // Pre-pass: for each externally-used scalar, find the basic block at which
+  // the extractelement will be placed by codegen. This mirrors what
+  // vectorizeTree does: the extract is placed at the nearest common dominator
+  // of all effective use sites. For a non-PHI user the effective site is the
+  // user's own block; for a PHI user it is the incoming block for the scalar
+  // operand (the predecessor of the PHI on the edge that carries the scalar).
+  // Using the NCD of all effective sites rather than the first-encountered
+  // user's block makes the extract-cost scale order-independent and correct
+  // even when users live in different loop nests.
+  SmallDenseMap<Value *, BasicBlock *> ScalarToExtractBlock;
+  for (const ExternalUser &EU : ExternalUses) {
+    if (!EU.User || isa<InsertElementInst>(EU.User))
+      continue;
+    if (EphValues.count(EU.User))
+      continue;
+    BasicBlock *UserParent = cast<Instruction>(EU.User)->getParent();
+    if (!DT->isReachableFromEntry(UserParent) || UserParent->isEHPad() ||
+        isa_and_present<UnreachableInst>(UserParent->getTerminator()))
+      continue;
+    BasicBlock *UseBlock = nullptr;
+    if (auto *PHI = dyn_cast<PHINode>(EU.User)) {
+      // When the PHI itself is inside a loop, the extractelement is placed
+      // in the incoming block for the scalar operand (the predecessor edge),
+      // not in the PHI's own block.  This applies to LCSSA phis at an inner-
+      // loop exit that are still inside an outer loop: the incoming block is
+      // in the inner loop while the PHI block is in the outer loop.
+      // When the PHI is outside all loops (a true loop-exit phi), codegen
+      // uses a vector phi at the exit block and the extract stays there
+      // (scale = 1), so we keep the PHI's own block as the effective site.
+      if (LI->getLoopFor(PHI->getParent())) {
+        for (unsigned Idx : seq<unsigned>(PHI->getNumIncomingValues())) {
+          if (PHI->getIncomingValue(Idx) != EU.Scalar)
+            continue;
+          BasicBlock *InBB = PHI->getIncomingBlock(Idx);
+          UseBlock =
+              UseBlock ? DT->findNearestCommonDominator(UseBlock, InBB) : InBB;
+        }
+      }
+      if (!UseBlock)
+        UseBlock = cast<Instruction>(EU.User)->getParent();
+    } else {
+      UseBlock = cast<Instruction>(EU.User)->getParent();
+    }
+    auto [It, Inserted] = ScalarToExtractBlock.try_emplace(EU.Scalar, UseBlock);
+    if (!Inserted && It->second && UseBlock)
+      It->second = DT->findNearestCommonDominator(It->second, UseBlock);
+  }
+
   SmallDenseSet<std::pair<Value *, Value *>, 8> CheckedScalarUser;
   for (ExternalUser &EU : ExternalUses) {
     LLVM_DEBUG(dbgs() << "SLP: Computing cost for external use of TreeEntry "
@@ -19836,8 +19885,28 @@ InstructionCost BoUpSLP::getTreeCost(InstructionCost TreeCost,
       }
     }
 
-    ExtraCost = ScaleCost(ExtraCost, *Entry, EU.Scalar,
-                          cast_or_null<Instruction>(EU.User));
+    // Scale the extract cost by the execution frequency of the block where
+    // codegen will place the extractelement. That block is the nearest common
+    // dominator of all effective use sites (precomputed in ScalarToExtractBlock
+    // above), which is order-independent. For scalars kept as originals the
+    // existing ScaleCost path (user-block based) remains correct, since the
+    // scalar instruction executes at its definition site's frequency.
+    if (!ExternalUsesAsOriginalScalar.contains(EU.Scalar)) {
+      if (ExtraCost.isValid() && ExtraCost != 0) {
+        BasicBlock *ExtractBB = ScalarToExtractBlock.lookup(EU.Scalar);
+        if (const Loop *L = ExtractBB ? LI->getLoopFor(ExtractBB) : nullptr) {
+          uint64_t Scale = getLoopNestScale(
+              findInnermostNonInvariantLoop(L, ArrayRef<Value *>(EU.Scalar)));
+          LLVM_DEBUG(dbgs()
+                     << "SLP: Extract scale " << Scale << " (NCD block) for "
+                     << EU.Scalar->getNameOrAsOperand() << "\n");
+          ExtraCost *= Scale;
+        }
+      }
+    } else {
+      ExtraCost = ScaleCost(ExtraCost, *Entry, EU.Scalar,
+                            cast_or_null<Instruction>(EU.User));
+    }
 
     ExtractCost += ExtraCost;
   }
diff --git a/llvm/test/Transforms/SLPVectorizer/AArch64/lcssa-phi-extract-scale.ll b/llvm/test/Transforms/SLPVectorizer/AArch64/lcssa-phi-extract-scale.ll
index 4ad0e6934d7a5..9b8520c122066 100644
--- a/llvm/test/Transforms/SLPVectorizer/AArch64/lcssa-phi-extract-scale.ll
+++ b/llvm/test/Transforms/SLPVectorizer/AArch64/lcssa-phi-extract-scale.ll
@@ -9,35 +9,43 @@ define double @test(ptr %obj, ptr %arr, i32 %n) {
 ; CHECK-SAME: ptr [[OBJ:%.*]], ptr [[ARR:%.*]], i32 [[N:%.*]]) #[[ATTR0:[0-9]+]] {
 ; CHECK-NEXT:  [[ENTRY:.*]]:
 ; CHECK-NEXT:    [[POS1:%.*]] = getelementptr inbounds i8, ptr [[OBJ]], i64 44
+; CHECK-NEXT:    [[POS2:%.*]] = getelementptr inbounds i8, ptr [[OBJ]], i64 48
 ; CHECK-NEXT:    [[COND_ENTER:%.*]] = icmp sgt i32 [[N]], 0
 ; CHECK-NEXT:    br i1 [[COND_ENTER]], label %[[OUTER_PREHEADER:.*]], label %[[EXIT:.*]]
 ; CHECK:       [[OUTER_PREHEADER]]:
 ; CHECK-NEXT:    br label %[[OUTER_HEADER:.*]]
 ; CHECK:       [[OUTER_HEADER]]:
-; CHECK-NEXT:    [[LOCAL_4_128:%.*]] = phi i32 [ 0, %[[OUTER_PREHEADER]] ], [ [[LCSSA_IV:%.*]], %[[OUTER_LATCH:.*]] ]
+; CHECK-NEXT:    %"$vphi.1" = phi i32 [ 16, %[[OUTER_PREHEADER]] ], [ [[DOTSINK6_I_LCSSA:%.*]], %[[OUTER_LATCH:.*]] ]
+; CHECK-NEXT:    %"$vphi.0" = phi i32 [ 4, %[[OUTER_PREHEADER]] ], [ [[DOTSINK_I_LCSSA:%.*]], %[[OUTER_LATCH]] ]
+; CHECK-NEXT:    [[LOCAL_4_128:%.*]] = phi i32 [ 0, %[[OUTER_PREHEADER]] ], [ [[LCSSA_IV:%.*]], %[[OUTER_LATCH]] ]
 ; CHECK-NEXT:    [[LOCAL_3_127:%.*]] = phi i32 [ 0, %[[OUTER_PREHEADER]] ], [ [[LOCAL_3_16_LCSSA:%.*]], %[[OUTER_LATCH]] ]
-; CHECK-NEXT:    [[TMP0:%.*]] = phi <2 x i32> [ <i32 4, i32 16>, %[[OUTER_PREHEADER]] ], [ [[TMP15:%.*]], %[[OUTER_LATCH]] ]
 ; CHECK-NEXT:    br label %[[INNER_HEADER:.*]]
 ; CHECK:       [[INNER_HEADER]]:
-; CHECK-NEXT:    [[LOCAL_4_128420:%.*]] = phi i32 [ [[LOCAL_4_128]], %[[OUTER_HEADER]] ], [ [[INC:%.*]], %[[INNER_BODY:.*]] ]
+; CHECK-NEXT:    %"$vphi.1418" = phi i32 [ %"$vphi.1", %[[OUTER_HEADER]] ], [ [[DOTSINK6_I:%.*]], %[[INNER_BODY:.*]] ]
+; CHECK-NEXT:    %"$vphi.0419" = phi i32 [ %"$vphi.0", %[[OUTER_HEADER]] ], [ [[DOTSINK_I:%.*]], %[[INNER_BODY]] ]
+; CHECK-NEXT:    [[LOCAL_4_128420:%.*]] = phi i32 [ [[LOCAL_4_128]], %[[OUTER_HEADER]] ], [ [[INC:%.*]], %[[INNER_BODY]] ]
 ; CHECK-NEXT:    [[LOCAL_3_127421:%.*]] = phi i32 [ [[LOCAL_3_127]], %[[OUTER_HEADER]] ], [ [[LOCAL_3_16:%.*]], %[[INNER_BODY]] ]
 ; CHECK-NEXT:    [[CHUNK_IV:%.*]] = phi i64 [ 0, %[[OUTER_HEADER]] ], [ [[CHUNK_IV_NEXT:%.*]], %[[INNER_BODY]] ]
-; CHECK-NEXT:    [[TMP1:%.*]] = phi <2 x i32> [ [[TMP0]], %[[OUTER_HEADER]] ], [ [[TMP14:%.*]], %[[INNER_BODY]] ]
-; CHECK-NEXT:    [[TMP2:%.*]] = extractelement <2 x i32> [[TMP1]], i32 0
-; CHECK-NEXT:    [[TMP3:%.*]] = icmp eq <2 x i32> [[TMP1]], zeroinitializer
-; CHECK-NEXT:    [[TMP4:%.*]] = add <2 x i32> [[TMP1]], splat (i32 -1)
-; CHECK-NEXT:    [[TMP5:%.*]] = select <2 x i1> [[TMP3]], <2 x i32> splat (i32 16), <2 x i32> [[TMP4]]
-; CHECK-NEXT:    [[TMP6:%.*]] = shufflevector <2 x i32> [[TMP1]], <2 x i32> [[TMP5]], <4 x i32> <i32 1, i32 0, i32 2, i32 3>
-; CHECK-NEXT:    [[TMP7:%.*]] = icmp ult <4 x i32> [[TMP6]], splat (i32 17)
-; CHECK-NEXT:    [[TMP8:%.*]] = call i1 @llvm.vector.reduce.and.v4i1(<4 x i1> [[TMP7]])
+; CHECK-NEXT:    [[C27:%.*]] = icmp ult i32 %"$vphi.0419", 17
+; CHECK-NEXT:    [[C28:%.*]] = icmp ult i32 %"$vphi.1418", 17
+; CHECK-NEXT:    [[WIDE_CHK:%.*]] = and i1 [[C28]], [[C27]]
+; CHECK-NEXT:    [[DOTNOT3_I17:%.*]] = icmp eq i32 %"$vphi.0419", 0
+; CHECK-NEXT:    [[V29:%.*]] = add i32 %"$vphi.0419", -1
+; CHECK-NEXT:    [[TMP10:%.*]] = select i1 [[DOTNOT3_I17]], i32 16, i32 [[V29]]
+; CHECK-NEXT:    [[C30:%.*]] = icmp ult i32 [[TMP10]], 17
+; CHECK-NEXT:    [[WIDE_CHK354:%.*]] = and i1 [[WIDE_CHK]], [[C30]]
+; CHECK-NEXT:    [[DOTNOT4_I19:%.*]] = icmp eq i32 %"$vphi.1418", 0
+; CHECK-NEXT:    [[V31:%.*]] = add i32 %"$vphi.1418", -1
+; CHECK-NEXT:    [[TMP11:%.*]] = select i1 [[DOTNOT4_I19]], i32 16, i32 [[V31]]
+; CHECK-NEXT:    [[C32:%.*]] = icmp ult i32 [[TMP11]], 17
+; CHECK-NEXT:    [[TMP8:%.*]] = and i1 [[C32]], [[WIDE_CHK354]]
 ; CHECK-NEXT:    br i1 [[TMP8]], label %[[INNER_BODY]], label %[[DEOPT:.*]]
 ; CHECK:       [[INNER_BODY]]:
 ; CHECK-NEXT:    [[CHUNK_IV_NEXT]] = add nuw nsw i64 [[CHUNK_IV]], 1
-; CHECK-NEXT:    [[V34:%.*]] = zext nneg i32 [[TMP2]] to i64
+; CHECK-NEXT:    [[V34:%.*]] = zext nneg i32 %"$vphi.0419" to i64
 ; CHECK-NEXT:    [[V35:%.*]] = getelementptr inbounds nuw [4 x i8], ptr [[ARR]], i64 [[V34]]
 ; CHECK-NEXT:    [[V36:%.*]] = load i32, ptr [[V35]], align 4
-; CHECK-NEXT:    [[TMP9:%.*]] = extractelement <2 x i32> [[TMP1]], i32 1
-; CHECK-NEXT:    [[V37:%.*]] = zext nneg i32 [[TMP9]] to i64
+; CHECK-NEXT:    [[V37:%.*]] = zext nneg i32 %"$vphi.1418" to i64
 ; CHECK-NEXT:    [[V38:%.*]] = getelementptr inbounds nuw [4 x i8], ptr [[ARR]], i64 [[V37]]
 ; CHECK-NEXT:    [[V39:%.*]] = load i32, ptr [[V38]], align 4
 ; CHECK-NEXT:    [[V40:%.*]] = sub i32 [[V36]], [[V39]]
@@ -47,11 +55,9 @@ define double @test(ptr %obj, ptr %arr, i32 %n) {
 ; CHECK-NEXT:    store i32 [[SPEC_SELECT_I13]], ptr [[V38]], align 4
 ; CHECK-NEXT:    [[V43:%.*]] = sitofp i32 [[SPEC_SELECT_I13]] to double
 ; CHECK-NEXT:    [[V44:%.*]] = fmul nnan double [[V43]], f0x3E00000000200000
-; CHECK-NEXT:    [[TMP10:%.*]] = extractelement <2 x i32> [[TMP5]], i32 0
 ; CHECK-NEXT:    [[V45:%.*]] = zext nneg i32 [[TMP10]] to i64
 ; CHECK-NEXT:    [[V46:%.*]] = getelementptr inbounds nuw [4 x i8], ptr [[ARR]], i64 [[V45]]
 ; CHECK-NEXT:    [[V47:%.*]] = load i32, ptr [[V46]], align 4
-; CHECK-NEXT:    [[TMP11:%.*]] = extractelement <2 x i32> [[TMP5]], i32 1
 ; CHECK-NEXT:    [[V48:%.*]] = zext nneg i32 [[TMP11]] to i64
 ; CHECK-NEXT:    [[V49:%.*]] = getelementptr inbounds nuw [4 x i8], ptr [[ARR]], i64 [[V48]]
 ; CHECK-NEXT:    [[V50:%.*]] = load i32, ptr [[V49]], align 4
@@ -60,10 +66,14 @@ define double @test(ptr %obj, ptr %arr, i32 %n) {
 ; CHECK-NEXT:    [[V53:%.*]] = icmp slt i32 [[V51]], 0
 ; CHECK-NEXT:    [[SPEC_SELECT_I:%.*]] = select i1 [[V53]], i32 [[V52]], i32 [[V51]]
 ; CHECK-NEXT:    store i32 [[SPEC_SELECT_I]], ptr [[V49]], align 4
-; CHECK-NEXT:    [[TMP12:%.*]] = icmp eq <2 x i32> [[TMP5]], zeroinitializer
-; CHECK-NEXT:    [[TMP13:%.*]] = add nsw <2 x i32> [[TMP5]], splat (i32 -1)
-; CHECK-NEXT:    [[TMP14]] = select <2 x i1> [[TMP12]], <2 x i32> splat (i32 16), <2 x i32> [[TMP13]]
-; CHECK-NEXT:    store <2 x i32> [[TMP14]], ptr [[POS1]], align 4
+; CHECK-NEXT:    [[DOTNOT3_I:%.*]] = icmp eq i32 [[TMP10]], 0
+; CHECK-NEXT:    [[V54:%.*]] = add nsw i32 [[TMP10]], -1
+; CHECK-NEXT:    [[DOTSINK_I]] = select i1 [[DOTNOT3_I]], i32 16, i32 [[V54]]
+; CHECK-NEXT:    store i32 [[DOTSINK_I]], ptr [[POS1]], align 4
+; CHECK-NEXT:    [[DOTNOT4_I:%.*]] = icmp eq i32 [[TMP11]], 0
+; CHECK-NEXT:    [[V55:%.*]] = add nsw i32 [[TMP11]], -1
+; CHECK-NEXT:    [[DOTSINK6_I]] = select i1 [[DOTNOT4_I]], i32 16, i32 [[V55]]
+; CHECK-NEXT:    store i32 [[DOTSINK6_I]], ptr [[POS2]], align 8
 ; CHECK-NEXT:    [[V56:%.*]] = sitofp i32 [[SPEC_SELECT_I]] to double
 ; CHECK-NEXT:    [[V57:%.*]] = fmul nnan double [[V56]], f0x3E00000000200000
 ; CHECK-NEXT:    [[V58:%.*]] = fmul nnan double [[V44]], [[V44]]
@@ -78,16 +88,17 @@ define double @test(ptr %obj, ptr %arr, i32 %n) {
 ; CHECK-NEXT:    [[V64:%.*]] = and i1 [[DOTNOT]], [[CHUNK_COND]]
 ; CHECK-NEXT:    br i1 [[V64]], label %[[INNER_HEADER]], label %[[OUTER_LATCH]]
 ; CHECK:       [[DEOPT]]:
-; CHECK-NEXT:    %"$vphi.0419.lcssa" = phi i32 [ [[TMP2]], %[[INNER_HEADER]] ]
+; CHECK-NEXT:    %"$vphi.0419.lcssa" = phi i32 [ %"$vphi.0419", %[[INNER_HEADER]] ]
 ; CHECK-NEXT:    [[LOCAL_4_128420_LCSSA:%.*]] = phi i32 [ [[LOCAL_4_128420]], %[[INNER_HEADER]] ]
 ; CHECK-NEXT:    [[LOCAL_3_127421_LCSSA:%.*]] = phi i32 [ [[LOCAL_3_127421]], %[[INNER_HEADER]] ]
 ; CHECK-NEXT:    call void @sink_use(i32 %"$vphi.0419.lcssa", i32 [[LOCAL_4_128420_LCSSA]], i32 [[LOCAL_3_127421_LCSSA]])
 ; CHECK-NEXT:    ret double 0.000000e+00
 ; CHECK:       [[OUTER_LATCH]]:
+; CHECK-NEXT:    [[DOTSINK_I_LCSSA]] = phi i32 [ [[DOTSINK_I]], %[[INNER_BODY]] ]
+; CHECK-NEXT:    [[DOTSINK6_I_LCSSA]] = phi i32 [ [[DOTSINK6_I]], %[[INNER_BODY]] ]
 ; CHECK-NEXT:    [[LOCAL_3_16_LCSSA]] = phi i32 [ [[LOCAL_3_16]], %[[INNER_BODY]] ]
 ; CHECK-NEXT:    [[LCSSA_IV]] = phi i32 [ [[INC]], %[[INNER_BODY]] ]
 ; CHECK-NEXT:    [[DOTNOT_LCSSA:%.*]] = phi i1 [ [[DOTNOT]], %[[INNER_BODY]] ]
-; CHECK-NEXT:    [[TMP15]] = phi <2 x i32> [ [[TMP14]], %[[INNER_BODY]] ]
 ; CHECK-NEXT:    br i1 [[DOTNOT_LCSSA]], label %[[OUTER_HEADER]], label %[[EXIT_LOOP:.*]]
 ; CHECK:       [[EXIT_LOOP]]:
 ; CHECK-NEXT:    [[FINAL:%.*]] = phi i32 [ [[LOCAL_3_16_LCSSA]], %[[OUTER_LATCH]] ]
@@ -218,33 +229,43 @@ define double @test_inloop_first(ptr %obj, ptr %arr, i32 %n) {
 ; CHECK-SAME: ptr [[OBJ:%.*]], ptr [[ARR:%.*]], i32 [[N:%.*]]) #[[ATTR0]] {
 ; CHECK-NEXT:  [[ENTRY:.*]]:
 ; CHECK-NEXT:    [[POS1:%.*]] = getelementptr inbounds i8, ptr [[OBJ]], i64 44
+; CHECK-NEXT:    [[POS2:%.*]] = getelementptr inbounds i8, ptr [[OBJ]], i64 48
 ; CHECK-NEXT:    [[COND_ENTER:%.*]] = icmp sgt i32 [[N]], 0
 ; CHECK-NEXT:    br i1 [[COND_ENTER]], label %[[OUTER_PREHEADER:.*]], label %[[EXIT:.*]]
 ; CHECK:       [[OUTER_PREHEADER]]:
 ; CHECK-NEXT:    br label %[[OUTER_HEADER:.*]]
 ; CHECK:       [[OUTER_HEADER]]:
-; CHECK-NEXT:    [[LOCAL_4_128:%.*]] = phi i32 [ 0, %[[OUTER_PREHEADER]] ], [ [[LCSSA_IV:%.*]], %[[OUTER_LATCH:.*]] ]
+; CHECK-NEXT:    %"$vphi.1" = phi i32 [ 16, %[[OUTER_PREHEADER]] ], [ [[DOTSINK6_I_LCSSA:%.*]], %[[OUTER_LATCH:.*]] ]
+; CHECK-NEXT:    %"$vphi.0" = phi i32 [ 4, %[[OUTER_PREHEADER]] ], [ [[DOTSINK_I_LCSSA:%.*]], %[[OUTER_LATCH]] ]
+; CHECK-NEXT:    [[LOCAL_4_128:%.*]] = phi i32 [ 0, %[[OUTER_PREHEADER]] ], [ [[LCSSA_IV:%.*]], %[[OUTER_LATCH]] ]
 ; CHECK-NEXT:    [[LOCAL_3_127:%.*]] = phi i32 [ 0, %[[OUTER_PREHEADER]] ], [ [[LOCAL_3_16_LCSSA:%.*]], %[[OUTER_LATCH]] ]
-; CHECK-NEXT:    [[TMP0:%.*]] = phi <2 x i32> [ <i32 4, i32 16>, %[[OUTER_PREHEADER]] ], [ [[TMP9:%.*]], %[[OUTER_LATCH]] ]
 ; CHECK-NEXT:    br label %[[INNER_HEADER:.*]]
 ; CHECK:       [[INNER_HEADER]]:
-; CHECK-NEXT:    [[LOCAL_4_128420:%.*]] = phi i32 [ [[LOCAL_4_128]], %[[OUTER_HEADER]] ], [ [[INC:%.*]], %[[INNER_BODY:.*]] ]
+; CHECK-NEXT:    %"$vphi.1418" = phi i32 [ %"$vphi.1", %[[OUTER_HEADER]] ], [ [[DOTSINK6_I:%.*]], %[[INNER_BODY:.*]] ]
+; CHECK-NEXT:    %"$vphi.0419" = phi i32 [ %"$vphi.0", %[[OUTER_HEADER]] ], [ [[DOTSINK_I:%.*]], %[[INNER_BODY]] ]
+; CHECK-NEXT:    [[LOCAL_4_128420:%.*]] = phi i32 [ [[LOCAL_4_128]], %[[OUTER_HEADER]] ], [ [[INC:%.*]], %[[INNER_BODY]] ]
 ; CHECK-NEXT:    [[LOCAL_3_127421:%.*]] = phi i32 [ [[LOCAL_3_127]], %[[OUTER_HEADER]] ], [ [[LOCAL_3_16:%.*]], %[[INNER_BODY]] ]
 ; CHECK-NEXT:    [[CHUNK_IV:%.*]] = phi i64 [ 0, %[[OUTER_HEADER]] ], [ [[CHUNK_IV_NEXT:%.*]], %[[INNER_BODY]] ]
-; CHECK-NEXT:    [[TMP1:%.*]] = phi <2 x i32> [ [[TMP0]], %[[OUTER_HEADER]] ], [ [[TMP15:%.*]], %[[INNER_BODY]] ]
-; CHECK-NEXT:    [[TMP2:%.*]] = extractelement <2 x i32> [[TMP1]], i32 0
-; CHECK-NEXT:    [[TMP3:%.*]] = icmp eq <2 x i32> [[TMP1]], zeroinitializer
-; CHECK-NEXT:    [[TMP4:%.*]] = add <2 x i32> [[TMP1]], splat (i32 -1)
-; CHECK-NEXT:    [[TMP5:%.*]] = select <2 x i1> [[TMP3]], <2 x i32> splat (i32 16), <2 x i32> [[TMP4]]
-; CHECK-NEXT:    [[TMP6:%.*]] = shufflevector <2 x i32> [[TMP1]], <2 x i32> [[TMP5]], <4 x i32> <i32 1, i32 0, i32 2, i32 3>
-; CHECK-NEXT:    [[TMP7:%.*]] = icmp ult <4 x i32> [[TMP6]], splat (i32 17)
-; CHECK-NEXT:    [[TMP8:%.*]] = call i1 @llvm.vector.reduce.and.v4i1(<4 x i1> [[TMP7]])
+; CHECK-NEXT:    [[C27:%.*]] = icmp ult i32 %"$vphi.0419", 17
+; CHECK-NEXT:    [[C28:%.*]] = icmp ult i32 %"$vphi.1418", 17
+; CHECK-NEXT:    [[WIDE_CHK:%.*]] = and i1 [[C28]], [[C27]]
+; CHECK-NEXT:    [[DOTNOT3_I17:%.*]] = icmp eq i32 %"$vphi.0419", 0
+; CHECK-NEXT:    [[V29:%.*]] = add i32 %"$vphi.0419", -1
+; CHECK-NEXT:    [[TMP11:%.*]] = select i1 [[DOTNOT3_I17]], i32 16, i32 [[V29]]
+; CHECK-NEXT:    [[C30:%.*]] = icmp ult i32 [[TMP11]], 17
+; CHECK-NEXT:    [[WIDE_CHK354:%.*]] = and i1 [[WIDE_CHK]], [[C30]]
+; CHECK-NEXT:    [[DOTNOT4_I19:%.*]] = icmp eq i32 %"$vphi.1418", 0
+; CHECK-NEXT:    [[V31:%.*]] = add i32 %"$vphi.1418", -1
+; CHECK-NEXT:    [[TMP12:%.*]] = select i1 [[DOTNOT4_I19]], i32 16, i32 [[V31]]
+; CHECK-NEXT:    [[C32:%.*]] = icmp ult i32 [[TMP12]], 17
+; CHECK-NEXT:    [[TMP8:%.*]] = and i1 [[C32]], [[WIDE_CHK354]]
 ; CHECK-NEXT:    br i1 [[TMP8]], label %[[INNER_BODY]], label %[[DEOPT:.*]]
 ; CHECK:       [[OUTER_LATCH]]:
+; CHECK-NEXT:    [[DOTSINK_I_LCSSA]] = phi i32 [ [[DOTSINK_I]], %[[INNER_BODY]] ]
+; CHECK-NEXT:    [[DOTSINK6_I_LCSSA]] = phi i32 [ [[DOTSINK6_I]], %[[INNER_BODY]] ]
 ; CHECK-NEXT:    [[LOCAL_3_16_LCSSA]] = phi i32 [ [[LOCAL_3_16]], %[[INNER_BODY]] ]
 ; CHECK-NEXT:    [[LCSSA_IV]] = phi i32 [ [[INC]], %[[INNER_BODY]] ]
 ; CHECK-NEXT:    [[DOTNOT_LCSSA:%.*]] = phi i1 [ [[DOTNOT:%.*]], %[[INNER_BODY]] ]
-; CHECK-NEXT:    [[TMP9]] = phi <2 x i32> [ [[TMP15]], %[[INNER_BODY]] ]
 ; CHECK-NEXT:    br i1 [[DOTNOT_LCSSA]], label %[[OUTER_HEADER]], label %[[EXIT_LOOP:.*]]
 ; CHECK:       [[EXIT_LOOP]]:
 ; CHECK-NEXT:    [[FINAL:%.*]] = phi i32 [ [[LOCAL_3_16_LCSSA]], %[[OUTER_LATCH]] ]
@@ -252,11 +273,10 @@ define double @test_inloop_first(ptr %obj, ptr %arr, i32 %n) {
 ; CHECK-NEXT:    br label %[[EXIT]]
 ; CHECK:       [[INNER_BODY]]:
 ; CHECK-NEXT:    [[CHUNK_IV_NEXT]] = add nuw nsw i64 [[CHUNK_IV]], 1
-; CHECK-NEXT:    [[V34:%.*]] = zext nneg i32 [[TMP2]] to i64
+; CHECK-NEXT:    [[V34:%.*]] = zext nneg i32 %"$vphi.0419" to i64
 ; CHECK-NEXT:    [[V35:%.*]] = getelementptr inbounds nuw [4 x i8], ptr [[ARR]], i64 [[V34]]
 ; CHECK-NEXT:    [[V36:%.*]] = load i32, ptr [[V35]], align 4
-; CHECK-NEXT:    [[TMP10:%.*]] = extractelement <2 x i32> [[TMP1]], i32 1
-; CHECK-NEXT:    [[V37:%.*]] = zext nneg i32 [[TMP10]] to i64
+; CHECK-NEXT:    [[V37:%.*]] = zext nneg i32 %"$vphi.1418" to i64
 ; CHECK-NEXT:    [[V38:%.*]] = getelementptr inbounds nuw [4 x i8], ptr [[ARR]], i64 [[V37]]
 ; CHECK-NEXT:    [[V39:%.*]] = load i32, ptr [[V38]], align 4
 ; CHECK-NEXT:    [[V40:%.*]] = sub i32 [[V36]], [[V39]]
@@ -266,11 +286,9 @@ define double @test_inloop_first(ptr %obj, ptr %arr, i32 %n) {
 ; CHECK-NEXT:    store i32 [[SPEC_SELECT_I13]], ptr [[V38]], align 4
 ; CHECK-NEXT:    [[V43:%.*]] = sitofp i32 [[SPEC_SELECT_I13]] to double
 ; CHECK-NEXT:    [[V44:%.*]] = fmul nnan double [[V43]], f0x3E00000000200000
-; CHECK-NEXT:    [[TMP11:%.*]] = extractelement <2 x i32> [[TMP5]], i32 0
 ; CHECK-NEXT:    [[V45:%.*]] = zext nneg i32 [[TMP11]] to i64
 ; CHECK-NEXT:    [[V46:%.*]] = getelementptr inbounds nuw [4 x i8], ptr [[ARR]], i64 [[V45]]
 ; CHECK-NEXT:    [[V47:%.*]] = load i32, ptr [[V46]], align 4
-; CHECK-NEXT:    [[TMP12:%.*]] = extractelement <2 x i32> [[TMP5]], i32 1
 ; CHECK-NEXT:    [[V48:%.*]] = zext nneg i32 [[TMP12]] to i64
 ; CHECK-NEXT:    [[V49:%.*]] = getelementptr inbounds nuw [4 x i8], ptr [[ARR]], i64 [[V48]]
 ; CHECK-NEXT:    [[V50:%.*]] = load i32, ptr [[V49]], align 4
@@ -279,10 +297,14 @@ define double @test_inloop_first(ptr %obj, ptr %arr, i32 %n) {
 ; CHECK-NEXT:    [[V53:%.*]] = icmp slt i32 [[V51]], 0
 ; CHECK-NEXT:    [[SPEC_SELECT_I:%.*]] = select i1 [[V53]], i32 [[V52]], i32 [[V51]]
 ; CHECK-NEXT:    store i32 [[SPEC_SELECT_I]], ptr [[V49]], align 4
-; CHECK-NEXT:    [[TMP13:%.*]] = icmp eq <2 x i32> [[TMP5]], zeroinitializer
-; CHECK-NEXT:    [[TMP14:%.*]] = add nsw <2 x i32> [[TMP5]], splat (i32 -1)
-; CHECK-NEXT:    [[TMP15]] = select <2 x i1> [[TMP13]], <2 x i32> splat (i32 16), <2 x i32> [[TMP14]]
-; CHECK-NEXT:    store <2 x i32> [[TMP15]], ptr [[POS1]], align 4
+; CHECK-NEXT:    [[DOTNOT3_I:%.*]] = icmp eq i32 [[TMP11]], 0
+; CHECK-NEXT:    [[V54:%.*]] = add nsw i32 [[TMP1...
[truncated]

Comment on lines +19575 to +19592
if (auto *PHI = dyn_cast<PHINode>(EU.User)) {
// When the PHI itself is inside a loop, the extractelement is placed
// in the incoming block for the scalar operand (the predecessor edge),
// not in the PHI's own block. This applies to LCSSA phis at an inner-
// loop exit that are still inside an outer loop: the incoming block is
// in the inner loop while the PHI block is in the outer loop.
// When the PHI is outside all loops (a true loop-exit phi), codegen
// uses a vector phi at the exit block and the extract stays there
// (scale = 1), so we keep the PHI's own block as the effective site.
if (LI->getLoopFor(PHI->getParent())) {
for (unsigned Idx : seq<unsigned>(PHI->getNumIncomingValues())) {
if (PHI->getIncomingValue(Idx) != EU.Scalar)
continue;
BasicBlock *InBB = PHI->getIncomingBlock(Idx);
UseBlock =
UseBlock ? DT->findNearestCommonDominator(UseBlock, InBB) : InBB;
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this supersede #199954?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In some sense. In most case, but that one still might be useful in general.

@bababuck bababuck left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Created using spr 1.3.7
@alexey-bataev
alexey-bataev merged commit 8d86dc2 into main May 30, 2026
7 of 10 checks passed
@alexey-bataev
alexey-bataev deleted the users/alexey-bataev/spr/slp-fix-extract-cost-scale-using-ncd-of-all-external-user-sites branch May 30, 2026 11:43
llvm-upstreamsync Bot pushed a commit to qualcomm/cpullvm-toolchain that referenced this pull request May 30, 2026
…r sites

ExtractCostCalculated deduplicates by scalar so only the first
ExternalUser determines the scale, making the cost depend on IR block
ordering via LLVM's reverse-insertion use-list order.
Add a pre-pass computing ScalarToExtractBlock - the nearest common
dominator of all effective extract sites per scalar. For PHI users inside
a loop the effective site is the incoming block; for PHI users outside
all loops it is the PHI's own block (scale = 1). The extract cost is
then scaled by getLoopNestScale of the NCD block, which is fully
order-independent.

Fixes #199548

Reviewers: bababuck, RKSimon, hiraditya

Pull Request: llvm/llvm-project#199962
llvm-sync Bot pushed a commit to arm/arm-toolchain that referenced this pull request May 30, 2026
…r sites

ExtractCostCalculated deduplicates by scalar so only the first
ExternalUser determines the scale, making the cost depend on IR block
ordering via LLVM's reverse-insertion use-list order.
Add a pre-pass computing ScalarToExtractBlock - the nearest common
dominator of all effective extract sites per scalar. For PHI users inside
a loop the effective site is the incoming block; for PHI users outside
all loops it is the PHI's own block (scale = 1). The extract cost is
then scaled by getLoopNestScale of the NCD block, which is fully
order-independent.

Fixes #199548

Reviewers: bababuck, RKSimon, hiraditya

Pull Request: llvm/llvm-project#199962
@john-brawn-arm

Copy link
Copy Markdown
Contributor

This is causing some performance regressions where it looks like in some cases the extract costs aren't being scaled correctly.

Example:

target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "thumbv8.1m.main-unknown-none-eabihf"

define void @fn(ptr %p, ptr %q) #0 {
entry:
  br label %loop

loop:
  %65 = phi i32 [ 0, %entry ], [ %350, %loop ]
  %66 = phi i32 [ 0, %entry ], [ %351, %loop ]
  %67 = phi i32 [ 0, %entry ], [ %363, %loop ]
  %68 = phi i32 [ 0, %entry ], [ %375, %loop ]
  %71 = phi i32 [ 0, %entry ], [ %376, %loop ]
  %72 = getelementptr inbounds nuw i8, ptr %p, i32 %66
  %73 = load i8, ptr %72, align 1
  %74 = getelementptr i8, ptr %q, i32 %65
  store i8 %73, ptr %74, align 1
  %77 = getelementptr i8, ptr %74, i32 1
  store i8 %73, ptr %77, align 1
  %79 = getelementptr inbounds nuw i8, ptr %72, i32 1
  %80 = load i8, ptr %79, align 1
  %81 = getelementptr i8, ptr %74, i32 2
  store i8 %80, ptr %81, align 1
  %84 = getelementptr i8, ptr %74, i32 3
  store i8 %80, ptr %84, align 1
  %86 = getelementptr i8, ptr %q, i32 %67
  store i8 %80, ptr %86, align 1
  %89 = getelementptr i8, ptr %86, i32 1
  store i8 %80, ptr %89, align 1
  %91 = getelementptr i8, ptr %86, i32 2
  store i8 %80, ptr %91, align 1
  %94 = getelementptr i8, ptr %86, i32 3
  store i8 %80, ptr %94, align 1
  %96 = getelementptr i8, ptr %q, i32 %68
  store i8 %80, ptr %96, align 1
  %99 = getelementptr i8, ptr %96, i32 1
  store i8 %80, ptr %99, align 1
  %101 = getelementptr i8, ptr %96, i32 2
  store i8 %80, ptr %101, align 1
  %104 = getelementptr i8, ptr %96, i32 3
  store i8 %80, ptr %104, align 1
  %106 = getelementptr inbounds nuw i8, ptr %p, i32 %66
  %107 = getelementptr inbounds nuw i8, ptr %106, i32 2
  %108 = load i8, ptr %107, align 1
  %109 = getelementptr i8, ptr %q, i32 %65
  %110 = getelementptr i8, ptr %109, i32 4
  store i8 %108, ptr %110, align 1
  %113 = getelementptr i8, ptr %109, i32 5
  store i8 %108, ptr %113, align 1
  %115 = getelementptr inbounds nuw i8, ptr %106, i32 3
  %116 = load i8, ptr %115, align 1
  %117 = getelementptr i8, ptr %109, i32 6
  store i8 %116, ptr %117, align 1
  %120 = getelementptr i8, ptr %109, i32 7
  store i8 %116, ptr %120, align 1
  %122 = getelementptr i8, ptr %q, i32 %67
  %123 = getelementptr i8, ptr %122, i32 4
  store i8 %116, ptr %123, align 1
  %126 = getelementptr i8, ptr %122, i32 5
  store i8 %116, ptr %126, align 1
  %128 = getelementptr i8, ptr %122, i32 6
  store i8 %116, ptr %128, align 1
  %131 = getelementptr i8, ptr %122, i32 7
  store i8 %116, ptr %131, align 1
  %133 = getelementptr i8, ptr %q, i32 %68
  %134 = getelementptr i8, ptr %133, i32 4
  store i8 %116, ptr %134, align 1
  %137 = getelementptr i8, ptr %133, i32 5
  store i8 %116, ptr %137, align 1
  %139 = getelementptr i8, ptr %133, i32 6
  store i8 %116, ptr %139, align 1
  %142 = getelementptr i8, ptr %133, i32 7
  store i8 %116, ptr %142, align 1
  %144 = getelementptr inbounds nuw i8, ptr %p, i32 %66
  %145 = getelementptr inbounds nuw i8, ptr %144, i32 4
  %146 = load i8, ptr %145, align 1
  %147 = getelementptr i8, ptr %q, i32 %65
  %148 = getelementptr i8, ptr %147, i32 8
  store i8 %146, ptr %148, align 1
  %151 = getelementptr i8, ptr %147, i32 9
  store i8 %146, ptr %151, align 1
  %153 = getelementptr inbounds nuw i8, ptr %144, i32 5
  %154 = load i8, ptr %153, align 1
  %155 = getelementptr i8, ptr %147, i32 10
  store i8 %154, ptr %155, align 1
  %158 = getelementptr i8, ptr %147, i32 11
  store i8 %154, ptr %158, align 1
  %160 = getelementptr i8, ptr %q, i32 %67
  %161 = getelementptr i8, ptr %160, i32 8
  store i8 %154, ptr %161, align 1
  %164 = getelementptr i8, ptr %160, i32 9
  store i8 %154, ptr %164, align 1
  %166 = getelementptr i8, ptr %160, i32 10
  store i8 %154, ptr %166, align 1
  %169 = getelementptr i8, ptr %160, i32 11
  store i8 %154, ptr %169, align 1
  %171 = getelementptr i8, ptr %q, i32 %68
  %172 = getelementptr i8, ptr %171, i32 8
  store i8 %154, ptr %172, align 1
  %175 = getelementptr i8, ptr %171, i32 9
  store i8 %154, ptr %175, align 1
  %177 = getelementptr i8, ptr %171, i32 10
  store i8 %154, ptr %177, align 1
  %180 = getelementptr i8, ptr %171, i32 11
  store i8 %154, ptr %180, align 1
  %182 = getelementptr inbounds nuw i8, ptr %p, i32 %66
  %183 = getelementptr inbounds nuw i8, ptr %182, i32 6
  %184 = load i8, ptr %183, align 1
  %185 = getelementptr i8, ptr %q, i32 %65
  %186 = getelementptr i8, ptr %185, i32 12
  store i8 %184, ptr %186, align 1
  %189 = getelementptr i8, ptr %185, i32 13
  store i8 %184, ptr %189, align 1
  %191 = getelementptr inbounds nuw i8, ptr %182, i32 7
  %192 = load i8, ptr %191, align 1
  %193 = getelementptr i8, ptr %185, i32 14
  store i8 %192, ptr %193, align 1
  %196 = getelementptr i8, ptr %185, i32 15
  store i8 %192, ptr %196, align 1
  %198 = getelementptr i8, ptr %q, i32 %67
  %199 = getelementptr i8, ptr %198, i32 12
  store i8 %192, ptr %199, align 1
  %202 = getelementptr i8, ptr %198, i32 13
  store i8 %192, ptr %202, align 1
  %204 = getelementptr i8, ptr %198, i32 14
  store i8 %192, ptr %204, align 1
  %207 = getelementptr i8, ptr %198, i32 15
  store i8 %192, ptr %207, align 1
  %209 = getelementptr i8, ptr %q, i32 %68
  %210 = getelementptr i8, ptr %209, i32 12
  store i8 %192, ptr %210, align 1
  %213 = getelementptr i8, ptr %209, i32 13
  store i8 %192, ptr %213, align 1
  %215 = getelementptr i8, ptr %209, i32 14
  store i8 %192, ptr %215, align 1
  %218 = getelementptr i8, ptr %209, i32 15
  store i8 %192, ptr %218, align 1
  %220 = getelementptr inbounds nuw i8, ptr %p, i32 %66
  %221 = getelementptr inbounds nuw i8, ptr %220, i32 8
  %222 = load i8, ptr %221, align 1
  %223 = getelementptr i8, ptr %q, i32 %65
  %224 = getelementptr i8, ptr %223, i32 16
  store i8 %222, ptr %224, align 1
  %227 = getelementptr i8, ptr %223, i32 17
  store i8 %222, ptr %227, align 1
  %229 = getelementptr inbounds nuw i8, ptr %220, i32 9
  %230 = load i8, ptr %229, align 1
  %231 = getelementptr i8, ptr %223, i32 18
  store i8 %222, ptr %231, align 1
  %234 = getelementptr i8, ptr %223, i32 19
  store i8 %222, ptr %234, align 1
  %236 = getelementptr i8, ptr %q, i32 %67
  %237 = getelementptr i8, ptr %236, i32 16
  store i8 %222, ptr %237, align 1
  %240 = getelementptr i8, ptr %236, i32 17
  store i8 %222, ptr %240, align 1
  %242 = getelementptr i8, ptr %236, i32 18
  store i8 %222, ptr %242, align 1
  %245 = getelementptr i8, ptr %236, i32 19
  store i8 %222, ptr %245, align 1
  %247 = getelementptr i8, ptr %q, i32 %68
  %248 = getelementptr i8, ptr %247, i32 16
  store i8 %222, ptr %248, align 1
  %251 = getelementptr i8, ptr %247, i32 17
  store i8 %222, ptr %251, align 1
  %253 = getelementptr i8, ptr %247, i32 18
  store i8 %222, ptr %253, align 1
  %256 = getelementptr i8, ptr %247, i32 19
  store i8 %222, ptr %256, align 1
  %258 = getelementptr inbounds nuw i8, ptr %p, i32 %66
  %259 = getelementptr inbounds nuw i8, ptr %258, i32 10
  %260 = load i8, ptr %259, align 1
  %261 = getelementptr i8, ptr %q, i32 %65
  %262 = getelementptr i8, ptr %261, i32 20
  store i8 %260, ptr %262, align 1
  %265 = getelementptr i8, ptr %261, i32 21
  store i8 %260, ptr %265, align 1
  %267 = getelementptr inbounds nuw i8, ptr %258, i32 11
  %268 = load i8, ptr %267, align 1
  %269 = getelementptr i8, ptr %261, i32 22
  store i8 %260, ptr %269, align 1
  %272 = getelementptr i8, ptr %261, i32 23
  store i8 %260, ptr %272, align 1
  %274 = getelementptr i8, ptr %q, i32 %67
  %275 = getelementptr i8, ptr %274, i32 20
  store i8 %260, ptr %275, align 1
  %278 = getelementptr i8, ptr %274, i32 21
  store i8 %260, ptr %278, align 1
  %280 = getelementptr i8, ptr %274, i32 22
  store i8 %260, ptr %280, align 1
  %283 = getelementptr i8, ptr %274, i32 23
  store i8 %260, ptr %283, align 1
  %285 = getelementptr i8, ptr %q, i32 %68
  %286 = getelementptr i8, ptr %285, i32 20
  store i8 %260, ptr %286, align 1
  %289 = getelementptr i8, ptr %285, i32 21
  store i8 %260, ptr %289, align 1
  %291 = getelementptr i8, ptr %285, i32 22
  store i8 %260, ptr %291, align 1
  %294 = getelementptr i8, ptr %285, i32 23
  store i8 %260, ptr %294, align 1
  %296 = getelementptr inbounds nuw i8, ptr %p, i32 %66
  %297 = getelementptr inbounds nuw i8, ptr %296, i32 12
  %298 = load i8, ptr %297, align 1
  %299 = getelementptr i8, ptr %q, i32 %65
  %300 = getelementptr i8, ptr %299, i32 24
  store i8 %298, ptr %300, align 1
  %303 = getelementptr i8, ptr %299, i32 25
  store i8 %298, ptr %303, align 1
  %305 = getelementptr inbounds nuw i8, ptr %296, i32 13
  %306 = load i8, ptr %305, align 1
  %307 = getelementptr i8, ptr %299, i32 26
  store i8 %306, ptr %307, align 1
  %310 = getelementptr i8, ptr %299, i32 27
  store i8 %306, ptr %310, align 1
  %312 = getelementptr i8, ptr %q, i32 %67
  %313 = getelementptr i8, ptr %312, i32 24
  store i8 %306, ptr %313, align 1
  %316 = getelementptr i8, ptr %312, i32 25
  store i8 %306, ptr %316, align 1
  %318 = getelementptr i8, ptr %312, i32 26
  store i8 %306, ptr %318, align 1
  %321 = getelementptr i8, ptr %312, i32 27
  store i8 %306, ptr %321, align 1
  %323 = getelementptr i8, ptr %q, i32 %68
  %324 = getelementptr i8, ptr %323, i32 24
  store i8 %306, ptr %324, align 1
  %327 = getelementptr i8, ptr %323, i32 25
  store i8 %306, ptr %327, align 1
  %329 = getelementptr i8, ptr %323, i32 26
  store i8 %306, ptr %329, align 1
  %332 = getelementptr i8, ptr %323, i32 27
  store i8 %306, ptr %332, align 1
  %334 = getelementptr inbounds nuw i8, ptr %p, i32 %66
  %335 = getelementptr inbounds nuw i8, ptr %334, i32 14
  %336 = load i8, ptr %335, align 1
  %337 = getelementptr i8, ptr %q, i32 %65
  %338 = getelementptr i8, ptr %337, i32 28
  store i8 %336, ptr %338, align 1
  %341 = getelementptr i8, ptr %337, i32 29
  store i8 %336, ptr %341, align 1
  %343 = getelementptr inbounds nuw i8, ptr %334, i32 15
  %344 = load i8, ptr %343, align 1
  %345 = getelementptr i8, ptr %337, i32 30
  store i8 %344, ptr %345, align 1
  %348 = getelementptr i8, ptr %337, i32 31
  store i8 %344, ptr %348, align 1
  %350 = add nuw nsw i32 %65, 32
  %351 = add nuw nsw i32 %66, 16
  %352 = getelementptr i8, ptr %q, i32 %67
  %353 = getelementptr i8, ptr %352, i32 28
  store i8 %344, ptr %353, align 1
  %356 = getelementptr i8, ptr %352, i32 29
  store i8 %344, ptr %356, align 1
  %358 = getelementptr i8, ptr %352, i32 30
  store i8 %344, ptr %358, align 1
  %361 = getelementptr i8, ptr %352, i32 31
  store i8 %344, ptr %361, align 1
  %363 = add nuw nsw i32 %67, 32
  %364 = getelementptr i8, ptr %q, i32 %68
  %365 = getelementptr i8, ptr %364, i32 28
  store i8 %344, ptr %365, align 1
  %368 = getelementptr i8, ptr %364, i32 29
  store i8 %344, ptr %368, align 1
  %370 = getelementptr i8, ptr %364, i32 30
  store i8 %344, ptr %370, align 1
  %373 = getelementptr i8, ptr %364, i32 31
  store i8 %344, ptr %373, align 1
  %375 = add nuw nsw i32 %68, 32
  %376 = add nuw nsw i32 %71, 16
  %377 = icmp eq i32 %376, 512
  br i1 %377, label %exit, label %loop

exit:
  ret void
}

attributes #0 = { "target-features"="+mve" }

opt -passes=slp-vectorizer previously didn't vectorize this, now it does generating a vector add and 4 extracts, but the cost of the 4 extracts is greater than what we gain from the add.

Looking at the -debug output before this PR it has

SLP: Computing cost for external use of TreeEntry 0 in lane 0
  User: nullptr
  Use: %0
  ExtractElement cost for i32 from <4 x i32>: 4
Scale 32 For entry 0
SLP: Computing cost for external use of TreeEntry 0 in lane 1
  User: nullptr
  Use: %1
  ExtractElement cost for i32 from <4 x i32>: 4
Scale 32 For entry 0
SLP: Computing cost for external use of TreeEntry 0 in lane 2
  User: nullptr
  Use: %2
  ExtractElement cost for i32 from <4 x i32>: 4
Scale 32 For entry 0
SLP: Computing cost for external use of TreeEntry 0 in lane 3
  User: nullptr
  Use: %3
  ExtractElement cost for i32 from <4 x i32>: 4
Scale 32 For entry 0
SLP: Spill Cost = <skipped>.
SLP: Extract Cost = 512.
SLP: Total Cost = 448.

now it has

SLP: Computing cost for external use of TreeEntry 0 in lane 0
  User: nullptr
  Use: %0
  ExtractElement cost for i32 from <4 x i32>: 4
SLP: Computing cost for external use of TreeEntry 0 in lane 1
  User: nullptr
  Use: %1
  ExtractElement cost for i32 from <4 x i32>: 4
SLP: Computing cost for external use of TreeEntry 0 in lane 2
  User: nullptr
  Use: %2
  ExtractElement cost for i32 from <4 x i32>: 4
SLP: Computing cost for external use of TreeEntry 0 in lane 3
  User: nullptr
  Use: %3
  ExtractElement cost for i32 from <4 x i32>: 4
SLP: Spill Cost = 0.
SLP: Extract Cost = 16.
SLP: Total Cost = -48.

The extract cost is no longer being scaled, so we incorrectly think the total cost is negative.

Adding some extra debug printing, it looks like what's going on is that in SLPVectorize.cpp in

    if (!ExternalUsesAsOriginalScalar.contains(EU.Scalar)) {
      if (ExtraCost.isValid() && ExtraCost != 0) {
        BasicBlock *ExtractBB = ScalarToExtractBlock.lookup(EU.Scalar);
        if (const Loop *L = ExtractBB ? LI->getLoopFor(ExtractBB) : nullptr) {
          uint64_t Scale = getLoopNestScale(
              findInnermostNonInvariantLoop(L, ArrayRef<Value *>(EU.Scalar)));
          LLVM_DEBUG(dbgs()
                     << "SLP: Extract scale " << Scale << " (NCD block) for "
                     << EU.Scalar->getNameOrAsOperand() << "\n");
          ExtraCost *= Scale;
        }

ExtractBB is null, so we don't calculate and multiply by Scale.

alexey-bataev added a commit that referenced this pull request Jun 2, 2026
The NCD pre-pass from #199962 only records extract blocks for external
uses that have a real user, so uses with a nullptr user were left
unscaled, making the loop in the report look profitable and get wrongly
vectorized. For a nullptr user vectorizeTree() places the extract right
after the vectorized instruction (entry block) and RAUWs the scalar, so
scale those extracts by the entry block frequency, restoring the
pre-#199962 behavior for that case.

Reviewers: 

Pull Request: #201193
@john-brawn-arm

Copy link
Copy Markdown
Contributor

This is causing some performance regressions where it looks like in some cases the extract costs aren't being scaled correctly.

#201193 has fixed this.

yingopq pushed a commit to yingopq/llvm-project that referenced this pull request Jun 5, 2026
The NCD pre-pass from llvm#199962 only records extract blocks for external
uses that have a real user, so uses with a nullptr user were left
unscaled, making the loop in the report look profitable and get wrongly
vectorized. For a nullptr user vectorizeTree() places the extract right
after the vectorized instruction (entry block) and RAUWs the scalar, so
scale those extracts by the entry block frequency, restoring the
pre-llvm#199962 behavior for that case.

Reviewers: 

Pull Request: llvm#201193
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[SLP][AArch64] #187391 regresses benchmark by 23% on Neoverse-N1

3 participants