[llvm] [VectorCombine] Don't fold non-idempotent shuffle reductions when shuffle duplicates element (PR #200778)

via llvm-commits llvm-commits at lists.llvm.org
Mon Jun 1 03:48:13 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-transforms

Author: lijinpei-amd

<details>
<summary>Changes</summary>

For odd vec-size, the shuffle scheme duplicates elements, making the transformation not equivalent when reduction-op non-idempotent.


---
Full diff: https://github.com/llvm/llvm-project/pull/200778.diff


2 Files Affected:

- (modified) llvm/lib/Transforms/Vectorize/VectorCombine.cpp (+8) 
- (modified) llvm/test/Transforms/VectorCombine/fold-shuffle-chains-to-reduce.ll (+44-4) 


``````````diff
diff --git a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
index da8a52a6b3dca..b3ad21fe1115b 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -4002,6 +4002,7 @@ bool VectorCombine::foldShuffleChainsToReduce(Instruction &I) {
   InstWorklist.push(VecOpEE);
 
   bool IsPartialReduction = false;
+  bool HasLaneDuplication = false;
 
   while (!InstWorklist.empty()) {
     Value *CI = InstWorklist.front();
@@ -4121,6 +4122,7 @@ bool VectorCombine::foldShuffleChainsToReduce(Instruction &I) {
       // Update mask values.
       ShuffleMaskHalf *= 2;
       ShuffleMaskHalf -= (ExpectedParityMask & 1);
+      HasLaneDuplication |= (ExpectedParityMask & 1) != 0;
       ExpectedParityMask >>= 1;
 
       OrigCost += TTI.getShuffleCost(TargetTransformInfo::SK_PermuteSingleSrc,
@@ -4152,6 +4154,12 @@ bool VectorCombine::foldShuffleChainsToReduce(Instruction &I) {
   if (ShouldBeCallOrBinInst && !IsPartialReduction)
     return false;
 
+  // If the parity masks duplicated any lane, the fold only preserves semantics
+  // for idempotent ops.
+  if (HasLaneDuplication && CommonBinOp &&
+      !Instruction::isIdempotent(*CommonBinOp))
+    return false;
+
   assert(VecSize != -1 && "Expected Match for Vector Size");
 
   Value *FinalVecV = PrevVecV[0];
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 e4cb41b238cf1..8111309dc5f53 100644
--- a/llvm/test/Transforms/VectorCombine/fold-shuffle-chains-to-reduce.ll
+++ b/llvm/test/Transforms/VectorCombine/fold-shuffle-chains-to-reduce.ll
@@ -47,11 +47,17 @@ define i16 @test_reduce_v3i16_and(<3 x i16> %a0) {
   ret i16 %5
 }
 
-define i16 @test_reduce_v6i16_xor(<6 x i16> %a0) {
-; CHECK-LABEL: define i16 @test_reduce_v6i16_xor(
+define i16 @test_no_reduce_v6i16_xor(<6 x i16> %a0) {
+; CHECK-LABEL: define i16 @test_no_reduce_v6i16_xor(
 ; CHECK-SAME: <6 x i16> [[A0:%.*]]) {
-; CHECK-NEXT:    [[TMP1:%.*]] = call i16 @llvm.vector.reduce.xor.v6i16(<6 x i16> [[A0]])
-; CHECK-NEXT:    ret i16 [[TMP1]]
+; CHECK-NEXT:    [[TMP1:%.*]] = shufflevector <6 x i16> [[A0]], <6 x i16> poison, <6 x i32> <i32 3, i32 4, i32 5, i32 poison, i32 poison, i32 poison>
+; CHECK-NEXT:    [[TMP2:%.*]] = xor <6 x i16> [[A0]], [[TMP1]]
+; CHECK-NEXT:    [[TMP3:%.*]] = shufflevector <6 x i16> [[TMP2]], <6 x i16> poison, <6 x i32> <i32 1, i32 2, i32 poison, i32 poison, i32 poison, i32 poison>
+; CHECK-NEXT:    [[TMP4:%.*]] = xor <6 x i16> [[TMP2]], [[TMP3]]
+; CHECK-NEXT:    [[TMP5:%.*]] = shufflevector <6 x i16> [[TMP4]], <6 x i16> poison, <6 x i32> <i32 1, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison>
+; CHECK-NEXT:    [[TMP6:%.*]] = xor <6 x i16> [[TMP4]], [[TMP5]]
+; CHECK-NEXT:    [[TMP7:%.*]] = extractelement <6 x i16> [[TMP6]], i64 0
+; CHECK-NEXT:    ret i16 [[TMP7]]
 ;
   %1 = shufflevector <6 x i16> %a0, <6 x i16> poison, <6 x i32> <i32 3, i32 4, i32 5, i32 poison, i32 poison, i32 poison>
   %2 = xor <6 x i16> %a0, %1
@@ -63,6 +69,40 @@ define i16 @test_reduce_v6i16_xor(<6 x i16> %a0) {
   ret i16 %7
 }
 
+; Non-idempotent op (add) on a non-power-of-2 vector: can't fold into reduction.
+define i32 @test_no_reduce_v3i32_add(<3 x i32> %a0) {
+; CHECK-LABEL: define i32 @test_no_reduce_v3i32_add(
+; CHECK-SAME: <3 x i32> [[A0:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = shufflevector <3 x i32> [[A0]], <3 x i32> poison, <3 x i32> <i32 1, i32 2, i32 poison>
+; CHECK-NEXT:    [[TMP2:%.*]] = add <3 x i32> [[A0]], [[TMP1]]
+; CHECK-NEXT:    [[TMP3:%.*]] = shufflevector <3 x i32> [[TMP2]], <3 x i32> poison, <3 x i32> <i32 1, i32 poison, i32 poison>
+; CHECK-NEXT:    [[TMP4:%.*]] = add <3 x i32> [[TMP2]], [[TMP3]]
+; CHECK-NEXT:    [[TMP5:%.*]] = extractelement <3 x i32> [[TMP4]], i64 0
+; CHECK-NEXT:    ret i32 [[TMP5]]
+;
+  %1 = shufflevector <3 x i32> %a0, <3 x i32> poison, <3 x i32> <i32 1, i32 2, i32 poison>
+  %2 = add <3 x i32> %a0, %1
+  %3 = shufflevector <3 x i32> %2, <3 x i32> poison, <3 x i32> <i32 1, i32 poison, i32 poison>
+  %4 = add <3 x i32> %2, %3
+  %5 = extractelement <3 x i32> %4, i64 0
+  ret i32 %5
+}
+
+; Idempotent op (smax) on a non-power-of-2 vector: lane duplication is harmless
+define i16 @test_reduce_v3i16_smax(<3 x i16> %a0) {
+; CHECK-LABEL: define i16 @test_reduce_v3i16_smax(
+; CHECK-SAME: <3 x i16> [[A0:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = call i16 @llvm.vector.reduce.smax.v3i16(<3 x i16> [[A0]])
+; CHECK-NEXT:    ret i16 [[TMP1]]
+;
+  %1 = shufflevector <3 x i16> %a0, <3 x i16> poison, <3 x i32> <i32 1, i32 2, i32 poison>
+  %2 = tail call <3 x i16> @llvm.smax.v3i16(<3 x i16> %a0, <3 x i16> %1)
+  %3 = shufflevector <3 x i16> %2, <3 x i16> poison, <3 x i32> <i32 1, i32 poison, i32 poison>
+  %4 = tail call <3 x i16> @llvm.smax.v3i16(<3 x i16> %2, <3 x i16> %3)
+  %5 = extractelement <3 x i16> %4, i64 0
+  ret i16 %5
+}
+
 define i16 @test_reduce_v8i16_2(<8 x i16> %a0) {
 ; CHECK-LABEL: define i16 @test_reduce_v8i16_2(
 ; CHECK-SAME: <8 x i16> [[A0:%.*]]) {

``````````

</details>


https://github.com/llvm/llvm-project/pull/200778


More information about the llvm-commits mailing list