[llvm] [X86] Prefer SIMD min/max/abs for scalars when staying in XMM domain (PR #210654)
via llvm-commits
llvm-commits at lists.llvm.org
Mon Jul 20 00:41:25 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-backend-x86
Author: Shaurya Srivastava (Shaurya2k06)
<details>
<summary>Changes</summary>
For memory-bound scalar `smin`/`smax`/`umin`/`umax`/`abs` of `i32` loads
whose result is only stored (or fed to `insert_vector_elt`), prefer SSE
`PMIN`/`PMAX`/`PABS` over `cmp`+`cmov` / `neg`+`cmov`, matching GCC on
cases like `sort2` and scalar abs-to-memory.
### Approach
DAGCombine (`combineScalarMinMaxToSIMD` / `combineScalarAbsToSIMD` via
`PerformDAGCombine`) widens to `v4i32` through `SCALAR_TO_VECTOR`, which
ISel folds to `movd` + `PMIN`/`PMAX`/`PABS` + `movd`. Doing this in the
combiner (rather than custom `LowerOperation`) lets paired `smin`+`smax`
of the same loads both convert before legalization so CSE can share the
`SCALAR_TO_VECTOR` nodes.
### Profitability / safety
- **Sources:** only plain non-extending `i32` loads. `EXTRACT_VECTOR_ELT`
and constants typically require a GPR path; re-widening would introduce
a GPR↔XMM domain cross.
- **Sinks:** single use that is `store` or `insert_vector_elt`.
- **Type:** `i32` only. Min/max needs SSE4.1; abs needs SSSE3.
- **Guards:** `OptForSize`, `NoImplicitFloat`, `useSoftFloat`.
- Call site only invokes the combines for **scalar** nodes.
- Min/max relies on scalar `i32` min/max remaining `Expand` so
`shouldScalarizeBinop` does not undo the `extract(vector_binop)` form.
Abs is unary (not a binop), so that interlock does not apply.
### Test plan
- [x] `llvm/test/CodeGen/X86/scalar-minmax-simd.ll` — positive sort2 /
store minmax/abs (x86_64 SSE4.1/AVX + i686 SSE4.1); negatives for GPR
result, `noimplicitfloat`, `optsize`, and load+constant
- [x] Existing `smin.ll` / `smax.ll` / `umin.ll` / `umax.ll` / `abs.ll`
Fixes #<!-- -->210569
---
Patch is 29.58 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/210654.diff
2 Files Affected:
- (modified) llvm/lib/Target/X86/X86ISelLowering.cpp (+156)
- (added) llvm/test/CodeGen/X86/scalar-minmax-simd.ll (+686)
``````````diff
diff --git a/llvm/lib/Target/X86/X86ISelLowering.cpp b/llvm/lib/Target/X86/X86ISelLowering.cpp
index 791e04deb9583..696b9dc47c728 100644
--- a/llvm/lib/Target/X86/X86ISelLowering.cpp
+++ b/llvm/lib/Target/X86/X86ISelLowering.cpp
@@ -2802,6 +2802,11 @@ X86TargetLowering::X86TargetLowering(const X86TargetMachine &TM,
ISD::ADD,
ISD::SADDSAT,
ISD::SSUBSAT,
+ ISD::ABS,
+ ISD::SMIN,
+ ISD::SMAX,
+ ISD::UMIN,
+ ISD::UMAX,
ISD::FADD,
ISD::FSUB,
ISD::FNEG,
@@ -62973,6 +62978,144 @@ static SDValue combineINTRINSIC_VOID(SDNode *N, SelectionDAG &DAG,
return SDValue();
}
+/// True if V is a plain (non-extending) load of VT. Don't peek through
+/// bitcasts: that could pull a float-domain value into an integer vector.
+static bool isPlainScalarLoad(SDValue V, EVT VT) {
+ auto *Ld = dyn_cast<LoadSDNode>(V);
+ if (!Ld || Ld->getExtensionType() != ISD::NON_EXTLOAD)
+ return false;
+ return Ld->getMemoryVT() == VT;
+}
+
+/// True if V has a single use that is an XMM-domain sink (store or
+/// insert_vector_elt). Otherwise widening would force a GPR↔XMM cross.
+static bool isOnlyUsedByXMMDomainSink(SDValue V) {
+ if (!V->hasNUsesOfValue(1, V.getResNo()))
+ return false;
+ for (SDUse &Use : V->uses()) {
+ if (Use.getResNo() != V.getResNo())
+ continue;
+ unsigned O = Use.getUser()->getOpcode();
+ if (O != ISD::STORE && O != ISD::INSERT_VECTOR_ELT)
+ return false;
+ }
+ return true;
+}
+
+/// Combine scalar i32 min/max whose operands are loads and whose result is only
+/// stored (or insert_vector_elt) into a vector min/max on v4i32, which ISel
+/// folds to movd + PMIN/PMAX. This keeps memory-bound sort2 in the XMM domain,
+/// matching GCC, without introducing GPR↔XMM domain crosses for values that
+/// live in GPRs.
+///
+/// Only plain i32 loads are accepted as sources: EXTRACT_VECTOR_ELT typically
+/// materializes in a GPR (movd/pextr), so re-widening would create a domain
+/// cross. Constants and GPR operands are likewise rejected. Relies on scalar
+/// i32 min/max remaining Expand so shouldScalarizeBinop does not undo the
+/// extract(vector_binop) form we create.
+static SDValue combineScalarMinMaxToSIMD(SDNode *N, SelectionDAG &DAG,
+ const X86Subtarget &Subtarget) {
+ EVT VT = N->getValueType(0);
+ // Caller must only pass scalar nodes; still guard for safety.
+ if (!VT.isSimple() || VT.getSimpleVT() != MVT::i32 || !Subtarget.hasSSE41())
+ return SDValue();
+
+ const Function &F = DAG.getMachineFunction().getFunction();
+ if (F.hasFnAttribute(Attribute::NoImplicitFloat) || Subtarget.useSoftFloat())
+ return SDValue();
+ if (F.hasOptSize())
+ return SDValue();
+
+ SDValue Op0 = N->getOperand(0);
+ SDValue Op1 = N->getOperand(1);
+
+ // Both operands must be plain i32 loads — the only XMM-native source that
+ // doesn't already require a GPR↔XMM move.
+ if (!isPlainScalarLoad(Op0, VT) || !isPlainScalarLoad(Op1, VT))
+ return SDValue();
+
+ // Each load must only feed scalar min/max nodes, or a SCALAR_TO_VECTOR from
+ // a sibling min/max already combined this turn (CSE shares the S2V). This
+ // lets paired smin+smax of the same loads both convert. A GPR consumer
+ // would force the load into a GPR and block memory folding.
+ auto isScalarMinMax = [](SDNode *U) {
+ unsigned O = U->getOpcode();
+ return (O == ISD::SMIN || O == ISD::SMAX || O == ISD::UMIN ||
+ O == ISD::UMAX) &&
+ !U->getValueType(0).isVector();
+ };
+ auto checkLoadUses = [&](SDValue Ld) {
+ for (SDUse &Use : Ld->uses()) {
+ if (Use.getResNo() != Ld.getResNo())
+ continue;
+ SDNode *User = Use.getUser();
+ if (User->getOpcode() == ISD::SCALAR_TO_VECTOR)
+ continue;
+ if (!isScalarMinMax(User))
+ return false;
+ }
+ return true;
+ };
+ if (!checkLoadUses(Op0) || !checkLoadUses(Op1))
+ return SDValue();
+
+ if (!isOnlyUsedByXMMDomainSink(SDValue(N, 0)))
+ return SDValue();
+
+ SDLoc DL(N);
+ MVT VecVT = MVT::v4i32;
+ SDValue V0 = DAG.getNode(ISD::SCALAR_TO_VECTOR, DL, VecVT, Op0);
+ SDValue V1 = DAG.getNode(ISD::SCALAR_TO_VECTOR, DL, VecVT, Op1);
+ SDValue Vec = DAG.getNode(N->getOpcode(), DL, VecVT, V0, V1);
+ return DAG.getNode(ISD::EXTRACT_VECTOR_ELT, DL, VT, Vec,
+ DAG.getVectorIdxConstant(0, DL));
+}
+
+/// Combine scalar i32 abs of a plain load whose result is only stored (or
+/// insert_vector_elt) into a vector ABS on v4i32, which ISel folds to
+/// movd + PABSD. Requires SSSE3. Same domain-cross rationale as
+/// combineScalarMinMaxToSIMD.
+static SDValue combineScalarAbsToSIMD(SDNode *N, SelectionDAG &DAG,
+ const X86Subtarget &Subtarget) {
+ EVT VT = N->getValueType(0);
+ // Caller must only pass scalar nodes; still guard for safety.
+ if (!VT.isSimple() || VT.getSimpleVT() != MVT::i32 || !Subtarget.hasSSSE3())
+ return SDValue();
+
+ const Function &F = DAG.getMachineFunction().getFunction();
+ if (F.hasFnAttribute(Attribute::NoImplicitFloat) || Subtarget.useSoftFloat())
+ return SDValue();
+ if (F.hasOptSize())
+ return SDValue();
+
+ SDValue Op0 = N->getOperand(0);
+ if (!isPlainScalarLoad(Op0, VT))
+ return SDValue();
+
+ // The load must only feed this abs (or a SCALAR_TO_VECTOR from a sibling
+ // already combined this turn). A GPR consumer would force the load into a
+ // GPR and block memory folding.
+ for (SDUse &Use : Op0->uses()) {
+ if (Use.getResNo() != Op0.getResNo())
+ continue;
+ SDNode *User = Use.getUser();
+ if (User->getOpcode() == ISD::SCALAR_TO_VECTOR)
+ continue;
+ if (User->getOpcode() != ISD::ABS || User->getValueType(0).isVector())
+ return SDValue();
+ }
+
+ if (!isOnlyUsedByXMMDomainSink(SDValue(N, 0)))
+ return SDValue();
+
+ SDLoc DL(N);
+ MVT VecVT = MVT::v4i32;
+ SDValue V0 = DAG.getNode(ISD::SCALAR_TO_VECTOR, DL, VecVT, Op0);
+ SDValue Vec = DAG.getNode(ISD::ABS, DL, VecVT, V0);
+ return DAG.getNode(ISD::EXTRACT_VECTOR_ELT, DL, VT, Vec,
+ DAG.getVectorIdxConstant(0, DL));
+}
+
SDValue X86TargetLowering::PerformDAGCombine(SDNode *N,
DAGCombinerInfo &DCI) const {
SelectionDAG &DAG = DCI.DAG;
@@ -63003,6 +63146,19 @@ SDValue X86TargetLowering::PerformDAGCombine(SDNode *N,
case X86ISD::SUB: return combineX86AddSub(N, DAG, DCI, Subtarget);
case ISD::SADDSAT:
case ISD::SSUBSAT: return combineToHorizontalAddSub(N, DAG, Subtarget);
+ case ISD::ABS:
+ // Vector abs is already Legal/Custom; only try the scalar→SIMD widen.
+ if (!N->getValueType(0).isVector())
+ return combineScalarAbsToSIMD(N, DAG, Subtarget);
+ break;
+ case ISD::SMIN:
+ case ISD::SMAX:
+ case ISD::UMIN:
+ case ISD::UMAX:
+ // Vector min/max are already Legal; only try the scalar→SIMD widen.
+ if (!N->getValueType(0).isVector())
+ return combineScalarMinMaxToSIMD(N, DAG, Subtarget);
+ break;
case X86ISD::CLOAD:
case X86ISD::CSTORE: return combineX86CloadCstore(N, DAG);
case X86ISD::SBB: return combineSBB(N, DAG);
diff --git a/llvm/test/CodeGen/X86/scalar-minmax-simd.ll b/llvm/test/CodeGen/X86/scalar-minmax-simd.ll
new file mode 100644
index 0000000000000..8fc29706c613c
--- /dev/null
+++ b/llvm/test/CodeGen/X86/scalar-minmax-simd.ll
@@ -0,0 +1,686 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
+; RUN: llc < %s -mtriple=x86_64-unknown-unknown -mattr=+sse2 | FileCheck %s --check-prefixes=SSE2
+; RUN: llc < %s -mtriple=x86_64-unknown-unknown -mattr=+sse4.1 | FileCheck %s --check-prefixes=SSE41
+; RUN: llc < %s -mtriple=x86_64-unknown-unknown -mattr=+avx | FileCheck %s --check-prefixes=AVX
+; RUN: llc < %s -mtriple=i686-unknown-unknown -mattr=+sse4.1 | FileCheck %s --check-prefixes=X86-SSE41
+
+declare i32 @llvm.smin.i32(i32, i32)
+declare i32 @llvm.smax.i32(i32, i32)
+declare i32 @llvm.umin.i32(i32, i32)
+declare i32 @llvm.umax.i32(i32, i32)
+declare i32 @llvm.abs.i32(i32, i1)
+
+; Memory-bound sort2 (#210569): prefer vpmin/vpmax over cmp+cmov.
+define void @sort2_i32(ptr %a) nounwind {
+; SSE2-LABEL: sort2_i32:
+; SSE2: # %bb.0:
+; SSE2-NEXT: movl (%rdi), %eax
+; SSE2-NEXT: movl 4(%rdi), %ecx
+; SSE2-NEXT: cmpl %ecx, %eax
+; SSE2-NEXT: movl %ecx, %edx
+; SSE2-NEXT: cmovll %eax, %edx
+; SSE2-NEXT: cmovgl %eax, %ecx
+; SSE2-NEXT: movl %edx, (%rdi)
+; SSE2-NEXT: movl %ecx, 4(%rdi)
+; SSE2-NEXT: retq
+;
+; SSE41-LABEL: sort2_i32:
+; SSE41: # %bb.0:
+; SSE41-NEXT: movd {{.*#+}} xmm0 = mem[0],zero,zero,zero
+; SSE41-NEXT: movd {{.*#+}} xmm1 = mem[0],zero,zero,zero
+; SSE41-NEXT: movdqa %xmm1, %xmm2
+; SSE41-NEXT: pminsd %xmm0, %xmm2
+; SSE41-NEXT: pmaxsd %xmm0, %xmm1
+; SSE41-NEXT: movd %xmm2, (%rdi)
+; SSE41-NEXT: movd %xmm1, 4(%rdi)
+; SSE41-NEXT: retq
+;
+; AVX-LABEL: sort2_i32:
+; AVX: # %bb.0:
+; AVX-NEXT: vmovd {{.*#+}} xmm0 = mem[0],zero,zero,zero
+; AVX-NEXT: vmovd {{.*#+}} xmm1 = mem[0],zero,zero,zero
+; AVX-NEXT: vpminsd %xmm0, %xmm1, %xmm2
+; AVX-NEXT: vpmaxsd %xmm0, %xmm1, %xmm0
+; AVX-NEXT: vmovd %xmm2, (%rdi)
+; AVX-NEXT: vmovd %xmm0, 4(%rdi)
+; AVX-NEXT: retq
+;
+; X86-SSE41-LABEL: sort2_i32:
+; X86-SSE41: # %bb.0:
+; X86-SSE41-NEXT: movl {{[0-9]+}}(%esp), %eax
+; X86-SSE41-NEXT: movd {{.*#+}} xmm0 = mem[0],zero,zero,zero
+; X86-SSE41-NEXT: movd {{.*#+}} xmm1 = mem[0],zero,zero,zero
+; X86-SSE41-NEXT: movdqa %xmm1, %xmm2
+; X86-SSE41-NEXT: pminsd %xmm0, %xmm2
+; X86-SSE41-NEXT: pmaxsd %xmm0, %xmm1
+; X86-SSE41-NEXT: movd %xmm2, (%eax)
+; X86-SSE41-NEXT: movd %xmm1, 4(%eax)
+; X86-SSE41-NEXT: retl
+ %p0 = getelementptr inbounds i32, ptr %a, i64 0
+ %p1 = getelementptr inbounds i32, ptr %a, i64 1
+ %x = load i32, ptr %p0, align 4
+ %y = load i32, ptr %p1, align 4
+ %lo = call i32 @llvm.smin.i32(i32 %x, i32 %y)
+ %hi = call i32 @llvm.smax.i32(i32 %x, i32 %y)
+ store i32 %lo, ptr %p0, align 4
+ store i32 %hi, ptr %p1, align 4
+ ret void
+}
+
+define void @store_smin_i32(ptr %p, ptr %q, ptr %r) nounwind {
+; SSE2-LABEL: store_smin_i32:
+; SSE2: # %bb.0:
+; SSE2-NEXT: movl (%rdi), %eax
+; SSE2-NEXT: movl (%rsi), %ecx
+; SSE2-NEXT: cmpl %ecx, %eax
+; SSE2-NEXT: cmovll %eax, %ecx
+; SSE2-NEXT: movl %ecx, (%rdx)
+; SSE2-NEXT: retq
+;
+; SSE41-LABEL: store_smin_i32:
+; SSE41: # %bb.0:
+; SSE41-NEXT: movd {{.*#+}} xmm0 = mem[0],zero,zero,zero
+; SSE41-NEXT: movd {{.*#+}} xmm1 = mem[0],zero,zero,zero
+; SSE41-NEXT: pminsd %xmm0, %xmm1
+; SSE41-NEXT: movd %xmm1, (%rdx)
+; SSE41-NEXT: retq
+;
+; AVX-LABEL: store_smin_i32:
+; AVX: # %bb.0:
+; AVX-NEXT: vmovd {{.*#+}} xmm0 = mem[0],zero,zero,zero
+; AVX-NEXT: vmovd {{.*#+}} xmm1 = mem[0],zero,zero,zero
+; AVX-NEXT: vpminsd %xmm0, %xmm1, %xmm0
+; AVX-NEXT: vmovd %xmm0, (%rdx)
+; AVX-NEXT: retq
+;
+; X86-SSE41-LABEL: store_smin_i32:
+; X86-SSE41: # %bb.0:
+; X86-SSE41-NEXT: movl {{[0-9]+}}(%esp), %eax
+; X86-SSE41-NEXT: movl {{[0-9]+}}(%esp), %ecx
+; X86-SSE41-NEXT: movl {{[0-9]+}}(%esp), %edx
+; X86-SSE41-NEXT: movd {{.*#+}} xmm0 = mem[0],zero,zero,zero
+; X86-SSE41-NEXT: movd {{.*#+}} xmm1 = mem[0],zero,zero,zero
+; X86-SSE41-NEXT: pminsd %xmm0, %xmm1
+; X86-SSE41-NEXT: movd %xmm1, (%eax)
+; X86-SSE41-NEXT: retl
+ %x = load i32, ptr %p, align 4
+ %y = load i32, ptr %q, align 4
+ %m = call i32 @llvm.smin.i32(i32 %x, i32 %y)
+ store i32 %m, ptr %r, align 4
+ ret void
+}
+
+define void @store_smax_i32(ptr %p, ptr %q, ptr %r) nounwind {
+; SSE2-LABEL: store_smax_i32:
+; SSE2: # %bb.0:
+; SSE2-NEXT: movl (%rdi), %eax
+; SSE2-NEXT: movl (%rsi), %ecx
+; SSE2-NEXT: cmpl %ecx, %eax
+; SSE2-NEXT: cmovgl %eax, %ecx
+; SSE2-NEXT: movl %ecx, (%rdx)
+; SSE2-NEXT: retq
+;
+; SSE41-LABEL: store_smax_i32:
+; SSE41: # %bb.0:
+; SSE41-NEXT: movd {{.*#+}} xmm0 = mem[0],zero,zero,zero
+; SSE41-NEXT: movd {{.*#+}} xmm1 = mem[0],zero,zero,zero
+; SSE41-NEXT: pmaxsd %xmm0, %xmm1
+; SSE41-NEXT: movd %xmm1, (%rdx)
+; SSE41-NEXT: retq
+;
+; AVX-LABEL: store_smax_i32:
+; AVX: # %bb.0:
+; AVX-NEXT: vmovd {{.*#+}} xmm0 = mem[0],zero,zero,zero
+; AVX-NEXT: vmovd {{.*#+}} xmm1 = mem[0],zero,zero,zero
+; AVX-NEXT: vpmaxsd %xmm0, %xmm1, %xmm0
+; AVX-NEXT: vmovd %xmm0, (%rdx)
+; AVX-NEXT: retq
+;
+; X86-SSE41-LABEL: store_smax_i32:
+; X86-SSE41: # %bb.0:
+; X86-SSE41-NEXT: movl {{[0-9]+}}(%esp), %eax
+; X86-SSE41-NEXT: movl {{[0-9]+}}(%esp), %ecx
+; X86-SSE41-NEXT: movl {{[0-9]+}}(%esp), %edx
+; X86-SSE41-NEXT: movd {{.*#+}} xmm0 = mem[0],zero,zero,zero
+; X86-SSE41-NEXT: movd {{.*#+}} xmm1 = mem[0],zero,zero,zero
+; X86-SSE41-NEXT: pmaxsd %xmm0, %xmm1
+; X86-SSE41-NEXT: movd %xmm1, (%eax)
+; X86-SSE41-NEXT: retl
+ %x = load i32, ptr %p, align 4
+ %y = load i32, ptr %q, align 4
+ %m = call i32 @llvm.smax.i32(i32 %x, i32 %y)
+ store i32 %m, ptr %r, align 4
+ ret void
+}
+
+define void @store_umin_i32(ptr %p, ptr %q, ptr %r) nounwind {
+; SSE2-LABEL: store_umin_i32:
+; SSE2: # %bb.0:
+; SSE2-NEXT: movl (%rdi), %eax
+; SSE2-NEXT: movl (%rsi), %ecx
+; SSE2-NEXT: cmpl %ecx, %eax
+; SSE2-NEXT: cmovbl %eax, %ecx
+; SSE2-NEXT: movl %ecx, (%rdx)
+; SSE2-NEXT: retq
+;
+; SSE41-LABEL: store_umin_i32:
+; SSE41: # %bb.0:
+; SSE41-NEXT: movd {{.*#+}} xmm0 = mem[0],zero,zero,zero
+; SSE41-NEXT: movd {{.*#+}} xmm1 = mem[0],zero,zero,zero
+; SSE41-NEXT: pminud %xmm0, %xmm1
+; SSE41-NEXT: movd %xmm1, (%rdx)
+; SSE41-NEXT: retq
+;
+; AVX-LABEL: store_umin_i32:
+; AVX: # %bb.0:
+; AVX-NEXT: vmovd {{.*#+}} xmm0 = mem[0],zero,zero,zero
+; AVX-NEXT: vmovd {{.*#+}} xmm1 = mem[0],zero,zero,zero
+; AVX-NEXT: vpminud %xmm0, %xmm1, %xmm0
+; AVX-NEXT: vmovd %xmm0, (%rdx)
+; AVX-NEXT: retq
+;
+; X86-SSE41-LABEL: store_umin_i32:
+; X86-SSE41: # %bb.0:
+; X86-SSE41-NEXT: movl {{[0-9]+}}(%esp), %eax
+; X86-SSE41-NEXT: movl {{[0-9]+}}(%esp), %ecx
+; X86-SSE41-NEXT: movl {{[0-9]+}}(%esp), %edx
+; X86-SSE41-NEXT: movd {{.*#+}} xmm0 = mem[0],zero,zero,zero
+; X86-SSE41-NEXT: movd {{.*#+}} xmm1 = mem[0],zero,zero,zero
+; X86-SSE41-NEXT: pminud %xmm0, %xmm1
+; X86-SSE41-NEXT: movd %xmm1, (%eax)
+; X86-SSE41-NEXT: retl
+ %x = load i32, ptr %p, align 4
+ %y = load i32, ptr %q, align 4
+ %m = call i32 @llvm.umin.i32(i32 %x, i32 %y)
+ store i32 %m, ptr %r, align 4
+ ret void
+}
+
+define void @store_umax_i32(ptr %p, ptr %q, ptr %r) nounwind {
+; SSE2-LABEL: store_umax_i32:
+; SSE2: # %bb.0:
+; SSE2-NEXT: movl (%rdi), %eax
+; SSE2-NEXT: movl (%rsi), %ecx
+; SSE2-NEXT: cmpl %ecx, %eax
+; SSE2-NEXT: cmoval %eax, %ecx
+; SSE2-NEXT: movl %ecx, (%rdx)
+; SSE2-NEXT: retq
+;
+; SSE41-LABEL: store_umax_i32:
+; SSE41: # %bb.0:
+; SSE41-NEXT: movd {{.*#+}} xmm0 = mem[0],zero,zero,zero
+; SSE41-NEXT: movd {{.*#+}} xmm1 = mem[0],zero,zero,zero
+; SSE41-NEXT: pmaxud %xmm0, %xmm1
+; SSE41-NEXT: movd %xmm1, (%rdx)
+; SSE41-NEXT: retq
+;
+; AVX-LABEL: store_umax_i32:
+; AVX: # %bb.0:
+; AVX-NEXT: vmovd {{.*#+}} xmm0 = mem[0],zero,zero,zero
+; AVX-NEXT: vmovd {{.*#+}} xmm1 = mem[0],zero,zero,zero
+; AVX-NEXT: vpmaxud %xmm0, %xmm1, %xmm0
+; AVX-NEXT: vmovd %xmm0, (%rdx)
+; AVX-NEXT: retq
+;
+; X86-SSE41-LABEL: store_umax_i32:
+; X86-SSE41: # %bb.0:
+; X86-SSE41-NEXT: movl {{[0-9]+}}(%esp), %eax
+; X86-SSE41-NEXT: movl {{[0-9]+}}(%esp), %ecx
+; X86-SSE41-NEXT: movl {{[0-9]+}}(%esp), %edx
+; X86-SSE41-NEXT: movd {{.*#+}} xmm0 = mem[0],zero,zero,zero
+; X86-SSE41-NEXT: movd {{.*#+}} xmm1 = mem[0],zero,zero,zero
+; X86-SSE41-NEXT: pmaxud %xmm0, %xmm1
+; X86-SSE41-NEXT: movd %xmm1, (%eax)
+; X86-SSE41-NEXT: retl
+ %x = load i32, ptr %p, align 4
+ %y = load i32, ptr %q, align 4
+ %m = call i32 @llvm.umax.i32(i32 %x, i32 %y)
+ store i32 %m, ptr %r, align 4
+ ret void
+}
+
+; Negative: register operands / GPR result should stay on CMOV.
+define i32 @smin_gpr(i32 %x, i32 %y) nounwind {
+; SSE2-LABEL: smin_gpr:
+; SSE2: # %bb.0:
+; SSE2-NEXT: movl %esi, %eax
+; SSE2-NEXT: cmpl %esi, %edi
+; SSE2-NEXT: cmovll %edi, %eax
+; SSE2-NEXT: retq
+;
+; SSE41-LABEL: smin_gpr:
+; SSE41: # %bb.0:
+; SSE41-NEXT: movl %esi, %eax
+; SSE41-NEXT: cmpl %esi, %edi
+; SSE41-NEXT: cmovll %edi, %eax
+; SSE41-NEXT: retq
+;
+; AVX-LABEL: smin_gpr:
+; AVX: # %bb.0:
+; AVX-NEXT: movl %esi, %eax
+; AVX-NEXT: cmpl %esi, %edi
+; AVX-NEXT: cmovll %edi, %eax
+; AVX-NEXT: retq
+;
+; X86-SSE41-LABEL: smin_gpr:
+; X86-SSE41: # %bb.0:
+; X86-SSE41-NEXT: movl {{[0-9]+}}(%esp), %eax
+; X86-SSE41-NEXT: movl {{[0-9]+}}(%esp), %ecx
+; X86-SSE41-NEXT: cmpl %eax, %ecx
+; X86-SSE41-NEXT: cmovll %ecx, %eax
+; X86-SSE41-NEXT: retl
+ %m = call i32 @llvm.smin.i32(i32 %x, i32 %y)
+ ret i32 %m
+}
+
+define i32 @smin_load_to_gpr(ptr %p, ptr %q) nounwind {
+; SSE2-LABEL: smin_load_to_gpr:
+; SSE2: # %bb.0:
+; SSE2-NEXT: movl (%rdi), %ecx
+; SSE2-NEXT: movl (%rsi), %eax
+; SSE2-NEXT: cmpl %eax, %ecx
+; SSE2-NEXT: cmovll %ecx, %eax
+; SSE2-NEXT: retq
+;
+; SSE41-LABEL: smin_load_to_gpr:
+; SSE41: # %bb.0:
+; SSE41-NEXT: movl (%rdi), %ecx
+; SSE41-NEXT: movl (%rsi), %eax
+; SSE41-NEXT: cmpl %eax, %ecx
+; SSE41-NEXT: cmovll %ecx, %eax
+; SSE41-NEXT: retq
+;
+; AVX-LABEL: smin_load_to_gpr:
+; AVX: # %bb.0:
+; AVX-NEXT: movl (%rdi), %ecx
+; AVX-NEXT: movl (%rsi), %eax
+; AVX-NEXT: cmpl %eax, %ecx
+; AVX-NEXT: cmovll %ecx, %eax
+; AVX-NEXT: retq
+;
+; X86-SSE41-LABEL: smin_load_to_gpr:
+; X86-SSE41: # %bb.0:
+; X86-SSE41-NEXT: movl {{[0-9]+}}(%esp), %eax
+; X86-SSE41-NEXT: movl {{[0-9]+}}(%esp), %ecx
+; X86-SSE41-NEXT: movl (%ecx), %ecx
+; X86-SSE41-NEXT: movl (%eax), %eax
+; X86-SSE41-NEXT: cmpl %eax, %ecx
+; X86-SSE41-NEXT: cmovll %ecx, %eax
+; X86-SSE41-NEXT: retl
+ %x = load i32, ptr %p, align 4
+ %y = load i32, ptr %q, align 4
+ %m = call i32 @llvm.smin.i32(i32 %x, i32 %y)
+ ret i32 %m
+}
+
+; Negative: noimplicitfloat must not introduce XMM ops.
+define void @sort2_i32_noimplicitfloat(ptr %a) nounwind noimplicitfloat {
+; SSE2-LABEL: sort2_i32_noimplicitfloat:
+; SSE2: # %bb.0:
+; SSE2-NEXT: movl (%rdi), %eax
+; SSE2-NEXT: movl 4(%rdi), %ecx
+; SSE2-NEXT: cmpl %ecx, %eax
+; SSE2-NEXT: movl %ecx, %edx
+; SSE2-NEXT: cmovll %eax, %edx
+; SSE2-NEXT: cmovgl %eax, %ecx
+; SSE2-NEXT: movl %edx, (%rdi)
+; SSE2-NEXT: movl %ecx, 4(%rdi)
+; SSE2-NEXT: retq
+;
+; SSE41-LABEL: sort2_i32_noimplicitfloat:
+; SSE41: # %bb.0:
+; SSE41-NEXT: movl (%rdi), %eax
+; SSE41-NEXT: movl 4(%rdi), %ecx
+; SSE41-NEXT: cmpl %ecx, %eax
+; SSE41-NEXT: movl %ecx, %edx
+; SSE41-NEXT: cmovll %eax, %edx
+; SSE41-NEXT: cmovgl %eax, %ecx
+; SSE41-NEXT: movl %edx, (%rdi)
+; SSE41-NEXT: movl %ecx, 4(%rdi)
+; SSE41-NEXT: retq
+;
+; AVX-LABEL: sort2_i32_noimplicitfloat:
+; AVX: # %bb.0:
+; AVX-NEXT: movl (%rdi), %eax
+; AVX-NEXT: movl 4(%rdi), %ecx
+; AVX-NEXT: cmpl %ecx, %eax
+; AVX-NEXT: movl %ecx, %edx
+; AVX-NEXT: cmovll %eax, %edx
+; AVX-NEXT: cmovgl %eax, %ecx
+; AVX-NEXT: movl %edx, (%rdi)
+; AVX-NEXT: movl %ecx, 4(%rdi)
+; AVX-NEXT: retq
+;
+; X86-SSE41-LABEL: sort2_i32_noimplicitfloat:
+; X86-SSE41: # %bb.0:
+; X86-SSE41-NEXT: pushl %esi
+; X86-SSE41-NEXT: movl {{[0-9]+}}(%esp), %eax
+; X86-SSE41-NEXT: movl (%eax), %ecx
+; X86-SSE41-NEXT: movl 4(%eax), %edx
+; X86-SSE41-NEXT: cmpl %edx, %ecx
+; X86-SSE41-NEXT: movl %edx, %esi
+; X86-SSE41-NEXT: cmovll %ecx, %esi
+; X86-SSE41-NEXT: cmovgl %ecx, %edx
+; X86-SSE41-NEXT: movl %esi, (%eax)
+; X86-SSE41-NEXT: movl %edx, 4(%eax)
+; X86-SSE41-NEXT: popl %esi
+; X86-SSE41-NEXT: retl
+ %p0 = getelementptr inbounds i32, ptr %a, i64 0
+ %p1 = getelementptr inbounds i32, ptr %a, i64 1
+ %x = load i32, ptr %p0, align 4
+ %y = load i32, ptr %p1, align 4
+ %lo = ca...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/210654
More information about the llvm-commits
mailing list