[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