[llvm] [InstCombine] Fold commuted add of udiv/urem by two (#206272) (PR #207462)

Igor Shevlyakov via llvm-commits llvm-commits at lists.llvm.org
Sun Jul 5 09:10:03 PDT 2026


https://github.com/ishevlyakov updated https://github.com/llvm/llvm-project/pull/207462

>From e6369a25b3c50ed5be9b290c32ed45b5b55a2114 Mon Sep 17 00:00:00 2001
From: Igor Shevlyakov <igor at tachyum.com>
Date: Thu, 2 Jul 2026 11:40:06 -0700
Subject: [PATCH 1/2] [InstCombine] Fold commuted add of udiv/urem by two
 (#206272)

SimplifyAddWithRemainder recognizes `and X, lowmask` as a remainder and
`lshr X, N` as a division, folding e.g. `(x >> 1) + (x & 1)` into
`x - (x >> 1)`. The commuted form `add (and X, C), (lshr X, N)` was missed
because the swap that normalized operand order only matched a real
`urem`/`srem` (m_IRem), not the `and`-as-remainder spelling.

Match the division/remainder fold with both operand orders instead of
relying on that swap, so either spelling on either side is handled.

Fixes #206272

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply at anthropic.com>
---
 .../InstCombine/InstCombineAddSub.cpp         | 63 ++++++++++---------
 llvm/test/Transforms/InstCombine/add4.ll      | 26 ++++++++
 2 files changed, 61 insertions(+), 28 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
index 8c0dcc8029a1e..f687ea210da49 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
@@ -1192,34 +1192,41 @@ Value *InstCombinerImpl::SimplifyAddWithRemainder(BinaryOperator &I) {
     }
   }
 
-  // Match I = (X / C0) * C1 + (X % C0) * C2
-  Value *Div, *Rem;
-  APInt C1, C2;
-  if (!LHS->hasOneUse() || !MatchMul(LHS, Div, C1))
-    Div = LHS, C1 = APInt(I.getType()->getScalarSizeInBits(), 1);
-  if (!RHS->hasOneUse() || !MatchMul(RHS, Rem, C2))
-    Rem = RHS, C2 = APInt(I.getType()->getScalarSizeInBits(), 1);
-  if (match(Div, m_IRem(m_Value(), m_Value()))) {
-    std::swap(Div, Rem);
-    std::swap(C1, C2);
-  }
-  Value *DivOpV;
-  APInt DivOpC;
-  if (MatchRem(Rem, X, C0, IsSigned) &&
-      MatchDiv(Div, DivOpV, DivOpC, IsSigned) && X == DivOpV && C0 == DivOpC &&
-      // Avoid unprofitable replacement of and with mul.
-      !(C1.isOne() && !IsSigned && DivOpC.isPowerOf2() && DivOpC != 2)) {
-    APInt NewC = C1 - C2 * C0;
-    if (!NewC.isZero() && !Rem->hasOneUse())
-      return nullptr;
-    if (!isGuaranteedNotToBeUndef(X, &AC, &I, &DT))
-      return nullptr;
-    Value *MulXC2 = Builder.CreateMul(X, ConstantInt::get(X->getType(), C2));
-    if (NewC.isZero())
-      return MulXC2;
-    return Builder.CreateAdd(
-        Builder.CreateMul(Div, ConstantInt::get(X->getType(), NewC)), MulXC2);
-  }
+  // Match I = (X / C0) * C1 + (X % C0) * C2. The division and remainder may
+  // appear in either operand order, and the remainder may be spelled
+  // `and X, lowmask` rather than urem/srem, so try the fold with both argument
+  // orders instead of normalizing them.
+  auto FoldDivRem = [&](Value *DivSide, Value *RemSide) -> Value * {
+    Value *Div, *Rem;
+    APInt C1, C2;
+    if (!DivSide->hasOneUse() || !MatchMul(DivSide, Div, C1))
+      Div = DivSide, C1 = APInt(I.getType()->getScalarSizeInBits(), 1);
+    if (!RemSide->hasOneUse() || !MatchMul(RemSide, Rem, C2))
+      Rem = RemSide, C2 = APInt(I.getType()->getScalarSizeInBits(), 1);
+    Value *DivOpV;
+    APInt DivOpC;
+    if (MatchRem(Rem, X, C0, IsSigned) &&
+        MatchDiv(Div, DivOpV, DivOpC, IsSigned) && X == DivOpV &&
+        C0 == DivOpC &&
+        // Avoid unprofitable replacement of and with mul.
+        !(C1.isOne() && !IsSigned && DivOpC.isPowerOf2() && DivOpC != 2)) {
+      APInt NewC = C1 - C2 * C0;
+      if (!NewC.isZero() && !Rem->hasOneUse())
+        return nullptr;
+      if (!isGuaranteedNotToBeUndef(X, &AC, &I, &DT))
+        return nullptr;
+      Value *MulXC2 = Builder.CreateMul(X, ConstantInt::get(X->getType(), C2));
+      if (NewC.isZero())
+        return MulXC2;
+      return Builder.CreateAdd(
+          Builder.CreateMul(Div, ConstantInt::get(X->getType(), NewC)), MulXC2);
+    }
+    return nullptr;
+  };
+  if (Value *V = FoldDivRem(LHS, RHS))
+    return V;
+  if (Value *V = FoldDivRem(RHS, LHS))
+    return V;
 
   return nullptr;
 }
