Skip to content

[X86] combineShiftRightLogical - fold srl(vecreduce_umax(x),bw-1) as MOVMSK signbit reduction - #210281

Merged
RKSimon merged 4 commits into
llvm:mainfrom
RKSimon:x86-pr209714
Jul 20, 2026
Merged

[X86] combineShiftRightLogical - fold srl(vecreduce_umax(x),bw-1) as MOVMSK signbit reduction#210281
RKSimon merged 4 commits into
llvm:mainfrom
RKSimon:x86-pr209714

Conversation

@RKSimon

@RKSimon RKSimon commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

VectorCombine may have folded:
icmp_eq(vecreduce_or(splatsign(x)),0) --> icmp_sgt(vecreduce_umax(x),-1)

which DAG folds to:
srl(vecreduce_umax(x),bw-1).

This match attempts to lower:
srl(vecreduce_umax(x),bw-1) --> icmp_ne(movmsk(x),0) "any_of negative"
srl(not(vecreduce_umax(x)),bw-1) --> icmp_eq(movmsk(x),0) "none_of negative"

The correct fix would be to improve vecreduce_or costs to prevent VectorCombine doing this, but that change is far too big to be merged into 23.x - so I've created the narrow backend fix.

Fixes #209714

@RKSimon
RKSimon requested review from nikic and phoebewang July 17, 2026 09:07
@RKSimon

RKSimon commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

An alternative would be to remove the VectorCombine folds from 23.x - I'm not entirely happy with how constrained I had to make this backend patch, and until costs are improved VectorCombine is going to do some silly things......

@RKSimon
RKSimon marked this pull request as draft July 17, 2026 09:39
@RKSimon RKSimon changed the title [X86] combineShiftRightLogical - fold srl(vecreduce_umax(x),bw-1) as MOVMSK signbit reduction [WIP][X86] combineShiftRightLogical - fold srl(vecreduce_umax(x),bw-1) as MOVMSK signbit reduction Jul 17, 2026
@RKSimon

RKSimon commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

Converted to draft while I see if I can find a compromise vectorcombine patch

@llvmorg-github-actions

Copy link
Copy Markdown

@llvm/pr-subscribers-backend-x86

Author: Simon Pilgrim (RKSimon)

Changes

VectorCombine may have folded: icmp_eq(vecreduce_or(splatsign(x)),0) --> icmp_sgt(vecreduce_umax(x),-1) which DAG folds to: srl(vecreduce_umax(x),bw-1).

This match attempts to lower:
srl(vecreduce_umax(x),bw-1) -> icmp_ne(movmsk(x),0) "any_of negative"
srl(not(vecreduce_umax(x)),bw-1) -> icmp_eq(movmsk(x),0) "none_of negative"

The correct fix would be to improve vecreduce_or costs to prevent VectorCombine doing this, but that change is far too big to be merged into 23.x - so I've created the narrow vXi32 backend fix.

Fixes #209714


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

2 Files Affected:

  • (modified) llvm/lib/Target/X86/X86ISelLowering.cpp (+20)
  • (modified) llvm/test/CodeGen/X86/vector-reduce-umax.ll (+138-10)
diff --git a/llvm/lib/Target/X86/X86ISelLowering.cpp b/llvm/lib/Target/X86/X86ISelLowering.cpp
index 97e783c09f0f2..c2278183fa133 100644
--- a/llvm/lib/Target/X86/X86ISelLowering.cpp
+++ b/llvm/lib/Target/X86/X86ISelLowering.cpp
@@ -50910,6 +50910,26 @@ static SDValue combineShiftRightLogical(SDNode *N, SelectionDAG &DAG,
     }
   }
 
