[llvm] [InstSimplify] Fold bounds on non-underflowing subtraction (PR #218085)

via llvm-commits llvm-commits at lists.llvm.org
Fri Aug 21 18:50:57 PDT 2026


https://github.com/SomeFlyingThing created https://github.com/llvm/llvm-project/pull/218085

If X - Y cannot unsigned-underflow, X - Y is at most X. Use known bounds on X to fold unsigned comparisons of the subtraction, including bounds established by dominating assumptions.

Fixes #91619.

LLM assisted pr

>From 9d17b4fa4ec931c91466958f8687e0e0d885e472 Mon Sep 17 00:00:00 2001
From: SomeFlyingThing <306498559+SomeFlyingThing at users.noreply.github.com>
Date: Sat, 22 Aug 2026 13:46:07 +1200
Subject: [PATCH] [InstSimplify] Fold bounds on non-underflowing subtraction

If X - Y cannot unsigned-underflow, X - Y is at most X. Use known bounds on X to fold unsigned comparisons of the subtraction, including bounds established by dominating assumptions.

Fixes #91619.
---
 llvm/lib/Analysis/InstructionSimplify.cpp     |  18 ++
 .../InstSimplify/icmp-subtraction-bounds.ll   | 162 ++++++++++++++++++
 2 files changed, 180 insertions(+)
 create mode 100644 llvm/test/Transforms/InstSimplify/icmp-subtraction-bounds.ll

diff --git a/llvm/lib/Analysis/InstructionSimplify.cpp b/llvm/lib/Analysis/InstructionSimplify.cpp
index c76f4d2e9f327..b057c38c8008e 100644
--- a/llvm/lib/Analysis/InstructionSimplify.cpp
+++ b/llvm/lib/Analysis/InstructionSimplify.cpp
@@ -3343,6 +3343,24 @@ static Value *simplifyICmpWithBinOpOnLHS(CmpPredicate Pred, BinaryOperator *LBO,
       return getTrue(ITy);
   }
 
+  // If X - Y does not unsigned underflow, then X - Y <=u X. Use a known
+  // upper bound for X to simplify the comparison.
+  if (ICmpInst::isUnsigned(Pred) &&
+      LBO->getOpcode() == Instruction::Sub && MaxRecurse) {
+    Value *X = LBO->getOperand(0), *Y = LBO->getOperand(1);
+    if (Q.IIQ.hasNoUnsignedWrap(LBO) ||
+        isICmpTrue(ICmpInst::ICMP_ULE, Y, X, Q, MaxRecurse - 1)) {
+      CmpPredicate BoundPred = Pred;
+      bool Result = true;
+      if (Pred == ICmpInst::ICMP_UGT || Pred == ICmpInst::ICMP_UGE) {
+        BoundPred = ICmpInst::getInversePredicate(Pred);
+        Result = false;
+      }
+      if (isICmpTrue(BoundPred, X, RHS, Q, MaxRecurse - 1))
+        return ConstantInt::getBool(ITy, Result);
+    }
+  }
+
   // (sub C, X) == X, C is odd  --> false
   // (sub C, X) != X, C is odd  --> true
   if (match(LBO, m_Sub(m_APIntAllowPoison(C), m_Specific(RHS))) &&
diff --git a/llvm/test/Transforms/InstSimplify/icmp-subtraction-bounds.ll b/llvm/test/Transforms/InstSimplify/icmp-subtraction-bounds.ll
new file mode 100644
index 0000000000000..b0fad3ef11370
--- /dev/null
+++ b/llvm/test/Transforms/InstSimplify/icmp-subtraction-bounds.ll
@@ -0,0 +1,162 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt < %s -passes=instsimplify -S | FileCheck %s
+
+declare void @llvm.assume(i1 noundef)
+
+define i1 @issue_91619(i64 %a, i64 %b) {
+; CHECK-LABEL: define i1 @issue_91619(
+; CHECK-SAME: i64 [[A:%.*]], i64 [[B:%.*]]) {
+; CHECK-NEXT:    [[A_LE_B:%.*]] = icmp ule i64 [[A]], [[B]]
+; CHECK-NEXT:    call void @llvm.assume(i1 [[A_LE_B]])
+; CHECK-NEXT:    [[B_LE_MAX:%.*]] = icmp ule i64 [[B]], 9223372036854775807
+; CHECK-NEXT:    call void @llvm.assume(i1 [[B_LE_MAX]])
+; CHECK-NEXT:    ret i1 true
+;
+  %a.le.b = icmp ule i64 %a, %b
+  call void @llvm.assume(i1 %a.le.b)
+  %b.le.max = icmp ule i64 %b, 9223372036854775807
+  call void @llvm.assume(i1 %b.le.max)
+  %sub = sub i64 %b, %a
+  %cmp = icmp ule i64 %sub, 9223372036854775807
+  ret i1 %cmp
+}
+
+define i1 @sub_nuw_ule(i8 %x, i8 %y, i8 %limit) {
+; CHECK-LABEL: define i1 @sub_nuw_ule(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]], i8 [[LIMIT:%.*]]) {
+; CHECK-NEXT:    [[X_LE_LIMIT:%.*]] = icmp ule i8 [[X]], [[LIMIT]]
+; CHECK-NEXT:    call void @llvm.assume(i1 [[X_LE_LIMIT]])
+; CHECK-NEXT:    ret i1 true
+;
+  %sub = sub nuw i8 %x, %y
+  %x.le.limit = icmp ule i8 %x, %limit
+  call void @llvm.assume(i1 %x.le.limit)
+  %cmp = icmp ule i8 %sub, %limit
+  ret i1 %cmp
+}
+
+define i1 @sub_nuw_ugt(i8 %x, i8 %y, i8 %limit) {
+; CHECK-LABEL: define i1 @sub_nuw_ugt(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]], i8 [[LIMIT:%.*]]) {
+; CHECK-NEXT:    [[X_LE_LIMIT:%.*]] = icmp ule i8 [[X]], [[LIMIT]]
+; CHECK-NEXT:    call void @llvm.assume(i1 [[X_LE_LIMIT]])
+; CHECK-NEXT:    ret i1 false
+;
+  %sub = sub nuw i8 %x, %y
+  %x.le.limit = icmp ule i8 %x, %limit
+  call void @llvm.assume(i1 %x.le.limit)
+  %cmp = icmp ugt i8 %sub, %limit
+  ret i1 %cmp
+}
+
+define i1 @sub_nuw_ult(i8 %x, i8 %y, i8 %limit) {
+; CHECK-LABEL: define i1 @sub_nuw_ult(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]], i8 [[LIMIT:%.*]]) {
+; CHECK-NEXT:    [[X_LT_LIMIT:%.*]] = icmp ult i8 [[X]], [[LIMIT]]
+; CHECK-NEXT:    call void @llvm.assume(i1 [[X_LT_LIMIT]])
+; CHECK-NEXT:    ret i1 true
+;
+  %sub = sub nuw i8 %x, %y
+  %x.lt.limit = icmp ult i8 %x, %limit
+  call void @llvm.assume(i1 %x.lt.limit)
+  %cmp = icmp ult i8 %sub, %limit
+  ret i1 %cmp
+}
+
+define i1 @sub_nuw_uge(i8 %x, i8 %y, i8 %limit) {
+; CHECK-LABEL: define i1 @sub_nuw_uge(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]], i8 [[LIMIT:%.*]]) {
+; CHECK-NEXT:    [[X_LT_LIMIT:%.*]] = icmp ult i8 [[X]], [[LIMIT]]
+; CHECK-NEXT:    call void @llvm.assume(i1 [[X_LT_LIMIT]])
+; CHECK-NEXT:    ret i1 false
+;
+  %sub = sub nuw i8 %x, %y
+  %x.lt.limit = icmp ult i8 %x, %limit
+  call void @llvm.assume(i1 %x.lt.limit)
+  %cmp = icmp uge i8 %sub, %limit
+  ret i1 %cmp
+}
+
+define <2 x i1> @sub_nuw_vector(<2 x i8> %x, <2 x i8> %y) {
+; CHECK-LABEL: define <2 x i1> @sub_nuw_vector(
+; CHECK-SAME: <2 x i8> [[X:%.*]], <2 x i8> [[Y:%.*]]) {
+; CHECK-NEXT:    ret <2 x i1> splat (i1 true)
+;
+  %sub = sub nuw <2 x i8> %x, %y
+  %cmp = icmp ule <2 x i8> %sub, %x
+  ret <2 x i1> %cmp
+}
+
+define i1 @sub_nuw_swapped(i8 %x, i8 %y, i8 %limit) {
+; CHECK-LABEL: define i1 @sub_nuw_swapped(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]], i8 [[LIMIT:%.*]]) {
+; CHECK-NEXT:    [[X_LE_LIMIT:%.*]] = icmp ule i8 [[X]], [[LIMIT]]
+; CHECK-NEXT:    call void @llvm.assume(i1 [[X_LE_LIMIT]])
+; CHECK-NEXT:    ret i1 true
+;
+  %sub = sub nuw i8 %x, %y
+  %x.le.limit = icmp ule i8 %x, %limit
+  call void @llvm.assume(i1 %x.le.limit)
+  %cmp = icmp uge i8 %limit, %sub
+  ret i1 %cmp
+}
+
+define i1 @no_nuw(i8 %x, i8 %y, i8 %limit) {
+; CHECK-LABEL: define i1 @no_nuw(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]], i8 [[LIMIT:%.*]]) {
+; CHECK-NEXT:    [[SUB:%.*]] = sub i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[X_LE_LIMIT:%.*]] = icmp ule i8 [[X]], [[LIMIT]]
+; CHECK-NEXT:    call void @llvm.assume(i1 [[X_LE_LIMIT]])
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ule i8 [[SUB]], [[LIMIT]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %sub = sub i8 %x, %y
+  %x.le.limit = icmp ule i8 %x, %limit
+  call void @llvm.assume(i1 %x.le.limit)
+  %cmp = icmp ule i8 %sub, %limit
+  ret i1 %cmp
+}
+
+define i1 @no_upper_bound(i8 %x, i8 %y, i8 %limit) {
+; CHECK-LABEL: define i1 @no_upper_bound(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]], i8 [[LIMIT:%.*]]) {
+; CHECK-NEXT:    [[SUB:%.*]] = sub nuw i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ule i8 [[SUB]], [[LIMIT]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %sub = sub nuw i8 %x, %y
+  %cmp = icmp ule i8 %sub, %limit
+  ret i1 %cmp
+}
+
+define i1 @strict_needs_strict_bound(i8 %x, i8 %y, i8 %limit) {
+; CHECK-LABEL: define i1 @strict_needs_strict_bound(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]], i8 [[LIMIT:%.*]]) {
+; CHECK-NEXT:    [[SUB:%.*]] = sub nuw i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[X_LE_LIMIT:%.*]] = icmp ule i8 [[X]], [[LIMIT]]
+; CHECK-NEXT:    call void @llvm.assume(i1 [[X_LE_LIMIT]])
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i8 [[SUB]], [[LIMIT]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %sub = sub nuw i8 %x, %y
+  %x.le.limit = icmp ule i8 %x, %limit
+  call void @llvm.assume(i1 %x.le.limit)
+  %cmp = icmp ult i8 %sub, %limit
+  ret i1 %cmp
+}
+
+define i1 @signed_predicate(i8 %x, i8 %y, i8 %limit) {
+; CHECK-LABEL: define i1 @signed_predicate(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]], i8 [[LIMIT:%.*]]) {
+; CHECK-NEXT:    [[SUB:%.*]] = sub nuw i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[X_LE_LIMIT:%.*]] = icmp sle i8 [[X]], [[LIMIT]]
+; CHECK-NEXT:    call void @llvm.assume(i1 [[X_LE_LIMIT]])
+; CHECK-NEXT:    [[CMP:%.*]] = icmp sle i8 [[SUB]], [[LIMIT]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %sub = sub nuw i8 %x, %y
+  %x.le.limit = icmp sle i8 %x, %limit
+  call void @llvm.assume(i1 %x.le.limit)
+  %cmp = icmp sle i8 %sub, %limit
+  ret i1 %cmp
+}



More information about the llvm-commits mailing list