[llvm] [InstSimplify] Fold icmp ult/ule (A nuw+ CA), (B nuw+ CB) to true when A <=u B and CA <=u CB (PR #201485)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Jun 3 17:52:19 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-transforms
Author: Aayush Shrivastava (iamaayushrivastava)
<details>
<summary>Changes</summary>
Fixes #<!-- -->201290
This PR adds a fold in `simplifyICmpWithBinOp()` to simplify `icmp ult/ule (A nuw+ CA), (B nuw+ CB)` to `true` when `A <=u B is provable and `CA <=u CB`.
---
Full diff: https://github.com/llvm/llvm-project/pull/201485.diff
2 Files Affected:
- (modified) llvm/lib/Analysis/InstructionSimplify.cpp (+33)
- (added) llvm/test/Transforms/InstSimplify/icmp-nuw-add-offset.ll (+151)
``````````diff
diff --git a/llvm/lib/Analysis/InstructionSimplify.cpp b/llvm/lib/Analysis/InstructionSimplify.cpp
index 81debece796b1..8159195799456 100644
--- a/llvm/lib/Analysis/InstructionSimplify.cpp
+++ b/llvm/lib/Analysis/InstructionSimplify.cpp
@@ -3422,6 +3422,39 @@ static Value *simplifyICmpWithBinOp(CmpPredicate Pred, Value *LHS, Value *RHS,
if (Value *V = simplifyICmpInst(Pred, Y, Z, Q, MaxRecurse - 1))
return V;
}
+
+ // icmp ult/ule (LBase nuw+ CA), (RBase nuw+ CB) --> true
+ // when LBase <=u RBase and CA <=u CB (strict ult also needs CA < CB or
+ // LBase <u RBase).
+ //
+ // nuw on the RHS add gives RBase+CB < 2^n, so CB <= 2^n-1-RBase.
+ // CA <=u CB then forces RBase+CA to also be in range. From LBase <=u RBase
+ // we get LBase+CA <=u RBase+CA (unsigned, no overflow), and from CA <=u CB
+ // we get RBase+CA <=u RBase+CB. Combining: LBase+CA <=u RBase+CB.
+ if (LBO && RBO && LBO->getOpcode() == Instruction::Add &&
+ RBO->getOpcode() == Instruction::Add &&
+ (Pred == ICmpInst::ICMP_ULT || Pred == ICmpInst::ICMP_ULE) &&
+ Q.IIQ.hasNoUnsignedWrap(cast<OverflowingBinaryOperator>(LBO)) &&
+ Q.IIQ.hasNoUnsignedWrap(cast<OverflowingBinaryOperator>(RBO))) {
+ Value *LBase, *RBase;
+ const APInt *CA, *CB;
+ if (match(LBO, m_c_Add(m_Value(LBase), m_APInt(CA))) &&
+ match(RBO, m_c_Add(m_Value(RBase), m_APInt(CB))) && CA->ule(*CB)) {
+ if (Value *IsLE = simplifyICmpInst(ICmpInst::ICMP_ULE, LBase, RBase, Q,
+ MaxRecurse - 1)) {
+ if (match(IsLE, m_One())) {
+ Type *CmpTy = getCompareTy(LHS);
+ if (Pred == ICmpInst::ICMP_ULE || CA->ult(*CB))
+ return getTrue(CmpTy);
+ // ult with CA == CB: strict ordering requires LBase <u RBase.
+ if (Value *IsLT = simplifyICmpInst(ICmpInst::ICMP_ULT, LBase, RBase,
+ Q, MaxRecurse - 1))
+ if (match(IsLT, m_One()))
+ return getTrue(CmpTy);
+ }
+ }
+ }
+ }
}
if (LBO)
diff --git a/llvm/test/Transforms/InstSimplify/icmp-nuw-add-offset.ll b/llvm/test/Transforms/InstSimplify/icmp-nuw-add-offset.ll
new file mode 100644
index 0000000000000..2739a51a464cc
--- /dev/null
+++ b/llvm/test/Transforms/InstSimplify/icmp-nuw-add-offset.ll
@@ -0,0 +1,151 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=instsimplify -S | FileCheck %s
+
+; Fold icmp ult/ule (LBase nuw+ CA), (RBase nuw+ CB) -> true
+; when LBase <=u RBase is provable and CA <=u CB.
+;
+; Soundness: nuw on the RHS add gives RBase+CB < 2^n, so CB <= 2^n-1-RBase.
+; CA <=u CB forces RBase+CA to also be in range. From LBase <=u RBase we get
+; LBase+CA <=u RBase+CA (unsigned, no overflow), and from CA <=u CB we get
+; RBase+CA <=u RBase+CB. Combining: LBase+CA <=u RBase+CB.
+; For ult: one of the two inequalities must be strict.
+
+; Motivating case from the issue: (x & m) + C <u m + C+1 -> true
+define i1 @and_plus_offset_ult_i32(i32 %x, i32 %m) {
+; CHECK-LABEL: @and_plus_offset_ult_i32(
+; CHECK-NEXT: ret i1 true
+;
+ %a = and i32 %x, %m
+ %lhs = add nuw i32 %a, 1024
+ %rhs = add nuw i32 %m, 1025
+ %cmp = icmp ult i32 %lhs, %rhs
+ ret i1 %cmp
+}
+
+; ule variant: equal offsets, (x & m) + C <=u m + C -> true
+define i1 @and_plus_offset_ule_equal(i32 %x, i32 %m) {
+; CHECK-LABEL: @and_plus_offset_ule_equal(
+; CHECK-NEXT: ret i1 true
+;
+ %a = and i32 %x, %m
+ %lhs = add nuw i32 %a, 1024
+ %rhs = add nuw i32 %m, 1024
+ %cmp = icmp ule i32 %lhs, %rhs
+ ret i1 %cmp
+}
+
+; ule variant: CB > CA, (x & m) + C <=u m + C+5 -> true
+define i1 @and_plus_offset_ule_strict(i32 %x, i32 %m) {
+; CHECK-LABEL: @and_plus_offset_ule_strict(
+; CHECK-NEXT: ret i1 true
+;
+ %a = and i32 %x, %m
+ %lhs = add nuw i32 %a, 100
+ %rhs = add nuw i32 %m, 105
+ %cmp = icmp ule i32 %lhs, %rhs
+ ret i1 %cmp
+}
+
+; i64 variant
+define i1 @and_plus_offset_ult_i64(i64 %x, i64 %m) {
+; CHECK-LABEL: @and_plus_offset_ult_i64(
+; CHECK-NEXT: ret i1 true
+;
+ %a = and i64 %x, %m
+ %lhs = add nuw i64 %a, 4096
+ %rhs = add nuw i64 %m, 4097
+ %cmp = icmp ult i64 %lhs, %rhs
+ ret i1 %cmp
+}
+
+; Constant operand is on the first (non-canonical) side of add.
+define i1 @and_plus_offset_ult_commuted(i32 %x, i32 %m) {
+; CHECK-LABEL: @and_plus_offset_ult_commuted(
+; CHECK-NEXT: ret i1 true
+;
+ %a = and i32 %x, %m
+ %lhs = add nuw i32 1024, %a
+ %rhs = add nuw i32 1025, %m
+ %cmp = icmp ult i32 %lhs, %rhs
+ ret i1 %cmp
+}
+
+; --- Negative tests ---
+
+; ult with CA == CB should NOT fold (LBase could equal RBase).
+define i1 @and_plus_equal_offset_ult_negative(i32 %x, i32 %m) {
+; CHECK-LABEL: @and_plus_equal_offset_ult_negative(
+; CHECK-NEXT: [[A:%.*]] = and i32 [[X:%.*]], [[M:%.*]]
+; CHECK-NEXT: [[LHS:%.*]] = add nuw i32 [[A]], 1024
+; CHECK-NEXT: [[RHS:%.*]] = add nuw i32 [[M]], 1024
+; CHECK-NEXT: [[CMP:%.*]] = icmp ult i32 [[LHS]], [[RHS]]
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %a = and i32 %x, %m
+ %lhs = add nuw i32 %a, 1024
+ %rhs = add nuw i32 %m, 1024
+ %cmp = icmp ult i32 %lhs, %rhs
+ ret i1 %cmp
+}
+
+; Missing nuw on LHS should NOT fold.
+define i1 @and_plus_offset_no_lhs_nuw_negative(i32 %x, i32 %m) {
+; CHECK-LABEL: @and_plus_offset_no_lhs_nuw_negative(
+; CHECK-NEXT: [[A:%.*]] = and i32 [[X:%.*]], [[M:%.*]]
+; CHECK-NEXT: [[LHS:%.*]] = add i32 [[A]], 1024
+; CHECK-NEXT: [[RHS:%.*]] = add nuw i32 [[M]], 1025
+; CHECK-NEXT: [[CMP:%.*]] = icmp ult i32 [[LHS]], [[RHS]]
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %a = and i32 %x, %m
+ %lhs = add i32 %a, 1024
+ %rhs = add nuw i32 %m, 1025
+ %cmp = icmp ult i32 %lhs, %rhs
+ ret i1 %cmp
+}
+
+; Missing nuw on RHS should NOT fold.
+define i1 @and_plus_offset_no_rhs_nuw_negative(i32 %x, i32 %m) {
+; CHECK-LABEL: @and_plus_offset_no_rhs_nuw_negative(
+; CHECK-NEXT: [[A:%.*]] = and i32 [[X:%.*]], [[M:%.*]]
+; CHECK-NEXT: [[LHS:%.*]] = add nuw i32 [[A]], 1024
+; CHECK-NEXT: [[RHS:%.*]] = add i32 [[M]], 1025
+; CHECK-NEXT: [[CMP:%.*]] = icmp ult i32 [[LHS]], [[RHS]]
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %a = and i32 %x, %m
+ %lhs = add nuw i32 %a, 1024
+ %rhs = add i32 %m, 1025
+ %cmp = icmp ult i32 %lhs, %rhs
+ ret i1 %cmp
+}
+
+; CA > CB should NOT fold (wrong direction).
+define i1 @and_plus_offset_ca_gt_cb_negative(i32 %x, i32 %m) {
+; CHECK-LABEL: @and_plus_offset_ca_gt_cb_negative(
+; CHECK-NEXT: [[A:%.*]] = and i32 [[X:%.*]], [[M:%.*]]
+; CHECK-NEXT: [[LHS:%.*]] = add nuw i32 [[A]], 1025
+; CHECK-NEXT: [[RHS:%.*]] = add nuw i32 [[M]], 1024
+; CHECK-NEXT: [[CMP:%.*]] = icmp ult i32 [[LHS]], [[RHS]]
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %a = and i32 %x, %m
+ %lhs = add nuw i32 %a, 1025
+ %rhs = add nuw i32 %m, 1024
+ %cmp = icmp ult i32 %lhs, %rhs
+ ret i1 %cmp
+}
+
+; Unrelated base values (no provable ordering) should NOT fold.
+define i1 @unrelated_bases_negative(i32 %x, i32 %y) {
+; CHECK-LABEL: @unrelated_bases_negative(
+; CHECK-NEXT: [[LHS:%.*]] = add nuw i32 [[X:%.*]], 1024
+; CHECK-NEXT: [[RHS:%.*]] = add nuw i32 [[Y:%.*]], 1025
+; CHECK-NEXT: [[CMP:%.*]] = icmp ult i32 [[LHS]], [[RHS]]
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %lhs = add nuw i32 %x, 1024
+ %rhs = add nuw i32 %y, 1025
+ %cmp = icmp ult i32 %lhs, %rhs
+ ret i1 %cmp
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/201485
More information about the llvm-commits
mailing list