[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