[llvm] [InstCombine] Fold lshr/ashr (mul X, 2^N + 2^M) -> add (X, lshr/ashr X, N - M) (PR #210140)
via llvm-commits
llvm-commits at lists.llvm.org
Sat Jul 18 08:47:09 PDT 2026
https://github.com/AZero13 updated https://github.com/llvm/llvm-project/pull/210140
>From f7cfd83c4b16bb9d6a9bdd60a7db52dac00c4c47 Mon Sep 17 00:00:00 2001
From: AZero13 <gfunni234 at gmail.com>
Date: Thu, 16 Jul 2026 14:48:15 -0400
Subject: [PATCH 1/2] [InstCombine] Precommit tests for lshr/ashr (mul X, 2^N +
2^M) -> add (X, lshr/ashr X, N - M)
---
llvm/test/Transforms/InstCombine/ashr.ll | 8 ++++++++
llvm/test/Transforms/InstCombine/lshr.ll | 24 ++++++++++++++++++++++++
2 files changed, 32 insertions(+)
create mode 100644 llvm/test/Transforms/InstCombine/ashr.ll
diff --git a/llvm/test/Transforms/InstCombine/ashr.ll b/llvm/test/Transforms/InstCombine/ashr.ll
new file mode 100644
index 0000000000000..ae7b8b8edc6da
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/ashr.ll
@@ -0,0 +1,8 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+
+; ashr (mul nsw X, 20), 4 -> add nsw X, ashr X, 2
+define i32 @ashr_mul_nsw_2bits(i32 %x) {
+ %mul = mul nsw i32 %x, 20
+ %shr = ashr i32 %mul, 4
+ ret i32 %shr
+}
diff --git a/llvm/test/Transforms/InstCombine/lshr.ll b/llvm/test/Transforms/InstCombine/lshr.ll
index be78220ee47ea..a24ce93d95a86 100644
--- a/llvm/test/Transforms/InstCombine/lshr.ll
+++ b/llvm/test/Transforms/InstCombine/lshr.ll
@@ -1627,3 +1627,27 @@ define i32 @lshr_two_i32(i32 %x) {
%shr = lshr i32 2, %x
ret i32 %shr
}
+
+; lshr (mul nuw X, 20), 4 -> add nuw X, lshr X, 2
+define i32 @lshr_mul_nuw_2bits(i32 %x) {
+; CHECK-LABEL: @lshr_mul_nuw_2bits(
+; CHECK-NEXT: [[MUL:%.*]] = mul nuw i32 [[X:%.*]], 20
+; CHECK-NEXT: [[SHR:%.*]] = lshr i32 [[MUL]], 4
+; CHECK-NEXT: ret i32 [[SHR]]
+;
+ %mul = mul nuw i32 %x, 20
+ %shr = lshr i32 %mul, 4
+ ret i32 %shr
+}
+
+; lshr (mul nsw X, 20), 4 -> add nsw X, lshr X, 2
+define i32 @lshr_mul_nsw_2bits(i32 %x) {
+; CHECK-LABEL: @lshr_mul_nsw_2bits(
+; CHECK-NEXT: [[MUL:%.*]] = mul nsw i32 [[X:%.*]], 20
+; CHECK-NEXT: [[SHR:%.*]] = lshr i32 [[MUL]], 4
+; CHECK-NEXT: ret i32 [[SHR]]
+;
+ %mul = mul nsw i32 %x, 20
+ %shr = lshr i32 %mul, 4
+ ret i32 %shr
+}
>From fe3d036149690c10ecb345248b847e7ce87bfd04 Mon Sep 17 00:00:00 2001
From: AZero13 <gfunni234 at gmail.com>
Date: Thu, 16 Jul 2026 14:50:00 -0400
Subject: [PATCH 2/2] [InstCombine] Fold lshr/ashr (mul X, 2^N + 2^M) -> add
(X, lshr/ashr X, N - M)
Alive 2: https://alive2.llvm.org/ce/z/cFMK7p
---
.../InstCombine/InstCombineShifts.cpp | 55 +++++++++++--------
llvm/test/Transforms/InstCombine/ashr-lshr.ll | 36 +++++++++---
llvm/test/Transforms/InstCombine/ashr.ll | 8 ---
llvm/test/Transforms/InstCombine/lshr.ll | 8 +--
4 files changed, 63 insertions(+), 44 deletions(-)
delete mode 100644 llvm/test/Transforms/InstCombine/ashr.ll
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp b/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp
index 241bc3bba51c2..4488a482fa9c4 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp
@@ -1630,20 +1630,24 @@ Instruction *InstCombinerImpl::visitLShr(BinaryOperator &I) {
}
const APInt *MulC;
- if (match(Op0, m_NUWMul(m_Value(X), m_APInt(MulC)))) {
- if (BitWidth > 2 && (*MulC - 1).isPowerOf2() &&
- MulC->logBase2() == ShAmtC) {
- // Look for a "splat" mul pattern - it replicates bits across each half
- // of a value, so a right shift simplifies back to just X:
- // lshr i[2N] (mul nuw X, (2^N)+1), N --> X
- if (ShAmtC * 2 == BitWidth)
- return replaceInstUsesWith(I, X);
+ if (match(Op0, m_NUWMul(m_Value(X), m_APInt(MulC))) && BitWidth > 2 &&
+ (*MulC - 1).isPowerOf2() && MulC->logBase2() == ShAmtC &&
+ ShAmtC * 2 == BitWidth)
+ // lshr i[2N] (mul nuw X, (2^N)+1), N --> X
+ return replaceInstUsesWith(I, X);
- // lshr (mul nuw (X, 2^N + 1)), N -> add nuw (X, lshr(X, N))
- if (Op0->hasOneUse()) {
- auto *NewAdd = BinaryOperator::CreateNUWAdd(
- X, Builder.CreateLShr(X, ConstantInt::get(Ty, ShAmtC), "",
- I.isExact()));
+ if (match(Op0, m_OneUse(m_NUWMul(m_Value(X), m_APInt(MulC)))) &&
+ BitWidth > 2 && MulC->logBase2() == ShAmtC && ShAmtC < BitWidth - 1) {
+ APInt M_API = *MulC - APInt::getOneBitSet(BitWidth, ShAmtC);
+
+ if (M_API.isPowerOf2()) {
+ unsigned M = M_API.logBase2();
+
+ if (M <= ShAmtC) {
+ Value *ShiftedX = Builder.CreateLShr(
+ X, ConstantInt::get(Ty, ShAmtC - M), "", I.isExact());
+
+ auto *NewAdd = BinaryOperator::CreateNUWAdd(X, ShiftedX);
NewAdd->setHasNoSignedWrap(
cast<OverflowingBinaryOperator>(Op0)->hasNoSignedWrap());
return NewAdd;
@@ -1903,17 +1907,20 @@ Instruction *InstCombinerImpl::visitAShr(BinaryOperator &I) {
const APInt *MulC;
if (match(Op0, m_OneUse(m_NSWMul(m_Value(X), m_APInt(MulC)))) &&
- (BitWidth > 2 && (*MulC - 1).isPowerOf2() &&
- MulC->logBase2() == ShAmt &&
- (ShAmt < BitWidth - 1))) /* Minus 1 for the sign bit */ {
-
- // ashr (mul nsw (X, 2^N + 1)), N -> add nsw (X, ashr(X, N))
- auto *NewAdd = BinaryOperator::CreateNSWAdd(
- X,
- Builder.CreateAShr(X, ConstantInt::get(Ty, ShAmt), "", I.isExact()));
- NewAdd->setHasNoUnsignedWrap(
- cast<OverflowingBinaryOperator>(Op0)->hasNoUnsignedWrap());
- return NewAdd;
+ BitWidth > 2 && MulC->logBase2() == ShAmt && (ShAmt < BitWidth - 1)) {
+ APInt M_API = *MulC - APInt::getOneBitSet(BitWidth, ShAmt);
+ if (M_API.isPowerOf2()) {
+ unsigned M = M_API.logBase2();
+ // ashr (mul nsw (X, 2^N + 2^M)), N -> add nsw (X, ashr(X, N - M))
+ Value *ShiftedX =
+ M == 0 ? X
+ : Builder.CreateAShr(X, ConstantInt::get(Ty, ShAmt - M), "",
+ I.isExact());
+ auto *NewAdd = BinaryOperator::CreateNSWAdd(X, ShiftedX);
+ NewAdd->setHasNoUnsignedWrap(
+ cast<OverflowingBinaryOperator>(Op0)->hasNoUnsignedWrap());
+ return NewAdd;
+ }
}
}
diff --git a/llvm/test/Transforms/InstCombine/ashr-lshr.ll b/llvm/test/Transforms/InstCombine/ashr-lshr.ll
index db0ecbfed1e8e..62bc1c750ab09 100644
--- a/llvm/test/Transforms/InstCombine/ashr-lshr.ll
+++ b/llvm/test/Transforms/InstCombine/ashr-lshr.ll
@@ -652,6 +652,18 @@ define i32 @lshr_mul_times_3_div_2_no_flags(i32 %0) {
ret i32 %lshr
}
+; Signed-only multiplication must use the logical-shift fold.
+define i32 @lshr_mul_times_3_div_2_nsw(i32 %x) {
+; CHECK-LABEL: @lshr_mul_times_3_div_2_nsw(
+; CHECK-NEXT: [[TMP1:%.*]] = lshr i32 [[X:%.*]], 1
+; CHECK-NEXT: [[LSHR:%.*]] = add nsw i32 [[X]], [[TMP1]]
+; CHECK-NEXT: ret i32 [[LSHR]]
+;
+ %mul = mul nsw i32 %x, 3
+ %lshr = lshr i32 %mul, 1
+ ret i32 %lshr
+}
+
; Negative test
define i32 @mul_times_3_div_2_multiuse_lshr(i32 %x) {
@@ -752,8 +764,7 @@ define i32 @ashr_mul_times_3_div_2(i32 %0) {
define i32 @ashr_mul_times_3_div_2_exact(i32 %x) {
; CHECK-LABEL: @ashr_mul_times_3_div_2_exact(
-; CHECK-NEXT: [[TMP1:%.*]] = ashr exact i32 [[X:%.*]], 1
-; CHECK-NEXT: [[ASHR:%.*]] = add nsw i32 [[X]], [[TMP1]]
+; CHECK-NEXT: [[ASHR:%.*]] = shl nsw i32 [[X:%.*]], 1
; CHECK-NEXT: ret i32 [[ASHR]]
;
%mul = mul nsw i32 %x, 3
@@ -804,8 +815,7 @@ define i32 @mul_times_3_div_2_multiuse_ashr(i32 %x) {
define i32 @ashr_mul_times_3_div_2_exact_2(i32 %x) {
; CHECK-LABEL: @ashr_mul_times_3_div_2_exact_2(
-; CHECK-NEXT: [[TMP1:%.*]] = ashr exact i32 [[X:%.*]], 1
-; CHECK-NEXT: [[ASHR:%.*]] = add nsw i32 [[X]], [[TMP1]]
+; CHECK-NEXT: [[ASHR:%.*]] = shl nsw i32 [[X:%.*]], 1
; CHECK-NEXT: ret i32 [[ASHR]]
;
%mul = mul nsw i32 %x, 3
@@ -826,8 +836,7 @@ define i32 @ashr_mul_times_5_div_4(i32 %0) {
define i32 @ashr_mul_times_5_div_4_exact(i32 %x) {
; CHECK-LABEL: @ashr_mul_times_5_div_4_exact(
-; CHECK-NEXT: [[TMP1:%.*]] = ashr exact i32 [[X:%.*]], 2
-; CHECK-NEXT: [[ASHR:%.*]] = add nsw i32 [[X]], [[TMP1]]
+; CHECK-NEXT: [[ASHR:%.*]] = shl nsw i32 [[X:%.*]], 1
; CHECK-NEXT: ret i32 [[ASHR]]
;
%mul = mul nsw i32 %x, 5
@@ -865,8 +874,7 @@ define i32 @mul_times_5_div_4_multiuse_ashr(i32 %x) {
define i32 @ashr_mul_times_5_div_4_exact_2(i32 %x) {
; CHECK-LABEL: @ashr_mul_times_5_div_4_exact_2(
-; CHECK-NEXT: [[TMP1:%.*]] = ashr exact i32 [[X:%.*]], 2
-; CHECK-NEXT: [[ASHR:%.*]] = add nsw i32 [[X]], [[TMP1]]
+; CHECK-NEXT: [[ASHR:%.*]] = shl nsw i32 [[X:%.*]], 1
; CHECK-NEXT: ret i32 [[ASHR]]
;
%mul = mul nsw i32 %x, 5
@@ -874,6 +882,18 @@ define i32 @ashr_mul_times_5_div_4_exact_2(i32 %x) {
ret i32 %ashr
}
+; ashr (mul nsw X, 2^4 + 2^2), 4 -> add nsw X, ashr X, 2
+define i32 @ashr_mul_nsw_2bits(i32 %x) {
+; CHECK-LABEL: @ashr_mul_nsw_2bits(
+; CHECK-NEXT: [[TMP1:%.*]] = ashr i32 [[X:%.*]], 2
+; CHECK-NEXT: [[SHR:%.*]] = add nsw i32 [[X]], [[TMP1]]
+; CHECK-NEXT: ret i32 [[SHR]]
+;
+ %mul = mul nsw i32 %x, 20
+ %shr = ashr i32 %mul, 4
+ ret i32 %shr
+}
+
define i32 @lsb_mask_sign_zext(i32 %x) {
; CHECK-LABEL: @lsb_mask_sign_zext(
diff --git a/llvm/test/Transforms/InstCombine/ashr.ll b/llvm/test/Transforms/InstCombine/ashr.ll
deleted file mode 100644
index ae7b8b8edc6da..0000000000000
--- a/llvm/test/Transforms/InstCombine/ashr.ll
+++ /dev/null
@@ -1,8 +0,0 @@
-; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
-
-; ashr (mul nsw X, 20), 4 -> add nsw X, ashr X, 2
-define i32 @ashr_mul_nsw_2bits(i32 %x) {
- %mul = mul nsw i32 %x, 20
- %shr = ashr i32 %mul, 4
- ret i32 %shr
-}
diff --git a/llvm/test/Transforms/InstCombine/lshr.ll b/llvm/test/Transforms/InstCombine/lshr.ll
index a24ce93d95a86..b58375ea5f49e 100644
--- a/llvm/test/Transforms/InstCombine/lshr.ll
+++ b/llvm/test/Transforms/InstCombine/lshr.ll
@@ -702,8 +702,8 @@ define i32 @shl_add_lshr_neg(i32 %x, i32 %y, i32 %z) {
define i32 @mul_splat_fold_wrong_mul_const(i32 %x) {
; CHECK-LABEL: @mul_splat_fold_wrong_mul_const(
-; CHECK-NEXT: [[M:%.*]] = mul nuw i32 [[X:%.*]], 65538
-; CHECK-NEXT: [[T:%.*]] = lshr i32 [[M]], 16
+; CHECK-NEXT: [[TMP1:%.*]] = lshr i32 [[X:%.*]], 15
+; CHECK-NEXT: [[T:%.*]] = add nuw i32 [[X]], [[TMP1]]
; CHECK-NEXT: ret i32 [[T]]
;
%m = mul nuw i32 %x, 65538
@@ -1631,8 +1631,8 @@ define i32 @lshr_two_i32(i32 %x) {
; lshr (mul nuw X, 20), 4 -> add nuw X, lshr X, 2
define i32 @lshr_mul_nuw_2bits(i32 %x) {
; CHECK-LABEL: @lshr_mul_nuw_2bits(
-; CHECK-NEXT: [[MUL:%.*]] = mul nuw i32 [[X:%.*]], 20
-; CHECK-NEXT: [[SHR:%.*]] = lshr i32 [[MUL]], 4
+; CHECK-NEXT: [[TMP1:%.*]] = lshr i32 [[X:%.*]], 2
+; CHECK-NEXT: [[SHR:%.*]] = add nuw i32 [[X]], [[TMP1]]
; CHECK-NEXT: ret i32 [[SHR]]
;
%mul = mul nuw i32 %x, 20
More information about the llvm-commits
mailing list