+  // VectorCombine may have folded:
+  // icmp_eq(vecreduce_or(splatsign(x)),0) --> icmp_sgt(vecreduce_umax(x),-1)
+  // which DAG folds to: srl(vecreduce_umax(x),bw-1).
+  // This attempts to reconstruct the signbit reduction.
+  if (sd_match(N1, m_SpecificInt(EltSizeInBits - 1))) {
+    SDValue X = N0;
+    ISD::CondCode CC = ISD::SETNE;
+    if (sd_match(N0, m_Not(m_Value(X))))
+      CC = ISD::SETEQ;
+    if (X.getOpcode() == ISD::VECREDUCE_UMAX) {
+      SDValue V = X.getOperand(0);
+      if (DAG.getTargetLoweringInfo().isTypeLegal(V.getValueType()) &&
+          (V.getValueType() == MVT::v4i32 || V.getValueType() == MVT::v8i32)) {
+        V = DAG.getNode(X86ISD::MOVMSK, DL, MVT::i32, V);
+        V = DAG.getSetCC(DL, MVT::i8, V, DAG.getConstant(0, DL, MVT::i32), CC);
+        return DAG.getZExtOrTrunc(V, DL, VT);
+      }
+    }
+  }
+
   // Only do this on the last DAG combine as it can interfere with other
   // combines.
   if (!DCI.isAfterLegalizeDAG())
diff --git a/llvm/test/CodeGen/X86/vector-reduce-umax.ll b/llvm/test/CodeGen/X86/vector-reduce-umax.ll
index b45c2914bdccd..ca252968b4898 100644
--- a/llvm/test/CodeGen/X86/vector-reduce-umax.ll
+++ b/llvm/test/CodeGen/X86/vector-reduce-umax.ll
@@ -1,14 +1,14 @@
 ; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
-; RUN: llc < %s -mtriple=i686--   -mattr=+sse2     | FileCheck %s --check-prefixes=SSE,SSE2,X86-SSE2
-; RUN: llc < %s -mtriple=x86_64-- -mattr=+sse2     | FileCheck %s --check-prefixes=SSE,SSE2,X64-SSE2
-; RUN: llc < %s -mtriple=i686--   -mattr=+sse4.1   | FileCheck %s --check-prefixes=SSE,SSE4,X86-SSE4,X86-SSE41
-; RUN: llc < %s -mtriple=x86_64-- -mattr=+sse4.1   | FileCheck %s --check-prefixes=SSE,SSE4,X64-SSE4,X64-SSE41
-; RUN: llc < %s -mtriple=i686--   -mattr=+sse4.2   | FileCheck %s --check-prefixes=SSE,SSE4,X86-SSE4,X86-SSE42
-; RUN: llc < %s -mtriple=x86_64-- -mattr=+sse4.2   | FileCheck %s --check-prefixes=SSE,SSE4,X64-SSE4,X64-SSE42
-; RUN: llc < %s -mtriple=i686--   -mattr=+avx      | FileCheck %s --check-prefixes=AVX,AVX1,X86-AVX1
-; RUN: llc < %s -mtriple=x86_64-- -mattr=+avx      | FileCheck %s --check-prefixes=AVX,AVX1,X64-AVX1
-; RUN: llc < %s -mtriple=i686--   -mattr=+avx2     | FileCheck %s --check-prefixes=AVX,AVX2,X86-AVX2
-; RUN: llc < %s -mtriple=x86_64-- -mattr=+avx2     | FileCheck %s --check-prefixes=AVX,AVX2,X64-AVX2
+; RUN: llc < %s -mtriple=i686--   -mattr=+sse2     | FileCheck %s --check-prefixes=SSE,SSE2,X86-SSE,X86-SSE2
+; RUN: llc < %s -mtriple=x86_64-- -mattr=+sse2     | FileCheck %s --check-prefixes=SSE,SSE2,X64-SSE,X64-SSE2
+; RUN: llc < %s -mtriple=i686--   -mattr=+sse4.1   | FileCheck %s --check-prefixes=SSE,SSE4,X86-SSE,X86-SSE4,X86-SSE41
+; RUN: llc < %s -mtriple=x86_64-- -mattr=+sse4.1   | FileCheck %s --check-prefixes=SSE,SSE4,X64-SSE,X64-SSE4,X64-SSE41
+; RUN: llc < %s -mtriple=i686--   -mattr=+sse4.2   | FileCheck %s --check-prefixes=SSE,SSE4,X86-SSE,X86-SSE4,X86-SSE42
+; RUN: llc < %s -mtriple=x86_64-- -mattr=+sse4.2   | FileCheck %s --check-prefixes=SSE,SSE4,X64-SSE,X64-SSE4,X64-SSE42
+; RUN: llc < %s -mtriple=i686--   -mattr=+avx      | FileCheck %s --check-prefixes=AVX,AVX1,X86-AVX,X86-AVX1
+; RUN: llc < %s -mtriple=x86_64-- -mattr=+avx      | FileCheck %s --check-prefixes=AVX,AVX1,X64-AVX,X64-AVX1
+; RUN: llc < %s -mtriple=i686--   -mattr=+avx2     | FileCheck %s --check-prefixes=AVX,AVX2,X86-AVX,X86-AVX2
+; RUN: llc < %s -mtriple=x86_64-- -mattr=+avx2     | FileCheck %s --check-prefixes=AVX,AVX2,X64-AVX,X64-AVX2
 ; RUN: llc < %s -mtriple=x86_64-- -mattr=+avx512f,+avx512bw | FileCheck %s --check-prefixes=AVX,AVX512,AVX512BW
 ; RUN: llc < %s -mtriple=x86_64-- -mattr=+avx512f,+avx512bw,+avx512dq,+avx512vl | FileCheck %s --check-prefixes=AVX,AVX512,AVX512VL
 
