[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