[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