@@ -3504,6 +3504,134 @@ define i8 @test_v128i8(<128 x i8> %a0) nounwind {
   ret i8 %1
 }
 
+; PR209714
+define i32 @reduce_umax_v4i32_signbit(ptr %pa, ptr %pb) {
+; X86-SSE-LABEL: reduce_umax_v4i32_signbit:
+; X86-SSE:       # %bb.0:
+; X86-SSE-NEXT:    movl {{[0-9]+}}(%esp), %eax
+; X86-SSE-NEXT:    movl {{[0-9]+}}(%esp), %ecx
+; X86-SSE-NEXT:    movaps (%ecx), %xmm0
+; X86-SSE-NEXT:    andps (%eax), %xmm0
+; X86-SSE-NEXT:    movmskps %xmm0, %ecx
+; X86-SSE-NEXT:    xorl %eax, %eax
+; X86-SSE-NEXT:    testl %ecx, %ecx
+; X86-SSE-NEXT:    sete %al
+; X86-SSE-NEXT:    retl
+;
+; X64-SSE-LABEL: reduce_umax_v4i32_signbit:
+; X64-SSE:       # %bb.0:
+; X64-SSE-NEXT:    movaps (%rsi), %xmm0
+; X64-SSE-NEXT:    andps (%rdi), %xmm0
+; X64-SSE-NEXT:    movmskps %xmm0, %ecx
+; X64-SSE-NEXT:    xorl %eax, %eax
+; X64-SSE-NEXT:    testl %ecx, %ecx
+; X64-SSE-NEXT:    sete %al
+; X64-SSE-NEXT:    retq
+;
+; X86-AVX-LABEL: reduce_umax_v4i32_signbit:
+; X86-AVX:       # %bb.0:
+; X86-AVX-NEXT:    movl {{[0-9]+}}(%esp), %ecx
+; X86-AVX-NEXT:    movl {{[0-9]+}}(%esp), %eax
+; X86-AVX-NEXT:    vmovaps (%eax), %xmm0
+; X86-AVX-NEXT:    xorl %eax, %eax
+; X86-AVX-NEXT:    vtestps (%ecx), %xmm0
+; X86-AVX-NEXT:    sete %al
+; X86-AVX-NEXT:    retl
+;
+; X64-AVX-LABEL: reduce_umax_v4i32_signbit:
+; X64-AVX:       # %bb.0:
+; X64-AVX-NEXT:    vmovaps (%rdi), %xmm0
+; X64-AVX-NEXT:    xorl %eax, %eax
+; X64-AVX-NEXT:    vtestps (%rsi), %xmm0
+; X64-AVX-NEXT:    sete %al
+; X64-AVX-NEXT:    retq
+;
+; AVX512-LABEL: reduce_umax_v4i32_signbit:
+; AVX512:       # %bb.0:
+; AVX512-NEXT:    vmovaps (%rdi), %xmm0
+; AVX512-NEXT:    xorl %eax, %eax
+; AVX512-NEXT:    vtestps (%rsi), %xmm0
+; AVX512-NEXT:    sete %al
+; AVX512-NEXT:    retq
+  %a = load <4 x i32>, ptr %pa
+  %b = load <4 x i32>, ptr %pb
+  %a.not = xor <4 x i32> %a, splat (i32 -1)
+  %and = and <4 x i32> %b, %a
+  %rdx = call i32 @llvm.vector.reduce.umax.v4i32(<4 x i32> %and)
+  %cmp = icmp sgt i32 %rdx, -1
+  %res = zext i1 %cmp to i32
+  ret i32 %res
+}
+
+
+; PR209714
+define i32 @reduce_umax_v8i32_signbit_not(ptr %pa, ptr %pb) {
+; X86-SSE-LABEL: reduce_umax_v8i32_signbit_not:
+; X86-SSE:       # %bb.0:
+; X86-SSE-NEXT:    movl {{[0-9]+}}(%esp), %eax
+; X86-SSE-NEXT:    movl {{[0-9]+}}(%esp), %ecx
+; X86-SSE-NEXT:    movaps (%ecx), %xmm0
+; X86-SSE-NEXT:    movaps 16(%ecx), %xmm1
+; X86-SSE-NEXT:    andnps 16(%eax), %xmm1
+; X86-SSE-NEXT:    andnps (%eax), %xmm0
+; X86-SSE-NEXT:    orps %xmm1, %xmm0
+; X86-SSE-NEXT:    movmskps %xmm0, %ecx
+; X86-SSE-NEXT:    xorl %eax, %eax
+; X86-SSE-NEXT:    testl %ecx, %ecx
+; X86-SSE-NEXT:    sete %al
+; X86-SSE-NEXT:    retl
+;
+; X64-SSE-LABEL: reduce_umax_v8i32_signbit_not:
+; X64-SSE:       # %bb.0:
+; X64-SSE-NEXT:    movaps (%rdi), %xmm0
+; X64-SSE-NEXT:    movaps 16(%rdi), %xmm1
+; X64-SSE-NEXT:    andnps 16(%rsi), %xmm1
+; X64-SSE-NEXT:    andnps (%rsi), %xmm0
+; X64-SSE-NEXT:    orps %xmm1, %xmm0
+; X64-SSE-NEXT:    movmskps %xmm0, %ecx
+; X64-SSE-NEXT:    xorl %eax, %eax
+; X64-SSE-NEXT:    testl %ecx, %ecx
+; X64-SSE-NEXT:    sete %al
+; X64-SSE-NEXT:    retq
+;
+; X86-AVX-LABEL: reduce_umax_v8i32_signbit_not:
+; X86-AVX:       # %bb.0:
+; X86-AVX-NEXT:    movl {{[0-9]+}}(%esp), %ecx
+; X86-AVX-NEXT:    movl {{[0-9]+}}(%esp), %eax
+; X86-AVX-NEXT:    vmovaps (%eax), %ymm0
+; X86-AVX-NEXT:    xorl %eax, %eax
+; X86-AVX-NEXT:    vtestps (%ecx), %ymm0
+; X86-AVX-NEXT:    setb %al
+; X86-AVX-NEXT:    vzeroupper
+; X86-AVX-NEXT:    retl
+;
+; X64-AVX-LABEL: reduce_umax_v8i32_signbit_not:
+; X64-AVX:       # %bb.0:
+; X64-AVX-NEXT:    vmovaps (%rdi), %ymm0
+; X64-AVX-NEXT:    xorl %eax, %eax
+; X64-AVX-NEXT:    vtestps (%rsi), %ymm0
+; X64-AVX-NEXT:    setb %al
+; X64-AVX-NEXT:    vzeroupper
+; X64-AVX-NEXT:    retq
+;
+; AVX512-LABEL: reduce_umax_v8i32_signbit_not:
+; AVX512:       # %bb.0:
+; AVX512-NEXT:    vmovaps (%rdi), %ymm0
+; AVX512-NEXT:    xorl %eax, %eax
+; AVX512-NEXT:    vtestps (%rsi), %ymm0
+; AVX512-NEXT:    setb %al
+; AVX512-NEXT:    vzeroupper
+; AVX512-NEXT:    retq
+  %a = load <8 x i32>, ptr %pa
+  %b = load <8 x i32>, ptr %pb
+  %a.not = xor <8 x i32> %a, splat (i32 -1)
+  %and = and <8 x i32> %b, %a.not
+  %rdx = call i32 @llvm.vector.reduce.umax.v8i32(<8 x i32> %and)
+  %cmp = icmp sgt i32 %rdx, -1
+  %res = zext i1 %cmp to i32
+  ret i32 %res
+}
+
 declare i64 @llvm.vector.reduce.umax.v2i64(<2 x i64>)
 declare i64 @llvm.vector.reduce.umax.v4i64(<4 x i64>)
 declare i64 @llvm.vector.reduce.umax.v8i64(<8 x i64>)

