[llvm] [InstCombine] smin(smax(X, -1), 1) -> scmp(X, 0) and smax(smin(X, 1), -1) -> scmp(X, 0) (PR #145736)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Jun 25 09:23:25 PDT 2025
https://github.com/AZero13 created https://github.com/llvm/llvm-project/pull/145736
Motivating case: https://godbolt.org/z/Wxcc51jcj
Alive2: https://alive2.llvm.org/ce/z/-bPPAg
>From 8bda1574ee79386164ff4feec94fe2d3dcd90555 Mon Sep 17 00:00:00 2001
From: Rose <gfunni234 at gmail.com>
Date: Wed, 25 Jun 2025 11:47:21 -0400
Subject: [PATCH 1/3] [InstCombine] Pre-commit tests (NFC)
---
.../Transforms/InstCombine/compare-3way.ll | 90 ++++++++++++++-----
1 file changed, 70 insertions(+), 20 deletions(-)
diff --git a/llvm/test/Transforms/InstCombine/compare-3way.ll b/llvm/test/Transforms/InstCombine/compare-3way.ll
index 5d443cd45238c..c3bf97af9be09 100644
--- a/llvm/test/Transforms/InstCombine/compare-3way.ll
+++ b/llvm/test/Transforms/InstCombine/compare-3way.ll
@@ -81,8 +81,8 @@ unreached:
define void @test_low_sle(i64 %a, i64 %b) {
; CHECK-LABEL: define void @test_low_sle
; CHECK-SAME: (i64 [[A:%.*]], i64 [[B:%.*]]) {
-; CHECK-NEXT: [[TMP1:%.*]] = icmp slt i64 [[A]], [[B]]
-; CHECK-NEXT: br i1 [[TMP1]], label [[UNREACHED:%.*]], label [[NORMAL:%.*]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i64 [[A]], [[B]]
+; CHECK-NEXT: br i1 [[CMP]], label [[UNREACHED:%.*]], label [[NORMAL:%.*]]
; CHECK: normal:
; CHECK-NEXT: ret void
; CHECK: unreached:
@@ -105,8 +105,8 @@ unreached:
define void @test_low_ne(i64 %a, i64 %b) {
; CHECK-LABEL: define void @test_low_ne
; CHECK-SAME: (i64 [[A:%.*]], i64 [[B:%.*]]) {
-; CHECK-NEXT: [[TMP1:%.*]] = icmp slt i64 [[A]], [[B]]
-; CHECK-NEXT: br i1 [[TMP1]], label [[NORMAL:%.*]], label [[UNREACHED:%.*]]
+; CHECK-NEXT: [[CMP_NOT:%.*]] = icmp slt i64 [[A]], [[B]]
+; CHECK-NEXT: br i1 [[CMP_NOT]], label [[NORMAL:%.*]], label [[UNREACHED:%.*]]
; CHECK: normal:
; CHECK-NEXT: ret void
; CHECK: unreached:
@@ -130,8 +130,8 @@ unreached:
define void @test_low_eq(i64 %a, i64 %b) {
; CHECK-LABEL: define void @test_low_eq
; CHECK-SAME: (i64 [[A:%.*]], i64 [[B:%.*]]) {
-; CHECK-NEXT: [[TMP1:%.*]] = icmp slt i64 [[A]], [[B]]
-; CHECK-NEXT: br i1 [[TMP1]], label [[UNREACHED:%.*]], label [[NORMAL:%.*]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i64 [[A]], [[B]]
+; CHECK-NEXT: br i1 [[CMP]], label [[UNREACHED:%.*]], label [[NORMAL:%.*]]
; CHECK: normal:
; CHECK-NEXT: ret void
; CHECK: unreached:
@@ -154,8 +154,8 @@ unreached:
define void @test_mid_sgt(i64 %a, i64 %b) {
; CHECK-LABEL: define void @test_mid_sgt
; CHECK-SAME: (i64 [[A:%.*]], i64 [[B:%.*]]) {
-; CHECK-NEXT: [[TMP1:%.*]] = icmp sgt i64 [[A]], [[B]]
-; CHECK-NEXT: br i1 [[TMP1]], label [[UNREACHED:%.*]], label [[NORMAL:%.*]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp sgt i64 [[A]], [[B]]
+; CHECK-NEXT: br i1 [[CMP]], label [[UNREACHED:%.*]], label [[NORMAL:%.*]]
; CHECK: normal:
; CHECK-NEXT: ret void
; CHECK: unreached:
@@ -178,8 +178,8 @@ unreached:
define void @test_mid_slt(i64 %a, i64 %b) {
; CHECK-LABEL: define void @test_mid_slt
; CHECK-SAME: (i64 [[A:%.*]], i64 [[B:%.*]]) {
-; CHECK-NEXT: [[TMP1:%.*]] = icmp slt i64 [[A]], [[B]]
-; CHECK-NEXT: br i1 [[TMP1]], label [[UNREACHED:%.*]], label [[NORMAL:%.*]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i64 [[A]], [[B]]
+; CHECK-NEXT: br i1 [[CMP]], label [[UNREACHED:%.*]], label [[NORMAL:%.*]]
; CHECK: normal:
; CHECK-NEXT: ret void
; CHECK: unreached:
@@ -252,8 +252,8 @@ unreached:
define void @test_mid_ne(i64 %a, i64 %b) {
; CHECK-LABEL: define void @test_mid_ne
; CHECK-SAME: (i64 [[A:%.*]], i64 [[B:%.*]]) {
-; CHECK-NEXT: [[EQ:%.*]] = icmp eq i64 [[A]], [[B]]
-; CHECK-NEXT: br i1 [[EQ]], label [[NORMAL:%.*]], label [[UNREACHED:%.*]]
+; CHECK-NEXT: [[CMP_NOT:%.*]] = icmp eq i64 [[A]], [[B]]
+; CHECK-NEXT: br i1 [[CMP_NOT]], label [[NORMAL:%.*]], label [[UNREACHED:%.*]]
; CHECK: normal:
; CHECK-NEXT: ret void
; CHECK: unreached:
@@ -277,8 +277,8 @@ unreached:
define void @test_mid_eq(i64 %a, i64 %b) {
; CHECK-LABEL: define void @test_mid_eq
; CHECK-SAME: (i64 [[A:%.*]], i64 [[B:%.*]]) {
-; CHECK-NEXT: [[EQ:%.*]] = icmp eq i64 [[A]], [[B]]
-; CHECK-NEXT: br i1 [[EQ]], label [[UNREACHED:%.*]], label [[NORMAL:%.*]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp eq i64 [[A]], [[B]]
+; CHECK-NEXT: br i1 [[CMP]], label [[UNREACHED:%.*]], label [[NORMAL:%.*]]
; CHECK: normal:
; CHECK-NEXT: ret void
; CHECK: unreached:
@@ -348,8 +348,8 @@ unreached:
define void @test_high_sge(i64 %a, i64 %b) {
; CHECK-LABEL: define void @test_high_sge
; CHECK-SAME: (i64 [[A:%.*]], i64 [[B:%.*]]) {
-; CHECK-NEXT: [[TMP1:%.*]] = icmp sgt i64 [[A]], [[B]]
-; CHECK-NEXT: br i1 [[TMP1]], label [[UNREACHED:%.*]], label [[NORMAL:%.*]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp sgt i64 [[A]], [[B]]
+; CHECK-NEXT: br i1 [[CMP]], label [[UNREACHED:%.*]], label [[NORMAL:%.*]]
; CHECK: normal:
; CHECK-NEXT: ret void
; CHECK: unreached:
@@ -396,8 +396,8 @@ unreached:
define void @test_high_ne(i64 %a, i64 %b) {
; CHECK-LABEL: define void @test_high_ne
; CHECK-SAME: (i64 [[A:%.*]], i64 [[B:%.*]]) {
-; CHECK-NEXT: [[TMP1:%.*]] = icmp sgt i64 [[A]], [[B]]
-; CHECK-NEXT: br i1 [[TMP1]], label [[NORMAL:%.*]], label [[UNREACHED:%.*]]
+; CHECK-NEXT: [[CMP_NOT:%.*]] = icmp sgt i64 [[A]], [[B]]
+; CHECK-NEXT: br i1 [[CMP_NOT]], label [[NORMAL:%.*]], label [[UNREACHED:%.*]]
; CHECK: normal:
; CHECK-NEXT: ret void
; CHECK: unreached:
@@ -421,8 +421,8 @@ unreached:
define void @test_high_eq(i64 %a, i64 %b) {
; CHECK-LABEL: define void @test_high_eq
; CHECK-SAME: (i64 [[A:%.*]], i64 [[B:%.*]]) {
-; CHECK-NEXT: [[TMP1:%.*]] = icmp sgt i64 [[A]], [[B]]
-; CHECK-NEXT: br i1 [[TMP1]], label [[UNREACHED:%.*]], label [[NORMAL:%.*]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp sgt i64 [[A]], [[B]]
+; CHECK-NEXT: br i1 [[CMP]], label [[UNREACHED:%.*]], label [[NORMAL:%.*]]
; CHECK: normal:
; CHECK-NEXT: ret void
; CHECK: unreached:
@@ -560,3 +560,53 @@ unreached:
call void @use(i32 %result)
ret void
}
+
+define i32 @smax_smin_to_scmp(i32 %x) {
+; CHECK-LABEL: define i32 @smax_smin_to_scmp
+; CHECK-SAME: (i32 [[X:%.*]]) {
+; CHECK-NEXT: [[COND:%.*]] = call i32 @llvm.smax.i32(i32 [[X]], i32 -1)
+; CHECK-NEXT: [[COND5:%.*]] = call i32 @llvm.smin.i32(i32 [[COND]], i32 1)
+; CHECK-NEXT: ret i32 [[COND5]]
+;
+ %cond = call i32 @llvm.smax.i32(i32 %x, i32 -1)
+ %cond5 = call i32 @llvm.smin.i32(i32 %cond, i32 1)
+ ret i32 %cond5
+}
+
+define i16 @smax_smin_to_scmp_i16(i16 %x) {
+; CHECK-LABEL: define i16 @smax_smin_to_scmp_i16
+; CHECK-SAME: (i16 [[X:%.*]]) {
+; CHECK-NEXT: [[COND:%.*]] = call i16 @llvm.smax.i16(i16 [[X]], i16 -1)
+; CHECK-NEXT: [[COND5:%.*]] = call i16 @llvm.smin.i16(i16 [[COND]], i16 1)
+; CHECK-NEXT: ret i16 [[COND5]]
+;
+ %cond = call i16 @llvm.smax.i16(i16 %x, i16 -1)
+ %cond5 = call i16 @llvm.smin.i16(i16 %cond, i16 1)
+ ret i16 %cond5
+}
+
+define i32 @test_max_min_neg(i32 %x) {
+; CHECK-LABEL: define i32 @test_max_min_neg
+; CHECK-SAME: (i32 [[X:%.*]]) {
+; CHECK-NEXT: [[COND:%.*]] = call i32 @llvm.smax.i32(i32 [[X]], i32 -2)
+; CHECK-NEXT: [[COND5:%.*]] = call i32 @llvm.smin.i32(i32 [[COND]], i32 1)
+; CHECK-NEXT: ret i32 [[COND5]]
+;
+ %cond = call i32 @llvm.smax.i32(i32 %x, i32 -2)
+ %cond5 = call i32 @llvm.smin.i32(i32 %cond, i32 1)
+ ret i32 %cond5
+}
+
+define i32 @test_no_optimization_multiple_uses(i32 %x) {
+; CHECK-LABEL: define i32 @test_no_optimization_multiple_uses
+; CHECK-SAME: (i32 [[X:%.*]]) {
+; CHECK-NEXT: [[COND:%.*]] = call i32 @llvm.smax.i32(i32 [[X]], i32 -1)
+; CHECK-NEXT: [[COND5:%.*]] = call i32 @llvm.smin.i32(i32 [[COND]], i32 1)
+; CHECK-NEXT: [[SUM:%.*]] = add i32 [[COND]], [[COND5]]
+; CHECK-NEXT: ret i32 [[SUM]]
+;
+ %cond = call i32 @llvm.smax.i32(i32 %x, i32 -1)
+ %cond5 = call i32 @llvm.smin.i32(i32 %cond, i32 1)
+ %sum = add i32 %cond, %cond5
+ ret i32 %sum
+}
>From af5032d4d3e911f717766c1b551ac0252cdf7b0b Mon Sep 17 00:00:00 2001
From: Rose <gfunni234 at gmail.com>
Date: Wed, 25 Jun 2025 12:03:26 -0400
Subject: [PATCH 2/3] Update compare-3way.ll
---
.../test/Transforms/InstCombine/compare-3way.ll | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)
diff --git a/llvm/test/Transforms/InstCombine/compare-3way.ll b/llvm/test/Transforms/InstCombine/compare-3way.ll
index c3bf97af9be09..2c2733d361d75 100644
--- a/llvm/test/Transforms/InstCombine/compare-3way.ll
+++ b/llvm/test/Transforms/InstCombine/compare-3way.ll
@@ -585,6 +585,19 @@ define i16 @smax_smin_to_scmp_i16(i16 %x) {
ret i16 %cond5
}
+; Test the reversed pattern: smax(smin(X, 1), -1) -> scmp(X, 0)
+define i32 @smin_smax_to_scmp(i32 %x) {
+; CHECK-LABEL: define i32 @smin_smax_to_scmp
+; CHECK-SAME: (i32 [[X:%.*]]) {
+; CHECK-NEXT: [[TMP1:%.*]] = call i32 @llvm.smax.i32(i32 [[X]], i32 -1)
+; CHECK-NEXT: [[COND5:%.*]] = call i32 @llvm.smin.i32(i32 [[TMP1]], i32 1)
+; CHECK-NEXT: ret i32 [[COND5]]
+;
+ %cond = call i32 @llvm.smin.i32(i32 %x, i32 1)
+ %cond5 = call i32 @llvm.smax.i32(i32 %cond, i32 -1)
+ ret i32 %cond5
+}
+
define i32 @test_max_min_neg(i32 %x) {
; CHECK-LABEL: define i32 @test_max_min_neg
; CHECK-SAME: (i32 [[X:%.*]]) {
@@ -597,8 +610,8 @@ define i32 @test_max_min_neg(i32 %x) {
ret i32 %cond5
}
-define i32 @test_no_optimization_multiple_uses(i32 %x) {
-; CHECK-LABEL: define i32 @test_no_optimization_multiple_uses
+define i32 @test_multiple_uses(i32 %x) {
+; CHECK-LABEL: define i32 @test_multiple_uses
; CHECK-SAME: (i32 [[X:%.*]]) {
; CHECK-NEXT: [[COND:%.*]] = call i32 @llvm.smax.i32(i32 [[X]], i32 -1)
; CHECK-NEXT: [[COND5:%.*]] = call i32 @llvm.smin.i32(i32 [[COND]], i32 1)
>From 78fb682a9b03b6e374f7e41b6aa77afd5b5d2b0e Mon Sep 17 00:00:00 2001
From: Rose <gfunni234 at gmail.com>
Date: Wed, 25 Jun 2025 12:22:35 -0400
Subject: [PATCH 3/3] [InstCombine] smin(smax(X, -1), 1) -> scmp(X, 0) and
smax(smin(X, 1), -1) -> scmp(X, 0)
Motivating case: https://godbolt.org/z/Wxcc51jcj
Alive2: https://alive2.llvm.org/ce/z/-bPPAg
---
.../InstCombine/InstCombineCalls.cpp | 28 +++++++++++++++++++
.../Transforms/InstCombine/compare-3way.ll | 9 ++----
2 files changed, 31 insertions(+), 6 deletions(-)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
index b6ed1dc4331d2..5f1624abf68e2 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
@@ -1920,6 +1920,34 @@ Instruction *InstCombinerImpl::visitCallInst(CallInst &CI) {
case Intrinsic::smin: {
Value *I0 = II->getArgOperand(0), *I1 = II->getArgOperand(1);
Value *X, *Y;
+
+ // smin(smax(X, -1), 1) -> scmp(X, 0)
+ // smax(smin(X, 1), -1) -> scmp(X, 0)
+ // These patterns clamp X to [-1, 1] which is equivalent to a three-way
+ // comparison
+
+ // TODO: Is One use needed? yes it is one intrinisc less, but these do
+ // expand.
+ if (IID == Intrinsic::smin) {
+ if (match(I0, m_OneUse(m_Intrinsic<Intrinsic::smax>(m_Value(X),
+ m_AllOnes()))) &&
+ match(I1, m_One())) {
+ Value *Zero = ConstantInt::get(X->getType(), 0);
+ return replaceInstUsesWith(
+ CI,
+ Builder.CreateIntrinsic(II->getType(), Intrinsic::scmp, {X, Zero}));
+ }
+ }
+
+ if (IID == Intrinsic::smax) {
+ if (match(I0, m_OneUse(m_Intrinsic<Intrinsic::smin>(m_Value(X), m_One()))) &&
+ match(I1, m_AllOnes())) {
+ Value *Zero = ConstantInt::get(X->getType(), 0);
+ return replaceInstUsesWith(CI,
+ Builder.CreateIntrinsic(II->getType(), Intrinsic::scmp, {X, Zero}));
+ }
+ }
+
if (match(I0, m_SExt(m_Value(X))) && match(I1, m_SExt(m_Value(Y))) &&
(I0->hasOneUse() || I1->hasOneUse()) && X->getType() == Y->getType()) {
Value *NarrowMaxMin = Builder.CreateBinaryIntrinsic(IID, X, Y);
diff --git a/llvm/test/Transforms/InstCombine/compare-3way.ll b/llvm/test/Transforms/InstCombine/compare-3way.ll
index 2c2733d361d75..8f65756775f1b 100644
--- a/llvm/test/Transforms/InstCombine/compare-3way.ll
+++ b/llvm/test/Transforms/InstCombine/compare-3way.ll
@@ -564,8 +564,7 @@ unreached:
define i32 @smax_smin_to_scmp(i32 %x) {
; CHECK-LABEL: define i32 @smax_smin_to_scmp
; CHECK-SAME: (i32 [[X:%.*]]) {
-; CHECK-NEXT: [[COND:%.*]] = call i32 @llvm.smax.i32(i32 [[X]], i32 -1)
-; CHECK-NEXT: [[COND5:%.*]] = call i32 @llvm.smin.i32(i32 [[COND]], i32 1)
+; CHECK-NEXT: [[COND5:%.*]] = call i32 @llvm.scmp.i32.i32(i32 [[X]], i32 0)
; CHECK-NEXT: ret i32 [[COND5]]
;
%cond = call i32 @llvm.smax.i32(i32 %x, i32 -1)
@@ -576,8 +575,7 @@ define i32 @smax_smin_to_scmp(i32 %x) {
define i16 @smax_smin_to_scmp_i16(i16 %x) {
; CHECK-LABEL: define i16 @smax_smin_to_scmp_i16
; CHECK-SAME: (i16 [[X:%.*]]) {
-; CHECK-NEXT: [[COND:%.*]] = call i16 @llvm.smax.i16(i16 [[X]], i16 -1)
-; CHECK-NEXT: [[COND5:%.*]] = call i16 @llvm.smin.i16(i16 [[COND]], i16 1)
+; CHECK-NEXT: [[COND5:%.*]] = call i16 @llvm.scmp.i16.i16(i16 [[X]], i16 0)
; CHECK-NEXT: ret i16 [[COND5]]
;
%cond = call i16 @llvm.smax.i16(i16 %x, i16 -1)
@@ -589,8 +587,7 @@ define i16 @smax_smin_to_scmp_i16(i16 %x) {
define i32 @smin_smax_to_scmp(i32 %x) {
; CHECK-LABEL: define i32 @smin_smax_to_scmp
; CHECK-SAME: (i32 [[X:%.*]]) {
-; CHECK-NEXT: [[TMP1:%.*]] = call i32 @llvm.smax.i32(i32 [[X]], i32 -1)
-; CHECK-NEXT: [[COND5:%.*]] = call i32 @llvm.smin.i32(i32 [[TMP1]], i32 1)
+; CHECK-NEXT: [[COND5:%.*]] = call i32 @llvm.scmp.i32.i32(i32 [[X]], i32 0)
; CHECK-NEXT: ret i32 [[COND5]]
;
%cond = call i32 @llvm.smin.i32(i32 %x, i32 1)
More information about the llvm-commits
mailing list