[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
Tue Aug 4 11:54:42 PDT 2026
https://github.com/AZero13 updated https://github.com/llvm/llvm-project/pull/210140
>From 50be12fc87262612a5f3a8803d08b91d98f1be4b 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 14863787f2fde5f01a40d3dfd988d777ea6b76d9 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/InstCombineMulDivRem.cpp | 21 +++++++
.../InstCombine/InstCombineShifts.cpp | 62 ++++++++++---------
llvm/test/Transforms/InstCombine/ashr-lshr.ll | 24 +++++++
llvm/test/Transforms/InstCombine/ashr.ll | 8 ---
llvm/test/Transforms/InstCombine/lshr.ll | 8 +--
5 files changed, 82 insertions(+), 41 deletions(-)
delete mode 100644 llvm/test/Transforms/InstCombine/ashr.ll
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp b/llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp
index f63bff2f16e7a..f2fafce9bfcc0 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp
@@ -1402,6 +1402,27 @@ Instruction *InstCombinerImpl::commonIDivTransforms(BinaryOperator &I) {
Mul->setHasNoSignedWrap(OBO->hasNoSignedWrap());
return Mul;
}
+ // (X * C1) / C2 -> (X * (C1/D)) / (C2/D) if D = gcd(C1, C2) > 1.
+ if (Op0->hasOneUse()) {
+ APInt GCD = IsSigned
+ ? APIntOps::GreatestCommonDivisor(C1->abs(), C2->abs())
+ : APIntOps::GreatestCommonDivisor(*C1, *C2);
+ if (GCD.ugt(1)) {
+ APInt NewC1 = IsSigned ? C1->sdiv(GCD) : C1->udiv(GCD);
+ APInt NewC2 = IsSigned ? C2->sdiv(GCD) : C2->udiv(GCD);
+
+ auto *OldMul = cast<OverflowingBinaryOperator>(Op0);
+ Value *NewMul = Builder.CreateMul(X, ConstantInt::get(Ty, NewC1), "",
+ OldMul->hasNoUnsignedWrap(),
+ OldMul->hasNoSignedWrap());
+
+ Constant *NewDivisor = ConstantInt::get(Ty, NewC2);
+ auto *NewDiv =
+ BinaryOperator::Create(I.getOpcode(), NewMul, NewDivisor);
+ NewDiv->setIsExact(I.isExact());
+ return NewDiv;
+ }
+ }
}
if ((IsSigned && match(Op0, m_NSWShl(m_Value(X), m_APInt(C1))) &&
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp b/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp
index 241bc3bba51c2..61323ed6e40f6 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineShifts.cpp
@@ -1630,24 +1630,27 @@ 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);
-
- // 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()));
- NewAdd->setHasNoSignedWrap(
- cast<OverflowingBinaryOperator>(Op0)->hasNoSignedWrap());
- return NewAdd;
- }
+ 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);
+
+ 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();
+
+ // lshr (mul nuw (X, 2^N + 2^M)), N -> add nuw (X, lshr(X, N - M))
+ 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;
}
// The one-use check is not strictly necessary, but codegen may not be
@@ -1903,17 +1906,18 @@ 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 = 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..1af55d5bfd2d6 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) {
@@ -874,6 +886,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