RKSimon added 2 commits July 17, 2026 18:56
…MOVMSK signbit reduction

VectorCombine may have folded: icmp_eq(vecreduce_or(splatsign(x)),0) --> icmp_sgt(vecreduce_umax(x),-1) which DAG folds to: srl(vecreduce_umax(x),bw-1).

This match attempts to lower:
srl(vecreduce_umax(x),bw-1) --> icmp_ne(movmsk(x),z) "any_of negative"
srl(not(vecreduce_umax(x)),bw-1) --> icmp_eq(movmsk(x),z) "none_of negative"

The correct fix would be to improve vecreduce_or costs to prevent VectorCombine doing this, but that change is far too big to be merged into 23.x - so I've created a more narrow backend fix.

Fixes llvm#209714
@RKSimon
RKSimon requested a review from topperc July 17, 2026 18:15
@RKSimon
RKSimon marked this pull request as ready for review July 17, 2026 18:15
@RKSimon RKSimon changed the title [WIP][X86] combineShiftRightLogical - fold srl(vecreduce_umax(x),bw-1) as MOVMSK signbit reduction [X86] combineShiftRightLogical - fold srl(vecreduce_umax(x),bw-1) as MOVMSK signbit reduction Jul 17, 2026
} else if (VecVT == MVT::v8i16) {
V = DAG.getNode(X86ISD::PACKSS, DL, MVT::v16i8, V, V);
}
V = getPMOVMSKB(DL, V, DAG, Subtarget);

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.

