[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 Jun 29 14:48:28 PDT 2026


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

When the LHS of a vector saturating subtraction is known (via KnownBits
analysis) to fit within 8 or 16 bits, we can reinterpret the wide-lane
operation as a byte or word saturating subtract instead. x86 has native
vpsubusb/vpsubusw instructions but no vpsubusd/vpsubusq, so wide USUBSAT
on i32/i64 lanes is currently emulated with vpmaxu* + vsub*, requiring
two instructions plus a constant load.

The transformation works by OR-ing the upper bits of the RHS with 1s
(using APInt::getBitsSetFrom), so that the narrowed lanes corresponding
to the upper bytes/words always saturate to zero, leaving only the low
byte/word of each lane doing real work. Both operands are then bitcast
to the narrow vector type and a single vpsubusb/vpsubusw is emitted.

For example, llvm.usub.sat.v8i64 where the LHS is masked to 8 bits:

Before:
  vpmaxuq  zmm0, zmm0, zmm1
  vpsubq   zmm0, zmm0, zmm1

After:
  vporq    zmm1, zmm1, [upper_mask]{1to8}
  vpsubusb zmm0, zmm0, zmm1

This is implemented in LowerADDSAT_SUBSAT. MVT::v8i64 and MVT::v16i32
are registered as Custom for USUBSAT so our lowering code is invoked.

Fixes https://github.com/llvm/llvm-project/issues/195462



>This patch was developed with Claude (Anthropic) as a learning aid to
>understand LLVM's SelectionDAG lowering and KnownBits infrastructure.

>From bf89c8169b97196e26d864434d7726c5373ff5a5 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/Target/X86/X86ISelLowering.cpp |  32 +++++++
 llvm/test/CodeGen/X86/psubus.ll         | 112 ++++++++++++------------
 llvm/test/CodeGen/X86/usubsat-narrow.ll |  70 +++++++++++++++
 3 files changed, 156 insertions(+), 58 deletions(-)
 create mode 100644 llvm/test/CodeGen/X86/usubsat-narrow.ll

diff --git a/llvm/lib/Target/X86/X86ISelLowering.cpp b/llvm/lib/Target/X86/X86ISelLowering.cpp
index 7a382490c5027..0eddc72546754 100644
--- a/llvm/lib/Target/X86/X86ISelLowering.cpp
+++ b/llvm/lib/Target/X86/X86ISelLowering.cpp
@@ -2074,6 +2074,7 @@ X86TargetLowering::X86TargetLowering(const X86TargetMachine &TM,
       setOperationAction(ISD::ROTR,             VT, Legal);
       setOperationAction(ISD::ABS,              VT, Legal);
       setOperationAction(ISD::CTPOP,            VT, Custom);
+      setOperationAction(ISD::USUBSAT,          VT, Custom);
     }
 
     for (auto VT : { MVT::v64i8, MVT::v32i16 }) {
@@ -29922,6 +29923,37 @@ static SDValue LowerADDSAT_SUBSAT(SDValue Op, SelectionDAG &DAG,
 
   unsigned BitWidth = VT.getScalarSizeInBits();
   if (Opcode == ISD::USUBSAT) {
+    // Narrow a vXiN USUBSAT to vXi8 or vXi16 when LHS is known to fit in fewer bits (higher bits are zero)
+    // x86 has native vpsubusb/vpsubusw but no vpsubusd/vpsubusq.
+    // If CountMaxActiveBits(N0) <= NarrowBits we know every element of N0 fits in NarrowBits.
+    // The upper bits (ScalarBits - NarrowBits) bits of N0 are zero.
+    // We fill the same upper bits of N1 with 0xFF...F so that the byte/word saturating subtract on those positions
+    // always saturates to zero, leaving only narrow low bits doing the real work.
+    unsigned ScalarBits = VT.getScalarSizeInBits();
+    if (ScalarBits == 32 || ScalarBits == 64) {
+      KnownBits Known = DAG.computeKnownBits(X);
+      unsigned ActiveBits = Known.countMaxActiveBits();
+      unsigned NarrowBits = 0;
+      if (ActiveBits <= 8)
+        NarrowBits = 8;
+      else if (ActiveBits <= 16)
+        NarrowBits = 16;
+      if (NarrowBits) {
+        unsigned Scale = ScalarBits / NarrowBits;
+        unsigned NumElts = VT.getVectorNumElements() * Scale;
+        MVT NarrowSVT = MVT::getIntegerVT(NarrowBits);
+        MVT NarrowVT = MVT::getVectorVT(NarrowSVT, NumElts);
+        if (TLI.isOperationLegal(ISD::USUBSAT, NarrowVT)) {
+          APInt UpperMask = APInt::getBitsSetFrom(ScalarBits, NarrowBits);
+          SDValue Mask = DAG.getConstant(UpperMask, DL, VT);
+          SDValue YWithUpperOnes = DAG.getNode(ISD::OR, DL, VT, Y, Mask);
+          SDValue NarrowX = DAG.getBitcast(NarrowVT, X);
+          SDValue NarrowY = DAG.getBitcast(NarrowVT, YWithUpperOnes);
+          SDValue NarrowSub = DAG.getNode(ISD::USUBSAT, DL, NarrowVT, NarrowX, NarrowY);
+          return DAG.getBitcast(VT, NarrowSub);
+        }
+      }
+    }
     if (!TLI.isOperationLegal(ISD::UMAX, VT) || useVPTERNLOG(Subtarget, VT)) {
       // Handle a special-case with a bit-hack instead of cmp+select:
       // usubsat X, SMIN --> (X ^ SMIN) & (X s>> BW-1)
diff --git a/llvm/test/CodeGen/X86/psubus.ll b/llvm/test/CodeGen/X86/psubus.ll
index b93862be0a1b4..c0c71804ab425 100644
--- a/llvm/test/CodeGen/X86/psubus.ll
+++ b/llvm/test/CodeGen/X86/psubus.ll
@@ -2916,40 +2916,33 @@ define <8 x i32> @test34(<8 x i32> %a0, <8 x i64> %a1) {
 ; SSE2OR3-NEXT:    pxor %xmm8, %xmm3
 ; SSE2OR3-NEXT:    por %xmm2, %xmm3
 ; SSE2OR3-NEXT:    shufps {{.*#+}} xmm3 = xmm3[0,2],xmm9[0,2]
-; SSE2OR3-NEXT:    movdqa %xmm0, %xmm2
-; SSE2OR3-NEXT:    psubd %xmm3, %xmm2
-; SSE2OR3-NEXT:    pxor %xmm6, %xmm3
-; SSE2OR3-NEXT:    por %xmm6, %xmm0
-; SSE2OR3-NEXT:    pcmpgtd %xmm3, %xmm0
-; SSE2OR3-NEXT:    pand %xmm2, %xmm0
-; SSE2OR3-NEXT:    movdqa %xmm5, %xmm2
-; SSE2OR3-NEXT:    pxor %xmm6, %xmm2
-; SSE2OR3-NEXT:    pshufd {{.*#+}} xmm3 = xmm2[0,0,2,2]
-; SSE2OR3-NEXT:    movdqa %xmm7, %xmm9
-; SSE2OR3-NEXT:    pcmpgtd %xmm3, %xmm9
-; SSE2OR3-NEXT:    pshufd {{.*#+}} xmm2 = xmm2[1,1,3,3]
-; SSE2OR3-NEXT:    pcmpeqd %xmm6, %xmm2
-; SSE2OR3-NEXT:    pand %xmm9, %xmm2
-; SSE2OR3-NEXT:    pand %xmm2, %xmm5
-; SSE2OR3-NEXT:    pxor %xmm8, %xmm2
-; SSE2OR3-NEXT:    por %xmm5, %xmm2
-; SSE2OR3-NEXT:    movdqa %xmm4, %xmm3
+; SSE2OR3-NEXT:    movaps {{.*#+}} xmm2 = [4294967040,4294967040,4294967040,4294967040]
+; SSE2OR3-NEXT:    orps %xmm2, %xmm3
+; SSE2OR3-NEXT:    psubusb %xmm3, %xmm0
+; SSE2OR3-NEXT:    movdqa %xmm5, %xmm3
 ; SSE2OR3-NEXT:    pxor %xmm6, %xmm3
-; SSE2OR3-NEXT:    pshufd {{.*#+}} xmm5 = xmm3[0,0,2,2]
-; SSE2OR3-NEXT:    pcmpgtd %xmm5, %xmm7
+; SSE2OR3-NEXT:    pshufd {{.*#+}} xmm9 = xmm3[0,0,2,2]
+; SSE2OR3-NEXT:    movdqa %xmm7, %xmm10
+; SSE2OR3-NEXT:    pcmpgtd %xmm9, %xmm10
 ; SSE2OR3-NEXT:    pshufd {{.*#+}} xmm3 = xmm3[1,1,3,3]
 ; SSE2OR3-NEXT:    pcmpeqd %xmm6, %xmm3
-; SSE2OR3-NEXT:    pand %xmm7, %xmm3
-; SSE2OR3-NEXT:    pxor %xmm3, %xmm8
-; SSE2OR3-NEXT:    pand %xmm4, %xmm3
-; SSE2OR3-NEXT:    por %xmm8, %xmm3
-; SSE2OR3-NEXT:    shufps {{.*#+}} xmm3 = xmm3[0,2],xmm2[0,2]
-; SSE2OR3-NEXT:    movdqa %xmm1, %xmm2
-; SSE2OR3-NEXT:    psubd %xmm3, %xmm2
-; SSE2OR3-NEXT:    pxor %xmm6, %xmm3
-; SSE2OR3-NEXT:    por %xmm6, %xmm1
-; SSE2OR3-NEXT:    pcmpgtd %xmm3, %xmm1
-; SSE2OR3-NEXT:    pand %xmm2, %xmm1
+; SSE2OR3-NEXT:    pand %xmm10, %xmm3
+; SSE2OR3-NEXT:    pand %xmm3, %xmm5
+; SSE2OR3-NEXT:    pxor %xmm8, %xmm3
+; SSE2OR3-NEXT:    por %xmm5, %xmm3
+; SSE2OR3-NEXT:    movdqa %xmm4, %xmm5
+; SSE2OR3-NEXT:    pxor %xmm6, %xmm5
+; SSE2OR3-NEXT:    pshufd {{.*#+}} xmm9 = xmm5[0,0,2,2]
+; SSE2OR3-NEXT:    pcmpgtd %xmm9, %xmm7
+; SSE2OR3-NEXT:    pshufd {{.*#+}} xmm5 = xmm5[1,1,3,3]
+; SSE2OR3-NEXT:    pcmpeqd %xmm6, %xmm5
+; SSE2OR3-NEXT:    pand %xmm7, %xmm5
+; SSE2OR3-NEXT:    pxor %xmm5, %xmm8
+; SSE2OR3-NEXT:    pand %xmm4, %xmm5
+; SSE2OR3-NEXT:    por %xmm8, %xmm5
+; SSE2OR3-NEXT:    shufps {{.*#+}} xmm5 = xmm5[0,2],xmm3[0,2]
+; SSE2OR3-NEXT:    orps %xmm2, %xmm5
+; SSE2OR3-NEXT:    psubusb %xmm5, %xmm1
 ; SSE2OR3-NEXT:    retq
 ;
 ; SSE41-LABEL: test34:
@@ -2982,29 +2975,30 @@ define <8 x i32> @test34(<8 x i32> %a0, <8 x i64> %a1) {
 ; SSE41-NEXT:    movapd %xmm8, %xmm3
 ; SSE41-NEXT:    blendvpd %xmm0, %xmm2, %xmm3
 ; SSE41-NEXT:    shufps {{.*#+}} xmm3 = xmm3[0,2],xmm10[0,2]
-; SSE41-NEXT:    pmaxud %xmm3, %xmm6
-; SSE41-NEXT:    psubd %xmm3, %xmm6
+; SSE41-NEXT:    movaps {{.*#+}} xmm2 = [4294967040,4294967040,4294967040,4294967040]
+; SSE41-NEXT:    orps %xmm2, %xmm3
+; SSE41-NEXT:    psubusb %xmm3, %xmm6
 ; SSE41-NEXT:    movdqa %xmm5, %xmm0
 ; SSE41-NEXT:    pxor %xmm7, %xmm0
-; SSE41-NEXT:    pshufd {{.*#+}} xmm2 = xmm0[0,0,2,2]
-; SSE41-NEXT:    movdqa %xmm9, %xmm3
-; SSE41-NEXT:    pcmpgtd %xmm2, %xmm3
+; SSE41-NEXT:    pshufd {{.*#+}} xmm3 = xmm0[0,0,2,2]
+; SSE41-NEXT:    movdqa %xmm9, %xmm10
+; SSE41-NEXT:    pcmpgtd %xmm3, %xmm10
 ; SSE41-NEXT:    pshufd {{.*#+}} xmm0 = xmm0[1,1,3,3]
 ; SSE41-NEXT:    pcmpeqd %xmm7, %xmm0
-; SSE41-NEXT:    pand %xmm3, %xmm0
-; SSE41-NEXT:    movapd %xmm8, %xmm2
-; SSE41-NEXT:    blendvpd %xmm0, %xmm5, %xmm2
+; SSE41-NEXT:    pand %xmm10, %xmm0
+; SSE41-NEXT:    movapd %xmm8, %xmm3
+; SSE41-NEXT:    blendvpd %xmm0, %xmm5, %xmm3
 ; SSE41-NEXT:    movdqa %xmm4, %xmm0
 ; SSE41-NEXT:    pxor %xmm7, %xmm0
-; SSE41-NEXT:    pshufd {{.*#+}} xmm3 = xmm0[0,0,2,2]
-; SSE41-NEXT:    pcmpgtd %xmm3, %xmm9
+; SSE41-NEXT:    pshufd {{.*#+}} xmm5 = xmm0[0,0,2,2]
+; SSE41-NEXT:    pcmpgtd %xmm5, %xmm9
 ; SSE41-NEXT:    pshufd {{.*#+}} xmm0 = xmm0[1,1,3,3]
 ; SSE41-NEXT:    pcmpeqd %xmm7, %xmm0
 ; SSE41-NEXT:    pand %xmm9, %xmm0
 ; SSE41-NEXT:    blendvpd %xmm0, %xmm4, %xmm8
-; SSE41-NEXT:    shufps {{.*#+}} xmm8 = xmm8[0,2],xmm2[0,2]
-; SSE41-NEXT:    pmaxud %xmm8, %xmm1
-; SSE41-NEXT:    psubd %xmm8, %xmm1
+; SSE41-NEXT:    shufps {{.*#+}} xmm8 = xmm8[0,2],xmm3[0,2]
+; SSE41-NEXT:    orps %xmm2, %xmm8
+; SSE41-NEXT:    psubusb %xmm8, %xmm1
 ; SSE41-NEXT:    movdqa %xmm6, %xmm0
 ; SSE41-NEXT:    retq
 ;
@@ -3025,19 +3019,20 @@ define <8 x i32> @test34(<8 x i32> %a0, <8 x i64> %a1) {
 ; AVX1-NEXT:    vpcmpgtq %xmm5, %xmm6, %xmm5
 ; AVX1-NEXT:    vblendvpd %xmm5, %xmm2, %xmm7, %xmm2
 ; AVX1-NEXT:    vshufps {{.*#+}} xmm2 = xmm2[0,2],xmm3[0,2]
-; AVX1-NEXT:    vextractf128 $1, %ymm0, %xmm3
-; AVX1-NEXT:    vpmaxud %xmm2, %xmm3, %xmm3
-; AVX1-NEXT:    vpsubd %xmm2, %xmm3, %xmm2
-; AVX1-NEXT:    vextractf128 $1, %ymm1, %xmm3
-; AVX1-NEXT:    vpxor %xmm4, %xmm3, %xmm5
-; AVX1-NEXT:    vpcmpgtq %xmm5, %xmm6, %xmm5
-; AVX1-NEXT:    vblendvpd %xmm5, %xmm3, %xmm7, %xmm3
+; AVX1-NEXT:    vbroadcastss {{.*#+}} xmm3 = [4294967040,4294967040,4294967040,4294967040]
+; AVX1-NEXT:    vorps %xmm3, %xmm2, %xmm2
+; AVX1-NEXT:    vextractf128 $1, %ymm0, %xmm5
+; AVX1-NEXT:    vpsubusb %xmm2, %xmm5, %xmm2
+; AVX1-NEXT:    vextractf128 $1, %ymm1, %xmm5
+; AVX1-NEXT:    vpxor %xmm4, %xmm5, %xmm8
+; AVX1-NEXT:    vpcmpgtq %xmm8, %xmm6, %xmm8
+; AVX1-NEXT:    vblendvpd %xmm8, %xmm5, %xmm7, %xmm5
 ; AVX1-NEXT:    vpxor %xmm4, %xmm1, %xmm4
 ; AVX1-NEXT:    vpcmpgtq %xmm4, %xmm6, %xmm4
 ; AVX1-NEXT:    vblendvpd %xmm4, %xmm1, %xmm7, %xmm1
-; AVX1-NEXT:    vshufps {{.*#+}} xmm1 = xmm1[0,2],xmm3[0,2]
-; AVX1-NEXT:    vpmaxud %xmm1, %xmm0, %xmm0
-; AVX1-NEXT:    vpsubd %xmm1, %xmm0, %xmm0
+; AVX1-NEXT:    vshufps {{.*#+}} xmm1 = xmm1[0,2],xmm5[0,2]
+; AVX1-NEXT:    vorps %xmm3, %xmm1, %xmm1
+; AVX1-NEXT:    vpsubusb %xmm1, %xmm0, %xmm0
 ; AVX1-NEXT:    vinsertf128 $1, %xmm2, %ymm0, %ymm0
 ; AVX1-NEXT:    retq
 ;
@@ -3056,16 +3051,17 @@ define <8 x i32> @test34(<8 x i32> %a0, <8 x i64> %a1) {
 ; AVX2-NEXT:    vblendvpd %ymm3, %ymm1, %ymm6, %ymm1
 ; AVX2-NEXT:    vshufps {{.*#+}} ymm1 = ymm1[0,2],ymm2[0,2],ymm1[4,6],ymm2[4,6]
 ; AVX2-NEXT:    vpermpd {{.*#+}} ymm1 = ymm1[0,2,1,3]
-; AVX2-NEXT:    vpmaxud %ymm1, %ymm0, %ymm0
-; AVX2-NEXT:    vpsubd %ymm1, %ymm0, %ymm0
+; AVX2-NEXT:    vbroadcastss {{.*#+}} ymm2 = [4294967040,4294967040,4294967040,4294967040,4294967040,4294967040,4294967040,4294967040]
+; AVX2-NEXT:    vorps %ymm2, %ymm1, %ymm1
+; AVX2-NEXT:    vpsubusb %ymm1, %ymm0, %ymm0
 ; AVX2-NEXT:    retq
 ;
 ; AVX512-LABEL: test34:
 ; AVX512:       # %bb.0:
 ; AVX512-NEXT:    vpandd {{\.?LCPI[0-9]+_[0-9]+}}(%rip){1to8}, %ymm0, %ymm0
 ; AVX512-NEXT:    vpmovusqd %zmm1, %ymm1
-; AVX512-NEXT:    vpmaxud %ymm1, %ymm0, %ymm0
-; AVX512-NEXT:    vpsubd %ymm1, %ymm0, %ymm0
+; AVX512-NEXT:    vpord {{\.?LCPI[0-9]+_[0-9]+}}(%rip){1to8}, %ymm1, %ymm1
+; AVX512-NEXT:    vpsubusb %ymm1, %ymm0, %ymm0
 ; AVX512-NEXT:    retq
   %mask = and <8 x i32> %a0, <i32 1, i32 1, i32 1, i32 1, i32 1, i32 1, i32 1, i32 1>
   %zext = zext <8 x i32> %mask to <8 x i64>
diff --git a/llvm/test/CodeGen/X86/usubsat-narrow.ll b/llvm/test/CodeGen/X86/usubsat-narrow.ll
new file mode 100644
index 0000000000000..4ac6e1652b70a
--- /dev/null
+++ b/llvm/test/CodeGen/X86/usubsat-narrow.ll
@@ -0,0 +1,70 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; RUN: llc < %s -mtriple=x86_64-unknown-unknown -mattr=+avx512bw,+avx512vl | FileCheck %s
+
+declare <8 x i64> @llvm.usub.sat.v8i64(<8 x i64>, <8 x i64>)
+declare <4 x i64> @llvm.usub.sat.v4i64(<4 x i64>, <4 x i64>)
+declare <8 x i32> @llvm.usub.sat.v8i32(<8 x i32>, <8 x i32>)
+
+; v8i64 narrowed to v64i8 - LHS masked to 8 bits, should use vpsubusb
+define <8 x i64> @usubsat_v8i64_i8_narrow(<8 x i64> %x, <8 x i64> %y) nounwind {
+; CHECK-LABEL: usubsat_v8i64_i8_narrow:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    vpandq {{\.?LCPI[0-9]+_[0-9]+}}(%rip){1to8}, %zmm0, %zmm0
+; CHECK-NEXT:    vporq {{\.?LCPI[0-9]+_[0-9]+}}(%rip){1to8}, %zmm1, %zmm1
+; CHECK-NEXT:    vpsubusb %zmm1, %zmm0, %zmm0
+; CHECK-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
+}
+
+; v4i64 narrowed to v32i8 - LHS masked to 8 bits, should use vpsubusb
+define <4 x i64> @usubsat_v4i64_i8_narrow(<4 x i64> %x, <4 x i64> %y) nounwind {
+; CHECK-LABEL: usubsat_v4i64_i8_narrow:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    vpandq {{\.?LCPI[0-9]+_[0-9]+}}(%rip){1to4}, %ymm0, %ymm0
+; CHECK-NEXT:    vporq {{\.?LCPI[0-9]+_[0-9]+}}(%rip){1to4}, %ymm1, %ymm1
+; CHECK-NEXT:    vpsubusb %ymm1, %ymm0, %ymm0
+; CHECK-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
+}
+
+; v8i32 narrowed to v32i8 - LHS masked to 8 bits, should use vpsubusb
+define <8 x i32> @usubsat_v8i32_i8_narrow(<8 x i32> %x, <8 x i32> %y) nounwind {
+; CHECK-LABEL: usubsat_v8i32_i8_narrow:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    vpandd {{\.?LCPI[0-9]+_[0-9]+}}(%rip){1to8}, %ymm0, %ymm0
+; CHECK-NEXT:    vpord {{\.?LCPI[0-9]+_[0-9]+}}(%rip){1to8}, %ymm1, %ymm1
+; CHECK-NEXT:    vpsubusb %ymm1, %ymm0, %ymm0
+; CHECK-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
+}
+
+; v8i32 narrowed to v16i16 - LHS masked to 16 bits, should use vpsubusw
+define <8 x i32> @usubsat_v8i32_i16_narrow(<8 x i32> %x, <8 x i32> %y) nounwind {
+; CHECK-LABEL: usubsat_v8i32_i16_narrow:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    vpxor %xmm2, %xmm2, %xmm2
+; CHECK-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]
+; CHECK-NEXT:    vpord {{\.?LCPI[0-9]+_[0-9]+}}(%rip){1to8}, %ymm1, %ymm1
+; CHECK-NEXT:    vpsubusw %ymm1, %ymm0, %ymm0
+; CHECK-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 {
+; CHECK-LABEL: usubsat_v8i32_no_narrow:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    vpmaxud %ymm1, %ymm0, %ymm0
+; CHECK-NEXT:    vpsubd %ymm1, %ymm0, %ymm0
+; CHECK-NEXT:    retq
+  %z = call <8 x i32> @llvm.usub.sat.v8i32(<8 x i32> %x, <8 x i32> %y)
+  ret <8 x i32> %z
+}



More information about the llvm-commits mailing list