[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