[llvm] [InstCombine] Fold `min(X+1, Y) - min(X, Y) --> zext X < Y` (PR #157782)

via llvm-commits llvm-commits at lists.llvm.org
Tue Sep 9 19:54:38 PDT 2025


https://github.com/benwu25 updated https://github.com/llvm/llvm-project/pull/157782

>From 72eb14a268c5ec3e33260ff2b8393b38867cbb21 Mon Sep 17 00:00:00 2001
From: benwu25 <soggysocks206 at gmail.com>
Date: Tue, 9 Sep 2025 18:34:46 -0700
Subject: [PATCH 1/2] [InstCombine] New test (#157524)

---
 llvm/test/Transforms/InstCombine/min-zext.ll | 112 +++++++++++++++++++
 1 file changed, 112 insertions(+)
 create mode 100644 llvm/test/Transforms/InstCombine/min-zext.ll

diff --git a/llvm/test/Transforms/InstCombine/min-zext.ll b/llvm/test/Transforms/InstCombine/min-zext.ll
new file mode 100644
index 0000000000000..4e56aebd9fc68
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/min-zext.ll
@@ -0,0 +1,112 @@
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+define i32 @test_smin(i32 %arg0, i32 %arg1) {
+; CHECK-LABEL: define i32 @test_smin(
+; CHECK-NEXT: %v0 = tail call i32 @llvm.smin.i32(i32 %arg0, i32 %arg1)
+; CHECK-NEXT: %v1 = add nsw i32 %arg0, 1
+; CHECK-NEXT: %v2 = tail call i32 @llvm.smin.i32(i32 %v1, i32 %arg1)
+; CHECK-NEXT: %v3 = sub i32 %v2, %v0
+; CHECK-NEXT: ret i32 %v3
+;
+  %v0 = tail call i32 @llvm.smin.i32(i32 %arg0, i32 %arg1)
+  %v1 = add nsw i32 %arg0, 1
+  %v2 = tail call i32 @llvm.smin.i32(i32 %v1, i32 %arg1)
+  %v3 = sub i32 %v2, %v0
+  ret i32 %v3
+}
+
+define i32 @test_umin(i32 %arg0, i32 %arg1) {
+; CHECK-LABEL: define i32 @test_umin(
+; CHECK-NEXT: %v0 = tail call i32 @llvm.umin.i32(i32 %arg0, i32 %arg1)
+; CHECK-NEXT: %v1 = add nuw i32 %arg0, 1
+; CHECK-NEXT: %v2 = tail call i32 @llvm.umin.i32(i32 %v1, i32 %arg1)
+; CHECK-NEXT: %v3 = sub i32 %v2, %v0
+; CHECK-NEXT: ret i32 %v3
+;
+  %v0 = tail call i32 @llvm.umin.i32(i32 %arg0, i32 %arg1)
+  %v1 = add nuw i32 %arg0, 1
+  %v2 = tail call i32 @llvm.umin.i32(i32 %v1, i32 %arg1)
+  %v3 = sub i32 %v2, %v0
+  ret i32 %v3
+}
+
+define i1 @test_smin_i1(i1 %arg0, i1 %arg1) {
+; CHECK-LABEL: define i1 @test_smin_i1(
+; CHECK-NEXT: %v0 = or i1 %arg0, %arg1
+; CHECK-NEXT: %v3 = xor i1 %v0, true
+; CHECK-NEXT: ret i1 %v3
+;
+  %v0 = tail call i1 @llvm.smin.i1(i1 %arg0, i1 %arg1)
+  %v1 = add nsw i1 %arg0, 1
+  %v2 = tail call i1 @llvm.smin.i1(i1 %v1, i1 %arg1)
+  %v3 = sub i1 %v2, %v0
+  ret i1 %v3
+}
+
+declare void @use(i2)
+
+define i2 @test_smin_use_operands(i2 %arg0, i2 %arg1) {
+; CHECK-LABEL: define i2 @test_smin_use_operands(
+; CHECK-NEXT: %v0 = tail call i2 @llvm.smin.i2(i2 %arg0, i2 %arg1)
+; CHECK-NEXT: %v1 = add nsw i2 %arg0, 1
+; CHECK-NEXT: %v2 = tail call i2 @llvm.smin.i2(i2 %v1, i2 %arg1)
+; CHECK-NEXT: %v3 = sub i2 %v2, %v0
+; CHECK-NEXT: call void @use(i2 %v2)
+; CHECK-NEXT: call void @use(i2 %v0)
+; CHECK-NEXT: ret i2 %v3
+;
+  %v0 = tail call i2 @llvm.smin.i2(i2 %arg0, i2 %arg1)
+  %v1 = add nsw i2 %arg0, 1
+  %v2 = tail call i2 @llvm.smin.i2(i2 %v1, i2 %arg1)
+  %v3 = sub i2 %v2, %v0 
+  call void @use(i2 %v2)
+  call void @use(i2 %v0)
+  ret i2 %v3 
+}
+
+define i2 @test_smin_use_operand(i2 %arg0, i2 %arg1) {
+; CHECK-LABEL: define i2 @test_smin_use_operand(
+; CHECK-NEXT: %v0 = tail call i2 @llvm.smin.i2(i2 %arg0, i2 %arg1)
+; CHECK-NEXT: %v1 = add nsw i2 %arg0, 1
+; CHECK-NEXT: %v2 = tail call i2 @llvm.smin.i2(i2 %v1, i2 %arg1)
+; CHECK-NEXT: %v3 = sub i2 %v2, %v0
+; CHECK-NEXT: call void @use(i2 %v2)
+; CHECK-NEXT: ret i2 %v3
+;
+  %v0 = tail call i2 @llvm.smin.i2(i2 %arg0, i2 %arg1)
+  %v1 = add nsw i2 %arg0, 1
+  %v2 = tail call i2 @llvm.smin.i2(i2 %v1, i2 %arg1)
+  %v3 = sub i2 %v2, %v0 
+  call void @use(i2 %v2)
+  ret i2 %v3 
+}
+
+define i32 @test_smin_missing_nsw(i32 %arg0, i32 %arg1) {
+; CHECK-LABEL: define i32 @test_smin_missing_nsw(
+; CHECK-NEXT: %v0 = tail call i32 @llvm.smin.i32(i32 %arg0, i32 %arg1)
+; CHECK-NEXT: %v1 = add i32 %arg0, 1
+; CHECK-NEXT: %v2 = tail call i32 @llvm.smin.i32(i32 %v1, i32 %arg1)
+; CHECK-NEXT: %v3 = sub i32 %v2, %v0
+; CHECK-NEXT: ret i32 %v3
+;
+  %v0 = tail call i32 @llvm.smin.i32(i32 %arg0, i32 %arg1)
+  %v1 = add i32 %arg0, 1
+  %v2 = tail call i32 @llvm.smin.i32(i32 %v1, i32 %arg1)
+  %v3 = sub i32 %v2, %v0
+  ret i32 %v3
+}
+
+define i32 @test_umin_missing_nuw(i32 %arg0, i32 %arg1) {
+; CHECK-LABEL: define i32 @test_umin_missing_nuw(
+; CHECK-NEXT: %v0 = tail call i32 @llvm.umin.i32(i32 %arg0, i32 %arg1)
+; CHECK-NEXT: %v1 = add i32 %arg0, 1
+; CHECK-NEXT: %v2 = tail call i32 @llvm.umin.i32(i32 %v1, i32 %arg1)
+; CHECK-NEXT: %v3 = sub i32 %v2, %v0
+; CHECK-NEXT: ret i32 %v3
+;
+  %v0 = tail call i32 @llvm.umin.i32(i32 %arg0, i32 %arg1)
+  %v1 = add i32 %arg0, 1
+  %v2 = tail call i32 @llvm.umin.i32(i32 %v1, i32 %arg1)
+  %v3 = sub i32 %v2, %v0
+  ret i32 %v3
+}

>From b8d16acadffdf1e20f8bb75f953447be2a7feb4d Mon Sep 17 00:00:00 2001
From: benwu25 <soggysocks206 at gmail.com>
Date: Tue, 9 Sep 2025 19:28:53 -0700
Subject: [PATCH 2/2] [InstCombine] Fold min(X+1, Y) - min(X, Y) --> zext X < Y
 (#157524)

This fold is invalid for @llvm.smin.i1, since smin(-1, 0) == -1
(take X = Y = 0). Otherwise, if X+1 has the appropriate nsw or nuw,
this transform replaces a sub and at least one min with an icmp and
a zext. It is also invalid for i1 in general, but it seems that other
folds take care of i1. In #157524, this expression was folded to a
select, but it seems that select X < Y, 1, 0 can be canonicalized to
zext X < Y.
---
 .../InstCombine/InstCombineAddSub.cpp         | 18 ++++
 llvm/test/Transforms/InstCombine/min-zext.ll  | 84 ++++++++++---------
 2 files changed, 62 insertions(+), 40 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
index d934638c15e75..63c6fc52322d6 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
@@ -2719,6 +2719,24 @@ Instruction *InstCombinerImpl::visitSub(BinaryOperator &I) {
     return BinaryOperator::CreateSub(X, Not);
   }
 
+  // min(X+1, Y) - min(X, Y) --> zext X < Y
+  // Replacing a sub and at least one min with an icmp
+  // and a zext is a potential improvement.
+  if (match(Op0, m_c_SMin(m_c_NSWAdd(m_Value(X), m_One()), m_Value(Y))) &&
+      match(Op1, m_c_SMin(m_Value(X), m_Value(Y))) &&
+      I.getType()->getScalarSizeInBits() != 1 &&
+      (Op0->hasOneUse() || Op1->hasOneUse())) {
+    Value *Cond = Builder.CreateICmpSLT(X, Y);
+    return new ZExtInst(Cond, I.getType());
+  }
+  if (match(Op0, m_c_UMin(m_c_NUWAdd(m_Value(X), m_One()), m_Value(Y))) &&
+      match(Op1, m_c_UMin(m_Value(X), m_Value(Y))) &&
+      I.getType()->getScalarSizeInBits() != 1 &&
+      (Op0->hasOneUse() || Op1->hasOneUse())) {
+    Value *Cond = Builder.CreateICmpULT(X, Y);
+    return new ZExtInst(Cond, I.getType());
+  }
+
   // Optimize pointer differences into the same array into a size.  Consider:
   //  &A[10] - &A[0]: we should compile this to "10".
   Value *LHSOp, *RHSOp;
diff --git a/llvm/test/Transforms/InstCombine/min-zext.ll b/llvm/test/Transforms/InstCombine/min-zext.ll
index 4e56aebd9fc68..43af1f48bcfed 100644
--- a/llvm/test/Transforms/InstCombine/min-zext.ll
+++ b/llvm/test/Transforms/InstCombine/min-zext.ll
@@ -1,12 +1,12 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
 ; RUN: opt < %s -passes=instcombine -S | FileCheck %s
 
 define i32 @test_smin(i32 %arg0, i32 %arg1) {
 ; CHECK-LABEL: define i32 @test_smin(
-; CHECK-NEXT: %v0 = tail call i32 @llvm.smin.i32(i32 %arg0, i32 %arg1)
-; CHECK-NEXT: %v1 = add nsw i32 %arg0, 1
-; CHECK-NEXT: %v2 = tail call i32 @llvm.smin.i32(i32 %v1, i32 %arg1)
-; CHECK-NEXT: %v3 = sub i32 %v2, %v0
-; CHECK-NEXT: ret i32 %v3
+; CHECK-SAME: i32 [[ARG0:%.*]], i32 [[ARG1:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = icmp slt i32 [[ARG0]], [[ARG1]]
+; CHECK-NEXT:    [[V3:%.*]] = zext i1 [[TMP1]] to i32
+; CHECK-NEXT:    ret i32 [[V3]]
 ;
   %v0 = tail call i32 @llvm.smin.i32(i32 %arg0, i32 %arg1)
   %v1 = add nsw i32 %arg0, 1
@@ -17,11 +17,10 @@ define i32 @test_smin(i32 %arg0, i32 %arg1) {
 
 define i32 @test_umin(i32 %arg0, i32 %arg1) {
 ; CHECK-LABEL: define i32 @test_umin(
-; CHECK-NEXT: %v0 = tail call i32 @llvm.umin.i32(i32 %arg0, i32 %arg1)
-; CHECK-NEXT: %v1 = add nuw i32 %arg0, 1
-; CHECK-NEXT: %v2 = tail call i32 @llvm.umin.i32(i32 %v1, i32 %arg1)
-; CHECK-NEXT: %v3 = sub i32 %v2, %v0
-; CHECK-NEXT: ret i32 %v3
+; CHECK-SAME: i32 [[ARG0:%.*]], i32 [[ARG1:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = icmp ult i32 [[ARG0]], [[ARG1]]
+; CHECK-NEXT:    [[V3:%.*]] = zext i1 [[TMP1]] to i32
+; CHECK-NEXT:    ret i32 [[V3]]
 ;
   %v0 = tail call i32 @llvm.umin.i32(i32 %arg0, i32 %arg1)
   %v1 = add nuw i32 %arg0, 1
@@ -32,9 +31,10 @@ define i32 @test_umin(i32 %arg0, i32 %arg1) {
 
 define i1 @test_smin_i1(i1 %arg0, i1 %arg1) {
 ; CHECK-LABEL: define i1 @test_smin_i1(
-; CHECK-NEXT: %v0 = or i1 %arg0, %arg1
-; CHECK-NEXT: %v3 = xor i1 %v0, true
-; CHECK-NEXT: ret i1 %v3
+; CHECK-SAME: i1 [[ARG0:%.*]], i1 [[ARG1:%.*]]) {
+; CHECK-NEXT:    [[V0:%.*]] = or i1 [[ARG0]], [[ARG1]]
+; CHECK-NEXT:    [[V3:%.*]] = xor i1 [[V0]], true
+; CHECK-NEXT:    ret i1 [[V3]]
 ;
   %v0 = tail call i1 @llvm.smin.i1(i1 %arg0, i1 %arg1)
   %v1 = add nsw i1 %arg0, 1
@@ -47,47 +47,50 @@ declare void @use(i2)
 
 define i2 @test_smin_use_operands(i2 %arg0, i2 %arg1) {
 ; CHECK-LABEL: define i2 @test_smin_use_operands(
-; CHECK-NEXT: %v0 = tail call i2 @llvm.smin.i2(i2 %arg0, i2 %arg1)
-; CHECK-NEXT: %v1 = add nsw i2 %arg0, 1
-; CHECK-NEXT: %v2 = tail call i2 @llvm.smin.i2(i2 %v1, i2 %arg1)
-; CHECK-NEXT: %v3 = sub i2 %v2, %v0
-; CHECK-NEXT: call void @use(i2 %v2)
-; CHECK-NEXT: call void @use(i2 %v0)
-; CHECK-NEXT: ret i2 %v3
+; CHECK-SAME: i2 [[ARG0:%.*]], i2 [[ARG1:%.*]]) {
+; CHECK-NEXT:    [[V0:%.*]] = tail call i2 @llvm.smin.i2(i2 [[ARG0]], i2 [[ARG1]])
+; CHECK-NEXT:    [[V1:%.*]] = add nsw i2 [[ARG0]], 1
+; CHECK-NEXT:    [[V2:%.*]] = tail call i2 @llvm.smin.i2(i2 [[V1]], i2 [[ARG1]])
+; CHECK-NEXT:    [[V3:%.*]] = sub i2 [[V2]], [[V0]]
+; CHECK-NEXT:    call void @use(i2 [[V2]])
+; CHECK-NEXT:    call void @use(i2 [[V0]])
+; CHECK-NEXT:    ret i2 [[V3]]
 ;
   %v0 = tail call i2 @llvm.smin.i2(i2 %arg0, i2 %arg1)
   %v1 = add nsw i2 %arg0, 1
   %v2 = tail call i2 @llvm.smin.i2(i2 %v1, i2 %arg1)
-  %v3 = sub i2 %v2, %v0 
+  %v3 = sub i2 %v2, %v0
   call void @use(i2 %v2)
   call void @use(i2 %v0)
-  ret i2 %v3 
+  ret i2 %v3
 }
 
 define i2 @test_smin_use_operand(i2 %arg0, i2 %arg1) {
 ; CHECK-LABEL: define i2 @test_smin_use_operand(
-; CHECK-NEXT: %v0 = tail call i2 @llvm.smin.i2(i2 %arg0, i2 %arg1)
-; CHECK-NEXT: %v1 = add nsw i2 %arg0, 1
-; CHECK-NEXT: %v2 = tail call i2 @llvm.smin.i2(i2 %v1, i2 %arg1)
-; CHECK-NEXT: %v3 = sub i2 %v2, %v0
-; CHECK-NEXT: call void @use(i2 %v2)
-; CHECK-NEXT: ret i2 %v3
+; CHECK-SAME: i2 [[ARG0:%.*]], i2 [[ARG1:%.*]]) {
+; CHECK-NEXT:    [[V1:%.*]] = add nsw i2 [[ARG0]], 1
+; CHECK-NEXT:    [[V2:%.*]] = tail call i2 @llvm.smin.i2(i2 [[V1]], i2 [[ARG1]])
+; CHECK-NEXT:    [[TMP1:%.*]] = icmp slt i2 [[ARG0]], [[ARG1]]
+; CHECK-NEXT:    [[V3:%.*]] = zext i1 [[TMP1]] to i2
+; CHECK-NEXT:    call void @use(i2 [[V2]])
+; CHECK-NEXT:    ret i2 [[V3]]
 ;
   %v0 = tail call i2 @llvm.smin.i2(i2 %arg0, i2 %arg1)
   %v1 = add nsw i2 %arg0, 1
   %v2 = tail call i2 @llvm.smin.i2(i2 %v1, i2 %arg1)
-  %v3 = sub i2 %v2, %v0 
+  %v3 = sub i2 %v2, %v0
   call void @use(i2 %v2)
-  ret i2 %v3 
+  ret i2 %v3
 }
 
 define i32 @test_smin_missing_nsw(i32 %arg0, i32 %arg1) {
 ; CHECK-LABEL: define i32 @test_smin_missing_nsw(
-; CHECK-NEXT: %v0 = tail call i32 @llvm.smin.i32(i32 %arg0, i32 %arg1)
-; CHECK-NEXT: %v1 = add i32 %arg0, 1
-; CHECK-NEXT: %v2 = tail call i32 @llvm.smin.i32(i32 %v1, i32 %arg1)
-; CHECK-NEXT: %v3 = sub i32 %v2, %v0
-; CHECK-NEXT: ret i32 %v3
+; CHECK-SAME: i32 [[ARG0:%.*]], i32 [[ARG1:%.*]]) {
+; CHECK-NEXT:    [[V0:%.*]] = tail call i32 @llvm.smin.i32(i32 [[ARG0]], i32 [[ARG1]])
+; CHECK-NEXT:    [[V1:%.*]] = add i32 [[ARG0]], 1
+; CHECK-NEXT:    [[V2:%.*]] = tail call i32 @llvm.smin.i32(i32 [[V1]], i32 [[ARG1]])
+; CHECK-NEXT:    [[V3:%.*]] = sub i32 [[V2]], [[V0]]
+; CHECK-NEXT:    ret i32 [[V3]]
 ;
   %v0 = tail call i32 @llvm.smin.i32(i32 %arg0, i32 %arg1)
   %v1 = add i32 %arg0, 1
@@ -98,11 +101,12 @@ define i32 @test_smin_missing_nsw(i32 %arg0, i32 %arg1) {
 
 define i32 @test_umin_missing_nuw(i32 %arg0, i32 %arg1) {
 ; CHECK-LABEL: define i32 @test_umin_missing_nuw(
-; CHECK-NEXT: %v0 = tail call i32 @llvm.umin.i32(i32 %arg0, i32 %arg1)
-; CHECK-NEXT: %v1 = add i32 %arg0, 1
-; CHECK-NEXT: %v2 = tail call i32 @llvm.umin.i32(i32 %v1, i32 %arg1)
-; CHECK-NEXT: %v3 = sub i32 %v2, %v0
-; CHECK-NEXT: ret i32 %v3
+; CHECK-SAME: i32 [[ARG0:%.*]], i32 [[ARG1:%.*]]) {
+; CHECK-NEXT:    [[V0:%.*]] = tail call i32 @llvm.umin.i32(i32 [[ARG0]], i32 [[ARG1]])
+; CHECK-NEXT:    [[V1:%.*]] = add i32 [[ARG0]], 1
+; CHECK-NEXT:    [[V2:%.*]] = tail call i32 @llvm.umin.i32(i32 [[V1]], i32 [[ARG1]])
+; CHECK-NEXT:    [[V3:%.*]] = sub i32 [[V2]], [[V0]]
+; CHECK-NEXT:    ret i32 [[V3]]
 ;
   %v0 = tail call i32 @llvm.umin.i32(i32 %arg0, i32 %arg1)
   %v1 = add i32 %arg0, 1



More information about the llvm-commits mailing list