[llvm] [InstCombine] Fold X + umax(X, 1) -> umax(X << 1, 1) (PR #222727)

Ethan Luis McDonough via llvm-commits llvm-commits at lists.llvm.org
Thu Sep 10 10:53:18 PDT 2026


https://github.com/EthanLuisMcDonough created https://github.com/llvm/llvm-project/pull/222727

This patch folds `X + umax(X, 1)` into `umax(X << 1, 1)` provided `X + X` does not overflow. The `X + umax(X, 1)` pattern can be found when using libstdc++'s `vector::push_back` with clang.

Alive2 proof for all passing testcases: https://alive2.llvm.org/ce/z/-_Gt7C

>From abd869d214b0fafc9d545c3f22ce3d114239dba3 Mon Sep 17 00:00:00 2001
From: Ethan Luis McDonough <emcdonou at qti.qualcomm.com>
Date: Thu, 10 Sep 2026 10:25:43 -0700
Subject: [PATCH 1/2] Add pre-commit tests

---
 .../Transforms/InstCombine/add-self-umax-1.ll | 138 ++++++++++++++++++
 1 file changed, 138 insertions(+)
 create mode 100644 llvm/test/Transforms/InstCombine/add-self-umax-1.ll

diff --git a/llvm/test/Transforms/InstCombine/add-self-umax-1.ll b/llvm/test/Transforms/InstCombine/add-self-umax-1.ll
new file mode 100644
index 0000000000000..b51696f82c617
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/add-self-umax-1.ll
@@ -0,0 +1,138 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+; X + umax(X, 1) --> umax(X << 1, 1) if X + X does not overflow.
+
+define i32 @src_i32_check_no_overflow(i32 noundef %a) {
+; CHECK-LABEL: define i32 @src_i32_check_no_overflow(
+; CHECK-SAME: i32 noundef [[A:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp sgt i32 [[A]], -1
+; CHECK-NEXT:    tail call void @llvm.assume(i1 [[CMP]])
+; CHECK-NEXT:    [[COND:%.*]] = tail call i32 @llvm.umax.i32(i32 [[A]], i32 1)
+; CHECK-NEXT:    [[ADD:%.*]] = add nuw i32 [[COND]], [[A]]
+; CHECK-NEXT:    ret i32 [[ADD]]
+;
+entry:
+  %cmp = icmp sgt i32 %a, -1
+  tail call void @llvm.assume(i1 %cmp)
+  %cond = tail call i32 @llvm.umax.i32(i32 %a, i32 1)
+  %add = add i32 %cond, %a
+  ret i32 %add
+}
+
+define i32 @src_i32_nuw(i32 noundef %a) {
+; CHECK-LABEL: define i32 @src_i32_nuw(
+; CHECK-SAME: i32 noundef [[A:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[COND:%.*]] = tail call i32 @llvm.umax.i32(i32 [[A]], i32 1)
+; CHECK-NEXT:    [[ADD:%.*]] = add nuw i32 [[COND]], [[A]]
+; CHECK-NEXT:    ret i32 [[ADD]]
+;
+entry:
+  %cond = tail call i32 @llvm.umax.i32(i32 %a, i32 1)
+  %add = add nuw i32 %cond, %a
+  ret i32 %add
+}
+
+define i32 @src_i32_nsw(i32 noundef %a) {
+; CHECK-LABEL: define i32 @src_i32_nsw(
+; CHECK-SAME: i32 noundef [[A:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[COND:%.*]] = tail call i32 @llvm.umax.i32(i32 [[A]], i32 1)
+; CHECK-NEXT:    [[ADD:%.*]] = add nsw i32 [[A]], [[COND]]
+; CHECK-NEXT:    ret i32 [[ADD]]
+;
+entry:
+  %cond = tail call i32 @llvm.umax.i32(i32 1, i32 %a)
+  %add = add nsw i32 %a, %cond
+  ret i32 %add
+}
+
+define <4 x i32> @src_v4i32_nsw(<4 x i32> noundef %a) {
+; CHECK-LABEL: define <4 x i32> @src_v4i32_nsw(
+; CHECK-SAME: <4 x i32> noundef [[A:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[COND:%.*]] = tail call <4 x i32> @llvm.umax.v4i32(<4 x i32> [[A]], <4 x i32> splat (i32 1))
+; CHECK-NEXT:    [[ADD:%.*]] = add nuw <4 x i32> [[COND]], [[A]]
+; CHECK-NEXT:    ret <4 x i32> [[ADD]]
+;
+entry:
+  %cond = tail call <4 x i32> @llvm.umax.v4i32(<4 x i32> %a, <4 x i32> splat (i32 1))
+  %add = add nuw <4 x i32> %cond, %a
+  ret <4 x i32> %add
+}
+
+define i64 @src_i64_nuw(i64 noundef %a) {
+; CHECK-LABEL: define i64 @src_i64_nuw(
+; CHECK-SAME: i64 noundef [[A:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[COND:%.*]] = tail call i64 @llvm.umax.i64(i64 [[A]], i64 1)
+; CHECK-NEXT:    [[ADD:%.*]] = add nuw i64 [[COND]], [[A]]
+; CHECK-NEXT:    ret i64 [[ADD]]
+;
+entry:
+  %cond = tail call i64 @llvm.umax.i32(i64 %a, i64 1)
+  %add = add nuw i64 %cond, %a
+  ret i64 %add
+}
+
+define i128 @src_i128_nuw(i128 noundef %a) {
+; CHECK-LABEL: define i128 @src_i128_nuw(
+; CHECK-SAME: i128 noundef [[A:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[COND:%.*]] = tail call i128 @llvm.umax.i128(i128 [[A]], i128 1)
+; CHECK-NEXT:    [[ADD:%.*]] = add nuw i128 [[COND]], [[A]]
+; CHECK-NEXT:    ret i128 [[ADD]]
+;
+entry:
+  %cond = tail call i128 @llvm.umax.i128(i128 %a, i128 1)
+  %add = add nuw i128 %cond, %a
+  ret i128 %add
+}
+
+define i32 @src_i32(i32 noundef %a) {
+; CHECK-LABEL: define i32 @src_i32(
+; CHECK-SAME: i32 noundef [[A:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[COND:%.*]] = tail call i32 @llvm.umax.i32(i32 [[A]], i32 1)
+; CHECK-NEXT:    [[ADD:%.*]] = add i32 [[COND]], [[A]]
+; CHECK-NEXT:    ret i32 [[ADD]]
+;
+entry:
+  %cond = tail call i32 @llvm.umax.i32(i32 %a, i32 1)
+  %add = add i32 %cond, %a
+  ret i32 %add
+}
+
+define i32 @src_i32_check_overflow(i32 noundef %a) {
+; CHECK-LABEL: define i32 @src_i32_check_overflow(
+; CHECK-SAME: i32 noundef [[A:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i32 [[A]], -2147483647
+; CHECK-NEXT:    tail call void @llvm.assume(i1 [[CMP]])
+; CHECK-NEXT:    [[COND:%.*]] = tail call i32 @llvm.umax.i32(i32 [[A]], i32 1)
+; CHECK-NEXT:    [[ADD:%.*]] = add i32 [[COND]], [[A]]
+; CHECK-NEXT:    ret i32 [[ADD]]
+;
+entry:
+  %cmp = icmp ult i32 %a, -2147483647
+  tail call void @llvm.assume(i1 %cmp)
+  %cond = tail call i32 @llvm.umax.i32(i32 %a, i32 1)
+  %add = add i32 %cond, %a
+  ret i32 %add
+}
+
+define i32 @src_i32_max_2(i32 noundef %a) {
+; CHECK-LABEL: define i32 @src_i32_max_2(
+; CHECK-SAME: i32 noundef [[A:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[COND:%.*]] = tail call i32 @llvm.umax.i32(i32 [[A]], i32 2)
+; CHECK-NEXT:    [[ADD:%.*]] = add nuw i32 [[COND]], [[A]]
+; CHECK-NEXT:    ret i32 [[ADD]]
+;
+entry:
+  %cond = tail call i32 @llvm.umax.i32(i32 %a, i32 2)
+  %add = add nuw i32 %cond, %a
+  ret i32 %add
+}

>From 57e4e00f809fc985726311dd00d4971df9666d40 Mon Sep 17 00:00:00 2001
From: Ethan Luis McDonough <emcdonou at qti.qualcomm.com>
Date: Thu, 10 Sep 2026 10:30:07 -0700
Subject: [PATCH 2/2] Fold X + umax(X, 1) -> umax(X << 1, 1)

---
 .../InstCombine/InstCombineAddSub.cpp         | 16 +++++++++++++
 .../Transforms/InstCombine/add-self-umax-1.ll | 24 +++++++++----------
 2 files changed, 28 insertions(+), 12 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
index 317d587ddedd6..6cdc1f0260608 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
@@ -1984,6 +1984,22 @@ Instruction *InstCombinerImpl::visitAdd(BinaryOperator &I) {
     I.setHasNoUnsignedWrap(true);
   }
 
+  // X + umax(X, 1) --> umax(X << 1, 1) if X + X does not overflow.
+  if (match(&I, m_c_Add(m_Value(A), m_OneUse(m_c_UMax(m_Value(B), m_One())))) &&
+      A == B) {
+    auto Cache = A == LHSCache ? LHSCache : RHSCache;
+    auto One = ConstantInt::get(Ty, 1);
+    bool NoUW =
+        I.hasNoUnsignedWrap() || willNotOverflowUnsignedAdd(Cache, Cache, I);
+    // Do not apply this transformation if the original add might overflow.
+    if (NoUW || I.hasNoSignedWrap())
+      return replaceInstUsesWith(
+          I, Builder.CreateIntrinsic(
+                 Intrinsic::umax, {Ty},
+                 {One, Builder.CreateShl(A, One, "mul2", NoUW,
+                                         I.hasNoSignedWrap())}));
+  }
+
   if (Instruction *V = canonicalizeLowbitMask(I, Builder))
     return V;
 
diff --git a/llvm/test/Transforms/InstCombine/add-self-umax-1.ll b/llvm/test/Transforms/InstCombine/add-self-umax-1.ll
index b51696f82c617..529f06464d56f 100644
--- a/llvm/test/Transforms/InstCombine/add-self-umax-1.ll
+++ b/llvm/test/Transforms/InstCombine/add-self-umax-1.ll
@@ -9,8 +9,8 @@ define i32 @src_i32_check_no_overflow(i32 noundef %a) {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
 ; CHECK-NEXT:    [[CMP:%.*]] = icmp sgt i32 [[A]], -1
 ; CHECK-NEXT:    tail call void @llvm.assume(i1 [[CMP]])
-; CHECK-NEXT:    [[COND:%.*]] = tail call i32 @llvm.umax.i32(i32 [[A]], i32 1)
-; CHECK-NEXT:    [[ADD:%.*]] = add nuw i32 [[COND]], [[A]]
+; CHECK-NEXT:    [[MUL2:%.*]] = shl nuw i32 [[A]], 1
+; CHECK-NEXT:    [[ADD:%.*]] = call i32 @llvm.umax.i32(i32 [[MUL2]], i32 1)
 ; CHECK-NEXT:    ret i32 [[ADD]]
 ;
 entry:
@@ -25,8 +25,8 @@ define i32 @src_i32_nuw(i32 noundef %a) {
 ; CHECK-LABEL: define i32 @src_i32_nuw(
 ; CHECK-SAME: i32 noundef [[A:%.*]]) {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    [[COND:%.*]] = tail call i32 @llvm.umax.i32(i32 [[A]], i32 1)
-; CHECK-NEXT:    [[ADD:%.*]] = add nuw i32 [[COND]], [[A]]
+; CHECK-NEXT:    [[MUL2:%.*]] = shl nuw i32 [[A]], 1
+; CHECK-NEXT:    [[ADD:%.*]] = call i32 @llvm.umax.i32(i32 [[MUL2]], i32 1)
 ; CHECK-NEXT:    ret i32 [[ADD]]
 ;
 entry:
@@ -39,8 +39,8 @@ define i32 @src_i32_nsw(i32 noundef %a) {
 ; CHECK-LABEL: define i32 @src_i32_nsw(
 ; CHECK-SAME: i32 noundef [[A:%.*]]) {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    [[COND:%.*]] = tail call i32 @llvm.umax.i32(i32 [[A]], i32 1)
-; CHECK-NEXT:    [[ADD:%.*]] = add nsw i32 [[A]], [[COND]]
+; CHECK-NEXT:    [[MUL2:%.*]] = shl nsw i32 [[A]], 1
+; CHECK-NEXT:    [[ADD:%.*]] = call i32 @llvm.umax.i32(i32 [[MUL2]], i32 1)
 ; CHECK-NEXT:    ret i32 [[ADD]]
 ;
 entry:
@@ -53,8 +53,8 @@ define <4 x i32> @src_v4i32_nsw(<4 x i32> noundef %a) {
 ; CHECK-LABEL: define <4 x i32> @src_v4i32_nsw(
 ; CHECK-SAME: <4 x i32> noundef [[A:%.*]]) {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    [[COND:%.*]] = tail call <4 x i32> @llvm.umax.v4i32(<4 x i32> [[A]], <4 x i32> splat (i32 1))
-; CHECK-NEXT:    [[ADD:%.*]] = add nuw <4 x i32> [[COND]], [[A]]
+; CHECK-NEXT:    [[MUL2:%.*]] = shl nuw <4 x i32> [[A]], splat (i32 1)
+; CHECK-NEXT:    [[ADD:%.*]] = call <4 x i32> @llvm.umax.v4i32(<4 x i32> [[MUL2]], <4 x i32> splat (i32 1))
 ; CHECK-NEXT:    ret <4 x i32> [[ADD]]
 ;
 entry:
@@ -67,8 +67,8 @@ define i64 @src_i64_nuw(i64 noundef %a) {
 ; CHECK-LABEL: define i64 @src_i64_nuw(
 ; CHECK-SAME: i64 noundef [[A:%.*]]) {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    [[COND:%.*]] = tail call i64 @llvm.umax.i64(i64 [[A]], i64 1)
-; CHECK-NEXT:    [[ADD:%.*]] = add nuw i64 [[COND]], [[A]]
+; CHECK-NEXT:    [[MUL2:%.*]] = shl nuw i64 [[A]], 1
+; CHECK-NEXT:    [[ADD:%.*]] = call i64 @llvm.umax.i64(i64 [[MUL2]], i64 1)
 ; CHECK-NEXT:    ret i64 [[ADD]]
 ;
 entry:
@@ -81,8 +81,8 @@ define i128 @src_i128_nuw(i128 noundef %a) {
 ; CHECK-LABEL: define i128 @src_i128_nuw(
 ; CHECK-SAME: i128 noundef [[A:%.*]]) {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    [[COND:%.*]] = tail call i128 @llvm.umax.i128(i128 [[A]], i128 1)
-; CHECK-NEXT:    [[ADD:%.*]] = add nuw i128 [[COND]], [[A]]
+; CHECK-NEXT:    [[MUL2:%.*]] = shl nuw i128 [[A]], 1
+; CHECK-NEXT:    [[ADD:%.*]] = call i128 @llvm.umax.i128(i128 [[MUL2]], i128 1)
 ; CHECK-NEXT:    ret i128 [[ADD]]
 ;
 entry:



More information about the llvm-commits mailing list