[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