[llvm] [InstCombine] Fold X + umax(X, 1) -> umax(X << 1, 1) (PR #222727)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Sep 10 10:54:02 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-transforms
Author: Ethan Luis McDonough (EthanLuisMcDonough)
<details>
<summary>Changes</summary>
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
---
Full diff: https://github.com/llvm/llvm-project/pull/222727.diff
2 Files Affected:
- (modified) llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp (+16)
- (added) llvm/test/Transforms/InstCombine/add-self-umax-1.ll (+138)
``````````diff
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
new file mode 100644
index 0000000000000..529f06464d56f
--- /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: [[MUL2:%.*]] = shl nuw i32 [[A]], 1
+; CHECK-NEXT: [[ADD:%.*]] = call i32 @llvm.umax.i32(i32 [[MUL2]], i32 1)
+; 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: [[MUL2:%.*]] = shl nuw i32 [[A]], 1
+; CHECK-NEXT: [[ADD:%.*]] = call i32 @llvm.umax.i32(i32 [[MUL2]], i32 1)
+; 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: [[MUL2:%.*]] = shl nsw i32 [[A]], 1
+; CHECK-NEXT: [[ADD:%.*]] = call i32 @llvm.umax.i32(i32 [[MUL2]], i32 1)
+; 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: [[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:
+ %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: [[MUL2:%.*]] = shl nuw i64 [[A]], 1
+; CHECK-NEXT: [[ADD:%.*]] = call i64 @llvm.umax.i64(i64 [[MUL2]], i64 1)
+; 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: [[MUL2:%.*]] = shl nuw i128 [[A]], 1
+; CHECK-NEXT: [[ADD:%.*]] = call i128 @llvm.umax.i128(i128 [[MUL2]], i128 1)
+; 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
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/222727
More information about the llvm-commits
mailing list