[llvm] [X86] Narrow vXi32/vXi64 usubsat to vpsubusb/vpsubusw when LHS is known to fit in fewer bits (PR #206592)

via llvm-commits llvm-commits at lists.llvm.org
Mon Aug 17 20:59:59 PDT 2026


https://github.com/sipher-01 updated https://github.com/llvm/llvm-project/pull/206592

>From dc3906c331bca4e62f4b8f3be2eb684c89e47527 Mon Sep 17 00:00:00 2001
From: Sipher <sourav2003singhkatoch at gmail.com>
Date: Tue, 30 Jun 2026 03:04:39 +0530
Subject: [PATCH] [X86] Narrow vXi32/vXi64 USUBSAT to vXi8/vXi16 when LHS fits
 in fewer bits

When countMaxActiveBits(LHS) <= 8 or <= 16, reinterpret a wide vector
USUBSAT as vpsubusb/vpsubusw by OR-ing the upper bits of the RHS with 1s
so those byte/word lanes saturate to zero harmlessly.

This turns llvm.usub.sat.v8i64 emulation (vpmaxuq + vpsubq, 2 insns)
into a single vpsubusb when the LHS is known to fit in 8 bits.

Fixes https://github.com/llvm/llvm-project/issues/195462
---
 llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp |  35 ++
 llvm/test/CodeGen/X86/usubsat-narrow.ll       | 406 ++++++++++++++++++
 2 files changed, 441 insertions(+)
 create mode 100644 llvm/test/CodeGen/X86/usubsat-narrow.ll

diff --git a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
index c590f3c189ece..b61d755719464 100644
--- a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
@@ -4709,6 +4709,41 @@ SDValue DAGCombiner::visitSUBSAT(SDNode *N) {
   if (DAG.willNotOverflowSub(IsSigned, N0, N1))
     return DAG.getNode(ISD::SUB, DL, VT, N0, N1);
 
+  // Narrow a vXiN USUBSAT to a smaller type when both operands are known
+  // to fit in fewer bits. This allows targets with native narrow USUBSAT
+  // (e.g. vpsubusb/vpsubusw) to avoid emulation with vpmaxu + vsub.
+  if (!IsSigned && VT.isVector() && VT.isSimple()) {
+    unsigned ScalarBits = VT.getScalarSizeInBits();
+    if (ScalarBits > 8 && isPowerOf2_32(ScalarBits) &&
+        !TLI.isOperationLegal(ISD::USUBSAT, VT)) {
+      KnownBits Known0 = DAG.computeKnownBits(N0);
+      unsigned ActiveBits = Known0.countMaxActiveBits();
+      for (unsigned NarrowBits = PowerOf2Ceil(ActiveBits); NarrowBits < ScalarBits; NarrowBits *= 2) {
+        unsigned Scale = ScalarBits / NarrowBits;
+        unsigned NumElts = VT.getVectorNumElements() * Scale;
+        MVT NarrowSVT = MVT::getIntegerVT(NarrowBits);
+        MVT NarrowVT = MVT::getVectorVT(NarrowSVT, NumElts);
+
+        if (!TLI.isOperationLegalOrCustom(ISD::USUBSAT, NarrowVT))
+          continue;
+        KnownBits Known1 = DAG.computeKnownBits(N1);
+        if (Known1.countMaxActiveBits() <= NarrowBits) {
+          SDValue NarrowN0 = DAG.getBitcast(NarrowVT, N0);
+          SDValue NarrowN1 = DAG.getBitcast(NarrowVT, N1);
+          SDValue NarrowSub =
+              DAG.getNode(ISD::USUBSAT, DL, NarrowVT, NarrowN0, NarrowN1);
+          return DAG.getBitcast(VT, NarrowSub);
+        }
+        // TODO: If N1 doesn't fit in NarrowBits, we could OR the upper bits
+        // of N1 with 1s to force saturation in those lanes, allowing the
+        // narrow USUBSAT to still be used. This requires a TLI hook to check
+        // whether the constant can be folded as a broadcast memory operand
+        // (profitable on AVX512, not on SSE/AVX), to avoid introducing an
+        // extra register and instruction on non-AVX512 targets.
+        break;
+      }
+    }
+  }
   return SDValue();
 }
 
diff --git a/llvm/test/CodeGen/X86/usubsat-narrow.ll b/llvm/test/CodeGen/X86/usubsat-narrow.ll
new file mode 100644
index 0000000000000..31fd26cd5effe
--- /dev/null
+++ b/llvm/test/CodeGen/X86/usubsat-narrow.ll
@@ -0,0 +1,406 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; RUN: llc < %s -mtriple=x86_64-unknown-unknown -mcpu=x86-64 | FileCheck %s --check-prefix=SSE2
+; RUN: llc < %s -mtriple=x86_64-unknown-unknown -mcpu=x86-64-v2 | FileCheck %s --check-prefix=SSE42
+; RUN: llc < %s -mtriple=x86_64-unknown-unknown -mcpu=x86-64-v3| FileCheck %s --check-prefix=AVX2
+; RUN: llc < %s -mtriple=x86_64-unknown-unknown -mcpu=x86-64-v4 | FileCheck %s --check-prefix=AVX512
+
+; FIXME: LHS is known to fit in 8 bits but RHS is unknown — should fold to
+; vpsubusb once Path B (OR-mask narrowing) is implemented.
+define <8 x i64> @usubsat_v8i64_i8_narrow(<8 x i64> %x, <8 x i64> %y) nounwind {
+; SSE2-LABEL: usubsat_v8i64_i8_narrow:
+; SSE2:       # %bb.0:
+; SSE2-NEXT:    movdqa {{.*#+}} xmm8 = [255,0,0,0,0,0,0,0,255,0,0,0,0,0,0,0]
+; SSE2-NEXT:    pand %xmm8, %xmm3
+; SSE2-NEXT:    pand %xmm8, %xmm2
+; SSE2-NEXT:    pand %xmm8, %xmm1
+; SSE2-NEXT:    pand %xmm8, %xmm0
+; SSE2-NEXT:    movdqa {{.*#+}} xmm8 = [9223372039002259456,9223372039002259456]
+; SSE2-NEXT:    movdqa %xmm0, %xmm9
+; SSE2-NEXT:    psubq %xmm4, %xmm0
+; SSE2-NEXT:    pxor %xmm8, %xmm4
+; SSE2-NEXT:    por %xmm8, %xmm9
+; SSE2-NEXT:    movdqa %xmm9, %xmm10
+; SSE2-NEXT:    pcmpgtd %xmm4, %xmm10
+; SSE2-NEXT:    pshufd {{.*#+}} xmm11 = xmm10[0,0,2,2]
+; SSE2-NEXT:    pcmpeqd %xmm4, %xmm9
+; SSE2-NEXT:    pshufd {{.*#+}} xmm9 = xmm9[1,1,3,3]
+; SSE2-NEXT:    pand %xmm11, %xmm9
+; SSE2-NEXT:    movdqa {{.*#+}} xmm4 = [0,4294967295,0,4294967295]
+; SSE2-NEXT:    pand %xmm4, %xmm10
+; SSE2-NEXT:    por %xmm9, %xmm10
+; SSE2-NEXT:    pand %xmm10, %xmm0
+; SSE2-NEXT:    movdqa %xmm1, %xmm9
+; SSE2-NEXT:    psubq %xmm5, %xmm1
+; SSE2-NEXT:    pxor %xmm8, %xmm5
+; SSE2-NEXT:    por %xmm8, %xmm9
+; SSE2-NEXT:    movdqa %xmm9, %xmm10
+; SSE2-NEXT:    pcmpgtd %xmm5, %xmm10
+; SSE2-NEXT:    pshufd {{.*#+}} xmm11 = xmm10[0,0,2,2]
+; SSE2-NEXT:    pcmpeqd %xmm5, %xmm9
+; SSE2-NEXT:    pshufd {{.*#+}} xmm5 = xmm9[1,1,3,3]
+; SSE2-NEXT:    pand %xmm11, %xmm5
+; SSE2-NEXT:    pand %xmm4, %xmm10
+; SSE2-NEXT:    por %xmm5, %xmm10
+; SSE2-NEXT:    pand %xmm10, %xmm1
+; SSE2-NEXT:    movdqa %xmm2, %xmm5
+; SSE2-NEXT:    psubq %xmm6, %xmm2
+; SSE2-NEXT:    pxor %xmm8, %xmm6
+; SSE2-NEXT:    por %xmm8, %xmm5
+; SSE2-NEXT:    movdqa %xmm5, %xmm9
+; SSE2-NEXT:    pcmpgtd %xmm6, %xmm9
+; SSE2-NEXT:    pshufd {{.*#+}} xmm10 = xmm9[0,0,2,2]
+; SSE2-NEXT:    pcmpeqd %xmm6, %xmm5
+; SSE2-NEXT:    pshufd {{.*#+}} xmm5 = xmm5[1,1,3,3]
+; SSE2-NEXT:    pand %xmm10, %xmm5
+; SSE2-NEXT:    pand %xmm4, %xmm9
+; SSE2-NEXT:    por %xmm5, %xmm9
+; SSE2-NEXT:    pand %xmm9, %xmm2
+; SSE2-NEXT:    movdqa %xmm7, %xmm5
+; SSE2-NEXT:    pxor %xmm8, %xmm5
+; SSE2-NEXT:    por %xmm3, %xmm8
+; SSE2-NEXT:    movdqa %xmm8, %xmm6
+; SSE2-NEXT:    pcmpgtd %xmm5, %xmm6
+; SSE2-NEXT:    pshufd {{.*#+}} xmm9 = xmm6[0,0,2,2]
+; SSE2-NEXT:    pcmpeqd %xmm5, %xmm8
+; SSE2-NEXT:    pshufd {{.*#+}} xmm5 = xmm8[1,1,3,3]
+; SSE2-NEXT:    pand %xmm9, %xmm5
+; SSE2-NEXT:    pand %xmm4, %xmm6
+; SSE2-NEXT:    por %xmm5, %xmm6
+; SSE2-NEXT:    psubq %xmm7, %xmm3
+; SSE2-NEXT:    pand %xmm6, %xmm3
+; SSE2-NEXT:    retq
+;
+; SSE42-LABEL: usubsat_v8i64_i8_narrow:
+; SSE42:       # %bb.0:
+; SSE42-NEXT:    movdqa {{.*#+}} xmm8 = [255,0,0,0,0,0,0,0,255,0,0,0,0,0,0,0]
+; SSE42-NEXT:    pand %xmm8, %xmm3
+; SSE42-NEXT:    pand %xmm8, %xmm2
+; SSE42-NEXT:    pand %xmm8, %xmm1
+; SSE42-NEXT:    pand %xmm8, %xmm0
+; SSE42-NEXT:    movdqa {{.*#+}} xmm8 = [9223372036854775808,9223372036854775808]
+; SSE42-NEXT:    movdqa %xmm0, %xmm9
+; SSE42-NEXT:    psubq %xmm4, %xmm0
+; SSE42-NEXT:    pxor %xmm8, %xmm4
+; SSE42-NEXT:    por %xmm8, %xmm9
+; SSE42-NEXT:    pcmpgtq %xmm4, %xmm9
+; SSE42-NEXT:    pand %xmm9, %xmm0
+; SSE42-NEXT:    movdqa %xmm1, %xmm4
+; SSE42-NEXT:    psubq %xmm5, %xmm1
+; SSE42-NEXT:    pxor %xmm8, %xmm5
+; SSE42-NEXT:    por %xmm8, %xmm4
+; SSE42-NEXT:    pcmpgtq %xmm5, %xmm4
+; SSE42-NEXT:    pand %xmm4, %xmm1
+; SSE42-NEXT:    movdqa %xmm2, %xmm4
+; SSE42-NEXT:    psubq %xmm6, %xmm2
+; SSE42-NEXT:    pxor %xmm8, %xmm6
+; SSE42-NEXT:    por %xmm8, %xmm4
+; SSE42-NEXT:    pcmpgtq %xmm6, %xmm4
+; SSE42-NEXT:    pand %xmm4, %xmm2
+; SSE42-NEXT:    movdqa %xmm7, %xmm4
+; SSE42-NEXT:    pxor %xmm8, %xmm4
+; SSE42-NEXT:    por %xmm3, %xmm8
+; SSE42-NEXT:    pcmpgtq %xmm4, %xmm8
+; SSE42-NEXT:    psubq %xmm7, %xmm3
+; SSE42-NEXT:    pand %xmm8, %xmm3
+; SSE42-NEXT:    retq
+;
+; AVX2-LABEL: usubsat_v8i64_i8_narrow:
+; AVX2:       # %bb.0:
+; AVX2-NEXT:    vpbroadcastq {{.*#+}} ymm4 = [255,0,0,0,0,0,0,0,255,0,0,0,0,0,0,0,255,0,0,0,0,0,0,0,255,0,0,0,0,0,0,0]
+; AVX2-NEXT:    vpand %ymm4, %ymm1, %ymm1
+; AVX2-NEXT:    vpand %ymm4, %ymm0, %ymm0
+; AVX2-NEXT:    vpbroadcastq {{.*#+}} ymm4 = [9223372036854775808,9223372036854775808,9223372036854775808,9223372036854775808]
+; AVX2-NEXT:    vpxor %ymm4, %ymm2, %ymm5
+; AVX2-NEXT:    vpor %ymm4, %ymm0, %ymm6
+; AVX2-NEXT:    vpcmpgtq %ymm5, %ymm6, %ymm5
+; AVX2-NEXT:    vpsubq %ymm2, %ymm0, %ymm0
+; AVX2-NEXT:    vpand %ymm0, %ymm5, %ymm0
+; AVX2-NEXT:    vpxor %ymm4, %ymm3, %ymm2
+; AVX2-NEXT:    vpor %ymm4, %ymm1, %ymm4
+; AVX2-NEXT:    vpcmpgtq %ymm2, %ymm4, %ymm2
+; AVX2-NEXT:    vpsubq %ymm3, %ymm1, %ymm1
+; AVX2-NEXT:    vpand %ymm1, %ymm2, %ymm1
+; AVX2-NEXT:    retq
+;
+; AVX512-LABEL: usubsat_v8i64_i8_narrow:
+; AVX512:       # %bb.0:
+; AVX512-NEXT:    vpandq {{\.?LCPI[0-9]+_[0-9]+}}(%rip){1to8}, %zmm0, %zmm0
+; AVX512-NEXT:    vpmaxuq %zmm1, %zmm0, %zmm0
+; AVX512-NEXT:    vpsubq %zmm1, %zmm0, %zmm0
+; AVX512-NEXT:    retq
+  %masked = and <8 x i64> %x, <i64 255, i64 255, i64 255, i64 255, i64 255, i64 255, i64 255, i64 255>
+  %z = call <8 x i64> @llvm.usub.sat.v8i64(<8 x i64> %masked, <8 x i64> %y)
+  ret <8 x i64> %z
+}
+
+; FIXME: LHS is known to fit in 8 bits but RHS is unknown — should fold to
+; vpsubusb once Path B (OR-mask narrowing) is implemented.
+define <4 x i64> @usubsat_v4i64_i8_narrow(<4 x i64> %x, <4 x i64> %y) nounwind {
+; SSE2-LABEL: usubsat_v4i64_i8_narrow:
+; SSE2:       # %bb.0:
+; SSE2-NEXT:    movdqa {{.*#+}} xmm4 = [255,0,0,0,0,0,0,0,255,0,0,0,0,0,0,0]
+; SSE2-NEXT:    pand %xmm4, %xmm1
+; SSE2-NEXT:    pand %xmm4, %xmm0
+; SSE2-NEXT:    movdqa {{.*#+}} xmm4 = [9223372039002259456,9223372039002259456]
+; SSE2-NEXT:    movdqa %xmm0, %xmm5
+; SSE2-NEXT:    psubq %xmm2, %xmm0
+; SSE2-NEXT:    pxor %xmm4, %xmm2
+; SSE2-NEXT:    por %xmm4, %xmm5
+; SSE2-NEXT:    movdqa %xmm5, %xmm6
+; SSE2-NEXT:    pcmpgtd %xmm2, %xmm6
+; SSE2-NEXT:    pshufd {{.*#+}} xmm7 = xmm6[0,0,2,2]
+; SSE2-NEXT:    pcmpeqd %xmm2, %xmm5
+; SSE2-NEXT:    pshufd {{.*#+}} xmm2 = xmm5[1,1,3,3]
+; SSE2-NEXT:    pand %xmm7, %xmm2
+; SSE2-NEXT:    movdqa {{.*#+}} xmm5 = [0,4294967295,0,4294967295]
+; SSE2-NEXT:    pand %xmm5, %xmm6
+; SSE2-NEXT:    por %xmm2, %xmm6
+; SSE2-NEXT:    pand %xmm6, %xmm0
+; SSE2-NEXT:    movdqa %xmm3, %xmm2
+; SSE2-NEXT:    pxor %xmm4, %xmm2
+; SSE2-NEXT:    por %xmm1, %xmm4
+; SSE2-NEXT:    movdqa %xmm4, %xmm6
+; SSE2-NEXT:    pcmpgtd %xmm2, %xmm6
+; SSE2-NEXT:    pshufd {{.*#+}} xmm7 = xmm6[0,0,2,2]
+; SSE2-NEXT:    pcmpeqd %xmm2, %xmm4
+; SSE2-NEXT:    pshufd {{.*#+}} xmm2 = xmm4[1,1,3,3]
+; SSE2-NEXT:    pand %xmm7, %xmm2
+; SSE2-NEXT:    pand %xmm5, %xmm6
+; SSE2-NEXT:    por %xmm2, %xmm6
+; SSE2-NEXT:    psubq %xmm3, %xmm1
+; SSE2-NEXT:    pand %xmm6, %xmm1
+; SSE2-NEXT:    retq
+;
+; SSE42-LABEL: usubsat_v4i64_i8_narrow:
+; SSE42:       # %bb.0:
+; SSE42-NEXT:    movdqa {{.*#+}} xmm4 = [255,0,0,0,0,0,0,0,255,0,0,0,0,0,0,0]
+; SSE42-NEXT:    pand %xmm4, %xmm1
+; SSE42-NEXT:    pand %xmm4, %xmm0
+; SSE42-NEXT:    movdqa {{.*#+}} xmm4 = [9223372036854775808,9223372036854775808]
+; SSE42-NEXT:    movdqa %xmm0, %xmm5
+; SSE42-NEXT:    psubq %xmm2, %xmm0
+; SSE42-NEXT:    pxor %xmm4, %xmm2
+; SSE42-NEXT:    por %xmm4, %xmm5
+; SSE42-NEXT:    pcmpgtq %xmm2, %xmm5
+; SSE42-NEXT:    pand %xmm5, %xmm0
+; SSE42-NEXT:    movdqa %xmm3, %xmm2
+; SSE42-NEXT:    pxor %xmm4, %xmm2
+; SSE42-NEXT:    por %xmm1, %xmm4
+; SSE42-NEXT:    pcmpgtq %xmm2, %xmm4
+; SSE42-NEXT:    psubq %xmm3, %xmm1
+; SSE42-NEXT:    pand %xmm4, %xmm1
+; SSE42-NEXT:    retq
+;
+; AVX2-LABEL: usubsat_v4i64_i8_narrow:
+; AVX2:       # %bb.0:
+; AVX2-NEXT:    vpand {{\.?LCPI[0-9]+_[0-9]+}}(%rip), %ymm0, %ymm0
+; AVX2-NEXT:    vpbroadcastq {{.*#+}} ymm2 = [9223372036854775808,9223372036854775808,9223372036854775808,9223372036854775808]
+; AVX2-NEXT:    vpxor %ymm2, %ymm1, %ymm3
+; AVX2-NEXT:    vpor %ymm2, %ymm0, %ymm2
+; AVX2-NEXT:    vpcmpgtq %ymm3, %ymm2, %ymm2
+; AVX2-NEXT:    vpsubq %ymm1, %ymm0, %ymm0
+; AVX2-NEXT:    vpand %ymm0, %ymm2, %ymm0
+; AVX2-NEXT:    retq
+;
+; AVX512-LABEL: usubsat_v4i64_i8_narrow:
+; AVX512:       # %bb.0:
+; AVX512-NEXT:    vpandq {{\.?LCPI[0-9]+_[0-9]+}}(%rip){1to4}, %ymm0, %ymm0
+; AVX512-NEXT:    vpmaxuq %ymm1, %ymm0, %ymm0
+; AVX512-NEXT:    vpsubq %ymm1, %ymm0, %ymm0
+; AVX512-NEXT:    retq
+  %masked = and <4 x i64> %x, <i64 255, i64 255, i64 255, i64 255>
+  %z = call <4 x i64> @llvm.usub.sat.v4i64(<4 x i64> %masked, <4 x i64> %y)
+  ret <4 x i64> %z
+}
+
+; FIXME: LHS is known to fit in 8 bits but RHS is unknown — should fold to
+; vpsubusb once Path B (OR-mask narrowing) is implemented.
+define <8 x i32> @usubsat_v8i32_i8_narrow(<8 x i32> %x, <8 x i32> %y) nounwind {
+; SSE2-LABEL: usubsat_v8i32_i8_narrow:
+; SSE2:       # %bb.0:
+; SSE2-NEXT:    movdqa {{.*#+}} xmm4 = [255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0]
+; SSE2-NEXT:    pand %xmm4, %xmm1
+; SSE2-NEXT:    pand %xmm4, %xmm0
+; SSE2-NEXT:    movdqa {{.*#+}} xmm4 = [2147483648,2147483648,2147483648,2147483648]
+; SSE2-NEXT:    movdqa %xmm0, %xmm5
+; SSE2-NEXT:    psubd %xmm2, %xmm0
+; SSE2-NEXT:    pxor %xmm4, %xmm2
+; SSE2-NEXT:    por %xmm4, %xmm5
+; SSE2-NEXT:    pcmpgtd %xmm2, %xmm5
+; SSE2-NEXT:    pand %xmm5, %xmm0
+; SSE2-NEXT:    movdqa %xmm3, %xmm2
+; SSE2-NEXT:    pxor %xmm4, %xmm2
+; SSE2-NEXT:    por %xmm1, %xmm4
+; SSE2-NEXT:    pcmpgtd %xmm2, %xmm4
+; SSE2-NEXT:    psubd %xmm3, %xmm1
+; SSE2-NEXT:    pand %xmm4, %xmm1
+; SSE2-NEXT:    retq
+;
+; SSE42-LABEL: usubsat_v8i32_i8_narrow:
+; SSE42:       # %bb.0:
+; SSE42-NEXT:    movdqa {{.*#+}} xmm4 = [255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0]
+; SSE42-NEXT:    pand %xmm4, %xmm1
+; SSE42-NEXT:    pand %xmm4, %xmm0
+; SSE42-NEXT:    pmaxud %xmm2, %xmm0
+; SSE42-NEXT:    psubd %xmm2, %xmm0
+; SSE42-NEXT:    pmaxud %xmm3, %xmm1
+; SSE42-NEXT:    psubd %xmm3, %xmm1
+; SSE42-NEXT:    retq
+;
+; AVX2-LABEL: usubsat_v8i32_i8_narrow:
+; AVX2:       # %bb.0:
+; AVX2-NEXT:    vpand {{\.?LCPI[0-9]+_[0-9]+}}(%rip), %ymm0, %ymm0
+; AVX2-NEXT:    vpmaxud %ymm1, %ymm0, %ymm0
+; AVX2-NEXT:    vpsubd %ymm1, %ymm0, %ymm0
+; AVX2-NEXT:    retq
+;
+; AVX512-LABEL: usubsat_v8i32_i8_narrow:
+; AVX512:       # %bb.0:
+; AVX512-NEXT:    vpandd {{\.?LCPI[0-9]+_[0-9]+}}(%rip){1to8}, %ymm0, %ymm0
+; AVX512-NEXT:    vpmaxud %ymm1, %ymm0, %ymm0
+; AVX512-NEXT:    vpsubd %ymm1, %ymm0, %ymm0
+; AVX512-NEXT:    retq
+  %masked = and <8 x i32> %x, <i32 255, i32 255, i32 255, i32 255, i32 255, i32 255, i32 255, i32 255>
+  %z = call <8 x i32> @llvm.usub.sat.v8i32(<8 x i32> %masked, <8 x i32> %y)
+  ret <8 x i32> %z
+}
+
+; FIXME: LHS is known to fit in 16 bits but RHS is unknown — should fold to
+; vpsubusw once Path B (OR-mask narrowing) is implemented.
+define <8 x i32> @usubsat_v8i32_i16_narrow(<8 x i32> %x, <8 x i32> %y) nounwind {
+; SSE2-LABEL: usubsat_v8i32_i16_narrow:
+; SSE2:       # %bb.0:
+; SSE2-NEXT:    movdqa {{.*#+}} xmm4 = [65535,0,65535,0,65535,0,65535,0]
+; SSE2-NEXT:    pand %xmm4, %xmm1
+; SSE2-NEXT:    pand %xmm4, %xmm0
+; SSE2-NEXT:    movdqa {{.*#+}} xmm4 = [2147483648,2147483648,2147483648,2147483648]
+; SSE2-NEXT:    movdqa %xmm0, %xmm5
+; SSE2-NEXT:    psubd %xmm2, %xmm0
+; SSE2-NEXT:    pxor %xmm4, %xmm2
+; SSE2-NEXT:    por %xmm4, %xmm5
+; SSE2-NEXT:    pcmpgtd %xmm2, %xmm5
+; SSE2-NEXT:    pand %xmm5, %xmm0
+; SSE2-NEXT:    movdqa %xmm3, %xmm2
+; SSE2-NEXT:    pxor %xmm4, %xmm2
+; SSE2-NEXT:    por %xmm1, %xmm4
+; SSE2-NEXT:    pcmpgtd %xmm2, %xmm4
+; SSE2-NEXT:    psubd %xmm3, %xmm1
+; SSE2-NEXT:    pand %xmm4, %xmm1
+; SSE2-NEXT:    retq
+;
+; SSE42-LABEL: usubsat_v8i32_i16_narrow:
+; SSE42:       # %bb.0:
+; SSE42-NEXT:    pxor %xmm4, %xmm4
+; SSE42-NEXT:    pblendw {{.*#+}} xmm1 = xmm1[0],xmm4[1],xmm1[2],xmm4[3],xmm1[4],xmm4[5],xmm1[6],xmm4[7]
+; SSE42-NEXT:    pblendw {{.*#+}} xmm0 = xmm0[0],xmm4[1],xmm0[2],xmm4[3],xmm0[4],xmm4[5],xmm0[6],xmm4[7]
+; SSE42-NEXT:    pmaxud %xmm2, %xmm0
+; SSE42-NEXT:    psubd %xmm2, %xmm0
+; SSE42-NEXT:    pmaxud %xmm3, %xmm1
+; SSE42-NEXT:    psubd %xmm3, %xmm1
+; SSE42-NEXT:    retq
+;
+; AVX2-LABEL: usubsat_v8i32_i16_narrow:
+; AVX2:       # %bb.0:
+; AVX2-NEXT:    vpxor %xmm2, %xmm2, %xmm2
+; AVX2-NEXT:    vpblendw {{.*#+}} ymm0 = ymm0[0],ymm2[1],ymm0[2],ymm2[3],ymm0[4],ymm2[5],ymm0[6],ymm2[7],ymm0[8],ymm2[9],ymm0[10],ymm2[11],ymm0[12],ymm2[13],ymm0[14],ymm2[15]
+; AVX2-NEXT:    vpmaxud %ymm1, %ymm0, %ymm0
+; AVX2-NEXT:    vpsubd %ymm1, %ymm0, %ymm0
+; AVX2-NEXT:    retq
+;
+; AVX512-LABEL: usubsat_v8i32_i16_narrow:
+; AVX512:       # %bb.0:
+; AVX512-NEXT:    vpxor %xmm2, %xmm2, %xmm2
+; AVX512-NEXT:    vpblendw {{.*#+}} ymm0 = ymm0[0],ymm2[1],ymm0[2],ymm2[3],ymm0[4],ymm2[5],ymm0[6],ymm2[7],ymm0[8],ymm2[9],ymm0[10],ymm2[11],ymm0[12],ymm2[13],ymm0[14],ymm2[15]
+; AVX512-NEXT:    vpmaxud %ymm1, %ymm0, %ymm0
+; AVX512-NEXT:    vpsubd %ymm1, %ymm0, %ymm0
+; AVX512-NEXT:    retq
+  %masked = and <8 x i32> %x, <i32 65535, i32 65535, i32 65535, i32 65535, i32 65535, i32 65535, i32 65535, i32 65535>
+  %z = call <8 x i32> @llvm.usub.sat.v8i32(<8 x i32> %masked, <8 x i32> %y)
+  ret <8 x i32> %z
+}
+
+; Negative test: LHS values may exceed 16 bits, should NOT narrow
+define <8 x i32> @usubsat_v8i32_no_narrow(<8 x i32> %x, <8 x i32> %y) nounwind {
+; SSE2-LABEL: usubsat_v8i32_no_narrow:
+; SSE2:       # %bb.0:
+; SSE2-NEXT:    movdqa {{.*#+}} xmm4 = [2147483648,2147483648,2147483648,2147483648]
+; SSE2-NEXT:    movdqa %xmm0, %xmm5
+; SSE2-NEXT:    psubd %xmm2, %xmm0
+; SSE2-NEXT:    pxor %xmm4, %xmm2
+; SSE2-NEXT:    pxor %xmm4, %xmm5
+; SSE2-NEXT:    pcmpgtd %xmm2, %xmm5
+; SSE2-NEXT:    pand %xmm5, %xmm0
+; SSE2-NEXT:    movdqa %xmm3, %xmm2
+; SSE2-NEXT:    pxor %xmm4, %xmm2
+; SSE2-NEXT:    pxor %xmm1, %xmm4
+; SSE2-NEXT:    pcmpgtd %xmm2, %xmm4
+; SSE2-NEXT:    psubd %xmm3, %xmm1
+; SSE2-NEXT:    pand %xmm4, %xmm1
+; SSE2-NEXT:    retq
+;
+; SSE42-LABEL: usubsat_v8i32_no_narrow:
+; SSE42:       # %bb.0:
+; SSE42-NEXT:    pmaxud %xmm2, %xmm0
+; SSE42-NEXT:    psubd %xmm2, %xmm0
+; SSE42-NEXT:    pmaxud %xmm3, %xmm1
+; SSE42-NEXT:    psubd %xmm3, %xmm1
+; SSE42-NEXT:    retq
+;
+; AVX2-LABEL: usubsat_v8i32_no_narrow:
+; AVX2:       # %bb.0:
+; AVX2-NEXT:    vpmaxud %ymm1, %ymm0, %ymm0
+; AVX2-NEXT:    vpsubd %ymm1, %ymm0, %ymm0
+; AVX2-NEXT:    retq
+;
+; AVX512-LABEL: usubsat_v8i32_no_narrow:
+; AVX512:       # %bb.0:
+; AVX512-NEXT:    vpmaxud %ymm1, %ymm0, %ymm0
+; AVX512-NEXT:    vpsubd %ymm1, %ymm0, %ymm0
+; AVX512-NEXT:    retq
+  %z = call <8 x i32> @llvm.usub.sat.v8i32(<8 x i32> %x, <8 x i32> %y)
+  ret <8 x i32> %z
+}
+
+define <8 x i32> @usubsat_v8i32_both_narrow(<8 x i32> %x, <8 x i32> %y) {
+; SSE2-LABEL: usubsat_v8i32_both_narrow:
+; SSE2:       # %bb.0:
+; SSE2-NEXT:    movdqa {{.*#+}} xmm4 = [255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0]
+; SSE2-NEXT:    pand %xmm4, %xmm1
+; SSE2-NEXT:    pand %xmm4, %xmm0
+; SSE2-NEXT:    pand %xmm4, %xmm3
+; SSE2-NEXT:    psubusb %xmm3, %xmm1
+; SSE2-NEXT:    pand %xmm4, %xmm2
+; SSE2-NEXT:    psubusb %xmm2, %xmm0
+; SSE2-NEXT:    retq
+;
+; SSE42-LABEL: usubsat_v8i32_both_narrow:
+; SSE42:       # %bb.0:
+; SSE42-NEXT:    movdqa {{.*#+}} xmm4 = [255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0]
+; SSE42-NEXT:    pand %xmm4, %xmm1
+; SSE42-NEXT:    pand %xmm4, %xmm0
+; SSE42-NEXT:    pand %xmm4, %xmm3
+; SSE42-NEXT:    psubusb %xmm3, %xmm1
+; SSE42-NEXT:    pand %xmm4, %xmm2
+; SSE42-NEXT:    psubusb %xmm2, %xmm0
+; SSE42-NEXT:    retq
+;
+; AVX2-LABEL: usubsat_v8i32_both_narrow:
+; AVX2:       # %bb.0:
+; AVX2-NEXT:    vpbroadcastd {{.*#+}} ymm2 = [255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0]
+; AVX2-NEXT:    vpand %ymm2, %ymm0, %ymm0
+; AVX2-NEXT:    vpand %ymm2, %ymm1, %ymm1
+; AVX2-NEXT:    vpsubusb %ymm1, %ymm0, %ymm0
+; AVX2-NEXT:    retq
+;
+; AVX512-LABEL: usubsat_v8i32_both_narrow:
+; AVX512:       # %bb.0:
+; AVX512-NEXT:    vpbroadcastd {{.*#+}} ymm2 = [255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0]
+; AVX512-NEXT:    vpand %ymm2, %ymm0, %ymm0
+; AVX512-NEXT:    vpand %ymm2, %ymm1, %ymm1
+; AVX512-NEXT:    vpsubusb %ymm1, %ymm0, %ymm0
+; AVX512-NEXT:    retq
+  %mx = and <8 x i32> %x, <i32 255, i32 255, i32 255, i32 255, i32 255, i32 255, i32 255, i32 255>
+  %my = and <8 x i32> %y, <i32 255, i32 255, i32 255, i32 255, i32 255, i32 255, i32 255, i32 255>
+  %z = call <8 x i32> @llvm.usub.sat.v8i32(<8 x i32> %mx, <8 x i32> %my)
+  ret <8 x i32> %z
+}



More information about the llvm-commits mailing list