[llvm] [InstCombine] Fix i1 ssub.sat compare folding (PR #173742)
Justin Lebar via llvm-commits
llvm-commits at lists.llvm.org
Sun Jan 11 14:36:59 PST 2026
https://github.com/jlebar updated https://github.com/llvm/llvm-project/pull/173742
>From 8f490e5e845869cce979311bc4e36324d7b1dd6e Mon Sep 17 00:00:00 2001
From: Justin Lebar <justin.lebar at gmail.com>
Date: Sun, 11 Jan 2026 14:11:13 -0800
Subject: [PATCH] Fix incorrect folds of ssub.sat.i1.
For every type other than i1, ssub.sat x, y = 0 implies x == y. But for
i1, 1 - 0 = 0 (because the result of 1 saturates to 0).
The changes to instcombine are not strictly necessary. Instcombine
canonicalizes the ssub.sat.i1 before we arrive at these pattern-matches.
The real fix is in ValueTracking.
Nonetheless we agreed in review it makes sense to add these checks to
instcombine, even though they're currently unreachable:
https://github.com/llvm/llvm-project/pull/173742#issuecomment-3694590010
---
llvm/lib/Analysis/ValueTracking.cpp | 5 +++
.../InstCombine/InstCombineCompares.cpp | 10 ++++--
.../InstCombine/saturating-add-sub.ll | 36 +++++++++++++++++++
3 files changed, 49 insertions(+), 2 deletions(-)
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index 995aa8ed56de9..b7bf1a559d177 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -3540,6 +3540,11 @@ static bool isKnownNonZeroFromOperator(const Operator *I,
// NB: We don't do usub_sat here as in any case we can prove its
// non-zero, we will fold it to `sub nuw` in InstCombine.
case Intrinsic::ssub_sat:
+ // For most types, if x != y then ssub.sat x, y != 0. But
+ // ssub.sat.i1 1, 0 = 0, because 1 saturates to 0. This means
+ // isNonZeroSub will do the wrong thing for ssub.sat.i1.
+ if (BitWidth == 1)
+ return false;
return isNonZeroSub(DemandedElts, Q, BitWidth, II->getArgOperand(0),
II->getArgOperand(1), Depth);
case Intrinsic::sadd_sat:
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
index adf1649e5e1bc..1f1ff1597c9d4 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
@@ -3878,7 +3878,10 @@ Instruction *InstCombinerImpl::foldICmpEqIntrinsicWithConstant(
case Intrinsic::ssub_sat:
// ssub.sat(a, b) == 0 -> a == b
- if (C.isZero())
+ //
+ // Note this doesn't work for ssub.sat.i1 because ssub.sat.i1 1, 0 = 0
+ // (because 1 saturates to 0). Just skip the optimization for i1.
+ if (C.isZero() && II->getType()->getScalarSizeInBits() > 1)
return new ICmpInst(Pred, II->getArgOperand(0), II->getArgOperand(1));
break;
case Intrinsic::usub_sat: {
@@ -4269,7 +4272,10 @@ Instruction *InstCombinerImpl::foldICmpIntrinsicWithConstant(ICmpInst &Cmp,
}
case Intrinsic::ssub_sat:
// ssub.sat(a, b) spred 0 -> a spred b
- if (ICmpInst::isSigned(Pred)) {
+ //
+ // Note this doesn't work for ssub.sat.i1 because ssub.sat.i1 1, 0 = 0
+ // (because 1 saturates to 0). Just skip the optimization for i1.
+ if (ICmpInst::isSigned(Pred) && C.getBitWidth() > 1) {
if (C.isZero())
return new ICmpInst(Pred, II->getArgOperand(0), II->getArgOperand(1));
// X s<= 0 is cannonicalized to X s< 1
diff --git a/llvm/test/Transforms/InstCombine/saturating-add-sub.ll b/llvm/test/Transforms/InstCombine/saturating-add-sub.ll
index 1294f867f07c0..80676de47f193 100644
--- a/llvm/test/Transforms/InstCombine/saturating-add-sub.ll
+++ b/llvm/test/Transforms/InstCombine/saturating-add-sub.ll
@@ -451,6 +451,42 @@ declare i8 @llvm.usub.sat.i8(i8, i8)
declare i8 @llvm.ssub.sat.i8(i8, i8)
declare <2 x i8> @llvm.usub.sat.v2i8(<2 x i8>, <2 x i8>)
declare <2 x i8> @llvm.ssub.sat.v2i8(<2 x i8>, <2 x i8>)
+declare i1 @llvm.ssub.sat.i1(i1, i1)
+
+define i1 @test_ssub_sat_i1_cmp_ule_zero(i1 %a) {
+; CHECK-LABEL: @test_ssub_sat_i1_cmp_ule_zero(
+; CHECK-NEXT: [[CMP:%.*]] = xor i1 [[A:%.*]], true
+; CHECK-NEXT: [[SAT:%.*]] = call i1 @llvm.ssub.sat.i1(i1 [[A]], i1 [[CMP]])
+; CHECK-NEXT: [[RES:%.*]] = xor i1 [[SAT]], true
+; CHECK-NEXT: ret i1 [[RES]]
+;
+ %cmp = icmp ule i1 %a, false
+ %sat = call i1 @llvm.ssub.sat.i1(i1 %a, i1 %cmp)
+ %res = icmp ule i1 %sat, false
+ ret i1 %res
+}
+
+define i1 @test_ssub_sat_i1_cmp_eq_zero(i1 %a, i1 %b) {
+; CHECK-LABEL: @test_ssub_sat_i1_cmp_eq_zero(
+; CHECK-NEXT: [[SAT:%.*]] = call i1 @llvm.ssub.sat.i1(i1 [[A:%.*]], i1 [[B:%.*]])
+; CHECK-NEXT: [[RES:%.*]] = xor i1 [[SAT]], true
+; CHECK-NEXT: ret i1 [[RES]]
+;
+ %sat = call i1 @llvm.ssub.sat.i1(i1 %a, i1 %b)
+ %res = icmp eq i1 %sat, false
+ ret i1 %res
+}
+
+define i1 @test_ssub_sat_i1_cmp_sgt_allones(i1 %a, i1 %b) {
+; CHECK-LABEL: @test_ssub_sat_i1_cmp_sgt_allones(
+; CHECK-NEXT: [[SAT:%.*]] = call i1 @llvm.ssub.sat.i1(i1 [[A:%.*]], i1 [[B:%.*]])
+; CHECK-NEXT: [[RES:%.*]] = xor i1 [[SAT]], true
+; CHECK-NEXT: ret i1 [[RES]]
+;
+ %sat = call i1 @llvm.ssub.sat.i1(i1 %a, i1 %b)
+ %res = icmp sgt i1 %sat, true
+ ret i1 %res
+}
; Cannot canonicalize usub to uadd.
define i8 @test_scalar_usub_canonical(i8 %a) {
More information about the llvm-commits
mailing list