[llvm] Fold icmp eq A, 0 | icmp ult B, sub 0, A to overflow (PR #152762)

via llvm-commits llvm-commits at lists.llvm.org
Fri Aug 8 10:15:42 PDT 2025


https://github.com/AZero13 created https://github.com/llvm/llvm-project/pull/152762

None

>From ee5c15448f2adefcff8ac70fe84bac92481d920b Mon Sep 17 00:00:00 2001
From: AZero13 <gfunni234 at gmail.com>
Date: Fri, 8 Aug 2025 11:43:17 -0400
Subject: [PATCH 1/2] Pre-commit tests (NFC)

---
 .../Transforms/InstCombine/neg-and-zero.ll    | 71 +++++++++++++++++++
 1 file changed, 71 insertions(+)
 create mode 100644 llvm/test/Transforms/InstCombine/neg-and-zero.ll

diff --git a/llvm/test/Transforms/InstCombine/neg-and-zero.ll b/llvm/test/Transforms/InstCombine/neg-and-zero.ll
new file mode 100644
index 0000000000000..475e9a0c70190
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/neg-and-zero.ll
@@ -0,0 +1,71 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+define i32 @non_overflow(i32 %0, i32 %1) {
+; CHECK-LABEL: @non_overflow(
+; CHECK-NEXT:    [[TMP3:%.*]] = icmp eq i32 [[TMP0:%.*]], 0
+; CHECK-NEXT:    [[TMP6:%.*]] = sub i32 0, [[TMP0]]
+; CHECK-NEXT:    [[TMP7:%.*]] = icmp ult i32 [[TMP1:%.*]], [[TMP6]]
+; CHECK-NEXT:    [[TMP4:%.*]] = or i1 [[TMP3]], [[TMP7]]
+; CHECK-NEXT:    [[TMP5:%.*]] = zext i1 [[TMP4]] to i32
+; CHECK-NEXT:    ret i32 [[TMP5]]
+;
+  %3 = icmp eq i32 %0, 0
+  %4 = sub i32 0, %0
+  %5 = icmp ult i32 %1, %4
+  %6 = or i1 %3, %5
+  %7 = zext i1 %6 to i32
+  ret i32 %7
+}
+
+define i32 @overflow(i32 %0, i32 %1) {
+; CHECK-LABEL: @overflow(
+; CHECK-NEXT:    [[TMP3:%.*]] = sub i32 0, [[TMP0:%.*]]
+; CHECK-NEXT:    [[TMP6:%.*]] = icmp ne i32 [[TMP0]], 0
+; CHECK-NEXT:    [[TMP7:%.*]] = icmp uge i32 [[TMP1:%.*]], [[TMP3]]
+; CHECK-NEXT:    [[TMP4:%.*]] = and i1 [[TMP6]], [[TMP7]]
+; CHECK-NEXT:    [[TMP5:%.*]] = zext i1 [[TMP4]] to i32
+; CHECK-NEXT:    ret i32 [[TMP5]]
+;
+  %3 = sub i32 0, %0
+  %4 = icmp ne i32 %0, 0
+  %5 = icmp uge i32 %1, %3
+  %6 = and i1 %4, %5
+  %7 = zext i1 %6 to i32
+  ret i32 %7
+}
+
+
+define i32 @non_overflow_flipped(i32 %0, i32 %1) {
+; CHECK-LABEL: @non_overflow_flipped(
+; CHECK-NEXT:    [[TMP7:%.*]] = icmp eq i32 [[TMP0:%.*]], 0
+; CHECK-NEXT:    [[TMP3:%.*]] = sub i32 0, [[TMP0]]
+; CHECK-NEXT:    [[TMP4:%.*]] = icmp ule i32 [[TMP1:%.*]], [[TMP3]]
+; CHECK-NEXT:    [[TMP6:%.*]] = or i1 [[TMP7]], [[TMP4]]
+; CHECK-NEXT:    [[TMP5:%.*]] = zext i1 [[TMP6]] to i32
+; CHECK-NEXT:    ret i32 [[TMP5]]
+;
+  %3 = icmp eq i32 %0, 0
+  %4 = sub i32 0, %0
+  %5 = icmp uge i32 %4, %1
+  %6 = or i1 %3, %5
+  %7 = zext i1 %6 to i32
+  ret i32 %7
+}
+
+define i32 @overflow_flipped(i32 %0, i32 %1) {
+; CHECK-LABEL: @overflow_flipped(
+; CHECK-NEXT:    [[TMP3:%.*]] = sub i32 0, [[TMP0:%.*]]
+; CHECK-NEXT:    [[TMP7:%.*]] = icmp ne i32 [[TMP0]], 0
+; CHECK-NEXT:    [[TMP4:%.*]] = icmp ugt i32 [[TMP1:%.*]], [[TMP3]]
+; CHECK-NEXT:    [[TMP6:%.*]] = and i1 [[TMP7]], [[TMP4]]
+; CHECK-NEXT:    [[TMP5:%.*]] = zext i1 [[TMP6]] to i32
+; CHECK-NEXT:    ret i32 [[TMP5]]
+;
+  %3 = sub i32 0, %0
+  %4 = icmp ne i32 %0, 0
+  %5 = icmp ult i32 %3, %1
+  %6 = and i1 %4, %5
+  %7 = zext i1 %6 to i32
+  ret i32 %7
+}

>From 24f1209fd475cb8f4fadff322c4029e58017eb91 Mon Sep 17 00:00:00 2001
From: AZero13 <gfunni234 at gmail.com>
Date: Fri, 8 Aug 2025 13:14:44 -0400
Subject: [PATCH 2/2] Fold icmp eq A, 0 | icmp ult B, sub 0, A to overflow

---
 .../InstCombine/InstCombineAndOrXor.cpp       | 33 +++++++++++++++++++
 .../Transforms/InstCombine/neg-and-zero.ll    | 12 +++----
 2 files changed, 37 insertions(+), 8 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
index d7971e8e3caea..17ebc1f34740c 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
@@ -3359,6 +3359,39 @@ Value *InstCombinerImpl::foldAndOrOfICmps(ICmpInst *LHS, ICmpInst *RHS,
     }
   }
 
+  // Canonicalize the unsigned-add overflow/non-overflow boolean patterns:
+  //   (icmp eq A, 0) | (icmp ult B, sub 0, A)  --> icmp ule B, ~A
+  //   (icmp ne A, 0) & (icmp uge B, sub 0, A)  --> icmp ugt B, ~A
+  // Accept commuted order of the two icmps within the or/and, and
+  // commuted operand order within the ULT/UGE compare.
+  {
+    Value *A, *B;
+    // Try (LHS,RHS) order
+    if (PredL == (IsAnd ? ICmpInst::ICMP_NE : ICmpInst::ICMP_EQ) &&
+        match(LHS0, m_Value(A)) && match(LHS1, m_Zero()) &&
+        ((PredR == (IsAnd ? ICmpInst::ICMP_UGE : ICmpInst::ICMP_ULT) &&
+          ((match(RHS0, m_Value(B)) && match(RHS1, m_Neg(m_Specific(A)))) ||
+           (match(RHS1, m_Value(B)) && match(RHS0, m_Neg(m_Specific(A)))))))) {
+      if (IsLogical && !isGuaranteedNotToBePoison(B))
+        return nullptr;
+      Value *NotA = Builder.CreateNot(A);
+      return Builder.CreateICmp(IsAnd ? ICmpInst::ICMP_UGT : ICmpInst::ICMP_ULE,
+                                B, NotA);
+    }
+    // Try (RHS,LHS) order
+    if (PredR == (IsAnd ? ICmpInst::ICMP_NE : ICmpInst::ICMP_EQ) &&
+        match(RHS0, m_Value(A)) && match(RHS1, m_Zero()) &&
+        ((PredL == (IsAnd ? ICmpInst::ICMP_UGE : ICmpInst::ICMP_ULT) &&
+          ((match(LHS0, m_Value(B)) && match(LHS1, m_Neg(m_Specific(A)))) ||
+           (match(LHS1, m_Value(B)) && match(LHS0, m_Neg(m_Specific(A)))))))) {
+      if (IsLogical && !isGuaranteedNotToBePoison(B))
+        return nullptr;
+      Value *NotA = Builder.CreateNot(A);
+      return Builder.CreateICmp(IsAnd ? ICmpInst::ICMP_UGT : ICmpInst::ICMP_ULE,
+                                B, NotA);
+    }
+  }
+
   if (Value *V =
           foldAndOrOfICmpEqConstantAndICmp(LHS, RHS, IsAnd, IsLogical, Builder))
     return V;
diff --git a/llvm/test/Transforms/InstCombine/neg-and-zero.ll b/llvm/test/Transforms/InstCombine/neg-and-zero.ll
index 475e9a0c70190..80db22aad1dde 100644
--- a/llvm/test/Transforms/InstCombine/neg-and-zero.ll
+++ b/llvm/test/Transforms/InstCombine/neg-and-zero.ll
@@ -3,10 +3,8 @@
 
 define i32 @non_overflow(i32 %0, i32 %1) {
 ; CHECK-LABEL: @non_overflow(
-; CHECK-NEXT:    [[TMP3:%.*]] = icmp eq i32 [[TMP0:%.*]], 0
-; CHECK-NEXT:    [[TMP6:%.*]] = sub i32 0, [[TMP0]]
-; CHECK-NEXT:    [[TMP7:%.*]] = icmp ult i32 [[TMP1:%.*]], [[TMP6]]
-; CHECK-NEXT:    [[TMP4:%.*]] = or i1 [[TMP3]], [[TMP7]]
+; CHECK-NEXT:    [[TMP3:%.*]] = xor i32 [[TMP0:%.*]], -1
+; CHECK-NEXT:    [[TMP4:%.*]] = icmp ule i32 [[TMP1:%.*]], [[TMP3]]
 ; CHECK-NEXT:    [[TMP5:%.*]] = zext i1 [[TMP4]] to i32
 ; CHECK-NEXT:    ret i32 [[TMP5]]
 ;
@@ -20,10 +18,8 @@ define i32 @non_overflow(i32 %0, i32 %1) {
 
 define i32 @overflow(i32 %0, i32 %1) {
 ; CHECK-LABEL: @overflow(
-; CHECK-NEXT:    [[TMP3:%.*]] = sub i32 0, [[TMP0:%.*]]
-; CHECK-NEXT:    [[TMP6:%.*]] = icmp ne i32 [[TMP0]], 0
-; CHECK-NEXT:    [[TMP7:%.*]] = icmp uge i32 [[TMP1:%.*]], [[TMP3]]
-; CHECK-NEXT:    [[TMP4:%.*]] = and i1 [[TMP6]], [[TMP7]]
+; CHECK-NEXT:    [[TMP3:%.*]] = xor i32 [[TMP0:%.*]], -1
+; CHECK-NEXT:    [[TMP4:%.*]] = icmp ugt i32 [[TMP1:%.*]], [[TMP3]]
 ; CHECK-NEXT:    [[TMP5:%.*]] = zext i1 [[TMP4]] to i32
 ; CHECK-NEXT:    ret i32 [[TMP5]]
 ;



More information about the llvm-commits mailing list