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

via llvm-commits llvm-commits at lists.llvm.org
Sun Jul 5 09:49:30 PDT 2026


Author: Igor Shevlyakov
Date: 2026-07-05T16:49:25Z
New Revision: e3c881815ea25acdf9a5f485b23a60c2eae8481c

URL: https://github.com/llvm/llvm-project/commit/e3c881815ea25acdf9a5f485b23a60c2eae8481c
DIFF: https://github.com/llvm/llvm-project/commit/e3c881815ea25acdf9a5f485b23a60c2eae8481c.diff

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

Fixes #206272.

`SimplifyAddWithRemainder` folds `(X / C0) * C1 + (X % C0) * C2`,
treating `and X, lowmask` as a remainder and `lshr X, N` as a division.
The commuted form `add (and X, C), (lshr X, N)` was missed because the
operand-order swap only recognized a real `urem`/`srem`.

Now the fold is tried with both operand orders instead of relying on
that swap. Verified with Alive2.

Supersedes #207249 (re-opened from the correct account; already
incorporates the both-operand-orders refactor suggested there by
nikic).

Prepared with AI assistance per the [LLVM AI Tool
Policy](https://llvm.org/docs/AIToolPolicy.html); not a "good first
issue".

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply at anthropic.com>

Added: 
    

Modified: 
    llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
    llvm/test/Transforms/InstCombine/add4.ll

Removed: 
    


################################################################################
diff  --git a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
index 8c0dcc8029a1e..fc81e0070ffb6 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
@@ -1192,34 +1192,38 @@ 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.
+  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
+}


        


More information about the llvm-commits mailing list