[llvm] [InstCombine] Fold `(A < B) ^ ((A - B) < 0)` into `ssub.with.overflow` (PR #216590)

via llvm-commits llvm-commits at lists.llvm.org
Sun Aug 16 11:07:31 PDT 2026


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

Match the pattern `(A < B) ^ ((A - B) < 0)` and fold it to `ssub.with.overflow`, returning the overflow bit. This also handles inverted cases for `!overflow`.

Proof: https://alive2.llvm.org/ce/z/g6JHma

>From 81a70da26f708818056be797e09aae1c17e88b25 Mon Sep 17 00:00:00 2001
From: AZero13 <gfunni234 at gmail.com>
Date: Sun, 16 Aug 2026 13:33:35 -0400
Subject: [PATCH 1/2] [InstCombine] Precommit tests for ssub.with.overflow
 idiom

---
 .../InstCombine/ssub-with-overflow-pattern.ll | 32 +++++++++++++++++++
 1 file changed, 32 insertions(+)
 create mode 100644 llvm/test/Transforms/InstCombine/ssub-with-overflow-pattern.ll

diff --git a/llvm/test/Transforms/InstCombine/ssub-with-overflow-pattern.ll b/llvm/test/Transforms/InstCombine/ssub-with-overflow-pattern.ll
new file mode 100644
index 0000000000000..f7efe30774e2f
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/ssub-with-overflow-pattern.ll
@@ -0,0 +1,32 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+define i1 @is_overflow_cmp(i8 %0, i8 %1) {
+; CHECK-LABEL: @is_overflow_cmp(
+; CHECK-NEXT:    [[TMP3:%.*]] = icmp slt i8 [[TMP0:%.*]], [[TMP1:%.*]]
+; CHECK-NEXT:    [[TMP4:%.*]] = sub i8 [[TMP0]], [[TMP1]]
+; CHECK-NEXT:    [[TMP5:%.*]] = icmp slt i8 [[TMP4]], 0
+; CHECK-NEXT:    [[TMP6:%.*]] = xor i1 [[TMP3]], [[TMP5]]
+; CHECK-NEXT:    ret i1 [[TMP6]]
+;
+  %3 = icmp slt i8 %0, %1
+  %4 = sub i8 %0, %1
+  %5 = icmp slt i8 %4, 0
+  %6 = xor i1 %3, %5
+  ret i1 %6
+}
+
+define i1 @is_no_overflow_cmp(i8 %0, i8 %1) {
+; CHECK-LABEL: @is_no_overflow_cmp(
+; CHECK-NEXT:    [[TMP3:%.*]] = sub i8 [[TMP0:%.*]], [[TMP1:%.*]]
+; CHECK-NEXT:    [[TMP4:%.*]] = icmp slt i8 [[TMP3]], 0
+; CHECK-NEXT:    [[TMP5:%.*]] = icmp sge i8 [[TMP0]], [[TMP1]]
+; CHECK-NEXT:    [[TMP6:%.*]] = xor i1 [[TMP5]], [[TMP4]]
+; CHECK-NEXT:    ret i1 [[TMP6]]
+;
+  %3 = sub i8 %0, %1
+  %4 = icmp slt i8 %3, 0
+  %5 = icmp sge i8 %0, %1
+  %6 = xor i1 %5, %4
+  ret i1 %6
+}

>From 719cd7b4ea092ee94d04d107af76d6db776e1bf2 Mon Sep 17 00:00:00 2001
From: AZero13 <gfunni234 at gmail.com>
Date: Sun, 16 Aug 2026 14:06:47 -0400
Subject: [PATCH 2/2] [InstCombine] Fold `(A < B) ^ ((A - B) < 0)` into
 `ssub.with.overflow`

Match the pattern `(A < B) ^ ((A - B) < 0)` and fold it to `ssub.with.overflow`, returning the overflow bit. This also handles inverted cases for `!overflow`.

Proof: https://alive2.llvm.org/ce/z/g6JHma
---
 .../InstCombine/InstCombineAndOrXor.cpp       | 60 +++++++++++++++++++
 .../InstCombine/ssub-with-overflow-pattern.ll | 13 ++--
 2 files changed, 65 insertions(+), 8 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
index b6f4a55c07e8a..a85fb6b609947 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
@@ -4746,11 +4746,71 @@ static Instruction *foldXorToXor(BinaryOperator &I,
   return nullptr;
 }
 
+static Value *foldSignedSubOverflowCheck(ICmpInst *Cmp0, ICmpInst *Cmp1,
+                                         InstCombiner::BuilderTy &Builder) {
+  auto IsSignBitCheck = [](ICmpInst *Cmp, Value *&Val) {
+    const APInt *C;
+    if (!match(Cmp->getOperand(1), m_APInt(C)))
+      return false;
+    bool TrueIfSigned;
+    if (!isSignBitCheck(Cmp->getPredicate(), *C, TrueIfSigned))
+      return false;
+    if (TrueIfSigned) {
+      Val = Cmp->getOperand(0);
+      return true;
+    }
+    return false;
+  };
+
+  Value *SubVal = nullptr;
+  ICmpInst *CmpOther = nullptr;
+  if (IsSignBitCheck(Cmp0, SubVal)) {
+    CmpOther = Cmp1;
+  } else if (IsSignBitCheck(Cmp1, SubVal)) {
+    CmpOther = Cmp0;
+  } else {
+    return nullptr;
+  }
+
+  Value *A = nullptr, *B = nullptr;
+  ICmpInst::Predicate Pred = CmpOther->getPredicate();
+  bool IsNot = false;
+  if (Pred == ICmpInst::ICMP_SLT) {
+    A = CmpOther->getOperand(0);
+    B = CmpOther->getOperand(1);
+  } else if (Pred == ICmpInst::ICMP_SGT) {
+    A = CmpOther->getOperand(1);
+    B = CmpOther->getOperand(0);
+  } else if (Pred == ICmpInst::ICMP_SGE) {
+    A = CmpOther->getOperand(0);
+    B = CmpOther->getOperand(1);
+    IsNot = true;
+  } else if (Pred == ICmpInst::ICMP_SLE) {
+    A = CmpOther->getOperand(1);
+    B = CmpOther->getOperand(0);
+    IsNot = true;
+  } else {
+    return nullptr;
+  }
+
+  if (!match(SubVal, m_Sub(m_Specific(A), m_Specific(B))))
+    return nullptr;
+
+  Function *F = Intrinsic::getOrInsertDeclaration(
+      Cmp0->getModule(), Intrinsic::ssub_with_overflow, A->getType());
+  Value *Call = Builder.CreateCall(F, {A, B});
+  Value *Extract = Builder.CreateExtractValue(Call, 1);
+  return IsNot ? Builder.CreateNot(Extract) : Extract;
+}
+
 Value *InstCombinerImpl::foldXorOfICmps(ICmpInst *LHS, ICmpInst *RHS,
                                         BinaryOperator &I) {
   assert(I.getOpcode() == Instruction::Xor && I.getOperand(0) == LHS &&
          I.getOperand(1) == RHS && "Should be 'xor' with these operands");
 
+  if (Value *V = foldSignedSubOverflowCheck(LHS, RHS, Builder))
+    return V;
+
   ICmpInst::Predicate PredL = LHS->getPredicate(), PredR = RHS->getPredicate();
   Value *LHS0 = LHS->getOperand(0), *LHS1 = LHS->getOperand(1);
   Value *RHS0 = RHS->getOperand(0), *RHS1 = RHS->getOperand(1);
diff --git a/llvm/test/Transforms/InstCombine/ssub-with-overflow-pattern.ll b/llvm/test/Transforms/InstCombine/ssub-with-overflow-pattern.ll
index f7efe30774e2f..5626c04114264 100644
--- a/llvm/test/Transforms/InstCombine/ssub-with-overflow-pattern.ll
+++ b/llvm/test/Transforms/InstCombine/ssub-with-overflow-pattern.ll
@@ -3,10 +3,8 @@
 
 define i1 @is_overflow_cmp(i8 %0, i8 %1) {
 ; CHECK-LABEL: @is_overflow_cmp(
-; CHECK-NEXT:    [[TMP3:%.*]] = icmp slt i8 [[TMP0:%.*]], [[TMP1:%.*]]
-; CHECK-NEXT:    [[TMP4:%.*]] = sub i8 [[TMP0]], [[TMP1]]
-; CHECK-NEXT:    [[TMP5:%.*]] = icmp slt i8 [[TMP4]], 0
-; CHECK-NEXT:    [[TMP6:%.*]] = xor i1 [[TMP3]], [[TMP5]]
+; CHECK-NEXT:    [[TMP3:%.*]] = call { i8, i1 } @llvm.ssub.with.overflow.i8(i8 [[TMP0:%.*]], i8 [[TMP1:%.*]])
+; CHECK-NEXT:    [[TMP6:%.*]] = extractvalue { i8, i1 } [[TMP3]], 1
 ; CHECK-NEXT:    ret i1 [[TMP6]]
 ;
   %3 = icmp slt i8 %0, %1
@@ -18,10 +16,9 @@ define i1 @is_overflow_cmp(i8 %0, i8 %1) {
 
 define i1 @is_no_overflow_cmp(i8 %0, i8 %1) {
 ; CHECK-LABEL: @is_no_overflow_cmp(
-; CHECK-NEXT:    [[TMP3:%.*]] = sub i8 [[TMP0:%.*]], [[TMP1:%.*]]
-; CHECK-NEXT:    [[TMP4:%.*]] = icmp slt i8 [[TMP3]], 0
-; CHECK-NEXT:    [[TMP5:%.*]] = icmp sge i8 [[TMP0]], [[TMP1]]
-; CHECK-NEXT:    [[TMP6:%.*]] = xor i1 [[TMP5]], [[TMP4]]
+; CHECK-NEXT:    [[TMP3:%.*]] = call { i8, i1 } @llvm.ssub.with.overflow.i8(i8 [[TMP0:%.*]], i8 [[TMP1:%.*]])
+; CHECK-NEXT:    [[TMP4:%.*]] = extractvalue { i8, i1 } [[TMP3]], 1
+; CHECK-NEXT:    [[TMP6:%.*]] = xor i1 [[TMP4]], true
 ; CHECK-NEXT:    ret i1 [[TMP6]]
 ;
   %3 = sub i8 %0, %1



More information about the llvm-commits mailing list