[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