[llvm] [InstCombine] Recognize non-negative subtraction patterns (PR #182597)

via llvm-commits llvm-commits at lists.llvm.org
Fri Feb 20 13:27:41 PST 2026


https://github.com/user1342234 created https://github.com/llvm/llvm-project/pull/182597

Alive2 proofs:
Select pattern: https://alive2.llvm.org/ce/z/YjBg-S
smin pattern: https://alive2.llvm.org/ce/z/-E2Tpc

Fixes #146131



>From 2dea57a270522637284e932a0e38b2c3b36b8b87 Mon Sep 17 00:00:00 2001
From: abu <ayywarepremium at gmail.com>
Date: Fri, 20 Feb 2026 12:08:15 -0800
Subject: [PATCH 1/2] [InstCombine] Recognize non-negative subtraction patterns

---
 .../Transforms/InstCombine/sext-nonneg-sub.ll | 123 ++++++++++++++++++
 1 file changed, 123 insertions(+)
 create mode 100644 llvm/test/Transforms/InstCombine/sext-nonneg-sub.ll

diff --git a/llvm/test/Transforms/InstCombine/sext-nonneg-sub.ll b/llvm/test/Transforms/InstCombine/sext-nonneg-sub.ll
new file mode 100644
index 0000000000000..58bfe870e9ab5
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/sext-nonneg-sub.ll
@@ -0,0 +1,123 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+; Test that b - smin(b, a) is recognized as non-negative
+define i64 @func1(i32 %a, i32 %b) {
+; CHECK-LABEL: define i64 @func1(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[SPEC_SELECT:%.*]] = tail call i32 @llvm.smin.i32(i32 [[B]], i32 [[A]])
+; CHECK-NEXT:    [[SUB:%.*]] = sub nsw i32 [[B]], [[SPEC_SELECT]]
+; CHECK-NEXT:    [[CONV:%.*]] = sext i32 [[SUB]] to i64
+; CHECK-NEXT:    ret i64 [[CONV]]
+;
+entry:
+  %spec.select = tail call i32 @llvm.smin.i32(i32 %b, i32 %a)
+  %sub = sub nsw i32 %b, %spec.select
+  %conv = sext i32 %sub to i64
+  ret i64 %conv
+}
+
+; Test that select (b < a), 0, (b - a) is recognized as non-negative
+define i64 @func3(i32 %a, i32 %b) {
+; CHECK-LABEL: define i64 @func3(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp slt i32 [[B]], [[A]]
+; CHECK-NEXT:    [[SUB:%.*]] = sub nsw i32 [[B]], [[A]]
+; CHECK-NEXT:    [[COND:%.*]] = select i1 [[CMP]], i32 0, i32 [[SUB]]
+; CHECK-NEXT:    [[CONV:%.*]] = sext i32 [[COND]] to i64
+; CHECK-NEXT:    ret i64 [[CONV]]
+;
+entry:
+  %cmp = icmp slt i32 %b, %a
+  %sub = sub nsw i32 %b, %a
+  %cond = select i1 %cmp, i32 0, i32 %sub
+  %conv = sext i32 %cond to i64
+  ret i64 %conv
+}
+
+; Test commutative smin pattern: a - smin(a, b) should also optimize
+define i64 @smin_commutative(i32 %a, i32 %b) {
+; CHECK-LABEL: define i64 @smin_commutative(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-NEXT:    [[MIN:%.*]] = call i32 @llvm.smin.i32(i32 [[A]], i32 [[B]])
+; CHECK-NEXT:    [[SUB:%.*]] = sub nsw i32 [[A]], [[MIN]]
+; CHECK-NEXT:    [[EXT:%.*]] = sext i32 [[SUB]] to i64
+; CHECK-NEXT:    ret i64 [[EXT]]
+;
+  %min = call i32 @llvm.smin.i32(i32 %a, i32 %b)
+  %sub = sub nsw i32 %a, %min
+  %ext = sext i32 %sub to i64
+  ret i64 %ext
+}
+
+
+; Test select with reversed operands: select (b > a), (b - a), 0
+define i64 @select_reversed(i32 %a, i32 %b) {
+; CHECK-LABEL: define i64 @select_reversed(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-NEXT:    [[CMP:%.*]] = icmp sgt i32 [[B]], [[A]]
+; CHECK-NEXT:    [[SUB:%.*]] = sub nsw i32 [[B]], [[A]]
+; CHECK-NEXT:    [[COND:%.*]] = select i1 [[CMP]], i32 [[SUB]], i32 0
+; CHECK-NEXT:    [[EXT:%.*]] = sext i32 [[COND]] to i64
+; CHECK-NEXT:    ret i64 [[EXT]]
+;
+  %cmp = icmp sgt i32 %b, %a
+  %sub = sub nsw i32 %b, %a
+  %cond = select i1 %cmp, i32 %sub, i32 0
+  %ext = sext i32 %cond to i64
+  ret i64 %ext
+}
+
+; NEGATIVE TEST: unguarded subtraction should NOT optimize
+define i64 @neg_unguarded_sub(i32 %a, i32 %b) {
+; CHECK-LABEL: define i64 @neg_unguarded_sub(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-NEXT:    [[SUB:%.*]] = sub nsw i32 [[B]], [[A]]
+; CHECK-NEXT:    [[EXT:%.*]] = sext i32 [[SUB]] to i64
+; CHECK-NEXT:    ret i64 [[EXT]]
+;
+  %sub = sub nsw i32 %b, %a
+  %ext = sext i32 %sub to i64
+  ret i64 %ext
+}
+
+; NEGATIVE TEST: wrong comparison operands in select
+define i64 @neg_wrong_cmp_select(i32 %a, i32 %b) {
+; CHECK-LABEL: define i64 @neg_wrong_cmp_select(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-NEXT:    [[CMP:%.*]] = icmp slt i32 [[A]], [[B]]
+; CHECK-NEXT:    [[SUB:%.*]] = sub nsw i32 [[B]], [[A]]
+; CHECK-NEXT:    [[COND:%.*]] = select i1 [[CMP]], i32 0, i32 [[SUB]]
+; CHECK-NEXT:    [[EXT:%.*]] = sext i32 [[COND]] to i64
+; CHECK-NEXT:    ret i64 [[EXT]]
+;
+  %cmp = icmp slt i32 %a, %b          ; compares a < b, but sub is b - a (mismatched)
+  %sub = sub nsw i32 %b, %a
+  %cond = select i1 %cmp, i32 0, i32 %sub
+  %ext = sext i32 %cond to i64
+  ret i64 %ext
+}
+
+
+
+; NEGATIVE TEST: select with non-zero constant
+define i64 @neg_select_nonzero(i32 %a, i32 %b) {
+; CHECK-LABEL: define i64 @neg_select_nonzero(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-NEXT:    [[CMP:%.*]] = icmp slt i32 [[B]], [[A]]
+; CHECK-NEXT:    [[SUB:%.*]] = sub nsw i32 [[B]], [[A]]
+; CHECK-NEXT:    [[COND:%.*]] = select i1 [[CMP]], i32 1, i32 [[SUB]]
+; CHECK-NEXT:    [[EXT:%.*]] = sext i32 [[COND]] to i64
+; CHECK-NEXT:    ret i64 [[EXT]]
+;
+  %cmp = icmp slt i32 %b, %a
+  %sub = sub nsw i32 %b, %a
+  %cond = select i1 %cmp, i32 1, i32 %sub  ; not zero!
+  %ext = sext i32 %cond to i64
+  ret i64 %ext
+}
+
+declare i32 @llvm.smin.i32(i32, i32)
+

>From 3b4ef4150193144fef403a956b2ade80709571b0 Mon Sep 17 00:00:00 2001
From: abu <ayywarepremium at gmail.com>
Date: Fri, 20 Feb 2026 12:58:26 -0800
Subject: [PATCH 2/2] Recognize non-negative subtraction patterns

---
 .../InstCombine/InstCombineCasts.cpp          | 28 +++++++++++++++++++
 .../Transforms/InstCombine/sext-nonneg-sub.ll |  8 +++---
 2 files changed, 32 insertions(+), 4 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
index 2f3c9c6a083bd..6ef20550a3ac3 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
@@ -1768,6 +1768,34 @@ Instruction *InstCombinerImpl::visitSExt(SExtInst &Sext) {
     return CI;
   }
 
+   // Match against non-negative x - smin(x,y)
+   Value *U, *V;
+   if (match(Src, m_NSWSub(m_Value(U), m_SMin(m_Deferred(U), m_Value(V)))) ||
+       match(Src, m_NSWSub(m_Value(U), m_SMin(m_Value(V), m_Deferred(U))))
+     ) {
+         auto CI = CastInst::Create(Instruction::ZExt, Src, DestTy);
+         CI->setNonNeg(true);
+         return CI;
+   }
+ 
+   // Match against non-negative select
+ 
+   Value *C, *D;
+   if (match(Src, m_Select(m_SpecificICmp(ICmpInst::ICMP_SLT, m_Value(C), m_Value(D)),
+                         m_Zero(),
+                         m_NSWSub(m_Deferred(C), m_Deferred(D)))) ||
+       match(Src, m_Select(m_SpecificICmp(ICmpInst::ICMP_SGT, m_Value(C), m_Value(D)),
+                         m_Zero(),
+                         m_NSWSub(m_Deferred(D), m_Deferred(C)))) ||
+       match(Src, m_Select(m_SpecificICmp(ICmpInst::ICMP_SGT, m_Value(C), m_Value(D)),
+                     m_NSWSub(m_Deferred(C), m_Deferred(D)),
+                     m_Zero()))) {
+ 
+       auto CI = CastInst::Create(Instruction::ZExt, Src, DestTy);
+       CI->setNonNeg(true);
+       return CI;
+   }
+
   // Try to extend the entire expression tree to the wide destination type.
   bool ShouldExtendExpression = true;
   Value *TruncSrc = nullptr;
diff --git a/llvm/test/Transforms/InstCombine/sext-nonneg-sub.ll b/llvm/test/Transforms/InstCombine/sext-nonneg-sub.ll
index 58bfe870e9ab5..a21d59979f1c6 100644
--- a/llvm/test/Transforms/InstCombine/sext-nonneg-sub.ll
+++ b/llvm/test/Transforms/InstCombine/sext-nonneg-sub.ll
@@ -8,7 +8,7 @@ define i64 @func1(i32 %a, i32 %b) {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
 ; CHECK-NEXT:    [[SPEC_SELECT:%.*]] = tail call i32 @llvm.smin.i32(i32 [[B]], i32 [[A]])
 ; CHECK-NEXT:    [[SUB:%.*]] = sub nsw i32 [[B]], [[SPEC_SELECT]]
-; CHECK-NEXT:    [[CONV:%.*]] = sext i32 [[SUB]] to i64
+; CHECK-NEXT:    [[CONV:%.*]] = zext nneg i32 [[SUB]] to i64
 ; CHECK-NEXT:    ret i64 [[CONV]]
 ;
 entry:
@@ -26,7 +26,7 @@ define i64 @func3(i32 %a, i32 %b) {
 ; CHECK-NEXT:    [[CMP:%.*]] = icmp slt i32 [[B]], [[A]]
 ; CHECK-NEXT:    [[SUB:%.*]] = sub nsw i32 [[B]], [[A]]
 ; CHECK-NEXT:    [[COND:%.*]] = select i1 [[CMP]], i32 0, i32 [[SUB]]
-; CHECK-NEXT:    [[CONV:%.*]] = sext i32 [[COND]] to i64
+; CHECK-NEXT:    [[CONV:%.*]] = zext nneg i32 [[COND]] to i64
 ; CHECK-NEXT:    ret i64 [[CONV]]
 ;
 entry:
@@ -43,7 +43,7 @@ define i64 @smin_commutative(i32 %a, i32 %b) {
 ; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
 ; CHECK-NEXT:    [[MIN:%.*]] = call i32 @llvm.smin.i32(i32 [[A]], i32 [[B]])
 ; CHECK-NEXT:    [[SUB:%.*]] = sub nsw i32 [[A]], [[MIN]]
-; CHECK-NEXT:    [[EXT:%.*]] = sext i32 [[SUB]] to i64
+; CHECK-NEXT:    [[EXT:%.*]] = zext nneg i32 [[SUB]] to i64
 ; CHECK-NEXT:    ret i64 [[EXT]]
 ;
   %min = call i32 @llvm.smin.i32(i32 %a, i32 %b)
@@ -60,7 +60,7 @@ define i64 @select_reversed(i32 %a, i32 %b) {
 ; CHECK-NEXT:    [[CMP:%.*]] = icmp sgt i32 [[B]], [[A]]
 ; CHECK-NEXT:    [[SUB:%.*]] = sub nsw i32 [[B]], [[A]]
 ; CHECK-NEXT:    [[COND:%.*]] = select i1 [[CMP]], i32 [[SUB]], i32 0
-; CHECK-NEXT:    [[EXT:%.*]] = sext i32 [[COND]] to i64
+; CHECK-NEXT:    [[EXT:%.*]] = zext nneg i32 [[COND]] to i64
 ; CHECK-NEXT:    ret i64 [[EXT]]
 ;
   %cmp = icmp sgt i32 %b, %a



More information about the llvm-commits mailing list