[llvm] [InstCombine] Fold uadd.sat comparison using known operand relation (PR #212776)
Lee Dong Wook via llvm-commits
llvm-commits at lists.llvm.org
Sun Aug 2 06:07:02 PDT 2026
https://github.com/dongwooklee96 updated https://github.com/llvm/llvm-project/pull/212776
>From fdb1a4b0987665dad670553e751f12ddb69fbd86 Mon Sep 17 00:00:00 2001
From: "Lee, DongWook" <sh95119 at gmail.com>
Date: Wed, 29 Jul 2026 21:02:17 +0900
Subject: [PATCH 1/3] [InstCombine] Add tests for non-constant uadd.sat
comparisons
---
.../Transforms/InstCombine/icmp-uadd-sat.ll | 90 +++++++++++++++++++
1 file changed, 90 insertions(+)
diff --git a/llvm/test/Transforms/InstCombine/icmp-uadd-sat.ll b/llvm/test/Transforms/InstCombine/icmp-uadd-sat.ll
index fc582708e0b75..071edc446309c 100644
--- a/llvm/test/Transforms/InstCombine/icmp-uadd-sat.ll
+++ b/llvm/test/Transforms/InstCombine/icmp-uadd-sat.ll
@@ -119,6 +119,95 @@ define i1 @icmp_sgt_basic(i16 %arg) {
ret i1 %cmp
}
+; ==============================================================================
+; Tests with non-constant operands
+; ==============================================================================
+define i1 @icmp_ult_assume_c_ule_c2(i8 %x, i8 %c, i8 %c2) {
+; CHECK-LABEL: define i1 @icmp_ult_assume_c_ule_c2(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[C:%.*]], i8 [[C2:%.*]]) {
+; CHECK-NEXT: [[COND:%.*]] = icmp ule i8 [[C]], [[C2]]
+; CHECK-NEXT: call void @llvm.assume(i1 [[COND]])
+; CHECK-NEXT: [[ADD:%.*]] = call i8 @llvm.uadd.sat.i8(i8 [[X]], i8 [[C]])
+; CHECK-NEXT: [[CMP:%.*]] = icmp ult i8 [[ADD]], [[C2]]
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %cond = icmp ule i8 %c, %c2
+ call void @llvm.assume(i1 %cond)
+ %add = call i8 @llvm.uadd.sat.i8(i8 %x, i8 %c)
+ %cmp = icmp ult i8 %add, %c2
+ ret i1 %cmp
+}
+
+define i1 @icmp_ult_equal_operands(i8 %x, i8 %c) {
+; CHECK-LABEL: define i1 @icmp_ult_equal_operands(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[C:%.*]]) {
+; CHECK-NEXT: ret i1 false
+;
+ %add = call i8 @llvm.uadd.sat.i8(i8 %x, i8 %c)
+ %cmp = icmp ult i8 %add, %c
+ ret i1 %cmp
+}
+
+define i1 @icmp_ult_assume_constant_c2(i8 %x, i8 %c) {
+; CHECK-LABEL: define i1 @icmp_ult_assume_constant_c2(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[C:%.*]]) {
+; CHECK-NEXT: [[COND:%.*]] = icmp ult i8 [[C]], 30
+; CHECK-NEXT: call void @llvm.assume(i1 [[COND]])
+; CHECK-NEXT: [[ADD:%.*]] = call i8 @llvm.uadd.sat.i8(i8 [[X]], i8 [[C]])
+; CHECK-NEXT: [[CMP:%.*]] = icmp ult i8 [[ADD]], 30
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %cond = icmp ult i8 %c, 30
+ call void @llvm.assume(i1 %cond)
+ %add = call i8 @llvm.uadd.sat.i8(i8 %x, i8 %c)
+ %cmp = icmp ult i8 %add, 30
+ ret i1 %cmp
+}
+
+define i1 @icmp_ult_no_assume(i8 %x, i8 %c, i8 %c2) {
+; CHECK-LABEL: define i1 @icmp_ult_no_assume(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[C:%.*]], i8 [[C2:%.*]]) {
+; CHECK-NEXT: [[ADD:%.*]] = call i8 @llvm.uadd.sat.i8(i8 [[X]], i8 [[C]])
+; CHECK-NEXT: [[CMP:%.*]] = icmp ult i8 [[ADD]], [[C2]]
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %add = call i8 @llvm.uadd.sat.i8(i8 %x, i8 %c)
+ %cmp = icmp ult i8 %add, %c2
+ ret i1 %cmp
+}
+
+define i1 @icmp_ult_assume_c_ugt_c2(i8 %x, i8 %c, i8 %c2) {
+; CHECK-LABEL: define i1 @icmp_ult_assume_c_ugt_c2(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[C:%.*]], i8 [[C2:%.*]]) {
+; CHECK-NEXT: [[COND:%.*]] = icmp ugt i8 [[C]], [[C2]]
+; CHECK-NEXT: call void @llvm.assume(i1 [[COND]])
+; CHECK-NEXT: [[ADD:%.*]] = call i8 @llvm.uadd.sat.i8(i8 [[X]], i8 [[C]])
+; CHECK-NEXT: [[CMP:%.*]] = icmp ult i8 [[ADD]], [[C2]]
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %cond = icmp ugt i8 %c, %c2
+ call void @llvm.assume(i1 %cond)
+ %add = call i8 @llvm.uadd.sat.i8(i8 %x, i8 %c)
+ %cmp = icmp ult i8 %add, %c2
+ ret i1 %cmp
+}
+
+define i1 @icmp_ule_assume_c_ule_c2(i8 %x, i8 %c, i8 %c2) {
+; CHECK-LABEL: define i1 @icmp_ule_assume_c_ule_c2(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[C:%.*]], i8 [[C2:%.*]]) {
+; CHECK-NEXT: [[COND:%.*]] = icmp ule i8 [[C]], [[C2]]
+; CHECK-NEXT: call void @llvm.assume(i1 [[COND]])
+; CHECK-NEXT: [[ADD:%.*]] = call i8 @llvm.uadd.sat.i8(i8 [[X]], i8 [[C]])
+; CHECK-NEXT: [[CMP:%.*]] = icmp ule i8 [[ADD]], [[C2]]
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %cond = icmp ule i8 %c, %c2
+ call void @llvm.assume(i1 %cond)
+ %add = call i8 @llvm.uadd.sat.i8(i8 %x, i8 %c)
+ %cmp = icmp ule i8 %add, %c2
+ ret i1 %cmp
+}
+
; ==============================================================================
; Tests with more than user
; ==============================================================================
@@ -258,5 +347,6 @@ declare <2 x i32> @llvm.uadd.sat.v2i32(<2 x i32>, <2 x i32>)
declare <2 x i16> @llvm.uadd.sat.v2i16(<2 x i16>, <2 x i16>)
declare <2 x i8> @llvm.uadd.sat.v2i8(<2 x i8>, <2 x i8>)
+declare void @llvm.assume(i1 noundef)
declare void @use.i8(i8)
declare void @use.v2i8(<2 x i8>)
>From 541378ed46ba5d60ce78c17f1c74d00f7dfe511a Mon Sep 17 00:00:00 2001
From: "Lee, DongWook" <sh95119 at gmail.com>
Date: Wed, 29 Jul 2026 21:03:14 +0900
Subject: [PATCH 2/3] [InstCombine] Fold uadd.sat comparison using known
operand relation
---
.../InstCombine/InstCombineCompares.cpp | 46 +++++++++++++++++++
.../InstCombine/InstCombineInternal.h | 1 +
.../Transforms/InstCombine/icmp-uadd-sat.ll | 8 ++--
3 files changed, 51 insertions(+), 4 deletions(-)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
index 70d584740f5b9..c463ca6b30ee2 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
@@ -4095,6 +4095,49 @@ Instruction *InstCombinerImpl::foldICmpBinOpWithConstant(ICmpInst &Cmp,
return foldICmpBinOpWithConstantViaTruthTable(Cmp, BO, C);
}
+/// Fold uadd.sat(X, C) <u C2 to X <u C2 - C when C2 >=u C is
+/// known to hold.
+static Instruction *foldICmpUAddSat(ICmpInst &Cmp, SaturatingInst *II,
+ InstCombiner::BuilderTy &Builder,
+ const SimplifyQuery &Q) {
+ // This transform may end up producing more than one instruction for the
+ // intrinsic, so limit it to one user of the intrinsic.
+ if (!II->hasOneUse())
+ return nullptr;
+
+ if (Cmp.getPredicate() != ICmpInst::ICMP_ULT)
+ return nullptr;
+
+ Value *X = II->getOperand(0);
+ Value *C = II->getOperand(1);
+ Value *C2 = Cmp.getOperand(1);
+
+ // Check whether C2 >=u C is known from assumptions or dominating conditions.
+ Value *IsKnown = simplifyICmpInst(ICmpInst::ICMP_UGE, C2, C, Q);
+
+ if (!IsKnown || !match(IsKnown, m_One()))
+ return nullptr;
+
+ Value *Limit = Builder.CreateSub(C2, C);
+ return new ICmpInst(ICmpInst::ICMP_ULT, X, Limit);
+}
+
+/// Try to fold an integer comparison whose operands are
+/// not required to be constants.
+Instruction *InstCombinerImpl::foldICmpInst(ICmpInst &Cmp) {
+ if (auto *II = dyn_cast<IntrinsicInst>(Cmp.getOperand(0))) {
+ switch (II->getIntrinsicID()) {
+ default:
+ break;
+ case Intrinsic::uadd_sat:
+ return foldICmpUAddSat(Cmp, cast<SaturatingInst>(II), Builder,
+ SQ.getWithInstruction(&Cmp));
+ }
+ }
+
+ return nullptr;
+}
+
static Instruction *
foldICmpUSubSatOrUAddSatWithConstant(CmpPredicate Pred, SaturatingInst *II,
const APInt &C,
@@ -7943,6 +7986,9 @@ Instruction *InstCombinerImpl::visitICmpInst(ICmpInst &I) {
if (Instruction *Res = foldICmpInstWithConstant(I))
return Res;
+ if (Instruction *Res = foldICmpInst(I))
+ return Res;
+
// Try to match comparison as a sign bit test. Intentionally do this after
// foldICmpInstWithConstant() to potentially let other folds to happen first.
if (Instruction *New = foldSignBitTest(I))
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineInternal.h b/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
index 8b759e701da60..3abb2b4163514 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
+++ b/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
@@ -736,6 +736,7 @@ class LLVM_LIBRARY_VISIBILITY InstCombinerImpl final
Instruction *foldICmpWithConstant(ICmpInst &Cmp);
Instruction *foldIsMultipleOfAPowerOfTwo(ICmpInst &Cmp);
Instruction *foldICmpUsingBoolRange(ICmpInst &I);
+ Instruction *foldICmpInst(ICmpInst &Cmp);
Instruction *foldICmpInstWithConstant(ICmpInst &Cmp);
Instruction *foldICmpInstWithConstantNotInt(ICmpInst &Cmp);
Instruction *foldICmpInstWithConstantAllowPoison(ICmpInst &Cmp,
diff --git a/llvm/test/Transforms/InstCombine/icmp-uadd-sat.ll b/llvm/test/Transforms/InstCombine/icmp-uadd-sat.ll
index 071edc446309c..08580cfc3f66b 100644
--- a/llvm/test/Transforms/InstCombine/icmp-uadd-sat.ll
+++ b/llvm/test/Transforms/InstCombine/icmp-uadd-sat.ll
@@ -127,8 +127,8 @@ define i1 @icmp_ult_assume_c_ule_c2(i8 %x, i8 %c, i8 %c2) {
; CHECK-SAME: i8 [[X:%.*]], i8 [[C:%.*]], i8 [[C2:%.*]]) {
; CHECK-NEXT: [[COND:%.*]] = icmp ule i8 [[C]], [[C2]]
; CHECK-NEXT: call void @llvm.assume(i1 [[COND]])
-; CHECK-NEXT: [[ADD:%.*]] = call i8 @llvm.uadd.sat.i8(i8 [[X]], i8 [[C]])
-; CHECK-NEXT: [[CMP:%.*]] = icmp ult i8 [[ADD]], [[C2]]
+; CHECK-NEXT: [[LIMIT:%.*]] = sub i8 [[C2]], [[C]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp ult i8 [[X]], [[LIMIT]]
; CHECK-NEXT: ret i1 [[CMP]]
;
%cond = icmp ule i8 %c, %c2
@@ -153,8 +153,8 @@ define i1 @icmp_ult_assume_constant_c2(i8 %x, i8 %c) {
; CHECK-SAME: i8 [[X:%.*]], i8 [[C:%.*]]) {
; CHECK-NEXT: [[COND:%.*]] = icmp ult i8 [[C]], 30
; CHECK-NEXT: call void @llvm.assume(i1 [[COND]])
-; CHECK-NEXT: [[ADD:%.*]] = call i8 @llvm.uadd.sat.i8(i8 [[X]], i8 [[C]])
-; CHECK-NEXT: [[CMP:%.*]] = icmp ult i8 [[ADD]], 30
+; CHECK-NEXT: [[TMP1:%.*]] = sub nuw nsw i8 30, [[C]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp ult i8 [[X]], [[TMP1]]
; CHECK-NEXT: ret i1 [[CMP]]
;
%cond = icmp ult i8 %c, 30
>From a73169333937b0791a75d3c98c805818b4f3f1ed Mon Sep 17 00:00:00 2001
From: "Lee, DongWook" <sh95119 at gmail.com>
Date: Sat, 1 Aug 2026 17:45:05 +0900
Subject: [PATCH 3/3] fixup! [InstCombine] Add tests for non-constant uadd.sat
comparisons
---
.../Transforms/InstCombine/icmp-uadd-sat.ll | 80 ++-----------------
1 file changed, 5 insertions(+), 75 deletions(-)
diff --git a/llvm/test/Transforms/InstCombine/icmp-uadd-sat.ll b/llvm/test/Transforms/InstCombine/icmp-uadd-sat.ll
index 08580cfc3f66b..76d6bfc590958 100644
--- a/llvm/test/Transforms/InstCombine/icmp-uadd-sat.ll
+++ b/llvm/test/Transforms/InstCombine/icmp-uadd-sat.ll
@@ -120,10 +120,10 @@ define i1 @icmp_sgt_basic(i16 %arg) {
}
; ==============================================================================
-; Tests with non-constant operands
+; Test with non-constant operands
; ==============================================================================
-define i1 @icmp_ult_assume_c_ule_c2(i8 %x, i8 %c, i8 %c2) {
-; CHECK-LABEL: define i1 @icmp_ult_assume_c_ule_c2(
+define i1 @icmp_ult_nonconstant(i8 %x, i8 %c, i8 %c2) {
+; CHECK-LABEL: define i1 @icmp_ult_nonconstant(
; CHECK-SAME: i8 [[X:%.*]], i8 [[C:%.*]], i8 [[C2:%.*]]) {
; CHECK-NEXT: [[COND:%.*]] = icmp ule i8 [[C]], [[C2]]
; CHECK-NEXT: call void @llvm.assume(i1 [[COND]])
@@ -133,78 +133,8 @@ define i1 @icmp_ult_assume_c_ule_c2(i8 %x, i8 %c, i8 %c2) {
;
%cond = icmp ule i8 %c, %c2
call void @llvm.assume(i1 %cond)
- %add = call i8 @llvm.uadd.sat.i8(i8 %x, i8 %c)
- %cmp = icmp ult i8 %add, %c2
- ret i1 %cmp
-}
-
-define i1 @icmp_ult_equal_operands(i8 %x, i8 %c) {
-; CHECK-LABEL: define i1 @icmp_ult_equal_operands(
-; CHECK-SAME: i8 [[X:%.*]], i8 [[C:%.*]]) {
-; CHECK-NEXT: ret i1 false
-;
- %add = call i8 @llvm.uadd.sat.i8(i8 %x, i8 %c)
- %cmp = icmp ult i8 %add, %c
- ret i1 %cmp
-}
-
-define i1 @icmp_ult_assume_constant_c2(i8 %x, i8 %c) {
-; CHECK-LABEL: define i1 @icmp_ult_assume_constant_c2(
-; CHECK-SAME: i8 [[X:%.*]], i8 [[C:%.*]]) {
-; CHECK-NEXT: [[COND:%.*]] = icmp ult i8 [[C]], 30
-; CHECK-NEXT: call void @llvm.assume(i1 [[COND]])
-; CHECK-NEXT: [[TMP1:%.*]] = sub nuw nsw i8 30, [[C]]
-; CHECK-NEXT: [[CMP:%.*]] = icmp ult i8 [[X]], [[TMP1]]
-; CHECK-NEXT: ret i1 [[CMP]]
-;
- %cond = icmp ult i8 %c, 30
- call void @llvm.assume(i1 %cond)
- %add = call i8 @llvm.uadd.sat.i8(i8 %x, i8 %c)
- %cmp = icmp ult i8 %add, 30
- ret i1 %cmp
-}
-
-define i1 @icmp_ult_no_assume(i8 %x, i8 %c, i8 %c2) {
-; CHECK-LABEL: define i1 @icmp_ult_no_assume(
-; CHECK-SAME: i8 [[X:%.*]], i8 [[C:%.*]], i8 [[C2:%.*]]) {
-; CHECK-NEXT: [[ADD:%.*]] = call i8 @llvm.uadd.sat.i8(i8 [[X]], i8 [[C]])
-; CHECK-NEXT: [[CMP:%.*]] = icmp ult i8 [[ADD]], [[C2]]
-; CHECK-NEXT: ret i1 [[CMP]]
-;
- %add = call i8 @llvm.uadd.sat.i8(i8 %x, i8 %c)
- %cmp = icmp ult i8 %add, %c2
- ret i1 %cmp
-}
-
-define i1 @icmp_ult_assume_c_ugt_c2(i8 %x, i8 %c, i8 %c2) {
-; CHECK-LABEL: define i1 @icmp_ult_assume_c_ugt_c2(
-; CHECK-SAME: i8 [[X:%.*]], i8 [[C:%.*]], i8 [[C2:%.*]]) {
-; CHECK-NEXT: [[COND:%.*]] = icmp ugt i8 [[C]], [[C2]]
-; CHECK-NEXT: call void @llvm.assume(i1 [[COND]])
-; CHECK-NEXT: [[ADD:%.*]] = call i8 @llvm.uadd.sat.i8(i8 [[X]], i8 [[C]])
-; CHECK-NEXT: [[CMP:%.*]] = icmp ult i8 [[ADD]], [[C2]]
-; CHECK-NEXT: ret i1 [[CMP]]
-;
- %cond = icmp ugt i8 %c, %c2
- call void @llvm.assume(i1 %cond)
- %add = call i8 @llvm.uadd.sat.i8(i8 %x, i8 %c)
- %cmp = icmp ult i8 %add, %c2
- ret i1 %cmp
-}
-
-define i1 @icmp_ule_assume_c_ule_c2(i8 %x, i8 %c, i8 %c2) {
-; CHECK-LABEL: define i1 @icmp_ule_assume_c_ule_c2(
-; CHECK-SAME: i8 [[X:%.*]], i8 [[C:%.*]], i8 [[C2:%.*]]) {
-; CHECK-NEXT: [[COND:%.*]] = icmp ule i8 [[C]], [[C2]]
-; CHECK-NEXT: call void @llvm.assume(i1 [[COND]])
-; CHECK-NEXT: [[ADD:%.*]] = call i8 @llvm.uadd.sat.i8(i8 [[X]], i8 [[C]])
-; CHECK-NEXT: [[CMP:%.*]] = icmp ule i8 [[ADD]], [[C2]]
-; CHECK-NEXT: ret i1 [[CMP]]
-;
- %cond = icmp ule i8 %c, %c2
- call void @llvm.assume(i1 %cond)
- %add = call i8 @llvm.uadd.sat.i8(i8 %x, i8 %c)
- %cmp = icmp ule i8 %add, %c2
+ %sat = call i8 @llvm.uadd.sat.i8(i8 %x, i8 %c)
+ %cmp = icmp ult i8 %sat, %c2
ret i1 %cmp
}
More information about the llvm-commits
mailing list