[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