IIRC, getPMOVMSKB only handles vXi8 type, how vXi32/vXi64 get lowered by this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Its a badly named function - it was originally added to handle v32i8/v64i8 splitting, but has been used for more general MOVMSK lowering for some time. If you want I can rename it (and add some much needed asserts), but was trying to minimise churn for a release patch.

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.

I see. I agree it's good to do it in a follow up.

Comment thread llvm/lib/Target/X86/X86ISelLowering.cpp Outdated
SDValue V = X.getOperand(0);
EVT VecVT = V.getValueType();
if (DAG.getTargetLoweringInfo().isTypeLegal(VecVT) &&
(VecVT.is128BitVector() || VecVT.is256BitVector())) {

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.

Why exclude 512-bit?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'll add test coverage to check how bad 512-bit cases look like

@RKSimon
RKSimon requested a review from phoebewang July 20, 2026 09:25

@phoebewang phoebewang 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.

@RKSimon
RKSimon merged commit 8abc269 into llvm:main Jul 20, 2026
11 of 12 checks passed
@RKSimon
RKSimon deleted the x86-pr209714 branch July 20, 2026 12:08
dyung pushed a commit to llvmbot/llvm-project that referenced this pull request Jul 22, 2026
…MOVMSK signbit reduction (llvm#210281)

VectorCombine may have folded:
  icmp_eq(vecreduce_or(splatsign(x)),0) --> icmp_sgt(vecreduce_umax(x),-1)

which DAG folds to:
  srl(vecreduce_umax(x),bw-1).

This match attempts to lower:
  srl(vecreduce_umax(x),bw-1) --> icmp_ne(movmsk(x),0) "any_of negative"
  srl(not(vecreduce_umax(x)),bw-1) --> icmp_eq(movmsk(x),0) "none_of negative"

The correct fix would be to improve vecreduce_or costs to prevent
VectorCombine doing this, but that change is far too big to be merged
into 23.x - so I've created the narrow backend fix.

Fixes llvm#209714

(cherry picked from commit 8abc269)
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.

[X86] New VectorCombine regresses vtestps matching

2 participants