[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