[llvm] InstCombine: fold select(X+umax(X,A)<X, C, umin(X+umax(X,A),C)) -> umin(X+umax(X,A),C) (PR #210609)

via llvm-commits llvm-commits at lists.llvm.org
Sun Jul 19 09:26:42 PDT 2026


https://github.com/AZero13 created https://github.com/llvm/llvm-project/pull/210609

When X + umax(X, A) has nsw and A and C are non-negative, the unsigned wrap implies X + umax(X, A) >= 2^(bw-1). Since C < 2^(bw-1), umin will return C.

Alive2: https://alive2.llvm.org/ce/z/ajmtVW
Fixes #143030

>From 830f7b661a7559b87f9958a1f4b1fac6a2723595 Mon Sep 17 00:00:00 2001
From: AZero13 <gfunni234 at gmail.com>
Date: Sat, 18 Jul 2026 14:49:14 -0400
Subject: [PATCH] InstCombine: fold select(X+umax(X,A)<X, C,
 umin(X+umax(X,A),C)) -> umin(X+umax(X,A),C)

When X + umax(X, A) has nsw and A and C are non-negative, the unsigned wrap implies X + umax(X, A) >= 2^(bw-1). Since C < 2^(bw-1), umin will return C.

Alive2: https://alive2.llvm.org/ce/z/ajmtVW
Fixes #143030
---
 .../InstCombine/InstCombineSelect.cpp         |  26 +++++
 .../InstCombine/select-umin-umax.ll           | 107 ++++++++++++++++++
 2 files changed, 133 insertions(+)
 create mode 100644 llvm/test/Transforms/InstCombine/select-umin-umax.ll

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
index f107c15304d9f..328c0113f9a69 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
@@ -4850,6 +4850,32 @@ Instruction *InstCombinerImpl::visitSelectInst(SelectInst &SI) {
   if (Instruction *Mul = foldSelectZeroOrFixedOp(SI, *this))
     return Mul;
 
+  // fold select (X + umax(X, A) < X), C, umin(X + umax(X, A), C) -> umin(X +
+  // umax(X, A), C) If X + umax(X, A) has nsw and A and C are non-negative, the
+  // unsigned wrap implies X + umax(X, A) >= 2^(bw-1). Since C < 2^(bw-1), umin
+  // will return C.
+  {
+    auto *ICI = dyn_cast<ICmpInst>(CondVal);
+    Value *X, *A, *Sum, *CVal;
+    if (ICI && ICI->hasOneUse() && match(TrueVal, m_Value(CVal)) &&
+        match(FalseVal,
+              m_c_Intrinsic<Intrinsic::umin>(m_Value(Sum), m_Specific(CVal))) &&
+        match(Sum, m_c_Add(m_Value(X), m_c_UMax(m_Deferred(X), m_Value(A))))) {
+      ICmpInst::Predicate Pred = ICI->getPredicate();
+
+      if ((Pred == ICmpInst::ICMP_ULT && ICI->getOperand(0) == Sum &&
+           ICI->getOperand(1) == X) ||
+          (Pred == ICmpInst::ICMP_UGT && ICI->getOperand(0) == X &&
+           ICI->getOperand(1) == Sum)) {
+        auto *Add = cast<OverflowingBinaryOperator>(Sum);
+        if (Add->hasNoSignedWrap() &&
+            isKnownNonNegative(CVal, SQ.getWithInstruction(&SI)) &&
+            isKnownNonNegative(A, SQ.getWithInstruction(&SI)))
+          return replaceInstUsesWith(SI, FalseVal);
+      }
+    }
+  }
+
   // Turn (select C, (op X, Y), (op X, Z)) -> (op X, (select C, Y, Z))
   auto *TI = dyn_cast<Instruction>(TrueVal);
   auto *FI = dyn_cast<Instruction>(FalseVal);
diff --git a/llvm/test/Transforms/InstCombine/select-umin-umax.ll b/llvm/test/Transforms/InstCombine/select-umin-umax.ll
new file mode 100644
index 0000000000000..d95825d571342
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/select-umin-umax.ll
@@ -0,0 +1,107 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+define i64 @test_pos_constant(i64 %0) {
+; CHECK-LABEL: define i64 @test_pos_constant(
+; CHECK-SAME: i64 [[TMP0:%.*]]) {
+; CHECK-NEXT:    [[TMP2:%.*]] = call i64 @llvm.umax.i64(i64 [[TMP0]], i64 1)
+; CHECK-NEXT:    [[TMP3:%.*]] = add nsw i64 [[TMP2]], [[TMP0]]
+; CHECK-NEXT:    [[TMP4:%.*]] = call i64 @llvm.umin.i64(i64 [[TMP3]], i64 329406144173384850)
+; CHECK-NEXT:    ret i64 [[TMP4]]
+;
+  %2 = call i64 @llvm.umax.i64(i64 %0, i64 1)
+  %3 = add nsw i64 %2, %0
+  %4 = icmp ult i64 %3, %0
+  %5 = call i64 @llvm.umin.i64(i64 %3, i64 329406144173384850)
+  %6 = select i1 %4, i64 329406144173384850, i64 %5
+  ret i64 %6
+}
+
+define i64 @test_pos_variable_A(i64 %0, i64 %A) {
+; CHECK-LABEL: define i64 @test_pos_variable_A(
+; CHECK-SAME: i64 [[TMP0:%.*]], i64 [[A:%.*]]) {
+; CHECK-NEXT:    [[TMP2:%.*]] = call i64 @llvm.umax.i64(i64 [[TMP0]], i64 [[A]])
+; CHECK-NEXT:    [[TMP3:%.*]] = add nsw i64 [[TMP2]], [[TMP0]]
+; CHECK-NEXT:    [[TMP4:%.*]] = icmp ult i64 [[TMP3]], [[TMP0]]
+; CHECK-NEXT:    [[TMP5:%.*]] = call i64 @llvm.umin.i64(i64 [[TMP3]], i64 329406144173384850)
+; CHECK-NEXT:    [[TMP6:%.*]] = select i1 [[TMP4]], i64 329406144173384850, i64 [[TMP5]]
+; CHECK-NEXT:    ret i64 [[TMP6]]
+;
+  %2 = call i64 @llvm.umax.i64(i64 %0, i64 %A)
+  %3 = add nsw i64 %2, %0
+  %4 = icmp ult i64 %3, %0
+  %5 = call i64 @llvm.umin.i64(i64 %3, i64 329406144173384850)
+  %6 = select i1 %4, i64 329406144173384850, i64 %5
+  ret i64 %6
+}
+
+define i64 @test_neg_no_nsw(i64 %0) {
+; CHECK-LABEL: define i64 @test_neg_no_nsw(
+; CHECK-SAME: i64 [[TMP0:%.*]]) {
+; CHECK-NEXT:    [[TMP2:%.*]] = call i64 @llvm.umax.i64(i64 [[TMP0]], i64 1)
+; CHECK-NEXT:    [[TMP3:%.*]] = add i64 [[TMP2]], [[TMP0]]
+; CHECK-NEXT:    [[TMP4:%.*]] = icmp ult i64 [[TMP3]], [[TMP0]]
+; CHECK-NEXT:    [[TMP5:%.*]] = call i64 @llvm.umin.i64(i64 [[TMP3]], i64 329406144173384850)
+; CHECK-NEXT:    [[TMP6:%.*]] = select i1 [[TMP4]], i64 329406144173384850, i64 [[TMP5]]
+; CHECK-NEXT:    ret i64 [[TMP6]]
+;
+  %2 = call i64 @llvm.umax.i64(i64 %0, i64 1)
+  %3 = add i64 %2, %0
+  %4 = icmp ult i64 %3, %0
+  %5 = call i64 @llvm.umin.i64(i64 %3, i64 329406144173384850)
+  %6 = select i1 %4, i64 329406144173384850, i64 %5
+  ret i64 %6
+}
+
+define i64 @test_neg_C_too_large(i64 %0) {
+; CHECK-LABEL: define i64 @test_neg_C_too_large(
+; CHECK-SAME: i64 [[TMP0:%.*]]) {
+; CHECK-NEXT:    [[TMP2:%.*]] = call i64 @llvm.umax.i64(i64 [[TMP0]], i64 1)
+; CHECK-NEXT:    [[TMP3:%.*]] = call i64 @llvm.uadd.sat.i64(i64 [[TMP0]], i64 [[TMP2]])
+; CHECK-NEXT:    ret i64 [[TMP3]]
+;
+  %2 = call i64 @llvm.umax.i64(i64 %0, i64 1)
+  %3 = add nsw i64 %2, %0
+  %4 = icmp ult i64 %3, %0
+  %5 = call i64 @llvm.umin.i64(i64 %3, i64 -1)
+  %6 = select i1 %4, i64 -1, i64 %5
+  ret i64 %6
+}
+
+define i64 @test_pos_C_max_positive(i64 %0) {
+; CHECK-LABEL: define i64 @test_pos_C_max_positive(
+; CHECK-SAME: i64 [[TMP0:%.*]]) {
+; CHECK-NEXT:    [[TMP2:%.*]] = call i64 @llvm.umax.i64(i64 [[TMP0]], i64 1)
+; CHECK-NEXT:    [[TMP3:%.*]] = add nsw i64 [[TMP2]], [[TMP0]]
+; CHECK-NEXT:    [[TMP4:%.*]] = call i64 @llvm.umin.i64(i64 [[TMP3]], i64 9223372036854775807)
+; CHECK-NEXT:    ret i64 [[TMP4]]
+;
+  %2 = call i64 @llvm.umax.i64(i64 %0, i64 1)
+  %3 = add nsw i64 %2, %0
+  %4 = icmp ult i64 %3, %0
+  ; 9223372036854775807 is i64_max (0x7FFFFFFFFFFFFFFF)
+  %5 = call i64 @llvm.umin.i64(i64 %3, i64 9223372036854775807)
+  %6 = select i1 %4, i64 9223372036854775807, i64 %5
+  ret i64 %6
+}
+
+define i64 @test_neg_C_min_negative(i64 %0) {
+; CHECK-LABEL: define i64 @test_neg_C_min_negative(
+; CHECK-SAME: i64 [[TMP0:%.*]]) {
+; CHECK-NEXT:    [[TMP2:%.*]] = call i64 @llvm.umax.i64(i64 [[TMP0]], i64 1)
+; CHECK-NEXT:    [[TMP3:%.*]] = add nsw i64 [[TMP2]], [[TMP0]]
+; CHECK-NEXT:    [[TMP4:%.*]] = icmp ult i64 [[TMP3]], [[TMP0]]
+; CHECK-NEXT:    [[TMP5:%.*]] = call i64 @llvm.umin.i64(i64 [[TMP3]], i64 -9223372036854775808)
+; CHECK-NEXT:    [[TMP6:%.*]] = select i1 [[TMP4]], i64 -9223372036854775808, i64 [[TMP5]]
+; CHECK-NEXT:    ret i64 [[TMP6]]
+;
+  %2 = call i64 @llvm.umax.i64(i64 %0, i64 1)
+  %3 = add nsw i64 %2, %0
+  %4 = icmp ult i64 %3, %0
+  ; -9223372036854775808 is i64_min (0x8000000000000000)
+  %5 = call i64 @llvm.umin.i64(i64 %3, i64 -9223372036854775808)
+  %6 = select i1 %4, i64 -9223372036854775808, i64 %5
+  ret i64 %6
+}
+
+declare i64 @llvm.umax.i64(i64, i64)
+declare i64 @llvm.umin.i64(i64, i64)



More information about the llvm-commits mailing list