[llvm] [VectorCombine] foldShuffleChainsToReduce - add support for partial v… (PR #195119)

via llvm-commits llvm-commits at lists.llvm.org
Thu Apr 30 09:11:31 PDT 2026


https://github.com/AbdallahRashed created https://github.com/llvm/llvm-project/pull/195119

…ector reductions

Extend foldShuffleChainsToReduce to recognize partial reduction patterns where only a subvector of the full vector is being reduced.

For example, a <16 x i16> vector where the shuffle chain only reduces the lower 8 elements can now be folded into:
  shufflevector (extract lower <8 x i16>) + vector.reduce.smax

The detection works by noticing when the bottom-up walk through the shuffle/op chain ends before consuming the full vector. The number of levels visited determines the subvector size (2^levels), and an extract_subvector + scalar reduction replaces the original chain when profitable.

Fixes #194617
Supported by: Claude Opus 4.6 High 

>From af9d40c1316d65c4454602bad350b033f7640bcb Mon Sep 17 00:00:00 2001
From: AbdallahRashed <abdallah.mrashed at gmail.com>
Date: Thu, 30 Apr 2026 17:48:18 +0200
Subject: [PATCH] [VectorCombine] foldShuffleChainsToReduce - add support for
 partial vector reductions

Extend foldShuffleChainsToReduce to recognize partial reduction patterns
where only a subvector of the full vector is being reduced.

For example, a <16 x i16> vector where the shuffle chain only reduces the
lower 8 elements can now be folded into:
  shufflevector (extract lower <8 x i16>) + vector.reduce.smax

The detection works by noticing when the bottom-up walk through the
shuffle/op chain ends before consuming the full vector. The number of
levels visited determines the subvector size (2^levels), and an
extract_subvector + scalar reduction replaces the original chain when
profitable.

Fixes #194617
---
 .../Transforms/Vectorize/VectorCombine.cpp    | 39 ++++++++++++---
 .../fold-shuffle-chains-to-reduce.ll          | 50 +++++++++++++++++++
 2 files changed, 83 insertions(+), 6 deletions(-)

diff --git a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
index 5ba344ea9a808..33e94414a0c1b 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -3995,6 +3995,8 @@ bool VectorCombine::foldShuffleChainsToReduce(Instruction &I) {
 
   InstWorklist.push(VecOpEE);
 
+  bool IsPartialReduction = false;
+
   while (!InstWorklist.empty()) {
     Value *CI = InstWorklist.front();
     InstWorklist.pop();
@@ -4125,12 +4127,19 @@ bool VectorCombine::foldShuffleChainsToReduce(Instruction &I) {
 
       ShouldBeCallOrBinInst ^= 1;
     } else {
+      // Check if this is a partial reduction - the chain ended because
+      // the source vector is not a recognized op/shuffle.
+      if (ShouldBeCallOrBinInst && VisitedCnt >= 1 && CI == PrevVecV[0]) {
+        IsPartialReduction = true;
+        break;
+      }
       return false;
     }
   }
 
-  // Pattern should end with a shuffle op.
-  if (ShouldBeCallOrBinInst)
+  // Full reduction pattern should end with a shuffle op.
+  // Partial reduction ends when the source vector is reached.
+  if (ShouldBeCallOrBinInst && !IsPartialReduction)
     return false;
 
   assert(VecSize != -1 && "Expected Match for Vector Size");
@@ -4147,14 +4156,32 @@ bool VectorCombine::foldShuffleChainsToReduce(Instruction &I) {
   if (!ReducedOp)
     return false;
 
-  IntrinsicCostAttributes ICA(ReducedOp, FinalVecVTy, {FinalVecV});
-  InstructionCost NewCost = TTI.getIntrinsicInstrCost(ICA, CostKind);
+  InstructionCost NewCost = 0;
+  FixedVectorType *ReduceVecTy = FinalVecVTy;
+
+  if (IsPartialReduction) {
+    unsigned SubVecSize = ShuffleMaskHalf;
+    ReduceVecTy = FixedVectorType::get(FVT->getElementType(), SubVecSize);
+    NewCost += TTI.getShuffleCost(TargetTransformInfo::SK_ExtractSubvector,
+                                  ReduceVecTy, FinalVecVTy, {}, CostKind, 0);
+  }
+
+  IntrinsicCostAttributes ICA(ReducedOp, ReduceVecTy, {ReduceVecTy});
+  NewCost += TTI.getIntrinsicInstrCost(ICA, CostKind);
 
   if (NewCost >= OrigCost)
     return false;
 
-  auto *ReducedResult =
-      Builder.CreateIntrinsic(ReducedOp, {FinalVecV->getType()}, {FinalVecV});
+  Value *ReduceInput = FinalVecV;
+  if (IsPartialReduction) {
+    SmallVector<int> ExtractMask(ReduceVecTy->getNumElements());
+    for (int I = 0, E = ExtractMask.size(); I < E; ++I)
+      ExtractMask[I] = I;
+    ReduceInput = Builder.CreateShuffleVector(FinalVecV, ExtractMask);
+  }
+
+  auto *ReducedResult = Builder.CreateIntrinsic(
+      ReducedOp, {ReduceInput->getType()}, {ReduceInput});
   replaceValue(I, *ReducedResult);
 
   return true;
diff --git a/llvm/test/Transforms/VectorCombine/fold-shuffle-chains-to-reduce.ll b/llvm/test/Transforms/VectorCombine/fold-shuffle-chains-to-reduce.ll
index 403ce33b5344e..71809534016d1 100644
--- a/llvm/test/Transforms/VectorCombine/fold-shuffle-chains-to-reduce.ll
+++ b/llvm/test/Transforms/VectorCombine/fold-shuffle-chains-to-reduce.ll
@@ -193,3 +193,53 @@ define i16 @test_reduce_v6i16_xor_neg(<6 x i16> %a0) {
   %7 = extractelement <6 x i16> %6, i64 0
   ret i16 %7
 }
+
+; Partial reduction: reduce lower 8 elements of a 16-element vector using smax.
+define i16 @test_partial_reduce_v16i16_v8i16_smax(<16 x i16> %a0) {
+; CHECK-LABEL: define i16 @test_partial_reduce_v16i16_v8i16_smax(
+; CHECK-SAME: <16 x i16> [[A0:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = shufflevector <16 x i16> [[A0]], <16 x i16> poison, <8 x i32> <i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7>
+; CHECK-NEXT:    [[TMP2:%.*]] = call i16 @llvm.vector.reduce.smax.v8i16(<8 x i16> [[TMP1]])
+; CHECK-NEXT:    ret i16 [[TMP2]]
+;
+  %1 = shufflevector <16 x i16> %a0, <16 x i16> poison, <16 x i32> <i32 4, i32 5, i32 6, i32 7, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison>
+  %2 = tail call <16 x i16> @llvm.smax.v16i16(<16 x i16> %a0, <16 x i16> %1)
+  %3 = shufflevector <16 x i16> %2, <16 x i16> poison, <16 x i32> <i32 2, i32 3, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison>
+  %4 = tail call <16 x i16> @llvm.smax.v16i16(<16 x i16> %2, <16 x i16> %3)
+  %5 = shufflevector <16 x i16> %4, <16 x i16> poison, <16 x i32> <i32 1, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison>
+  %6 = tail call <16 x i16> @llvm.smax.v16i16(<16 x i16> %4, <16 x i16> %5)
+  %7 = extractelement <16 x i16> %6, i64 0
+  ret i16 %7
+}
+
+; Partial reduction: reduce lower 4 elements of an 8-element vector using add.
+define i32 @test_partial_reduce_v8i32_v4i32_add(<8 x i32> %a0) {
+; CHECK-LABEL: define i32 @test_partial_reduce_v8i32_v4i32_add(
+; CHECK-SAME: <8 x i32> [[A0:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = shufflevector <8 x i32> [[A0]], <8 x i32> poison, <4 x i32> <i32 0, i32 1, i32 2, i32 3>
+; CHECK-NEXT:    [[TMP2:%.*]] = call i32 @llvm.vector.reduce.add.v4i32(<4 x i32> [[TMP1]])
+; CHECK-NEXT:    ret i32 [[TMP2]]
+;
+  %1 = shufflevector <8 x i32> %a0, <8 x i32> poison, <8 x i32> <i32 2, i32 3, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison>
+  %2 = add <8 x i32> %a0, %1
+  %3 = shufflevector <8 x i32> %2, <8 x i32> poison, <8 x i32> <i32 1, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison>
+  %4 = add <8 x i32> %2, %3
+  %5 = extractelement <8 x i32> %4, i64 0
+  ret i32 %5
+}
+
+; Partial reduction: reduce lower 4 elements of a 16-element vector using umin.
+define i16 @test_partial_reduce_v16i16_v4i16_umin(<16 x i16> %a0) {
+; CHECK-LABEL: define i16 @test_partial_reduce_v16i16_v4i16_umin(
+; CHECK-SAME: <16 x i16> [[A0:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = shufflevector <16 x i16> [[A0]], <16 x i16> poison, <4 x i32> <i32 0, i32 1, i32 2, i32 3>
+; CHECK-NEXT:    [[TMP2:%.*]] = call i16 @llvm.vector.reduce.umin.v4i16(<4 x i16> [[TMP1]])
+; CHECK-NEXT:    ret i16 [[TMP2]]
+;
+  %1 = shufflevector <16 x i16> %a0, <16 x i16> poison, <16 x i32> <i32 2, i32 3, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison>
+  %2 = tail call <16 x i16> @llvm.umin.v16i16(<16 x i16> %a0, <16 x i16> %1)
+  %3 = shufflevector <16 x i16> %2, <16 x i16> poison, <16 x i32> <i32 1, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison>
+  %4 = tail call <16 x i16> @llvm.umin.v16i16(<16 x i16> %2, <16 x i16> %3)
+  %5 = extractelement <16 x i16> %4, i64 0
+  ret i16 %5
+}



More information about the llvm-commits mailing list