[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 05:33:39 PDT 2026
https://github.com/iamaayushrivastava created https://github.com/llvm/llvm-project/pull/206242
Fixes #206153
When optimizing for code size (`-Oz` / `minsize`), `smax(X, -1)` can be expressed more compactly as `X | (X >> (BW-1))`, an arithmetic right shift by `BW-1` sign-extends the sign bit to all bits, producing `0` for non-negative X and `-1` (all ones) for negative X, so OR-ing with X gives back X for the non-negative case and collapses to `-1` for the negative case, exactly the semantics of `smax(X, -1)` in two cheap bitwise instructions rather than a compare-and-conditional-move sequence.
The fix adds this fold in `DAGCombiner::visitIMINMAX()` guarded by `ForCodeSize`, only firing when the right-hand operand is all-ones and the target does not actively avoid shift-based transforms. On `x86-64` this saves one byte per call site (7 vs 8 bytes in `-Oz`), and the guard ensures no change in behavior at normal optimization levels or on targets that prefer to keep the conditional form.
>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] [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)
More information about the llvm-commits
mailing list