[llvm] [DAGCombiner] Fold smax(X, -1) to or(X, ashr(X, BW-1)) for code size (PR #206242)
Aayush Shrivastava via llvm-commits
llvm-commits at lists.llvm.org
Sat Jun 27 16:34:27 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 1/4] [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 2/4] [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 3/4] [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 4/4] [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
-}
More information about the llvm-commits
mailing list