[llvm] [DAGCombiner] Fold smax(X, -1)/smin(X, 0) to bitwise shift forms (PR #206242)

Aayush Shrivastava via llvm-commits llvm-commits at lists.llvm.org
Tue Jun 30 04:32:47 PDT 2026


https://github.com/iamaayushrivastava updated https://github.com/llvm/llvm-project/pull/206242

>From 251de0bb417b108851e7c978d4024798d5c2b369 Mon Sep 17 00:00:00 2001
From: iamaayushrivastava <iamaayushrivastava at gmail.com>
Date: Sat, 27 Jun 2026 17:55:58 +0530
Subject: [PATCH 01/10] [DAGCombiner] Fold smax(X, -1) to or(X, ashr(X, BW-1))
 for code size

---
 llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp | 16 ++++
 .../test/CodeGen/X86/smax-allones-codesize.ll | 74 +++++++++++++++++++
 2 files changed, 90 insertions(+)
 create mode 100644 llvm/test/CodeGen/X86/smax-allones-codesize.ll

diff --git a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
index 1b9a62211deaf..407ca8ee7bb5e 100644
--- a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
@@ -6307,6 +6307,22 @@ SDValue DAGCombiner::visitIMINMAX(SDNode *N) {
   if (SDValue RMINMAX = reassociateOps(Opcode, DL, N0, N1, N->getFlags()))
     return RMINMAX;
 
+  // For code size: smax(X, -1) -> or(X, ashr(X, BW-1))
+  // The arithmetic right shift sign-extends: 0 for X >= 0, -1 for X < 0.
+  // OR-ing X with this mask yields X when non-negative and -1 when negative,
+  // which matches smax(X, -1) using two instructions instead of compare+cmov.
+  if (ForCodeSize && Opcode == ISD::SMAX) {
+    if (auto *N1C = isConstOrConstSplat(N1)) {
+      if (N1C->isAllOnes() &&
+          !TLI.shouldAvoidTransformToShift(VT, VT.getScalarSizeInBits() - 1)) {
+        SDValue Shift = DAG.getNode(
+            ISD::SRA, DL, VT, N0,
+            DAG.getShiftAmountConstant(VT.getScalarSizeInBits() - 1, VT, DL));
+        return DAG.getNode(ISD::OR, DL, VT, N0, Shift);
+      }
+    }
+  }
+
   // If both operands are known to have the same sign (both non-negative or both
   // negative), flip between UMIN/UMAX and SMIN/SMAX.
   // Only do this if:
diff --git a/llvm/test/CodeGen/X86/smax-allones-codesize.ll b/llvm/test/CodeGen/X86/smax-allones-codesize.ll
new file mode 100644
index 0000000000000..3c1620baffe12
--- /dev/null
+++ b/llvm/test/CodeGen/X86/smax-allones-codesize.ll
@@ -0,0 +1,74 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
+; RUN: llc < %s -mtriple=x86_64-linux -O2 | FileCheck %s
+;
+; Verify smax(X, -1) folds to or(X, ashr(X, BW-1)) under minsize.
+; This saves a byte vs compare+cmov on x86-64 (-Oz / minsize attribute).
+; GCC PR 125921, LLVM issue #206153.
+
+; Without minsize: keep as compare+cmov
+define i32 @smax_allones_i32_normal(i32 %x) {
+; CHECK-LABEL: smax_allones_i32_normal:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    testl %edi, %edi
+; CHECK-NEXT:    movl $-1, %eax
+; CHECK-NEXT:    cmovnsl %edi, %eax
+; CHECK-NEXT:    retq
+  %r = call i32 @llvm.smax.i32(i32 %x, i32 -1)
+  ret i32 %r
+}
+
+; With minsize: fold to or(X, ashr(X, 31))
+define i32 @smax_allones_i32(i32 %x) minsize {
+; CHECK-LABEL: smax_allones_i32:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movl %edi, %eax
+; CHECK-NEXT:    sarl $31, %eax
+; CHECK-NEXT:    orl %edi, %eax
+; CHECK-NEXT:    retq
+  %r = call i32 @llvm.smax.i32(i32 %x, i32 -1)
+  ret i32 %r
+}
+
+; i64 variant with minsize
+define i64 @smax_allones_i64(i64 %x) minsize {
+; CHECK-LABEL: smax_allones_i64:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movq %rdi, %rax
+; CHECK-NEXT:    sarq $63, %rax
+; CHECK-NEXT:    orq %rdi, %rax
+; CHECK-NEXT:    retq
+  %r = call i64 @llvm.smax.i64(i64 %x, i64 -1)
+  ret i64 %r
+}
+
+; smax(X, 0) with minsize should NOT transform (no 2-instruction bitwise form)
+define i32 @smax_zero_minsize(i32 %x) minsize {
+; CHECK-LABEL: smax_zero_minsize:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    xorl %eax, %eax
+; CHECK-NEXT:    testl %edi, %edi
+; CHECK-NEXT:    cmovgl %edi, %eax
+; CHECK-NEXT:    retq
+  %r = call i32 @llvm.smax.i32(i32 %x, i32 0)
+  ret i32 %r
+}
+
+; smin(X, -1) with minsize should NOT transform (no 2-instruction bitwise form)
+define i32 @smin_allones_minsize(i32 %x) minsize {
+; CHECK-LABEL: smin_allones_minsize:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cmpl $-1, %edi
+; CHECK-NEXT:    pushq $-1
+; CHECK-NEXT:    .cfi_adjust_cfa_offset 8
+; CHECK-NEXT:    popq %rax
+; CHECK-NEXT:    .cfi_adjust_cfa_offset -8
+; CHECK-NEXT:    cmovll %edi, %eax
+; CHECK-NEXT:    retq
+  %r = call i32 @llvm.smin.i32(i32 %x, i32 -1)
+  ret i32 %r
+}
+
+declare i32 @llvm.smax.i32(i32, i32)
+declare i64 @llvm.smax.i64(i64, i64)
+declare i16 @llvm.smax.i16(i16, i16)
+declare i32 @llvm.smin.i32(i32, i32)

>From 99f397403606c3e61cfde0c88eea26865dde1712 Mon Sep 17 00:00:00 2001
From: iamaayushrivastava <iamaayushrivastava at gmail.com>
Date: Sun, 28 Jun 2026 00:02:05 +0530
Subject: [PATCH 02/10] [DAGCombiner] Fold smax(X, -1) to or(X, ashr(X, BW-1))
 unconditionally

---
 llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp |  6 ++-
 llvm/test/CodeGen/AArch64/smax-allones.ll     | 23 ++++++++++
 llvm/test/CodeGen/X86/known-never-zero.ll     | 12 ++---
 .../test/CodeGen/X86/smax-allones-codesize.ll | 46 +++++--------------
 4 files changed, 45 insertions(+), 42 deletions(-)
 create mode 100644 llvm/test/CodeGen/AArch64/smax-allones.ll

diff --git a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
index 407ca8ee7bb5e..e745cdd63bba1 100644
--- a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
@@ -6307,11 +6307,13 @@ SDValue DAGCombiner::visitIMINMAX(SDNode *N) {
   if (SDValue RMINMAX = reassociateOps(Opcode, DL, N0, N1, N->getFlags()))
     return RMINMAX;
 
-  // For code size: smax(X, -1) -> or(X, ashr(X, BW-1))
+  // smax(X, -1) -> or(X, ashr(X, BW-1))
   // The arithmetic right shift sign-extends: 0 for X >= 0, -1 for X < 0.
   // OR-ing X with this mask yields X when non-negative and -1 when negative,
   // which matches smax(X, -1) using two instructions instead of compare+cmov.
-  if (ForCodeSize && Opcode == ISD::SMAX) {
+  // Beneficial for code size on x86-64 and for instruction count on
+  // AArch64/APX.
+  if (Opcode == ISD::SMAX) {
     if (auto *N1C = isConstOrConstSplat(N1)) {
       if (N1C->isAllOnes() &&
           !TLI.shouldAvoidTransformToShift(VT, VT.getScalarSizeInBits() - 1)) {
diff --git a/llvm/test/CodeGen/AArch64/smax-allones.ll b/llvm/test/CodeGen/AArch64/smax-allones.ll
new file mode 100644
index 0000000000000..ef083f38d3ec4
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/smax-allones.ll
@@ -0,0 +1,23 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
+; RUN: llc < %s -mtriple=aarch64-linux -O2 | FileCheck %s
+;
+; Verify smax(X, -1) folds to or(X, ashr(X, BW-1)), reducing instruction count
+; on AArch64 (2 instructions -> 1 shifted ORR). LLVM issue #206153.
+
+define i32 @smax_allones_i32(i32 %x) {
+; CHECK-LABEL: smax_allones_i32:
+; CHECK:       // %bb.0:
+; CHECK-NEXT:    orr w0, w0, w0, asr #31
+; CHECK-NEXT:    ret
+  %r = call i32 @llvm.smax.i32(i32 %x, i32 -1)
+  ret i32 %r
+}
+
+define i64 @smax_allones_i64(i64 %x) {
+; CHECK-LABEL: smax_allones_i64:
+; CHECK:       // %bb.0:
+; CHECK-NEXT:    orr x0, x0, x0, asr #63
+; CHECK-NEXT:    ret
+  %r = call i64 @llvm.smax.i64(i64 %x, i64 -1)
+  ret i64 %r
+}
diff --git a/llvm/test/CodeGen/X86/known-never-zero.ll b/llvm/test/CodeGen/X86/known-never-zero.ll
index c90ab282d2255..478449bb1702d 100644
--- a/llvm/test/CodeGen/X86/known-never-zero.ll
+++ b/llvm/test/CodeGen/X86/known-never-zero.ll
@@ -927,9 +927,9 @@ define i32 @smax_known_zero(i32 %x, i32 %y) {
 ; X86-LABEL: smax_known_zero:
 ; X86:       # %bb.0:
 ; X86-NEXT:    movl {{[0-9]+}}(%esp), %eax
-; X86-NEXT:    testl %eax, %eax
-; X86-NEXT:    movl $-1, %ecx
-; X86-NEXT:    cmovnsl %eax, %ecx
+; X86-NEXT:    movl %eax, %ecx
+; X86-NEXT:    sarl $31, %ecx
+; X86-NEXT:    orl %eax, %ecx
 ; X86-NEXT:    bsfl %ecx, %ecx
 ; X86-NEXT:    movl $32, %eax
 ; X86-NEXT:    cmovnel %ecx, %eax
@@ -937,9 +937,9 @@ define i32 @smax_known_zero(i32 %x, i32 %y) {
 ;
 ; X64-LABEL: smax_known_zero:
 ; X64:       # %bb.0:
-; X64-NEXT:    testl %edi, %edi
-; X64-NEXT:    movl $-1, %ecx
-; X64-NEXT:    cmovnsl %edi, %ecx
+; X64-NEXT:    movl %edi, %ecx
+; X64-NEXT:    sarl $31, %ecx
+; X64-NEXT:    orl %edi, %ecx
 ; X64-NEXT:    movl $32, %eax
 ; X64-NEXT:    rep bsfl %ecx, %eax
 ; X64-NEXT:    retq
diff --git a/llvm/test/CodeGen/X86/smax-allones-codesize.ll b/llvm/test/CodeGen/X86/smax-allones-codesize.ll
index 3c1620baffe12..592a5c93feef6 100644
--- a/llvm/test/CodeGen/X86/smax-allones-codesize.ll
+++ b/llvm/test/CodeGen/X86/smax-allones-codesize.ll
@@ -1,24 +1,11 @@
 ; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
 ; RUN: llc < %s -mtriple=x86_64-linux -O2 | FileCheck %s
 ;
-; Verify smax(X, -1) folds to or(X, ashr(X, BW-1)) under minsize.
-; This saves a byte vs compare+cmov on x86-64 (-Oz / minsize attribute).
-; GCC PR 125921, LLVM issue #206153.
+; Verify smax(X, -1) folds to or(X, ashr(X, BW-1)) at all optimization levels.
+; On x86-64 this saves a byte vs compare+cmov; on AArch64/APX it also reduces
+; instruction count. GCC PR 125921, LLVM issue #206153.
 
-; Without minsize: keep as compare+cmov
-define i32 @smax_allones_i32_normal(i32 %x) {
-; CHECK-LABEL: smax_allones_i32_normal:
-; CHECK:       # %bb.0:
-; CHECK-NEXT:    testl %edi, %edi
-; CHECK-NEXT:    movl $-1, %eax
-; CHECK-NEXT:    cmovnsl %edi, %eax
-; CHECK-NEXT:    retq
-  %r = call i32 @llvm.smax.i32(i32 %x, i32 -1)
-  ret i32 %r
-}
-
-; With minsize: fold to or(X, ashr(X, 31))
-define i32 @smax_allones_i32(i32 %x) minsize {
+define i32 @smax_allones_i32(i32 %x) {
 ; CHECK-LABEL: smax_allones_i32:
 ; CHECK:       # %bb.0:
 ; CHECK-NEXT:    movl %edi, %eax
@@ -29,8 +16,7 @@ define i32 @smax_allones_i32(i32 %x) minsize {
   ret i32 %r
 }
 
-; i64 variant with minsize
-define i64 @smax_allones_i64(i64 %x) minsize {
+define i64 @smax_allones_i64(i64 %x) {
 ; CHECK-LABEL: smax_allones_i64:
 ; CHECK:       # %bb.0:
 ; CHECK-NEXT:    movq %rdi, %rax
@@ -41,9 +27,9 @@ define i64 @smax_allones_i64(i64 %x) minsize {
   ret i64 %r
 }
 
-; smax(X, 0) with minsize should NOT transform (no 2-instruction bitwise form)
-define i32 @smax_zero_minsize(i32 %x) minsize {
-; CHECK-LABEL: smax_zero_minsize:
+; smax(X, 0) should NOT transform -- no 2-instruction bitwise form
+define i32 @smax_zero(i32 %x) {
+; CHECK-LABEL: smax_zero:
 ; CHECK:       # %bb.0:
 ; CHECK-NEXT:    xorl %eax, %eax
 ; CHECK-NEXT:    testl %edi, %edi
@@ -53,22 +39,14 @@ define i32 @smax_zero_minsize(i32 %x) minsize {
   ret i32 %r
 }
 
-; smin(X, -1) with minsize should NOT transform (no 2-instruction bitwise form)
-define i32 @smin_allones_minsize(i32 %x) minsize {
-; CHECK-LABEL: smin_allones_minsize:
+; smin(X, -1) should NOT transform -- no 2-instruction bitwise form exists
+define i32 @smin_allones(i32 %x) {
+; CHECK-LABEL: smin_allones:
 ; CHECK:       # %bb.0:
 ; CHECK-NEXT:    cmpl $-1, %edi
-; CHECK-NEXT:    pushq $-1
-; CHECK-NEXT:    .cfi_adjust_cfa_offset 8
-; CHECK-NEXT:    popq %rax
-; CHECK-NEXT:    .cfi_adjust_cfa_offset -8
+; CHECK-NEXT:    movl $-1, %eax
 ; CHECK-NEXT:    cmovll %edi, %eax
 ; CHECK-NEXT:    retq
   %r = call i32 @llvm.smin.i32(i32 %x, i32 -1)
   ret i32 %r
 }
-
-declare i32 @llvm.smax.i32(i32, i32)
-declare i64 @llvm.smax.i64(i64, i64)
-declare i16 @llvm.smax.i16(i16, i16)
-declare i32 @llvm.smin.i32(i32, i32)

>From 8fc20528955302b2f0c02c4703ae74683cb7b6b6 Mon Sep 17 00:00:00 2001
From: iamaayushrivastava <iamaayushrivastava at gmail.com>
Date: Sun, 28 Jun 2026 01:46:43 +0530
Subject: [PATCH 03/10] [DAGCombiner] Fold smax(X,-1)/smin(X,0) to bitwise
 shift forms

---
 llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp | 45 +++++++++++++------
 llvm/test/CodeGen/AArch64/smax-allones.ll     | 22 ++++++++-
 llvm/test/CodeGen/RISCV/fpclamptosat.ll       |  4 +-
 .../CodeGen/RISCV/rvv/fpclamptosat_vec.ll     |  8 ++--
 .../test/CodeGen/X86/smax-allones-codesize.ll | 29 ++++++++++--
 5 files changed, 83 insertions(+), 25 deletions(-)

diff --git a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
index e745cdd63bba1..854faefe41524 100644
--- a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
@@ -6307,20 +6307,37 @@ SDValue DAGCombiner::visitIMINMAX(SDNode *N) {
   if (SDValue RMINMAX = reassociateOps(Opcode, DL, N0, N1, N->getFlags()))
     return RMINMAX;
 
-  // smax(X, -1) -> or(X, ashr(X, BW-1))
-  // The arithmetic right shift sign-extends: 0 for X >= 0, -1 for X < 0.
-  // OR-ing X with this mask yields X when non-negative and -1 when negative,
-  // which matches smax(X, -1) using two instructions instead of compare+cmov.
-  // Beneficial for code size on x86-64 and for instruction count on
-  // AArch64/APX.
-  if (Opcode == ISD::SMAX) {
-    if (auto *N1C = isConstOrConstSplat(N1)) {
-      if (N1C->isAllOnes() &&
-          !TLI.shouldAvoidTransformToShift(VT, VT.getScalarSizeInBits() - 1)) {
-        SDValue Shift = DAG.getNode(
-            ISD::SRA, DL, VT, N0,
-            DAG.getShiftAmountConstant(VT.getScalarSizeInBits() - 1, VT, DL));
-        return DAG.getNode(ISD::OR, DL, VT, N0, Shift);
+  // Fold sign-extension masks using arithmetic shift:
+  //   smax(X, -1) -> or(X,  ashr(X, BW-1))
+  //   smin(X,  0) -> and(X, ashr(X, BW-1))
+  // ashr(X, BW-1) sign-extends the sign bit: 0 for X>=0, -1 for X<0.
+  // OR with X yields X (non-negative) or -1 (negative) = smax(X,-1).
+  // AND with X yields 0 (non-negative) or X (negative)  = smin(X, 0).
+  // Both reduce to two instructions vs. a compare+cmov on x86-64.
+  // Only fold when the target has no native SMAX/SMIN instruction for this
+  // type (isOperationExpand), the type is legal (not needing splitting), and
+  // the operand is not a min/max chain (preserving saturation patterns such as
+  // RISCV-P sati which combine smin+smax into a single instruction).
+  if (TLI.isTypeLegal(VT) &&
+      !TLI.shouldAvoidTransformToShift(VT, VT.getScalarSizeInBits() - 1)) {
+    SDValue ShiftAmt =
+        DAG.getShiftAmountConstant(VT.getScalarSizeInBits() - 1, VT, DL);
+    if (Opcode == ISD::SMAX && TLI.isOperationExpand(ISD::SMAX, VT) &&
+        N0.getOpcode() != ISD::SMIN) {
+      if (auto *N1C = isConstOrConstSplat(N1)) {
+        if (N1C->isAllOnes()) {
+          SDValue Shift = DAG.getNode(ISD::SRA, DL, VT, N0, ShiftAmt);
+          return DAG.getNode(ISD::OR, DL, VT, N0, Shift);
+        }
+      }
+    }
+    if (Opcode == ISD::SMIN && TLI.isOperationExpand(ISD::SMIN, VT) &&
+        N0.getOpcode() != ISD::SMAX) {
+      if (auto *N1C = isConstOrConstSplat(N1)) {
+        if (N1C->isZero()) {
+          SDValue Shift = DAG.getNode(ISD::SRA, DL, VT, N0, ShiftAmt);
+          return DAG.getNode(ISD::AND, DL, VT, N0, Shift);
+        }
       }
     }
   }
diff --git a/llvm/test/CodeGen/AArch64/smax-allones.ll b/llvm/test/CodeGen/AArch64/smax-allones.ll
index ef083f38d3ec4..ac36691b4645c 100644
--- a/llvm/test/CodeGen/AArch64/smax-allones.ll
+++ b/llvm/test/CodeGen/AArch64/smax-allones.ll
@@ -1,8 +1,8 @@
 ; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
 ; RUN: llc < %s -mtriple=aarch64-linux -O2 | FileCheck %s
 ;
-; Verify smax(X, -1) folds to or(X, ashr(X, BW-1)), reducing instruction count
-; on AArch64 (2 instructions -> 1 shifted ORR). LLVM issue #206153.
+; Verify smax(X,-1) and smin(X,0) fold to shifted bitwise forms on AArch64,
+; reducing 2 instructions to 1 shifted ORR/AND. LLVM issue #206153.
 
 define i32 @smax_allones_i32(i32 %x) {
 ; CHECK-LABEL: smax_allones_i32:
@@ -21,3 +21,21 @@ define i64 @smax_allones_i64(i64 %x) {
   %r = call i64 @llvm.smax.i64(i64 %x, i64 -1)
   ret i64 %r
 }
+
+define i32 @smin_zero_i32(i32 %x) {
+; CHECK-LABEL: smin_zero_i32:
+; CHECK:       // %bb.0:
+; CHECK-NEXT:    and w0, w0, w0, asr #31
+; CHECK-NEXT:    ret
+  %r = call i32 @llvm.smin.i32(i32 %x, i32 0)
+  ret i32 %r
+}
+
+define i64 @smin_zero_i64(i64 %x) {
+; CHECK-LABEL: smin_zero_i64:
+; CHECK:       // %bb.0:
+; CHECK-NEXT:    and x0, x0, x0, asr #63
+; CHECK-NEXT:    ret
+  %r = call i64 @llvm.smin.i64(i64 %x, i64 0)
+  ret i64 %r
+}
diff --git a/llvm/test/CodeGen/RISCV/fpclamptosat.ll b/llvm/test/CodeGen/RISCV/fpclamptosat.ll
index 489277cb46ed7..47b258b6fdb70 100644
--- a/llvm/test/CodeGen/RISCV/fpclamptosat.ll
+++ b/llvm/test/CodeGen/RISCV/fpclamptosat.ll
@@ -6555,7 +6555,7 @@ define i32 @ustest_f16i32_nsat(half %x) {
 ; RV32-NEXT:    call __extendhfsf2
 ; RV32-NEXT:    fcvt.w.s a0, fa0, rtz
 ; RV32-NEXT:    srai a1, a0, 31
-; RV32-NEXT:    and a0, a1, a0
+; RV32-NEXT:    and a0, a0, a1
 ; RV32-NEXT:    sgtz a1, a0
 ; RV32-NEXT:    neg a1, a1
 ; RV32-NEXT:    and a0, a1, a0
@@ -6574,7 +6574,7 @@ define i32 @ustest_f16i32_nsat(half %x) {
 ; RV64-NEXT:    call __extendhfsf2
 ; RV64-NEXT:    fcvt.l.s a0, fa0, rtz
 ; RV64-NEXT:    srai a1, a0, 63
-; RV64-NEXT:    and a0, a1, a0
+; RV64-NEXT:    and a0, a0, a1
 ; RV64-NEXT:    sgtz a1, a0
 ; RV64-NEXT:    neg a1, a1
 ; RV64-NEXT:    and a0, a1, a0
diff --git a/llvm/test/CodeGen/RISCV/rvv/fpclamptosat_vec.ll b/llvm/test/CodeGen/RISCV/rvv/fpclamptosat_vec.ll
index 7129d4cbe4bba..96dfda743b721 100644
--- a/llvm/test/CodeGen/RISCV/rvv/fpclamptosat_vec.ll
+++ b/llvm/test/CodeGen/RISCV/rvv/fpclamptosat_vec.ll
@@ -7090,16 +7090,16 @@ define <4 x i32> @ustest_f16i32_nsat(<4 x half> %x) {
 ; CHECK-NOV-NEXT:    fcvt.l.s a0, fa0, rtz
 ; CHECK-NOV-NEXT:    srai a1, s4, 63
 ; CHECK-NOV-NEXT:    srai a2, s1, 63
-; CHECK-NOV-NEXT:    and a2, a2, s1
+; CHECK-NOV-NEXT:    and a2, s1, a2
 ; CHECK-NOV-NEXT:    srai a3, s2, 63
-; CHECK-NOV-NEXT:    and a1, a1, s4
+; CHECK-NOV-NEXT:    and a1, s4, a1
 ; CHECK-NOV-NEXT:    sgtz a4, a2
-; CHECK-NOV-NEXT:    and a3, a3, s2
+; CHECK-NOV-NEXT:    and a3, s2, a3
 ; CHECK-NOV-NEXT:    neg a4, a4
 ; CHECK-NOV-NEXT:    and a2, a4, a2
 ; CHECK-NOV-NEXT:    srai a4, a0, 63
 ; CHECK-NOV-NEXT:    sgtz a5, a3
-; CHECK-NOV-NEXT:    and a0, a4, a0
+; CHECK-NOV-NEXT:    and a0, a0, a4
 ; CHECK-NOV-NEXT:    sgtz a4, a1
 ; CHECK-NOV-NEXT:    neg a5, a5
 ; CHECK-NOV-NEXT:    and a3, a5, a3
diff --git a/llvm/test/CodeGen/X86/smax-allones-codesize.ll b/llvm/test/CodeGen/X86/smax-allones-codesize.ll
index 592a5c93feef6..5033aa98851c4 100644
--- a/llvm/test/CodeGen/X86/smax-allones-codesize.ll
+++ b/llvm/test/CodeGen/X86/smax-allones-codesize.ll
@@ -1,9 +1,10 @@
 ; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
 ; RUN: llc < %s -mtriple=x86_64-linux -O2 | FileCheck %s
 ;
-; Verify smax(X, -1) folds to or(X, ashr(X, BW-1)) at all optimization levels.
-; On x86-64 this saves a byte vs compare+cmov; on AArch64/APX it also reduces
-; instruction count. GCC PR 125921, LLVM issue #206153.
+; Verify smax(X, -1) and smin(X, 0) fold to bitwise shift forms.
+; smax(X, -1) = or(X,  ashr(X, BW-1)) -- saves a byte vs compare+cmov on x86-64
+; smin(X,  0) = and(X, ashr(X, BW-1)) -- saves a byte vs compare+cmov on x86-64
+; Both also reduce instruction count on AArch64/APX. GCC PR 125921, LLVM #206153.
 
 define i32 @smax_allones_i32(i32 %x) {
 ; CHECK-LABEL: smax_allones_i32:
@@ -39,6 +40,28 @@ define i32 @smax_zero(i32 %x) {
   ret i32 %r
 }
 
+define i32 @smin_zero_i32(i32 %x) {
+; CHECK-LABEL: smin_zero_i32:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movl %edi, %eax
+; CHECK-NEXT:    sarl $31, %eax
+; CHECK-NEXT:    andl %edi, %eax
+; CHECK-NEXT:    retq
+  %r = call i32 @llvm.smin.i32(i32 %x, i32 0)
+  ret i32 %r
+}
+
+define i64 @smin_zero_i64(i64 %x) {
+; CHECK-LABEL: smin_zero_i64:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movq %rdi, %rax
+; CHECK-NEXT:    sarq $63, %rax
+; CHECK-NEXT:    andq %rdi, %rax
+; CHECK-NEXT:    retq
+  %r = call i64 @llvm.smin.i64(i64 %x, i64 0)
+  ret i64 %r
+}
+
 ; smin(X, -1) should NOT transform -- no 2-instruction bitwise form exists
 define i32 @smin_allones(i32 %x) {
 ; CHECK-LABEL: smin_allones:

>From 86a630295b8f43a14a9aec2317daf8414817ee79 Mon Sep 17 00:00:00 2001
From: iamaayushrivastava <iamaayushrivastava at gmail.com>
Date: Sun, 28 Jun 2026 05:04:01 +0530
Subject: [PATCH 04/10] [DAGCombiner] Fold smax(X,-1)/smin(X,0) to bitwise
 shift forms

---
 llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp | 26 +++----
 llvm/test/CodeGen/X86/combine-smax.ll         | 37 +++++++++
 llvm/test/CodeGen/X86/combine-smin.ll         | 37 +++++++++
 .../test/CodeGen/X86/smax-allones-codesize.ll | 75 -------------------
 4 files changed, 84 insertions(+), 91 deletions(-)
 delete mode 100644 llvm/test/CodeGen/X86/smax-allones-codesize.ll

diff --git a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
index 854faefe41524..a899a47c1d1b3 100644
--- a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
@@ -6320,25 +6320,19 @@ SDValue DAGCombiner::visitIMINMAX(SDNode *N) {
   // RISCV-P sati which combine smin+smax into a single instruction).
   if (TLI.isTypeLegal(VT) &&
       !TLI.shouldAvoidTransformToShift(VT, VT.getScalarSizeInBits() - 1)) {
-    SDValue ShiftAmt =
-        DAG.getShiftAmountConstant(VT.getScalarSizeInBits() - 1, VT, DL);
     if (Opcode == ISD::SMAX && TLI.isOperationExpand(ISD::SMAX, VT) &&
-        N0.getOpcode() != ISD::SMIN) {
-      if (auto *N1C = isConstOrConstSplat(N1)) {
-        if (N1C->isAllOnes()) {
-          SDValue Shift = DAG.getNode(ISD::SRA, DL, VT, N0, ShiftAmt);
-          return DAG.getNode(ISD::OR, DL, VT, N0, Shift);
-        }
-      }
+        N0.getOpcode() != ISD::SMIN && isAllOnesConstant(N1)) {
+      SDValue ShiftAmt =
+          DAG.getShiftAmountConstant(VT.getScalarSizeInBits() - 1, VT, DL);
+      SDValue Shift = DAG.getNode(ISD::SRA, DL, VT, N0, ShiftAmt);
+      return DAG.getNode(ISD::OR, DL, VT, N0, Shift);
     }
     if (Opcode == ISD::SMIN && TLI.isOperationExpand(ISD::SMIN, VT) &&
-        N0.getOpcode() != ISD::SMAX) {
-      if (auto *N1C = isConstOrConstSplat(N1)) {
-        if (N1C->isZero()) {
-          SDValue Shift = DAG.getNode(ISD::SRA, DL, VT, N0, ShiftAmt);
-          return DAG.getNode(ISD::AND, DL, VT, N0, Shift);
-        }
-      }
+        N0.getOpcode() != ISD::SMAX && isNullConstant(N1)) {
+      SDValue ShiftAmt =
+          DAG.getShiftAmountConstant(VT.getScalarSizeInBits() - 1, VT, DL);
+      SDValue Shift = DAG.getNode(ISD::SRA, DL, VT, N0, ShiftAmt);
+      return DAG.getNode(ISD::AND, DL, VT, N0, Shift);
     }
   }
 
diff --git a/llvm/test/CodeGen/X86/combine-smax.ll b/llvm/test/CodeGen/X86/combine-smax.ll
index ac6f347edcd0d..1f802a24df0c7 100644
--- a/llvm/test/CodeGen/X86/combine-smax.ll
+++ b/llvm/test/CodeGen/X86/combine-smax.ll
@@ -199,5 +199,42 @@ define <16 x i8> @test_v16i8_demandedbits(<16 x i8> %x, <16 x i8> %y, <16 x i8>
   ret <16 x i8> %res
 }
 
+; smax(X, -1) -> or(X, ashr(X, BW-1)): saves a byte vs compare+cmov on x86-64.
+define i32 @smax_allones_i32(i32 %x) {
+; CHECK-LABEL: smax_allones_i32:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movl %edi, %eax
+; CHECK-NEXT:    sarl $31, %eax
+; CHECK-NEXT:    orl %edi, %eax
+; CHECK-NEXT:    retq
+  %r = call i32 @llvm.smax.i32(i32 %x, i32 -1)
+  ret i32 %r
+}
+
+define i64 @smax_allones_i64(i64 %x) {
+; CHECK-LABEL: smax_allones_i64:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movq %rdi, %rax
+; CHECK-NEXT:    sarq $63, %rax
+; CHECK-NEXT:    orq %rdi, %rax
+; CHECK-NEXT:    retq
+  %r = call i64 @llvm.smax.i64(i64 %x, i64 -1)
+  ret i64 %r
+}
+
+; smax(X, 0) should NOT transform -- no 2-instruction bitwise form exists.
+define i32 @smax_zero_i32(i32 %x) {
+; CHECK-LABEL: smax_zero_i32:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    xorl %eax, %eax
+; CHECK-NEXT:    testl %edi, %edi
+; CHECK-NEXT:    cmovgl %edi, %eax
+; CHECK-NEXT:    retq
+  %r = call i32 @llvm.smax.i32(i32 %x, i32 0)
+  ret i32 %r
+}
+
 declare i8 @llvm.smax.i8(i8, i8)
+declare i32 @llvm.smax.i32(i32, i32)
+declare i64 @llvm.smax.i64(i64, i64)
 declare <16 x i8> @llvm.smax.v16i8(<16 x i8>, <16 x i8>)
diff --git a/llvm/test/CodeGen/X86/combine-smin.ll b/llvm/test/CodeGen/X86/combine-smin.ll
index 35b1c54835e7b..5ec05f913eae6 100644
--- a/llvm/test/CodeGen/X86/combine-smin.ll
+++ b/llvm/test/CodeGen/X86/combine-smin.ll
@@ -198,5 +198,42 @@ define <16 x i8> @test_v16i8_demandedbits(<16 x i8> %x, <16 x i8> %y, <16 x i8>
   ret <16 x i8> %res
 }
 
+; smin(X, 0) -> and(X, ashr(X, BW-1)): saves a byte vs compare+cmov on x86-64.
+define i32 @smin_zero_i32(i32 %x) {
+; CHECK-LABEL: smin_zero_i32:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movl %edi, %eax
+; CHECK-NEXT:    sarl $31, %eax
+; CHECK-NEXT:    andl %edi, %eax
+; CHECK-NEXT:    retq
+  %r = call i32 @llvm.smin.i32(i32 %x, i32 0)
+  ret i32 %r
+}
+
+define i64 @smin_zero_i64(i64 %x) {
+; CHECK-LABEL: smin_zero_i64:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movq %rdi, %rax
+; CHECK-NEXT:    sarq $63, %rax
+; CHECK-NEXT:    andq %rdi, %rax
+; CHECK-NEXT:    retq
+  %r = call i64 @llvm.smin.i64(i64 %x, i64 0)
+  ret i64 %r
+}
+
+; smin(X, -1) should NOT transform -- no 2-instruction bitwise form exists.
+define i32 @smin_allones_i32(i32 %x) {
+; CHECK-LABEL: smin_allones_i32:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cmpl $-1, %edi
+; CHECK-NEXT:    movl $-1, %eax
+; CHECK-NEXT:    cmovll %edi, %eax
+; CHECK-NEXT:    retq
+  %r = call i32 @llvm.smin.i32(i32 %x, i32 -1)
+  ret i32 %r
+}
+
 declare i8 @llvm.smin.i8(i8, i8)
+declare i32 @llvm.smin.i32(i32, i32)
+declare i64 @llvm.smin.i64(i64, i64)
 declare <16 x i8> @llvm.smin.v16i8(<16 x i8>, <16 x i8>)
diff --git a/llvm/test/CodeGen/X86/smax-allones-codesize.ll b/llvm/test/CodeGen/X86/smax-allones-codesize.ll
deleted file mode 100644
index 5033aa98851c4..0000000000000
--- a/llvm/test/CodeGen/X86/smax-allones-codesize.ll
+++ /dev/null
@@ -1,75 +0,0 @@
-; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
-; RUN: llc < %s -mtriple=x86_64-linux -O2 | FileCheck %s
-;
-; Verify smax(X, -1) and smin(X, 0) fold to bitwise shift forms.
-; smax(X, -1) = or(X,  ashr(X, BW-1)) -- saves a byte vs compare+cmov on x86-64
-; smin(X,  0) = and(X, ashr(X, BW-1)) -- saves a byte vs compare+cmov on x86-64
-; Both also reduce instruction count on AArch64/APX. GCC PR 125921, LLVM #206153.
-
-define i32 @smax_allones_i32(i32 %x) {
-; CHECK-LABEL: smax_allones_i32:
-; CHECK:       # %bb.0:
-; CHECK-NEXT:    movl %edi, %eax
-; CHECK-NEXT:    sarl $31, %eax
-; CHECK-NEXT:    orl %edi, %eax
-; CHECK-NEXT:    retq
-  %r = call i32 @llvm.smax.i32(i32 %x, i32 -1)
-  ret i32 %r
-}
-
-define i64 @smax_allones_i64(i64 %x) {
-; CHECK-LABEL: smax_allones_i64:
-; CHECK:       # %bb.0:
-; CHECK-NEXT:    movq %rdi, %rax
-; CHECK-NEXT:    sarq $63, %rax
-; CHECK-NEXT:    orq %rdi, %rax
-; CHECK-NEXT:    retq
-  %r = call i64 @llvm.smax.i64(i64 %x, i64 -1)
-  ret i64 %r
-}
-
-; smax(X, 0) should NOT transform -- no 2-instruction bitwise form
-define i32 @smax_zero(i32 %x) {
-; CHECK-LABEL: smax_zero:
-; CHECK:       # %bb.0:
-; CHECK-NEXT:    xorl %eax, %eax
-; CHECK-NEXT:    testl %edi, %edi
-; CHECK-NEXT:    cmovgl %edi, %eax
-; CHECK-NEXT:    retq
-  %r = call i32 @llvm.smax.i32(i32 %x, i32 0)
-  ret i32 %r
-}
-
-define i32 @smin_zero_i32(i32 %x) {
-; CHECK-LABEL: smin_zero_i32:
-; CHECK:       # %bb.0:
-; CHECK-NEXT:    movl %edi, %eax
-; CHECK-NEXT:    sarl $31, %eax
-; CHECK-NEXT:    andl %edi, %eax
-; CHECK-NEXT:    retq
-  %r = call i32 @llvm.smin.i32(i32 %x, i32 0)
-  ret i32 %r
-}
-
-define i64 @smin_zero_i64(i64 %x) {
-; CHECK-LABEL: smin_zero_i64:
-; CHECK:       # %bb.0:
-; CHECK-NEXT:    movq %rdi, %rax
-; CHECK-NEXT:    sarq $63, %rax
-; CHECK-NEXT:    andq %rdi, %rax
-; CHECK-NEXT:    retq
-  %r = call i64 @llvm.smin.i64(i64 %x, i64 0)
-  ret i64 %r
-}
-
-; smin(X, -1) should NOT transform -- no 2-instruction bitwise form exists
-define i32 @smin_allones(i32 %x) {
-; CHECK-LABEL: smin_allones:
-; CHECK:       # %bb.0:
-; CHECK-NEXT:    cmpl $-1, %edi
-; CHECK-NEXT:    movl $-1, %eax
-; CHECK-NEXT:    cmovll %edi, %eax
-; CHECK-NEXT:    retq
-  %r = call i32 @llvm.smin.i32(i32 %x, i32 -1)
-  ret i32 %r
-}

>From 500a1ec137fe36d5f387fa417c08a22546feac70 Mon Sep 17 00:00:00 2001
From: iamaayushrivastava <iamaayushrivastava at gmail.com>
Date: Sun, 28 Jun 2026 13:38:26 +0530
Subject: [PATCH 05/10] [DAGCombiner] Update WebAssembly fpclamptosat test for
 smin(X,0) fold

---
 llvm/test/CodeGen/WebAssembly/fpclamptosat.ll | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/test/CodeGen/WebAssembly/fpclamptosat.ll b/llvm/test/CodeGen/WebAssembly/fpclamptosat.ll
index 267d94b601666..158e97c28ca31 100644
--- a/llvm/test/CodeGen/WebAssembly/fpclamptosat.ll
+++ b/llvm/test/CodeGen/WebAssembly/fpclamptosat.ll
@@ -1731,9 +1731,9 @@ define i32 @ustest_f16i32_nsat(half %x) {
 ; CHECK-NEXT:    call __extendhfsf2
 ; CHECK-NEXT:    i32.trunc_sat_f32_s
 ; CHECK-NEXT:    local.tee 0
+; CHECK-NEXT:    local.get 0
 ; CHECK-NEXT:    i32.const 31
 ; CHECK-NEXT:    i32.shr_s
-; CHECK-NEXT:    local.get 0
 ; CHECK-NEXT:    i32.and
 ; CHECK-NEXT:    local.tee 0
 ; CHECK-NEXT:    i32.const 0

>From 4331090edef5015f2bdecb52c9f5cf3fd181d967 Mon Sep 17 00:00:00 2001
From: iamaayushrivastava <iamaayushrivastava at gmail.com>
Date: Mon, 29 Jun 2026 13:18:15 +0530
Subject: [PATCH 06/10] [DAGCombiner] Update X86 probe-stack test for
 smax(X,-1) fold

---
 llvm/test/CodeGen/X86/probe-stack-eflags.ll | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/llvm/test/CodeGen/X86/probe-stack-eflags.ll b/llvm/test/CodeGen/X86/probe-stack-eflags.ll
index cc1839bba7e89..de65288a1c780 100644
--- a/llvm/test/CodeGen/X86/probe-stack-eflags.ll
+++ b/llvm/test/CodeGen/X86/probe-stack-eflags.ll
@@ -78,24 +78,26 @@ exit2: ; preds = %bb70.i, %bb13.i
 define void @CSE_eflags_live(i64 %length) "probe-stack"="__chkstk_darwin" {
 ; CHECK-LABEL: CSE_eflags_live:
 ; CHECK:       # %bb.0: # %entry
+; CHECK-NEXT:    testq %rdi, %rdi
+; CHECK-NEXT:    je .LBB1_2
+; CHECK-NEXT:  # %bb.1: # %if.end6
 ; CHECK-NEXT:    pushq %rbp
 ; CHECK-NEXT:    .cfi_def_cfa_offset 16
 ; CHECK-NEXT:    .cfi_offset %rbp, -16
 ; CHECK-NEXT:    movq %rsp, %rbp
 ; CHECK-NEXT:    .cfi_def_cfa_register %rbp
-; CHECK-NEXT:    testq %rdi, %rdi
-; CHECK-NEXT:    je .LBB1_2
-; CHECK-NEXT:  # %bb.1: # %if.end6
-; CHECK-NEXT:    movq $-1, %rax
-; CHECK-NEXT:    cmovnsq %rdi, %rax
+; CHECK-NEXT:    movq %rdi, %rax
+; CHECK-NEXT:    sarq $63, %rax
+; CHECK-NEXT:    orq %rdi, %rax
 ; CHECK-NEXT:    addq $15, %rax
 ; CHECK-NEXT:    andq $-16, %rax
 ; CHECK-NEXT:    callq __chkstk_darwin
 ; CHECK-NEXT:    subq %rax, %rsp
-; CHECK-NEXT:  .LBB1_2: # %cleanup78
 ; CHECK-NEXT:    movq %rbp, %rsp
 ; CHECK-NEXT:    popq %rbp
 ; CHECK-NEXT:    .cfi_def_cfa %rsp, 8
+; CHECK-NEXT:    .cfi_restore %rbp
+; CHECK-NEXT:  .LBB1_2: # %cleanup78
 ; CHECK-NEXT:    retq
 entry:
   %cmp4 = icmp eq i64 %length, 0

>From 42c242f6e562281bf0042fa8f01bdf19695e7c7a Mon Sep 17 00:00:00 2001
From: iamaayushrivastava <iamaayushrivastava at gmail.com>
Date: Mon, 29 Jun 2026 15:59:39 +0530
Subject: [PATCH 07/10] [DAGCombiner] Add FEAT_CSSC coverage to AArch64
 smax/smin test

---
 llvm/test/CodeGen/AArch64/smax-allones.ll | 60 ++++++++++++++++-------
 1 file changed, 41 insertions(+), 19 deletions(-)

diff --git a/llvm/test/CodeGen/AArch64/smax-allones.ll b/llvm/test/CodeGen/AArch64/smax-allones.ll
index ac36691b4645c..065f97d6441c2 100644
--- a/llvm/test/CodeGen/AArch64/smax-allones.ll
+++ b/llvm/test/CodeGen/AArch64/smax-allones.ll
@@ -1,41 +1,63 @@
 ; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
-; RUN: llc < %s -mtriple=aarch64-linux -O2 | FileCheck %s
+; RUN: llc < %s -mtriple=aarch64-linux -O2 | FileCheck %s --check-prefixes=NOCSSC
+; RUN: llc < %s -mtriple=aarch64-linux -O2 -mattr=+cssc | FileCheck %s --check-prefixes=CSSC
 ;
-; Verify smax(X,-1) and smin(X,0) fold to shifted bitwise forms on AArch64,
-; reducing 2 instructions to 1 shifted ORR/AND. LLVM issue #206153.
+; Verify smax(X,-1) and smin(X,0) fold to shifted bitwise forms on AArch64
+; without CSSC (1 shifted ORR/AND), and use native instructions with CSSC.
+; LLVM issue #206153.
 
 define i32 @smax_allones_i32(i32 %x) {
-; CHECK-LABEL: smax_allones_i32:
-; CHECK:       // %bb.0:
-; CHECK-NEXT:    orr w0, w0, w0, asr #31
-; CHECK-NEXT:    ret
+; NOCSSC-LABEL: smax_allones_i32:
+; NOCSSC:       // %bb.0:
+; NOCSSC-NEXT:    orr w0, w0, w0, asr #31
+; NOCSSC-NEXT:    ret
+;
+; CSSC-LABEL: smax_allones_i32:
+; CSSC:       // %bb.0:
+; CSSC-NEXT:    smax w0, w0, #-1
+; CSSC-NEXT:    ret
   %r = call i32 @llvm.smax.i32(i32 %x, i32 -1)
   ret i32 %r
 }
 
 define i64 @smax_allones_i64(i64 %x) {
-; CHECK-LABEL: smax_allones_i64:
-; CHECK:       // %bb.0:
-; CHECK-NEXT:    orr x0, x0, x0, asr #63
-; CHECK-NEXT:    ret
+; NOCSSC-LABEL: smax_allones_i64:
+; NOCSSC:       // %bb.0:
+; NOCSSC-NEXT:    orr x0, x0, x0, asr #63
+; NOCSSC-NEXT:    ret
+;
+; CSSC-LABEL: smax_allones_i64:
+; CSSC:       // %bb.0:
+; CSSC-NEXT:    smax x0, x0, #-1
+; CSSC-NEXT:    ret
   %r = call i64 @llvm.smax.i64(i64 %x, i64 -1)
   ret i64 %r
 }
 
 define i32 @smin_zero_i32(i32 %x) {
-; CHECK-LABEL: smin_zero_i32:
-; CHECK:       // %bb.0:
-; CHECK-NEXT:    and w0, w0, w0, asr #31
-; CHECK-NEXT:    ret
+; NOCSSC-LABEL: smin_zero_i32:
+; NOCSSC:       // %bb.0:
+; NOCSSC-NEXT:    and w0, w0, w0, asr #31
+; NOCSSC-NEXT:    ret
+;
+; CSSC-LABEL: smin_zero_i32:
+; CSSC:       // %bb.0:
+; CSSC-NEXT:    smin w0, w0, #0
+; CSSC-NEXT:    ret
   %r = call i32 @llvm.smin.i32(i32 %x, i32 0)
   ret i32 %r
 }
 
 define i64 @smin_zero_i64(i64 %x) {
-; CHECK-LABEL: smin_zero_i64:
-; CHECK:       // %bb.0:
-; CHECK-NEXT:    and x0, x0, x0, asr #63
-; CHECK-NEXT:    ret
+; NOCSSC-LABEL: smin_zero_i64:
+; NOCSSC:       // %bb.0:
+; NOCSSC-NEXT:    and x0, x0, x0, asr #63
+; NOCSSC-NEXT:    ret
+;
+; CSSC-LABEL: smin_zero_i64:
+; CSSC:       // %bb.0:
+; CSSC-NEXT:    smin x0, x0, #0
+; CSSC-NEXT:    ret
   %r = call i64 @llvm.smin.i64(i64 %x, i64 0)
   ret i64 %r
 }

>From e855533609604c1df499c67192b30ac72006b9d9 Mon Sep 17 00:00:00 2001
From: iamaayushrivastava <iamaayushrivastava at gmail.com>
Date: Mon, 29 Jun 2026 16:28:53 +0530
Subject: [PATCH 08/10] [DAGCombiner] Use sd_match for smax/smin constant
 matching and  drop -O2 from test

---
 llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp | 4 ++--
 llvm/test/CodeGen/AArch64/smax-allones.ll     | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
index a899a47c1d1b3..51e8a4ff7bd49 100644
--- a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
@@ -6321,14 +6321,14 @@ SDValue DAGCombiner::visitIMINMAX(SDNode *N) {
   if (TLI.isTypeLegal(VT) &&
       !TLI.shouldAvoidTransformToShift(VT, VT.getScalarSizeInBits() - 1)) {
     if (Opcode == ISD::SMAX && TLI.isOperationExpand(ISD::SMAX, VT) &&
-        N0.getOpcode() != ISD::SMIN && isAllOnesConstant(N1)) {
+        N0.getOpcode() != ISD::SMIN && sd_match(N1, m_AllOnes())) {
       SDValue ShiftAmt =
           DAG.getShiftAmountConstant(VT.getScalarSizeInBits() - 1, VT, DL);
       SDValue Shift = DAG.getNode(ISD::SRA, DL, VT, N0, ShiftAmt);
       return DAG.getNode(ISD::OR, DL, VT, N0, Shift);
     }
     if (Opcode == ISD::SMIN && TLI.isOperationExpand(ISD::SMIN, VT) &&
-        N0.getOpcode() != ISD::SMAX && isNullConstant(N1)) {
+        N0.getOpcode() != ISD::SMAX && sd_match(N1, m_Zero())) {
       SDValue ShiftAmt =
           DAG.getShiftAmountConstant(VT.getScalarSizeInBits() - 1, VT, DL);
       SDValue Shift = DAG.getNode(ISD::SRA, DL, VT, N0, ShiftAmt);
diff --git a/llvm/test/CodeGen/AArch64/smax-allones.ll b/llvm/test/CodeGen/AArch64/smax-allones.ll
index 065f97d6441c2..f22af8756a866 100644
--- a/llvm/test/CodeGen/AArch64/smax-allones.ll
+++ b/llvm/test/CodeGen/AArch64/smax-allones.ll
@@ -1,6 +1,6 @@
 ; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
-; RUN: llc < %s -mtriple=aarch64-linux -O2 | FileCheck %s --check-prefixes=NOCSSC
-; RUN: llc < %s -mtriple=aarch64-linux -O2 -mattr=+cssc | FileCheck %s --check-prefixes=CSSC
+; RUN: llc < %s -mtriple=aarch64-linux | FileCheck %s --check-prefixes=NOCSSC
+; RUN: llc < %s -mtriple=aarch64-linux -mattr=+cssc | FileCheck %s --check-prefixes=CSSC
 ;
 ; Verify smax(X,-1) and smin(X,0) fold to shifted bitwise forms on AArch64
 ; without CSSC (1 shifted ORR/AND), and use native instructions with CSSC.

>From c66b52e79a308b33a3cd78317da6a7ce0af7bfa5 Mon Sep 17 00:00:00 2001
From: iamaayushrivastava <iamaayushrivastava at gmail.com>
Date: Mon, 29 Jun 2026 20:49:51 +0530
Subject: [PATCH 09/10]  Use sd_match for smax/smin constant matching

---
 llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
index 51e8a4ff7bd49..3e86546b14341 100644
--- a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
@@ -6318,17 +6318,19 @@ SDValue DAGCombiner::visitIMINMAX(SDNode *N) {
   // type (isOperationExpand), the type is legal (not needing splitting), and
   // the operand is not a min/max chain (preserving saturation patterns such as
   // RISCV-P sati which combine smin+smax into a single instruction).
+  APInt C;
   if (TLI.isTypeLegal(VT) &&
-      !TLI.shouldAvoidTransformToShift(VT, VT.getScalarSizeInBits() - 1)) {
+      !TLI.shouldAvoidTransformToShift(VT, VT.getScalarSizeInBits() - 1) &&
+      sd_match(N1, m_ConstInt(C))) {
     if (Opcode == ISD::SMAX && TLI.isOperationExpand(ISD::SMAX, VT) &&
-        N0.getOpcode() != ISD::SMIN && sd_match(N1, m_AllOnes())) {
+        N0.getOpcode() != ISD::SMIN && C.isAllOnes()) {
       SDValue ShiftAmt =
           DAG.getShiftAmountConstant(VT.getScalarSizeInBits() - 1, VT, DL);
       SDValue Shift = DAG.getNode(ISD::SRA, DL, VT, N0, ShiftAmt);
       return DAG.getNode(ISD::OR, DL, VT, N0, Shift);
     }
     if (Opcode == ISD::SMIN && TLI.isOperationExpand(ISD::SMIN, VT) &&
-        N0.getOpcode() != ISD::SMAX && sd_match(N1, m_Zero())) {
+        N0.getOpcode() != ISD::SMAX && C.isZero()) {
       SDValue ShiftAmt =
           DAG.getShiftAmountConstant(VT.getScalarSizeInBits() - 1, VT, DL);
       SDValue Shift = DAG.getNode(ISD::SRA, DL, VT, N0, ShiftAmt);

>From 59066128747b53f4fc14c72c5ba72e7534d4126f Mon Sep 17 00:00:00 2001
From: iamaayushrivastava <iamaayushrivastava at gmail.com>
Date: Tue, 30 Jun 2026 17:01:51 +0530
Subject: [PATCH 10/10] [DAGCombiner] Fold smax(X,0) to and(X,~ashr(X,BW-1))
 when hasAndNot

---
 llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp | 17 +++--
 llvm/test/CodeGen/AArch64/cmp-select-sign.ll  | 10 +--
 llvm/test/CodeGen/AArch64/smax-allones.ll     | 34 +++++++++-
 llvm/test/CodeGen/X86/combine-smax.ll         |  2 +-
 llvm/test/CodeGen/X86/select-smin-smax.ll     | 68 ++++++++++++-------
 5 files changed, 95 insertions(+), 36 deletions(-)

diff --git a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
index 3e86546b14341..79751441c5aea 100644
--- a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
@@ -6310,10 +6310,11 @@ SDValue DAGCombiner::visitIMINMAX(SDNode *N) {
   // Fold sign-extension masks using arithmetic shift:
   //   smax(X, -1) -> or(X,  ashr(X, BW-1))
   //   smin(X,  0) -> and(X, ashr(X, BW-1))
+  //   smax(X,  0) -> and(X, ~ashr(X, BW-1))  (requires hasAndNot)
   // ashr(X, BW-1) sign-extends the sign bit: 0 for X>=0, -1 for X<0.
   // OR with X yields X (non-negative) or -1 (negative) = smax(X,-1).
   // AND with X yields 0 (non-negative) or X (negative)  = smin(X, 0).
-  // Both reduce to two instructions vs. a compare+cmov on x86-64.
+  // AND with NOT yields X (non-negative) or 0 (negative) = smax(X, 0).
   // Only fold when the target has no native SMAX/SMIN instruction for this
   // type (isOperationExpand), the type is legal (not needing splitting), and
   // the operand is not a min/max chain (preserving saturation patterns such as
@@ -6323,11 +6324,19 @@ SDValue DAGCombiner::visitIMINMAX(SDNode *N) {
       !TLI.shouldAvoidTransformToShift(VT, VT.getScalarSizeInBits() - 1) &&
       sd_match(N1, m_ConstInt(C))) {
     if (Opcode == ISD::SMAX && TLI.isOperationExpand(ISD::SMAX, VT) &&
-        N0.getOpcode() != ISD::SMIN && C.isAllOnes()) {
+        N0.getOpcode() != ISD::SMIN) {
       SDValue ShiftAmt =
           DAG.getShiftAmountConstant(VT.getScalarSizeInBits() - 1, VT, DL);
-      SDValue Shift = DAG.getNode(ISD::SRA, DL, VT, N0, ShiftAmt);
-      return DAG.getNode(ISD::OR, DL, VT, N0, Shift);
+      if (C.isAllOnes()) {
+        // smax(X, -1) -> or(X, ashr(X, BW-1))
+        SDValue Shift = DAG.getNode(ISD::SRA, DL, VT, N0, ShiftAmt);
+        return DAG.getNode(ISD::OR, DL, VT, N0, Shift);
+      }
+      if (C.isZero() && TLI.hasAndNot(N0)) {
+        // smax(X, 0) -> and(X, ~ashr(X, BW-1))
+        SDValue Shift = DAG.getNode(ISD::SRA, DL, VT, N0, ShiftAmt);
+        return DAG.getNode(ISD::AND, DL, VT, N0, DAG.getNOT(DL, Shift, VT));
+      }
     }
     if (Opcode == ISD::SMIN && TLI.isOperationExpand(ISD::SMIN, VT) &&
         N0.getOpcode() != ISD::SMAX && C.isZero()) {
diff --git a/llvm/test/CodeGen/AArch64/cmp-select-sign.ll b/llvm/test/CodeGen/AArch64/cmp-select-sign.ll
index 59f6db6bf67a9..249c12b0586c8 100644
--- a/llvm/test/CodeGen/AArch64/cmp-select-sign.ll
+++ b/llvm/test/CodeGen/AArch64/cmp-select-sign.ll
@@ -319,8 +319,9 @@ define i32 @or_neg_no_smin_but_zero(i32 %x, i32 %y) {
 ; CHECK-LABEL: or_neg_no_smin_but_zero:
 ; CHECK:       // %bb.0:
 ; CHECK-NEXT:    bic w8, w0, w0, asr #31
-; CHECK-NEXT:    cmn w1, w8
-; CHECK-NEXT:    cset w0, lt
+; CHECK-NEXT:    neg w8, w8
+; CHECK-NEXT:    cmp w8, w1
+; CHECK-NEXT:    cset w0, gt
 ; CHECK-NEXT:    ret
   %3 = call i32 @llvm.smax.i32(i32 %x, i32 0)
   %4 = sub i32 0, %3
@@ -434,8 +435,9 @@ define i32 @or_neg_no_smin_but_zero2(i32 %x, i32 %y) {
 ; CHECK-LABEL: or_neg_no_smin_but_zero2:
 ; CHECK:       // %bb.0:
 ; CHECK-NEXT:    bic w8, w0, w0, asr #31
-; CHECK-NEXT:    cmn w1, w8
-; CHECK-NEXT:    cset w0, ge
+; CHECK-NEXT:    neg w8, w8
+; CHECK-NEXT:    cmp w8, w1
+; CHECK-NEXT:    cset w0, le
 ; CHECK-NEXT:    ret
   %3 = call i32 @llvm.smax.i32(i32 %x, i32 0)
   %4 = sub i32 0, %3
diff --git a/llvm/test/CodeGen/AArch64/smax-allones.ll b/llvm/test/CodeGen/AArch64/smax-allones.ll
index f22af8756a866..570aa323f6654 100644
--- a/llvm/test/CodeGen/AArch64/smax-allones.ll
+++ b/llvm/test/CodeGen/AArch64/smax-allones.ll
@@ -2,9 +2,9 @@
 ; RUN: llc < %s -mtriple=aarch64-linux | FileCheck %s --check-prefixes=NOCSSC
 ; RUN: llc < %s -mtriple=aarch64-linux -mattr=+cssc | FileCheck %s --check-prefixes=CSSC
 ;
-; Verify smax(X,-1) and smin(X,0) fold to shifted bitwise forms on AArch64
-; without CSSC (1 shifted ORR/AND), and use native instructions with CSSC.
-; LLVM issue #206153.
+; Verify smax(X,-1), smin(X,0), and smax(X,0) fold to shifted bitwise forms
+; on AArch64 without CSSC (1 BIC/ORR/AND), and use native instructions with
+; CSSC. LLVM issue #206153.
 
 define i32 @smax_allones_i32(i32 %x) {
 ; NOCSSC-LABEL: smax_allones_i32:
@@ -61,3 +61,31 @@ define i64 @smin_zero_i64(i64 %x) {
   %r = call i64 @llvm.smin.i64(i64 %x, i64 0)
   ret i64 %r
 }
+
+define i32 @smax_zero_i32(i32 %x) {
+; NOCSSC-LABEL: smax_zero_i32:
+; NOCSSC:       // %bb.0:
+; NOCSSC-NEXT:    bic w0, w0, w0, asr #31
+; NOCSSC-NEXT:    ret
+;
+; CSSC-LABEL: smax_zero_i32:
+; CSSC:       // %bb.0:
+; CSSC-NEXT:    smax w0, w0, #0
+; CSSC-NEXT:    ret
+  %r = call i32 @llvm.smax.i32(i32 %x, i32 0)
+  ret i32 %r
+}
+
+define i64 @smax_zero_i64(i64 %x) {
+; NOCSSC-LABEL: smax_zero_i64:
+; NOCSSC:       // %bb.0:
+; NOCSSC-NEXT:    bic x0, x0, x0, asr #63
+; NOCSSC-NEXT:    ret
+;
+; CSSC-LABEL: smax_zero_i64:
+; CSSC:       // %bb.0:
+; CSSC-NEXT:    smax x0, x0, #0
+; CSSC-NEXT:    ret
+  %r = call i64 @llvm.smax.i64(i64 %x, i64 0)
+  ret i64 %r
+}
diff --git a/llvm/test/CodeGen/X86/combine-smax.ll b/llvm/test/CodeGen/X86/combine-smax.ll
index 1f802a24df0c7..36fff31ce7e3a 100644
--- a/llvm/test/CodeGen/X86/combine-smax.ll
+++ b/llvm/test/CodeGen/X86/combine-smax.ll
@@ -222,7 +222,7 @@ define i64 @smax_allones_i64(i64 %x) {
   ret i64 %r
 }
 
-; smax(X, 0) should NOT transform -- no 2-instruction bitwise form exists.
+; smax(X, 0) does NOT transform without BMI (hasAndNot=false on x86 without BMI).
 define i32 @smax_zero_i32(i32 %x) {
 ; CHECK-LABEL: smax_zero_i32:
 ; CHECK:       # %bb.0:
diff --git a/llvm/test/CodeGen/X86/select-smin-smax.ll b/llvm/test/CodeGen/X86/select-smin-smax.ll
index 3c2ec52f2c261..1a04f4eff405e 100644
--- a/llvm/test/CodeGen/X86/select-smin-smax.ll
+++ b/llvm/test/CodeGen/X86/select-smin-smax.ll
@@ -191,11 +191,16 @@ define i64 @test_i64_smax(i64 %a) nounwind {
 ;
 ; X86-BMI-LABEL: test_i64_smax:
 ; X86-BMI:       # %bb.0:
-; X86-BMI-NEXT:    movl {{[0-9]+}}(%esp), %edx
+; X86-BMI-NEXT:    movl {{[0-9]+}}(%esp), %ecx
 ; X86-BMI-NEXT:    xorl %eax, %eax
-; X86-BMI-NEXT:    testl %edx, %edx
-; X86-BMI-NEXT:    cmovlel %eax, %edx
-; X86-BMI-NEXT:    cmovnsl {{[0-9]+}}(%esp), %eax
+; X86-BMI-NEXT:    testl %ecx, %ecx
+; X86-BMI-NEXT:    js .LBB6_2
+; X86-BMI-NEXT:  # %bb.1:
+; X86-BMI-NEXT:    movl {{[0-9]+}}(%esp), %eax
+; X86-BMI-NEXT:  .LBB6_2:
+; X86-BMI-NEXT:    movl %ecx, %edx
+; X86-BMI-NEXT:    sarl $31, %edx
+; X86-BMI-NEXT:    andnl %ecx, %edx, %edx
 ; X86-BMI-NEXT:    retl
 ;
 ; X86-NOBMI-LABEL: test_i64_smax:
@@ -239,37 +244,52 @@ define i64 @test_i64_smin(i64 %a) nounwind {
 }
 
 define i128 @test_i128_smax(i128 %a) nounwind {
-; X64-LABEL: test_i128_smax:
-; X64:       # %bb.0:
-; X64-NEXT:    movq %rdi, %rax
-; X64-NEXT:    xorl %edx, %edx
-; X64-NEXT:    testq %rsi, %rsi
-; X64-NEXT:    cmovsq %rdx, %rax
-; X64-NEXT:    cmovgq %rsi, %rdx
-; X64-NEXT:    retq
+; X64-BMI-LABEL: test_i128_smax:
+; X64-BMI:       # %bb.0:
+; X64-BMI-NEXT:    xorl %eax, %eax
+; X64-BMI-NEXT:    testq %rsi, %rsi
+; X64-BMI-NEXT:    cmovnsq %rdi, %rax
+; X64-BMI-NEXT:    movq %rsi, %rcx
+; X64-BMI-NEXT:    sarq $63, %rcx
+; X64-BMI-NEXT:    andnq %rsi, %rcx, %rdx
+; X64-BMI-NEXT:    retq
+;
+; X64-NOBMI-LABEL: test_i128_smax:
+; X64-NOBMI:       # %bb.0:
+; X64-NOBMI-NEXT:    movq %rdi, %rax
+; X64-NOBMI-NEXT:    xorl %edx, %edx
+; X64-NOBMI-NEXT:    testq %rsi, %rsi
+; X64-NOBMI-NEXT:    cmovsq %rdx, %rax
+; X64-NOBMI-NEXT:    cmovgq %rsi, %rdx
+; X64-NOBMI-NEXT:    retq
 ;
 ; X86-BMI-LABEL: test_i128_smax:
 ; X86-BMI:       # %bb.0:
+; X86-BMI-NEXT:    pushl %ebx
 ; X86-BMI-NEXT:    pushl %edi
 ; X86-BMI-NEXT:    pushl %esi
-; X86-BMI-NEXT:    pushl %eax
 ; X86-BMI-NEXT:    movl {{[0-9]+}}(%esp), %eax
+; X86-BMI-NEXT:    movl {{[0-9]+}}(%esp), %edx
+; X86-BMI-NEXT:    xorl %ecx, %ecx
+; X86-BMI-NEXT:    testl %edx, %edx
+; X86-BMI-NEXT:    movl $0, %esi
+; X86-BMI-NEXT:    movl $0, %edi
+; X86-BMI-NEXT:    js .LBB8_2
+; X86-BMI-NEXT:  # %bb.1:
 ; X86-BMI-NEXT:    movl {{[0-9]+}}(%esp), %ecx
-; X86-BMI-NEXT:    xorl %edx, %edx
-; X86-BMI-NEXT:    testl %ecx, %ecx
-; X86-BMI-NEXT:    cmovlel %edx, %ecx
 ; X86-BMI-NEXT:    movl {{[0-9]+}}(%esp), %esi
-; X86-BMI-NEXT:    cmovsl %edx, %esi
 ; X86-BMI-NEXT:    movl {{[0-9]+}}(%esp), %edi
-; X86-BMI-NEXT:    cmovsl %edx, %edi
-; X86-BMI-NEXT:    cmovnsl {{[0-9]+}}(%esp), %edx
-; X86-BMI-NEXT:    movl %ecx, 12(%eax)
-; X86-BMI-NEXT:    movl %edx, 8(%eax)
-; X86-BMI-NEXT:    movl %edi, 4(%eax)
-; X86-BMI-NEXT:    movl %esi, (%eax)
-; X86-BMI-NEXT:    addl $4, %esp
+; X86-BMI-NEXT:  .LBB8_2:
+; X86-BMI-NEXT:    movl %edx, %ebx
+; X86-BMI-NEXT:    sarl $31, %ebx
+; X86-BMI-NEXT:    andnl %edx, %ebx, %edx
+; X86-BMI-NEXT:    movl %edx, 12(%eax)
+; X86-BMI-NEXT:    movl %edi, 8(%eax)
+; X86-BMI-NEXT:    movl %esi, 4(%eax)
+; X86-BMI-NEXT:    movl %ecx, (%eax)
 ; X86-BMI-NEXT:    popl %esi
 ; X86-BMI-NEXT:    popl %edi
+; X86-BMI-NEXT:    popl %ebx
 ; X86-BMI-NEXT:    retl $4
 ;
 ; X86-NOBMI-LABEL: test_i128_smax:



More information about the llvm-commits mailing list