[llvm] [InstCombine] fold icmp with add/sub instructions having the same operands (PR #143241)
Acthinks Yang via llvm-commits
llvm-commits at lists.llvm.org
Sat Jun 7 10:29:47 PDT 2025
https://github.com/Acthinks updated https://github.com/llvm/llvm-project/pull/143241
>From c0cf491f1a6d771968abc9d5e1edabbd8b1fa15d Mon Sep 17 00:00:00 2001
From: QiYue <yangzhh at mail.ustc.edu.cn>
Date: Sat, 7 Jun 2025 12:34:28 +0800
Subject: [PATCH 1/3] [InstrCombin] fold icmp with add/sub instructions having
the same operands
Closes#143211
---
.../InstCombine/InstCombineCompares.cpp | 41 +++++++
.../Transforms/InstCombine/icmp-subadd.ll | 114 ++++++++++++++++++
2 files changed, 155 insertions(+)
create mode 100644 llvm/test/Transforms/InstCombine/icmp-subadd.ll
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
index c112fae351817..1054d091fd226 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
@@ -7728,6 +7728,47 @@ Instruction *InstCombinerImpl::visitICmpInst(ICmpInst &I) {
}
}
+ // In case of a comparison with add/sub instructions having the same operands,
+ // check whether cmp operands have same signed no wrap. If so, just compare
+ // the sub's second operand and zero.
+ // For example:
+ // %tmp1 = sub nsw i8 %x, %y
+ // %tmp2 = add nsw i8 %x, %y
+ // %cmp = icmp sgt i8 %tmp1, %tmp2
+ // transform this into:
+ // %cmp = icmp slt i32 %y, 0
+ // This handles similar cases to transform.
+ {
+ Value *A, *B;
+ auto *I0 = dyn_cast<OverflowingBinaryOperator>(Op0);
+ auto *I1 = dyn_cast<OverflowingBinaryOperator>(Op1);
+ bool UnsignedCmp = ICmpInst::isUnsigned(Pred);
+ bool SignedCmp = ICmpInst::isSigned(Pred);
+ bool EqualityCmp = ICmpInst::isEquality(Pred);
+
+ if (I0 && I1) {
+ bool I0NUW = I0->hasNoUnsignedWrap();
+ bool I1NUW = I1->hasNoUnsignedWrap();
+ bool I0NSW = I0->hasNoSignedWrap();
+ bool I1NSW = I1->hasNoSignedWrap();
+ bool Swaped = false;
+ if ((UnsignedCmp && I0NUW && I1NUW) || (SignedCmp && I0NSW && I1NSW) ||
+ (EqualityCmp && I0NUW && I0NSW && I1NUW && I1NSW)) {
+ if (I0->getOpcode() == Instruction::Add &&
+ I1->getOpcode() == Instruction::Sub) {
+ std::swap(I0, I1);
+ Swaped = true;
+ }
+ if (match(I0, m_Sub(m_Value(A), m_Value(B))) &&
+ (match(I1, m_Add(m_Specific(A), m_Specific(B))) ||
+ match(I1, m_Add(m_Specific(B), m_Specific(A))))) {
+ return new ICmpInst(Swaped ? Pred : I.getSwappedPredicate(), B,
+ ConstantInt::get(Op0->getType(), 0));
+ }
+ }
+ }
+ }
+
// Try to optimize equality comparisons against alloca-based pointers.
if (Op0->getType()->isPointerTy() && I.isEquality()) {
assert(Op1->getType()->isPointerTy() &&
diff --git a/llvm/test/Transforms/InstCombine/icmp-subadd.ll b/llvm/test/Transforms/InstCombine/icmp-subadd.ll
new file mode 100644
index 0000000000000..46e5ebe57a1b0
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/icmp-subadd.ll
@@ -0,0 +1,114 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+define i1 @test-same-operands-sub-add-nsw-icmp-sgt(i8 %a, i8 %b) {
+; CHECK-LABEL: define i1 @test-same-operands-sub-add-nsw-icmp-sgt(
+; CHECK-SAME: i8 [[A:%.*]], i8 [[B:%.*]]) {
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i8 [[B]], 0
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %sub = sub nsw i8 %a, %b
+ %add = add nsw i8 %a, %b
+ %cmp = icmp sgt i8 %sub, %add
+ ret i1 %cmp
+}
+
+define i1 @test-same-operands-sub-add-nsw-icmp-slt(i8 %a, i8 %b) {
+; CHECK-LABEL: define i1 @test-same-operands-sub-add-nsw-icmp-slt(
+; CHECK-SAME: i8 [[A:%.*]], i8 [[B:%.*]]) {
+; CHECK-NEXT: [[CMP:%.*]] = icmp sgt i8 [[B]], 0
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %sub = sub nsw i8 %a, %b
+ %add = add nsw i8 %a, %b
+ %cmp = icmp slt i8 %sub, %add
+ ret i1 %cmp
+}
+
+define i1 @test-same-operands-sub-add-nsw-icmp-sle(i8 %a, i8 %b) {
+; CHECK-LABEL: define i1 @test-same-operands-sub-add-nsw-icmp-sle(
+; CHECK-SAME: i8 [[A:%.*]], i8 [[B:%.*]]) {
+; CHECK-NEXT: [[CMP:%.*]] = icmp sgt i8 [[B]], -1
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %sub = sub nsw i8 %a, %b
+ %add = add nsw i8 %a, %b
+ %cmp = icmp sle i8 %sub, %add
+ ret i1 %cmp
+}
+
+define i1 @test-same-operands-sub-add-nsw-nuw-icmp-eq(i8 %a, i8 %b) {
+; CHECK-LABEL: define i1 @test-same-operands-sub-add-nsw-nuw-icmp-eq(
+; CHECK-SAME: i8 [[A:%.*]], i8 [[B:%.*]]) {
+; CHECK-NEXT: [[CMP:%.*]] = icmp eq i8 [[B]], 0
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %sub = sub nuw nsw i8 %a, %b
+ %add = add nuw nsw i8 %a, %b
+ %cmp = icmp eq i8 %sub, %add
+ ret i1 %cmp
+}
+
+; Check not folded
+define i1 @test-same-operands-sub-add-nsw-icmp-eq(i8 %a, i8 %b) {
+; CHECK-LABEL: define i1 @test-same-operands-sub-add-nsw-icmp-eq(
+; CHECK-SAME: i8 [[A:%.*]], i8 [[B:%.*]]) {
+; CHECK-NEXT: [[SUB:%.*]] = sub nsw i8 [[A]], [[B]]
+; CHECK-NEXT: [[ADD:%.*]] = add nsw i8 [[A]], [[B]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp eq i8 [[SUB]], [[ADD]]
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %sub = sub nsw i8 %a, %b
+ %add = add nsw i8 %a, %b
+ %cmp = icmp eq i8 %sub, %add
+ ret i1 %cmp
+}
+
+define i1 @test-add-sub-nsw-icmp-sgt(i8 %a, i8 %b) {
+; CHECK-LABEL: define i1 @test-add-sub-nsw-icmp-sgt(
+; CHECK-SAME: i8 [[A:%.*]], i8 [[B:%.*]]) {
+; CHECK-NEXT: [[CMP:%.*]] = icmp sgt i8 [[B]], 0
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %sub = sub nsw i8 %a, %b
+ %add = add nsw i8 %a, %b
+ %cmp = icmp sgt i8 %add, %sub
+ ret i1 %cmp
+}
+
+define i1 @test-add-sub-nuw-icmp-uge(i8 %a, i8 %b) {
+; CHECK-LABEL: define i1 @test-add-sub-nuw-icmp-uge(
+; CHECK-SAME: i8 [[A:%.*]], i8 [[B:%.*]]) {
+; CHECK-NEXT: ret i1 true
+;
+ %sub = sub nuw i8 %a, %b
+ %add = add nuw i8 %a, %b
+ %cmp = icmp uge i8 %add, %sub
+ ret i1 %cmp
+}
+
+; Check not folded
+define i1 @test-add-sub-nuw-icmp-sge(i8 %a, i8 %b) {
+; CHECK-LABEL: define i1 @test-add-sub-nuw-icmp-sge(
+; CHECK-SAME: i8 [[A:%.*]], i8 [[B:%.*]]) {
+; CHECK-NEXT: [[SUB:%.*]] = sub nuw i8 [[A]], [[B]]
+; CHECK-NEXT: [[ADD:%.*]] = add nuw i8 [[A]], [[B]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp sge i8 [[ADD]], [[SUB]]
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %sub = sub nuw i8 %a, %b
+ %add = add nuw i8 %a, %b
+ %cmp = icmp sge i8 %add, %sub
+ ret i1 %cmp
+}
+
+define i1 @test-add-swap-sub-nuw-icmp-uge(i8 %a, i8 %b) {
+; CHECK-LABEL: define i1 @test-add-swap-sub-nuw-icmp-uge(
+; CHECK-SAME: i8 [[A:%.*]], i8 [[B:%.*]]) {
+; CHECK-NEXT: ret i1 true
+;
+ %sub = sub nuw i8 %a, %b
+ %add = add nuw i8 %b, %a
+ %cmp = icmp uge i8 %add, %sub
+ ret i1 %cmp
+}
>From 23585d361df80f957728b81ae868440af5d1feeb Mon Sep 17 00:00:00 2001
From: Acthinks <yangzhh at mail.ustc.edu.cn>
Date: Sat, 7 Jun 2025 19:58:52 +0800
Subject: [PATCH 2/3] Relax equality no wrap constraints & remove swap handling
---
.../InstCombine/InstCombineCompares.cpp | 13 +++--------
.../Transforms/InstCombine/icmp-subadd.ll | 22 +++++++++++++------
2 files changed, 18 insertions(+), 17 deletions(-)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
index 1054d091fd226..905ac3a71d114 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
@@ -7751,18 +7751,11 @@ Instruction *InstCombinerImpl::visitICmpInst(ICmpInst &I) {
bool I1NUW = I1->hasNoUnsignedWrap();
bool I0NSW = I0->hasNoSignedWrap();
bool I1NSW = I1->hasNoSignedWrap();
- bool Swaped = false;
if ((UnsignedCmp && I0NUW && I1NUW) || (SignedCmp && I0NSW && I1NSW) ||
- (EqualityCmp && I0NUW && I0NSW && I1NUW && I1NSW)) {
- if (I0->getOpcode() == Instruction::Add &&
- I1->getOpcode() == Instruction::Sub) {
- std::swap(I0, I1);
- Swaped = true;
- }
+ (EqualityCmp && ((I0NUW && I1NUW) || (I0NSW && I1NSW)))) {
if (match(I0, m_Sub(m_Value(A), m_Value(B))) &&
- (match(I1, m_Add(m_Specific(A), m_Specific(B))) ||
- match(I1, m_Add(m_Specific(B), m_Specific(A))))) {
- return new ICmpInst(Swaped ? Pred : I.getSwappedPredicate(), B,
+ match(I1, m_Add(m_Specific(A), m_Specific(B)))) {
+ return new ICmpInst(I.getSwappedPredicate(), B,
ConstantInt::get(Op0->getType(), 0));
}
}
diff --git a/llvm/test/Transforms/InstCombine/icmp-subadd.ll b/llvm/test/Transforms/InstCombine/icmp-subadd.ll
index 46e5ebe57a1b0..03b75097233fc 100644
--- a/llvm/test/Transforms/InstCombine/icmp-subadd.ll
+++ b/llvm/test/Transforms/InstCombine/icmp-subadd.ll
@@ -49,13 +49,10 @@ define i1 @test-same-operands-sub-add-nsw-nuw-icmp-eq(i8 %a, i8 %b) {
ret i1 %cmp
}
-; Check not folded
define i1 @test-same-operands-sub-add-nsw-icmp-eq(i8 %a, i8 %b) {
; CHECK-LABEL: define i1 @test-same-operands-sub-add-nsw-icmp-eq(
; CHECK-SAME: i8 [[A:%.*]], i8 [[B:%.*]]) {
-; CHECK-NEXT: [[SUB:%.*]] = sub nsw i8 [[A]], [[B]]
-; CHECK-NEXT: [[ADD:%.*]] = add nsw i8 [[A]], [[B]]
-; CHECK-NEXT: [[CMP:%.*]] = icmp eq i8 [[SUB]], [[ADD]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp eq i8 [[B]], 0
; CHECK-NEXT: ret i1 [[CMP]]
;
%sub = sub nsw i8 %a, %b
@@ -64,10 +61,13 @@ define i1 @test-same-operands-sub-add-nsw-icmp-eq(i8 %a, i8 %b) {
ret i1 %cmp
}
+; Should floded by foldICmpCommutative in the future
define i1 @test-add-sub-nsw-icmp-sgt(i8 %a, i8 %b) {
; CHECK-LABEL: define i1 @test-add-sub-nsw-icmp-sgt(
; CHECK-SAME: i8 [[A:%.*]], i8 [[B:%.*]]) {
-; CHECK-NEXT: [[CMP:%.*]] = icmp sgt i8 [[B]], 0
+; CHECK-NEXT: [[SUB:%.*]] = sub nsw i8 [[A]], [[B]]
+; CHECK-NEXT: [[ADD:%.*]] = add nsw i8 [[A]], [[B]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp sgt i8 [[ADD]], [[SUB]]
; CHECK-NEXT: ret i1 [[CMP]]
;
%sub = sub nsw i8 %a, %b
@@ -76,10 +76,14 @@ define i1 @test-add-sub-nsw-icmp-sgt(i8 %a, i8 %b) {
ret i1 %cmp
}
+; Should floded by foldICmpCommutative in the future
define i1 @test-add-sub-nuw-icmp-uge(i8 %a, i8 %b) {
; CHECK-LABEL: define i1 @test-add-sub-nuw-icmp-uge(
; CHECK-SAME: i8 [[A:%.*]], i8 [[B:%.*]]) {
-; CHECK-NEXT: ret i1 true
+; CHECK-NEXT: [[SUB:%.*]] = sub nuw i8 [[A]], [[B]]
+; CHECK-NEXT: [[ADD:%.*]] = add nuw i8 [[A]], [[B]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp uge i8 [[ADD]], [[SUB]]
+; CHECK-NEXT: ret i1 [[CMP]]
;
%sub = sub nuw i8 %a, %b
%add = add nuw i8 %a, %b
@@ -102,10 +106,14 @@ define i1 @test-add-sub-nuw-icmp-sge(i8 %a, i8 %b) {
ret i1 %cmp
}
+; Should floded by foldICmpCommutative in the future
define i1 @test-add-swap-sub-nuw-icmp-uge(i8 %a, i8 %b) {
; CHECK-LABEL: define i1 @test-add-swap-sub-nuw-icmp-uge(
; CHECK-SAME: i8 [[A:%.*]], i8 [[B:%.*]]) {
-; CHECK-NEXT: ret i1 true
+; CHECK-NEXT: [[SUB:%.*]] = sub nuw i8 [[A]], [[B]]
+; CHECK-NEXT: [[ADD:%.*]] = add nuw i8 [[B]], [[A]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp uge i8 [[ADD]], [[SUB]]
+; CHECK-NEXT: ret i1 [[CMP]]
;
%sub = sub nuw i8 %a, %b
%add = add nuw i8 %b, %a
>From 3b739455e88f1d677549fc33e7db0ddfcb90a53d Mon Sep 17 00:00:00 2001
From: Acthinks <yangzhh at mail.ustc.edu.cn>
Date: Sun, 8 Jun 2025 00:43:47 +0800
Subject: [PATCH 3/3] A more concise implementation of the original swap code;
looser equality constraints, such as nsw/nuw combinations, can be folded
---
.../InstCombine/InstCombineCompares.cpp | 27 ++++++++++---------
.../Transforms/InstCombine/icmp-subadd.ll | 21 ++++-----------
2 files changed, 20 insertions(+), 28 deletions(-)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
index 905ac3a71d114..738d39a3a2e79 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
@@ -7728,9 +7728,13 @@ Instruction *InstCombinerImpl::visitICmpInst(ICmpInst &I) {
}
}
- // In case of a comparison with add/sub instructions having the same operands,
- // check whether cmp operands have same signed no wrap. If so, just compare
- // the sub's second operand and zero.
+ // If comparing to add and sub instructions with the same operands, check if
+ // the cmp type and add/sub instruction overflow satisfy the following
+ // constraints:
+ // Signed cmp equals nsw;
+ // Unsigned cmp equals nuw;
+ // Equal cmp equals nsw or nuw.
+ // If yes, only the second operand of sub is compared to zero.
// For example:
// %tmp1 = sub nsw i8 %x, %y
// %tmp2 = add nsw i8 %x, %y
@@ -7742,22 +7746,21 @@ Instruction *InstCombinerImpl::visitICmpInst(ICmpInst &I) {
Value *A, *B;
auto *I0 = dyn_cast<OverflowingBinaryOperator>(Op0);
auto *I1 = dyn_cast<OverflowingBinaryOperator>(Op1);
- bool UnsignedCmp = ICmpInst::isUnsigned(Pred);
- bool SignedCmp = ICmpInst::isSigned(Pred);
- bool EqualityCmp = ICmpInst::isEquality(Pred);
-
if (I0 && I1) {
bool I0NUW = I0->hasNoUnsignedWrap();
bool I1NUW = I1->hasNoUnsignedWrap();
bool I0NSW = I0->hasNoSignedWrap();
bool I1NSW = I1->hasNoSignedWrap();
+ bool UnsignedCmp = ICmpInst::isUnsigned(Pred);
+ bool SignedCmp = ICmpInst::isSigned(Pred);
+ bool EqualityCmp = ICmpInst::isEquality(Pred);
+ CmpPredicate CmpPred;
if ((UnsignedCmp && I0NUW && I1NUW) || (SignedCmp && I0NSW && I1NSW) ||
- (EqualityCmp && ((I0NUW && I1NUW) || (I0NSW && I1NSW)))) {
- if (match(I0, m_Sub(m_Value(A), m_Value(B))) &&
- match(I1, m_Add(m_Specific(A), m_Specific(B)))) {
- return new ICmpInst(I.getSwappedPredicate(), B,
+ (EqualityCmp && ((I0NUW || I0NSW) && (I1NUW || I1NSW)))) {
+ if (match(&I, m_c_ICmp(CmpPred, m_Sub(m_Value(A), m_Value(B)),
+ m_c_Add(m_Deferred(A), m_Deferred(B)))))
+ return new ICmpInst(CmpPredicate::getSwapped(CmpPred), B,
ConstantInt::get(Op0->getType(), 0));
- }
}
}
}
diff --git a/llvm/test/Transforms/InstCombine/icmp-subadd.ll b/llvm/test/Transforms/InstCombine/icmp-subadd.ll
index 03b75097233fc..fd7e1250d893f 100644
--- a/llvm/test/Transforms/InstCombine/icmp-subadd.ll
+++ b/llvm/test/Transforms/InstCombine/icmp-subadd.ll
@@ -43,8 +43,8 @@ define i1 @test-same-operands-sub-add-nsw-nuw-icmp-eq(i8 %a, i8 %b) {
; CHECK-NEXT: [[CMP:%.*]] = icmp eq i8 [[B]], 0
; CHECK-NEXT: ret i1 [[CMP]]
;
- %sub = sub nuw nsw i8 %a, %b
- %add = add nuw nsw i8 %a, %b
+ %sub = sub nsw i8 %a, %b
+ %add = add nuw i8 %a, %b
%cmp = icmp eq i8 %sub, %add
ret i1 %cmp
}
@@ -61,13 +61,10 @@ define i1 @test-same-operands-sub-add-nsw-icmp-eq(i8 %a, i8 %b) {
ret i1 %cmp
}
-; Should floded by foldICmpCommutative in the future
define i1 @test-add-sub-nsw-icmp-sgt(i8 %a, i8 %b) {
; CHECK-LABEL: define i1 @test-add-sub-nsw-icmp-sgt(
; CHECK-SAME: i8 [[A:%.*]], i8 [[B:%.*]]) {
-; CHECK-NEXT: [[SUB:%.*]] = sub nsw i8 [[A]], [[B]]
-; CHECK-NEXT: [[ADD:%.*]] = add nsw i8 [[A]], [[B]]
-; CHECK-NEXT: [[CMP:%.*]] = icmp sgt i8 [[ADD]], [[SUB]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp sgt i8 [[B]], 0
; CHECK-NEXT: ret i1 [[CMP]]
;
%sub = sub nsw i8 %a, %b
@@ -76,14 +73,10 @@ define i1 @test-add-sub-nsw-icmp-sgt(i8 %a, i8 %b) {
ret i1 %cmp
}
-; Should floded by foldICmpCommutative in the future
define i1 @test-add-sub-nuw-icmp-uge(i8 %a, i8 %b) {
; CHECK-LABEL: define i1 @test-add-sub-nuw-icmp-uge(
; CHECK-SAME: i8 [[A:%.*]], i8 [[B:%.*]]) {
-; CHECK-NEXT: [[SUB:%.*]] = sub nuw i8 [[A]], [[B]]
-; CHECK-NEXT: [[ADD:%.*]] = add nuw i8 [[A]], [[B]]
-; CHECK-NEXT: [[CMP:%.*]] = icmp uge i8 [[ADD]], [[SUB]]
-; CHECK-NEXT: ret i1 [[CMP]]
+; CHECK-NEXT: ret i1 true
;
%sub = sub nuw i8 %a, %b
%add = add nuw i8 %a, %b
@@ -106,14 +99,10 @@ define i1 @test-add-sub-nuw-icmp-sge(i8 %a, i8 %b) {
ret i1 %cmp
}
-; Should floded by foldICmpCommutative in the future
define i1 @test-add-swap-sub-nuw-icmp-uge(i8 %a, i8 %b) {
; CHECK-LABEL: define i1 @test-add-swap-sub-nuw-icmp-uge(
; CHECK-SAME: i8 [[A:%.*]], i8 [[B:%.*]]) {
-; CHECK-NEXT: [[SUB:%.*]] = sub nuw i8 [[A]], [[B]]
-; CHECK-NEXT: [[ADD:%.*]] = add nuw i8 [[B]], [[A]]
-; CHECK-NEXT: [[CMP:%.*]] = icmp uge i8 [[ADD]], [[SUB]]
-; CHECK-NEXT: ret i1 [[CMP]]
+; CHECK-NEXT: ret i1 true
;
%sub = sub nuw i8 %a, %b
%add = add nuw i8 %b, %a
More information about the llvm-commits
mailing list