[llvm] [InstCombine] Fold smin(A - B, -1) + B into a clamp-like select (PR #167478)

via llvm-commits llvm-commits at lists.llvm.org
Tue Nov 11 01:20:44 PST 2025


https://github.com/Michael-Chen-NJU created https://github.com/llvm/llvm-project/pull/167478

Original pattern (simplified):
`add nsw (select (Cond, (sub nsw 0, B), smin (sub nsw A, B), -1)), B`

The pattern is folded into:
`select (Cond, 0, smin(A, B - 1))`

### Rationale

This transformation is based on the algebraic identity:
`smin(A - B, -1) + B  ==  smin(A, B - 1)`
(Under the assumption that `add nsw` holds, preventing signed overflow).

Fixes #166885

>From 8eefa4e505d7db3b744ebd28d2b894821c1ce1b0 Mon Sep 17 00:00:00 2001
From: Michael-Chen-NJU <2802328816 at qq.com>
Date: Tue, 11 Nov 2025 17:07:00 +0800
Subject: [PATCH 1/2] [InstCombine] Add test for folding smin(a - b, -1) + b
 into a clamp

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

diff --git a/llvm/test/Transforms/InstCombine/add_smin_sub_fold.ll b/llvm/test/Transforms/InstCombine/add_smin_sub_fold.ll
new file mode 100644
index 0000000000000..f014b49aa9c39
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/add_smin_sub_fold.ll
@@ -0,0 +1,138 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+declare i16 @llvm.smin.i16(i16, i16)
+declare i32 @llvm.smin.i32(i32, i32)
+declare <2 x i16> @llvm.smin.v2i16(<2 x i16>, <2 x i16>)
+declare void @use_i16(i16)
+
+define i16 @test_issue_166885(i16 %arg0, i16 %arg1) {
+; CHECK-LABEL: @test_issue_166885(
+; CHECK-NEXT:    %v2 = icmp slt i16 %arg1, 1
+; CHECK-NEXT:    %v3 = sub nsw i16 %arg1, 1
+; CHECK-NEXT:    %v4 = tail call i16 @llvm.smin.i16(i16 %arg0, i16 %v3)
+; CHECK-NEXT:    %v5 = select i1 %v2, i16 0, i16 %v4
+; CHECK-NEXT:    ret i16 %v5
+;
+  %v0 = sub nsw i16 0, %arg1
+  %v1 = sub nsw i16 %arg0, %arg1
+  %v2 = icmp slt i16 %arg1, 1
+  %v3 = tail call i16 @llvm.smin.i16(i16 %v1, i16 -1)
+  %v4 = select i1 %v2, i16 %v0, i16 %v3
+  %v5 = add nsw i16 %v4, %arg1
+  ret i16 %v5
+}
+
+define i16 @test_commutative(i16 %a, i16 %b) {
+; CHECK-LABEL: @test_commutative(
+; CHECK-NEXT:    %v2 = icmp slt i16 %b, 1
+; CHECK-NEXT:    %v3 = sub nsw i16 %b, 1
+; CHECK-NEXT:    %v4 = tail call i16 @llvm.smin.i16(i16 %a, i16 %v3)
+; CHECK-NEXT:    %v5 = select i1 %v2, i16 0, i16 %v4
+; CHECK-NEXT:    ret i16 %v5
+;
+  %v0 = sub nsw i16 0, %b
+  %v1 = sub nsw i16 %a, %b
+  %v2 = icmp slt i16 %b, 1
+  %v3 = tail call i16 @llvm.smin.i16(i16 %v1, i16 -1)
+  %v4 = select i1 %v2, i16 %v0, i16 %v3
+  %v5 = add nsw i16 %b, %v4
+  ret i16 %v5
+}
+
+define i32 @test_i32(i32 %a, i32 %b) {
+; CHECK-LABEL: @test_i32(
+; CHECK-NEXT:    %v2 = icmp slt i32 %b, 1
+; CHECK-NEXT:    %v3 = sub nsw i32 %b, 1
+; CHECK-NEXT:    %v4 = tail call i32 @llvm.smin.i32(i32 %a, i32 %v3)
+; CHECK-NEXT:    %v5 = select i1 %v2, i32 0, i32 %v4
+; CHECK-NEXT:    ret i32 %v5
+;
+  %v0 = sub nsw i32 0, %b
+  %v1 = sub nsw i32 %a, %b
+  %v2 = icmp slt i32 %b, 1
+  %v3 = tail call i32 @llvm.smin.i32(i32 %v1, i32 -1)
+  %v4 = select i1 %v2, i32 %v0, i32 %v3
+  %v5 = add nsw i32 %v4, %b
+  ret i32 %v5
+}
+
+define <2 x i16> @test_vector(<2 x i16> %a, <2 x i16> %b) {
+; CHECK-LABEL: @test_vector(
+; CHECK-NEXT:    %v2 = icmp slt <2 x i16> %b, <i16 1, i16 1>
+; CHECK-NEXT:    %v_minus_one = sub nsw <2 x i16> zeroinitializer, <i16 1, i16 1>
+; CHECK-NEXT:    %v3 = add <2 x i16> %b, %v_minus_one
+; CHECK-NEXT:    %v4 = tail call <2 x i16> @llvm.smin.v2i16(<2 x i16> %a, <2 x i16> %v3)
+; CHECK-NEXT:    %v5 = select <2 x i1> %v2, <2 x i16> zeroinitializer, <2 x i16> %v4
+; CHECK-NEXT:    ret <2 x i16> %v5
+;
+  %v0 = sub nsw <2 x i16> zeroinitializer, %b
+  %v1 = sub nsw <2 x i16> %a, %b
+  %v2 = icmp slt <2 x i16> %b, <i16 1, i16 1>
+  %v_minus_one = sub nsw <2 x i16> zeroinitializer, <i16 1, i16 1>
+
+  %v3 = tail call <2 x i16> @llvm.smin.v2i16(<2 x i16> %v1, <2 x i16> %v_minus_one)
+  %v4 = select <2 x i1> %v2, <2 x i16> %v0, <2 x i16> %v3
+  %v5 = add nsw <2 x i16> %v4, %b
+  ret <2 x i16> %v5
+}
+
+define i16 @test_multi_use(i16 %a, i16 %b) {
+; CHECK-LABEL: @test_multi_use(
+; CHECK-NEXT:    %v1 = sub nsw i16 %a, %b
+; CHECK-NEXT:    %v2 = icmp slt i16 %b, 1
+; CHECK-NEXT:    %v3 = tail call i16 @llvm.smin.i16(i16 %v1, i16 -1)
+; CHECK-NEXT:    call void @use_i16(i16 %v3)
+; CHECK-NEXT:    %v0 = sub nsw i16 0, %b
+; CHECK-NEXT:    %v4 = select i1 %v2, i16 %v0, i16 %v3
+; CHECK-NEXT:    %v5 = add nsw i16 %v4, %b
+; CHECK-NEXT:    ret i16 %v5
+;
+  %v0 = sub nsw i16 0, %b
+  %v1 = sub nsw i16 %a, %b
+  %v2 = icmp slt i16 %b, 1
+  %v3 = tail call i16 @llvm.smin.i16(i16 %v1, i16 -1)
+  call void @use_i16(i16 %v3)
+  %v4 = select i1 %v2, i16 %v0, i16 %v3
+  %v5 = add nsw i16 %v4, %b
+  ret i16 %v5
+}
+
+
+define i16 @test_negative_no_nsw_add(i16 %a, i16 %b) {
+; CHECK-LABEL: @test_negative_no_nsw_add(
+; CHECK-NEXT:    %v0 = sub nsw i16 0, %b
+; CHECK-NEXT:    %v1 = sub nsw i16 %a, %b
+; CHECK-NEXT:    %v2 = icmp slt i16 %b, 1
+; CHECK-NEXT:    %v3 = tail call i16 @llvm.smin.i16(i16 %v1, i16 -1)
+; CHECK-NEXT:    %v4 = select i1 %v2, i16 %v0, i16 %v3
+; CHECK-NEXT:    %v5 = add i16 %v4, %b
+; CHECK-NEXT:    ret i16 %v5
+;
+  %v0 = sub nsw i16 0, %b
+  %v1 = sub nsw i16 %a, %b
+  %v2 = icmp slt i16 %b, 1
+  %v3 = tail call i16 @llvm.smin.i16(i16 %v1, i16 -1)
+  %v4 = select i1 %v2, i16 %v0, i16 %v3
+  %v5 = add i16 %v4, %b
+  ret i16 %v5
+}
+
+define i16 @test_negative_no_nsw_sub(i16 %a, i16 %b) {
+; CHECK-LABEL: @test_negative_no_nsw_sub(
+; CHECK-NEXT:    %v0 = sub nsw i16 0, %b
+; CHECK-NEXT:    %v1 = sub i16 %a, %b
+; CHECK-NEXT:    %v2 = icmp slt i16 %b, 1
+; CHECK-NEXT:    %v3 = tail call i16 @llvm.smin.i16(i16 %v1, i16 -1)
+; CHECK-NEXT:    %v4 = select i1 %v2, i16 %v0, i16 %v3
+; CHECK-NEXT:    %v5 = add nsw i16 %v4, %b
+; CHECK-NEXT:    ret i16 %v5
+;
+  %v0 = sub nsw i16 0, %b
+  %v1 = sub i16 %a, %b
+  %v2 = icmp slt i16 %b, 1
+  %v3 = tail call i16 @llvm.smin.i16(i16 %v1, i16 -1)
+  %v4 = select i1 %v2, i16 %v0, i16 %v3
+  %v5 = add nsw i16 %v4, %b
+  ret i16 %v5
+}
\ No newline at end of file

>From 4f78a64e929be1f1006dd4925b1c12e1a3ae70de Mon Sep 17 00:00:00 2001
From: Michael-Chen-NJU <2802328816 at qq.com>
Date: Tue, 11 Nov 2025 17:07:33 +0800
Subject: [PATCH 2/2] [InstCombine] Fold smin(a - b, -1) + b into a clamp-like
 select

---
 .../InstCombine/InstCombineAddSub.cpp         | 28 ++++++
 .../InstCombine/add_smin_sub_fold.ll          | 87 +++++++++----------
 2 files changed, 71 insertions(+), 44 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
index 9bee523c7b7e5..b85d557a64540 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
@@ -1870,6 +1870,34 @@ Instruction *InstCombinerImpl::visitAdd(BinaryOperator &I) {
         Builder.CreateIntrinsic(Intrinsic::umax, {I.getType()}, {A, B}));
   }
 
+  // add nsw (select (Cond, (sub nsw 0, B), smin (sub nsw A, B), -1)), B
+  //   -> select (Cond, 0, smin(A, B - 1))
+  Value *Cond = nullptr;
+  Constant *K = nullptr;
+  Instruction *SubABInst = nullptr;
+  Value *V0_Captured = nullptr;
+  if (I.hasNoSignedWrap() &&
+      match(&I,
+            m_c_BinOp(m_Select(m_Value(Cond), m_Value(V0_Captured),
+                               m_OneUse(m_Intrinsic<Intrinsic::smin>(
+                                   m_Instruction(SubABInst), m_Constant(K)))),
+                      m_Value(B)))) {
+
+    if (match(V0_Captured, m_NSWSub(m_Zero(), m_Deferred(B))) &&
+        match(SubABInst, m_NSWSub(m_Value(A), m_Deferred(B))) && A != B) {
+
+      Constant *One = ConstantInt::get(I.getType(), 1);
+      Value *B_Minus_1 =
+          Builder.CreateSub(B, One, "", /*HasNUW=*/false, /*HasNSW=*/true);
+      Value *NewSMin = Builder.CreateIntrinsic(Intrinsic::smin, {I.getType()},
+                                               {A, B_Minus_1});
+      Value *Zero = Constant::getNullValue(I.getType());
+      Value *NewSelect = Builder.CreateSelect(Cond, Zero, NewSMin, "");
+
+      return replaceInstUsesWith(I, NewSelect);
+    }
+  }
+
   // ctpop(A) + ctpop(B) => ctpop(A | B) if A and B have no bits set in common.
   if (match(LHS, m_OneUse(m_Intrinsic<Intrinsic::ctpop>(m_Value(A)))) &&
       match(RHS, m_OneUse(m_Intrinsic<Intrinsic::ctpop>(m_Value(B)))) &&
diff --git a/llvm/test/Transforms/InstCombine/add_smin_sub_fold.ll b/llvm/test/Transforms/InstCombine/add_smin_sub_fold.ll
index f014b49aa9c39..0f292fcd6eb81 100644
--- a/llvm/test/Transforms/InstCombine/add_smin_sub_fold.ll
+++ b/llvm/test/Transforms/InstCombine/add_smin_sub_fold.ll
@@ -8,11 +8,11 @@ declare void @use_i16(i16)
 
 define i16 @test_issue_166885(i16 %arg0, i16 %arg1) {
 ; CHECK-LABEL: @test_issue_166885(
-; CHECK-NEXT:    %v2 = icmp slt i16 %arg1, 1
-; CHECK-NEXT:    %v3 = sub nsw i16 %arg1, 1
-; CHECK-NEXT:    %v4 = tail call i16 @llvm.smin.i16(i16 %arg0, i16 %v3)
-; CHECK-NEXT:    %v5 = select i1 %v2, i16 0, i16 %v4
-; CHECK-NEXT:    ret i16 %v5
+; CHECK-NEXT:    [[TMP1:%.*]] = add nsw i16 [[ARG1:%.*]], -1
+; CHECK-NEXT:    [[TMP2:%.*]] = call i16 @llvm.smin.i16(i16 [[ARG0:%.*]], i16 [[TMP1]])
+; CHECK-NEXT:    [[V2_INV:%.*]] = icmp sgt i16 [[ARG1]], 0
+; CHECK-NEXT:    [[V5:%.*]] = select i1 [[V2_INV]], i16 [[TMP2]], i16 0
+; CHECK-NEXT:    ret i16 [[V5]]
 ;
   %v0 = sub nsw i16 0, %arg1
   %v1 = sub nsw i16 %arg0, %arg1
@@ -25,11 +25,11 @@ define i16 @test_issue_166885(i16 %arg0, i16 %arg1) {
 
 define i16 @test_commutative(i16 %a, i16 %b) {
 ; CHECK-LABEL: @test_commutative(
-; CHECK-NEXT:    %v2 = icmp slt i16 %b, 1
-; CHECK-NEXT:    %v3 = sub nsw i16 %b, 1
-; CHECK-NEXT:    %v4 = tail call i16 @llvm.smin.i16(i16 %a, i16 %v3)
-; CHECK-NEXT:    %v5 = select i1 %v2, i16 0, i16 %v4
-; CHECK-NEXT:    ret i16 %v5
+; CHECK-NEXT:    [[TMP1:%.*]] = add nsw i16 [[B:%.*]], -1
+; CHECK-NEXT:    [[TMP2:%.*]] = call i16 @llvm.smin.i16(i16 [[A:%.*]], i16 [[TMP1]])
+; CHECK-NEXT:    [[V2_INV:%.*]] = icmp sgt i16 [[B]], 0
+; CHECK-NEXT:    [[V5:%.*]] = select i1 [[V2_INV]], i16 [[TMP2]], i16 0
+; CHECK-NEXT:    ret i16 [[V5]]
 ;
   %v0 = sub nsw i16 0, %b
   %v1 = sub nsw i16 %a, %b
@@ -42,11 +42,11 @@ define i16 @test_commutative(i16 %a, i16 %b) {
 
 define i32 @test_i32(i32 %a, i32 %b) {
 ; CHECK-LABEL: @test_i32(
-; CHECK-NEXT:    %v2 = icmp slt i32 %b, 1
-; CHECK-NEXT:    %v3 = sub nsw i32 %b, 1
-; CHECK-NEXT:    %v4 = tail call i32 @llvm.smin.i32(i32 %a, i32 %v3)
-; CHECK-NEXT:    %v5 = select i1 %v2, i32 0, i32 %v4
-; CHECK-NEXT:    ret i32 %v5
+; CHECK-NEXT:    [[TMP1:%.*]] = add nsw i32 [[B:%.*]], -1
+; CHECK-NEXT:    [[TMP2:%.*]] = call i32 @llvm.smin.i32(i32 [[A:%.*]], i32 [[TMP1]])
+; CHECK-NEXT:    [[V2_INV:%.*]] = icmp sgt i32 [[B]], 0
+; CHECK-NEXT:    [[V5:%.*]] = select i1 [[V2_INV]], i32 [[TMP2]], i32 0
+; CHECK-NEXT:    ret i32 [[V5]]
 ;
   %v0 = sub nsw i32 0, %b
   %v1 = sub nsw i32 %a, %b
@@ -59,12 +59,11 @@ define i32 @test_i32(i32 %a, i32 %b) {
 
 define <2 x i16> @test_vector(<2 x i16> %a, <2 x i16> %b) {
 ; CHECK-LABEL: @test_vector(
-; CHECK-NEXT:    %v2 = icmp slt <2 x i16> %b, <i16 1, i16 1>
-; CHECK-NEXT:    %v_minus_one = sub nsw <2 x i16> zeroinitializer, <i16 1, i16 1>
-; CHECK-NEXT:    %v3 = add <2 x i16> %b, %v_minus_one
-; CHECK-NEXT:    %v4 = tail call <2 x i16> @llvm.smin.v2i16(<2 x i16> %a, <2 x i16> %v3)
-; CHECK-NEXT:    %v5 = select <2 x i1> %v2, <2 x i16> zeroinitializer, <2 x i16> %v4
-; CHECK-NEXT:    ret <2 x i16> %v5
+; CHECK-NEXT:    [[TMP1:%.*]] = add nsw <2 x i16> [[B:%.*]], splat (i16 -1)
+; CHECK-NEXT:    [[TMP2:%.*]] = call <2 x i16> @llvm.smin.v2i16(<2 x i16> [[A:%.*]], <2 x i16> [[TMP1]])
+; CHECK-NEXT:    [[V2_INV:%.*]] = icmp sgt <2 x i16> [[B]], zeroinitializer
+; CHECK-NEXT:    [[V5:%.*]] = select <2 x i1> [[V2_INV]], <2 x i16> [[TMP2]], <2 x i16> zeroinitializer
+; CHECK-NEXT:    ret <2 x i16> [[V5]]
 ;
   %v0 = sub nsw <2 x i16> zeroinitializer, %b
   %v1 = sub nsw <2 x i16> %a, %b
@@ -79,14 +78,14 @@ define <2 x i16> @test_vector(<2 x i16> %a, <2 x i16> %b) {
 
 define i16 @test_multi_use(i16 %a, i16 %b) {
 ; CHECK-LABEL: @test_multi_use(
-; CHECK-NEXT:    %v1 = sub nsw i16 %a, %b
-; CHECK-NEXT:    %v2 = icmp slt i16 %b, 1
-; CHECK-NEXT:    %v3 = tail call i16 @llvm.smin.i16(i16 %v1, i16 -1)
-; CHECK-NEXT:    call void @use_i16(i16 %v3)
-; CHECK-NEXT:    %v0 = sub nsw i16 0, %b
-; CHECK-NEXT:    %v4 = select i1 %v2, i16 %v0, i16 %v3
-; CHECK-NEXT:    %v5 = add nsw i16 %v4, %b
-; CHECK-NEXT:    ret i16 %v5
+; CHECK-NEXT:    [[V0:%.*]] = sub nsw i16 0, [[B:%.*]]
+; CHECK-NEXT:    [[V1:%.*]] = sub nsw i16 [[A:%.*]], [[B]]
+; CHECK-NEXT:    [[V2:%.*]] = icmp slt i16 [[B]], 1
+; CHECK-NEXT:    [[V3:%.*]] = tail call i16 @llvm.smin.i16(i16 [[V1]], i16 -1)
+; CHECK-NEXT:    call void @use_i16(i16 [[V3]])
+; CHECK-NEXT:    [[V4:%.*]] = select i1 [[V2]], i16 [[V0]], i16 [[V3]]
+; CHECK-NEXT:    [[V5:%.*]] = add nsw i16 [[V4]], [[B]]
+; CHECK-NEXT:    ret i16 [[V5]]
 ;
   %v0 = sub nsw i16 0, %b
   %v1 = sub nsw i16 %a, %b
@@ -101,13 +100,13 @@ define i16 @test_multi_use(i16 %a, i16 %b) {
 
 define i16 @test_negative_no_nsw_add(i16 %a, i16 %b) {
 ; CHECK-LABEL: @test_negative_no_nsw_add(
-; CHECK-NEXT:    %v0 = sub nsw i16 0, %b
-; CHECK-NEXT:    %v1 = sub nsw i16 %a, %b
-; CHECK-NEXT:    %v2 = icmp slt i16 %b, 1
-; CHECK-NEXT:    %v3 = tail call i16 @llvm.smin.i16(i16 %v1, i16 -1)
-; CHECK-NEXT:    %v4 = select i1 %v2, i16 %v0, i16 %v3
-; CHECK-NEXT:    %v5 = add i16 %v4, %b
-; CHECK-NEXT:    ret i16 %v5
+; CHECK-NEXT:    [[V0:%.*]] = sub nsw i16 0, [[B:%.*]]
+; CHECK-NEXT:    [[V1:%.*]] = sub nsw i16 [[A:%.*]], [[B]]
+; CHECK-NEXT:    [[V2:%.*]] = icmp slt i16 [[B]], 1
+; CHECK-NEXT:    [[V3:%.*]] = tail call i16 @llvm.smin.i16(i16 [[V1]], i16 -1)
+; CHECK-NEXT:    [[V4:%.*]] = select i1 [[V2]], i16 [[V0]], i16 [[V3]]
+; CHECK-NEXT:    [[V5:%.*]] = add i16 [[V4]], [[B]]
+; CHECK-NEXT:    ret i16 [[V5]]
 ;
   %v0 = sub nsw i16 0, %b
   %v1 = sub nsw i16 %a, %b
@@ -120,13 +119,13 @@ define i16 @test_negative_no_nsw_add(i16 %a, i16 %b) {
 
 define i16 @test_negative_no_nsw_sub(i16 %a, i16 %b) {
 ; CHECK-LABEL: @test_negative_no_nsw_sub(
-; CHECK-NEXT:    %v0 = sub nsw i16 0, %b
-; CHECK-NEXT:    %v1 = sub i16 %a, %b
-; CHECK-NEXT:    %v2 = icmp slt i16 %b, 1
-; CHECK-NEXT:    %v3 = tail call i16 @llvm.smin.i16(i16 %v1, i16 -1)
-; CHECK-NEXT:    %v4 = select i1 %v2, i16 %v0, i16 %v3
-; CHECK-NEXT:    %v5 = add nsw i16 %v4, %b
-; CHECK-NEXT:    ret i16 %v5
+; CHECK-NEXT:    [[V0:%.*]] = sub nsw i16 0, [[B:%.*]]
+; CHECK-NEXT:    [[V1:%.*]] = sub i16 [[A:%.*]], [[B]]
+; CHECK-NEXT:    [[V2:%.*]] = icmp slt i16 [[B]], 1
+; CHECK-NEXT:    [[V3:%.*]] = tail call i16 @llvm.smin.i16(i16 [[V1]], i16 -1)
+; CHECK-NEXT:    [[V4:%.*]] = select i1 [[V2]], i16 [[V0]], i16 [[V3]]
+; CHECK-NEXT:    [[V5:%.*]] = add nsw i16 [[V4]], [[B]]
+; CHECK-NEXT:    ret i16 [[V5]]
 ;
   %v0 = sub nsw i16 0, %b
   %v1 = sub i16 %a, %b
@@ -135,4 +134,4 @@ define i16 @test_negative_no_nsw_sub(i16 %a, i16 %b) {
   %v4 = select i1 %v2, i16 %v0, i16 %v3
   %v5 = add nsw i16 %v4, %b
   ret i16 %v5
-}
\ No newline at end of file
+}



More information about the llvm-commits mailing list