[llvm] [InstCombine] Fix i1 ssub.sat compare folding (PR #173742)

Justin Lebar via llvm-commits llvm-commits at lists.llvm.org
Sat Dec 27 13:43:49 PST 2025


https://github.com/jlebar created https://github.com/llvm/llvm-project/pull/173742

None

>From a8926a0b3dd7f7aa023cdbd80115a6ce6408f2f4 Mon Sep 17 00:00:00 2001
From: Justin Lebar <justin.lebar at gmail.com>
Date: Fri, 26 Dec 2025 16:30:04 -0500
Subject: [PATCH] [InstCombine] Fix i1 ssub.sat compare folding

For i1, ssub.sat is (a & ~b). The old fold rewrote
(ssub.sat(a, b) == 0) and != 0 as a == b / a != b, which is wrong
for i1. Special-case i1 to fold to (!a | b) and (a & !b) instead.

This was found by a fuzzer I'm working on. The high-level design is to
randomly generate LLVM IR, run a pass on it, and then run the original
and new IR through the interpreter. They should produce the same
results. Right now I'm only fuzzing instcombine.
---
 .../InstCombine/InstCombineCompares.cpp       | 25 +++++++++++++++++--
 .../Transforms/InstCombine/cmp-intrinsic.ll   | 23 +++++++++++++++++
 2 files changed, 46 insertions(+), 2 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
index 7f1ced9505b9b..a4deea206ec0b 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
@@ -3877,9 +3877,30 @@ Instruction *InstCombinerImpl::foldICmpEqIntrinsicWithConstant(
   }
 
   case Intrinsic::ssub_sat:
-    // ssub.sat(a, b) == 0 -> a == b
-    if (C.isZero())
+    // ssub.sat(a, b) == 0 -> a == b, with a special case for i1.
+    if (C.isZero()) {
+      if (Ty->isIntegerTy(1)) {
+        // i1 ssub.sat(a, b):
+        //   a = 0, b = 0 -> 0
+        //   a = 0, b = 1 -> 0 (+1, saturates to 0)
+        //   a = 1, b = 0 -> 1
+        //   a = 1, b = 1 -> 0
+        // Therefore:
+        //   i1 ssub.sat(a, b) == 0 -> !a | b
+        //   i1 ssub.sat(a, b) != 0 -> a & !b
+        Value *A = II->getArgOperand(0);
+        Value *B = II->getArgOperand(1);
+        switch (Pred) {
+        case ICmpInst::ICMP_EQ:
+          return BinaryOperator::CreateOr(Builder.CreateNot(A), B);
+        case ICmpInst::ICMP_NE:
+          return BinaryOperator::CreateAnd(A, Builder.CreateNot(B));
+        default:
+          llvm_unreachable("Unexpected i1 ssub.sat compare predicate");
+        }
+      }
       return new ICmpInst(Pred, II->getArgOperand(0), II->getArgOperand(1));
+    }
     break;
   case Intrinsic::usub_sat: {
     // usub.sat(a, b) == 0  ->  a <= b
diff --git a/llvm/test/Transforms/InstCombine/cmp-intrinsic.ll b/llvm/test/Transforms/InstCombine/cmp-intrinsic.ll
index 19c4cc979d4ba..6284880fe3f4e 100644
--- a/llvm/test/Transforms/InstCombine/cmp-intrinsic.ll
+++ b/llvm/test/Transforms/InstCombine/cmp-intrinsic.ll
@@ -9,6 +9,7 @@ declare i33 @llvm.cttz.i33(i33, i1)
 declare i32 @llvm.ctlz.i32(i32, i1)
 declare i8 @llvm.umax.i8(i8, i8)
 declare i8 @llvm.uadd.sat.i8(i8, i8)
+declare i1 @llvm.ssub.sat.i1(i1, i1)
 declare i8 @llvm.ssub.sat.i8(i8, i8)
 declare i33 @llvm.ctlz.i33(i33, i1)
 declare i8 @llvm.ctpop.i8(i8)
@@ -893,6 +894,17 @@ define i1 @ssub_sat_ne_zero(i8 %x, i8 %y) {
   ret i1 %r
 }
 
+define i1 @ssub_sat_i1_ne_zero(i1 %x, i1 %y) {
+; CHECK-LABEL: @ssub_sat_i1_ne_zero(
+; CHECK-NEXT:    [[NOTY:%.*]] = xor i1 [[Y:%.*]], true
+; CHECK-NEXT:    [[R:%.*]] = and i1 [[X:%.*]], [[NOTY]]
+; CHECK-NEXT:    ret i1 [[R]]
+;
+  %m = call i1 @llvm.ssub.sat.i1(i1 %x, i1 %y)
+  %r = icmp ne i1 %m, 0
+  ret i1 %r
+}
+
 define i1 @ssub_sat_ne_fail_nonzero(i8 %x, i8 %y) {
 ; CHECK-LABEL: @ssub_sat_ne_fail_nonzero(
 ; CHECK-NEXT:    [[M:%.*]] = call i8 @llvm.ssub.sat.i8(i8 [[X:%.*]], i8 [[Y:%.*]])
@@ -904,6 +916,17 @@ define i1 @ssub_sat_ne_fail_nonzero(i8 %x, i8 %y) {
   ret i1 %r
 }
 
+define i1 @ssub_sat_i1_eq_zero(i1 %x, i1 %y) {
+; CHECK-LABEL: @ssub_sat_i1_eq_zero(
+; CHECK-NEXT:    [[NOTX:%.*]] = xor i1 [[X:%.*]], true
+; CHECK-NEXT:    [[R:%.*]] = or i1 [[NOTX]], [[Y:%.*]]
+; CHECK-NEXT:    ret i1 [[R]]
+;
+  %m = call i1 @llvm.ssub.sat.i1(i1 %x, i1 %y)
+  %r = icmp eq i1 %m, 0
+  ret i1 %r
+}
+
 define i1 @ssub_sat_eq_zero(i8 %x, i8 %y) {
 ; CHECK-LABEL: @ssub_sat_eq_zero(
 ; CHECK-NEXT:    [[R:%.*]] = icmp eq i8 [[X:%.*]], [[Y:%.*]]



More information about the llvm-commits mailing list