diff --git a/llvm/test/Transforms/InstCombine/add4.ll b/llvm/test/Transforms/InstCombine/add4.ll
index 15feb8a093e99..8fdb11e0b7547 100644
--- a/llvm/test/Transforms/InstCombine/add4.ll
+++ b/llvm/test/Transforms/InstCombine/add4.ll
@@ -412,3 +412,29 @@ define i32 @fold_add_sdiv_srem_by_two_no_mul(i32 noundef %arg) {
   %add = add i32 %div, %rem
   ret i32 %add
 }
+
+; The remainder may be `and X, 1` and appear on the LHS of the add. This
+; commuted form should fold identically to fold_add_udiv_urem_by_two_no_mul.
+define i32 @fold_add_udiv_urem_by_two_no_mul_commuted(i32 noundef %arg) {
+; CHECK-LABEL: @fold_add_udiv_urem_by_two_no_mul_commuted(
+; CHECK-NEXT:    [[LSHR:%.*]] = lshr i32 [[ARG:%.*]], 1
+; CHECK-NEXT:    [[ADD:%.*]] = sub i32 [[ARG]], [[LSHR]]
+; CHECK-NEXT:    ret i32 [[ADD]]
+;
+  %lshr = lshr i32 %arg, 1
+  %and = and i32 %arg, 1
+  %add = add i32 %and, %lshr
+  ret i32 %add
+}
+
+define <2 x i32> @fold_add_udiv_urem_by_two_no_mul_commuted_vec(<2 x i32> noundef %arg) {
+; CHECK-LABEL: @fold_add_udiv_urem_by_two_no_mul_commuted_vec(
+; CHECK-NEXT:    [[LSHR:%.*]] = lshr <2 x i32> [[ARG:%.*]], splat (i32 1)
+; CHECK-NEXT:    [[ADD:%.*]] = sub <2 x i32> [[ARG]], [[LSHR]]
+; CHECK-NEXT:    ret <2 x i32> [[ADD]]
+;
+  %lshr = lshr <2 x i32> %arg, splat (i32 1)
+  %and = and <2 x i32> %arg, splat (i32 1)
+  %add = add <2 x i32> %and, %lshr
+  ret <2 x i32> %add
+}

>From 373330f4a14e075db378e37fb778e2b62bbb5739 Mon Sep 17 00:00:00 2001
From: Igor Shevlyakov <igor at tachyum.com>
Date: Sun, 5 Jul 2026 09:09:52 -0700
Subject: [PATCH 2/2] Update InstCombineAddSub.cpp

fixup: trimmed comment
---
 llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
index f687ea210da49..fc81e0070ffb6 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
@@ -1192,10 +1192,7 @@ Value *InstCombinerImpl::SimplifyAddWithRemainder(BinaryOperator &I) {
     }
   }
 
-  // Match I = (X / C0) * C1 + (X % C0) * C2. The division and remainder may
-  // appear in either operand order, and the remainder may be spelled
-  // `and X, lowmask` rather than urem/srem, so try the fold with both argument
-  // orders instead of normalizing them.
+  // Match I = (X / C0) * C1 + (X % C0) * C2.
   auto FoldDivRem = [&](Value *DivSide, Value *RemSide) -> Value * {
     Value *Div, *Rem;
     APInt C1, C2;



More information about the llvm-commits mailing list