[llvm] [InstCombine] Fold uadd.sat comparison using known operand relation (PR #212776)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Jul 29 06:56:17 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-transforms
Author: Lee Dong Wook (dongwooklee96)
<details>
<summary>Changes</summary>
## Summary
- Fold `icmp ult (uadd.sat X, C), C2` to `icmp ult X, (C2 - C)`
when `C2 >=u C` is known from assumptions or dominating conditions.
- Add positive and negative InstCombine tests for the operand relation and
comparison predicate.
## Rationale
`uadd.sat(X, C)` is always at least `C` in the unsigned domain. When
`C2 >=u C`, testing whether the saturated sum is below `C2` is equivalent to
testing whether `X` is below `C2 - C`. The precondition also ensures that the
subtraction does not unsigned-wrap.
The fold uses `simplifyICmpInst` with the current `SimplifyQuery`, allowing the
required relationship to be established for non-constant operands through
`llvm.assume` or a dominating condition.
## Alive2
https://alive2.llvm.org/ce/z/aNG5MP
## Testing
- `build/bin/llvm-lit -sv llvm/test/Transforms/InstCombine/icmp-uadd-sat.ll`
Fixes #<!-- -->169763
---
Full diff: https://github.com/llvm/llvm-project/pull/212776.diff
3 Files Affected:
- (modified) llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp (+46)
- (modified) llvm/lib/Transforms/InstCombine/InstCombineInternal.h (+1)
- (modified) llvm/test/Transforms/InstCombine/icmp-uadd-sat.ll (+90)
``````````diff
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 fc582708e0b75..08580cfc3f66b 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: [[LIMIT:%.*]] = sub i8 [[C2]], [[C]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp ult i8 [[X]], [[LIMIT]]
+; 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: [[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
+ 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>)
``````````
</details>
https://github.com/llvm/llvm-project/pull/212776
More information about the llvm-commits
mailing list