[llvm] f4e42fb - [SCEV] Merge udiv exact logic into general udiv logic (#195840)
via llvm-commits
llvm-commits at lists.llvm.org
Thu May 7 00:44:03 PDT 2026
Author: Nikita Popov
Date: 2026-05-07T09:43:58+02:00
New Revision: f4e42fb59b2474d368d2f0828a834a4aa32d82c4
URL: https://github.com/llvm/llvm-project/commit/f4e42fb59b2474d368d2f0828a834a4aa32d82c4
DIFF: https://github.com/llvm/llvm-project/commit/f4e42fb59b2474d368d2f0828a834a4aa32d82c4.diff
LOG: [SCEV] Merge udiv exact logic into general udiv logic (#195840)
The udiv exact handling for constant RHS is already partially handling
in the generic udiv code:
https://github.com/llvm/llvm-project/blob/8a0c5d3f43b27c8e2895c6106d6b551d626979fd/llvm/lib/Analysis/ScalarEvolution.cpp#L3641-L3657
Move two remaining folds there as well:
* `(%a * %b)<nuw> / %b` to `%a`
* `(A*GCD * %b)<nuw> / (C*GCD)` to `(A * %b)<nuw> / C`
Removing common factors from a multiply does not require exact if the
multiply is nuw (and conversely, being exact is not sufficient without
nuw).
This makes getUDivExactExpr() equivalent to getUDivExpr(). I've retained
the method for now in case we want to add exact specific logic in the
future.
Proof: https://alive2.llvm.org/ce/z/iQeb6v
Added:
llvm/test/Analysis/ScalarEvolution/udiv.ll
Modified:
llvm/lib/Analysis/ScalarEvolution.cpp
llvm/test/Analysis/ScalarEvolution/mul-udiv-folds.ll
Removed:
################################################################################
diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index e15ed1591ed64..13e5a5d88d449 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -3643,7 +3643,7 @@ const SCEV *ScalarEvolution::getUDivExpr(SCEVUse LHS, SCEVUse RHS) {
SmallVector<SCEVUse, 4> Operands;
for (const SCEV *Op : M->operands())
Operands.push_back(getZeroExtendExpr(Op, ExtTy));
- if (getZeroExtendExpr(M, ExtTy) == getMulExpr(Operands))
+ if (getZeroExtendExpr(M, ExtTy) == getMulExpr(Operands)) {
// Find an operand that's safely divisible.
for (unsigned i = 0, e = M->getNumOperands(); i != e; ++i) {
const SCEV *Op = M->getOperand(i);
@@ -3654,6 +3654,21 @@ const SCEV *ScalarEvolution::getUDivExpr(SCEVUse LHS, SCEVUse RHS) {
return getMulExpr(Operands);
}
}
+
+ // Even if it's not divisible, try to remove a common factor.
+ if (const auto *LHSC = dyn_cast<SCEVConstant>(M->getOperand(0))) {
+ APInt Factor = APIntOps::GreatestCommonDivisor(LHSC->getAPInt(),
+ RHSC->getAPInt());
+ if (!Factor.isIntN(1)) {
+ SmallVector<SCEVUse, 2> NewOperands;
+ NewOperands.push_back(getConstant(LHSC->getAPInt().udiv(Factor)));
+ append_range(NewOperands, M->operands().drop_front());
+ const SCEV *NewMul = getMulExpr(NewOperands);
+ return getUDivExpr(NewMul,
+ getConstant(RHSC->getAPInt().udiv(Factor)));
+ }
+ }
+ }
}
// (A/B)/C --> A/(B*C) if safe and B*C can be folded.
@@ -3703,6 +3718,19 @@ const SCEV *ScalarEvolution::getUDivExpr(SCEVUse LHS, SCEVUse RHS) {
NegC->isNegative() && !NegC->isMinSignedValue() && *C == -*NegC)
return getZero(LHS->getType());
+ // (%a * %b)<nuw> / %b -> %a
+ const auto *Mul = dyn_cast<SCEVMulExpr>(LHS);
+ if (Mul && Mul->hasNoUnsignedWrap()) {
+ for (int i = 0, e = Mul->getNumOperands(); i != e; ++i) {
+ if (Mul->getOperand(i) == RHS) {
+ SmallVector<SCEVUse, 2> Operands;
+ append_range(Operands, Mul->operands().take_front(i));
+ append_range(Operands, Mul->operands().drop_front(i + 1));
+ return getMulExpr(Operands);
+ }
+ }
+ }
+
// TODO: Generalize to handle any common factors.
// udiv (mul nuw a, vscale), (mul nuw b, vscale) --> udiv a, b
const SCEV *NewLHS, *NewRHS;
@@ -3738,55 +3766,9 @@ APInt gcd(const SCEVConstant *C1, const SCEVConstant *C2) {
/// Get a canonical unsigned division expression, or something simpler if
/// possible. There is no representation for an exact udiv in SCEV IR, but we
-/// can attempt to remove factors from the LHS and RHS. We can't do this when
-/// it's not exact because the udiv may be clearing bits.
+/// can attempt to optimize it prior to construction.
const SCEV *ScalarEvolution::getUDivExactExpr(SCEVUse LHS, SCEVUse RHS) {
- // TODO: we could try to find factors in all sorts of things, but for now we
- // just deal with u/exact (multiply, constant). See SCEVDivision towards the
- // end of this file for inspiration.
-
- const SCEVMulExpr *Mul = dyn_cast<SCEVMulExpr>(LHS);
- if (!Mul || !Mul->hasNoUnsignedWrap())
- return getUDivExpr(LHS, RHS);
-
- if (const SCEVConstant *RHSCst = dyn_cast<SCEVConstant>(RHS)) {
- // If the mulexpr multiplies by a constant, then that constant must be the
- // first element of the mulexpr.
- if (const auto *LHSCst = dyn_cast<SCEVConstant>(Mul->getOperand(0))) {
- if (LHSCst == RHSCst) {
- SmallVector<SCEVUse, 2> Operands(drop_begin(Mul->operands()));
- return getMulExpr(Operands);
- }
-
- // We can't just assume that LHSCst divides RHSCst cleanly, it could be
- // that there's a factor provided by one of the other terms. We need to
- // check.
- APInt Factor = gcd(LHSCst, RHSCst);
- if (!Factor.isIntN(1)) {
- LHSCst =
- cast<SCEVConstant>(getConstant(LHSCst->getAPInt().udiv(Factor)));
- RHSCst =
- cast<SCEVConstant>(getConstant(RHSCst->getAPInt().udiv(Factor)));
- SmallVector<SCEVUse, 2> Operands;
- Operands.push_back(LHSCst);
- append_range(Operands, Mul->operands().drop_front());
- LHS = getMulExpr(Operands);
- RHS = RHSCst;
- Mul = dyn_cast<SCEVMulExpr>(LHS);
- if (!Mul)
- return getUDivExactExpr(LHS, RHS);
- }
- }
- }
-
- for (int i = 0, e = Mul->getNumOperands(); i != e; ++i) {
- if (Mul->getOperand(i) == RHS) {
- SmallVector<SCEVUse, 2> Operands;
- append_range(Operands, Mul->operands().take_front(i));
- append_range(Operands, Mul->operands().drop_front(i + 1));
- return getMulExpr(Operands);
- }
- }
+ // Currently there is no exact specific logic.
return getUDivExpr(LHS, RHS);
}
diff --git a/llvm/test/Analysis/ScalarEvolution/mul-udiv-folds.ll b/llvm/test/Analysis/ScalarEvolution/mul-udiv-folds.ll
index e1c62309142d0..9e7142adb1ae8 100644
--- a/llvm/test/Analysis/ScalarEvolution/mul-udiv-folds.ll
+++ b/llvm/test/Analysis/ScalarEvolution/mul-udiv-folds.ll
@@ -138,7 +138,7 @@ define void @dividend_not_known_multiple_of_divisor(i64 %x) {
; CHECK-NEXT: %m3 = mul i64 %div.16, 2
; CHECK-NEXT: --> (2 * ((2 * %x) /u 16))<nuw><nsw> U: [0,2305843009213693951) S: [0,2305843009213693951)
; CHECK-NEXT: %m4 = udiv i64 %m3, 4
-; CHECK-NEXT: --> ((2 * ((2 * %x) /u 16))<nuw><nsw> /u 4) U: [0,576460752303423488) S: [0,576460752303423488)
+; CHECK-NEXT: --> ((2 * %x) /u 32) U: [0,576460752303423488) S: [0,576460752303423488)
; CHECK-NEXT: Determining loop execution counts for: @dividend_not_known_multiple_of_divisor
;
entry:
diff --git a/llvm/test/Analysis/ScalarEvolution/udiv.ll b/llvm/test/Analysis/ScalarEvolution/udiv.ll
new file mode 100644
index 0000000000000..7defe258b29de
--- /dev/null
+++ b/llvm/test/Analysis/ScalarEvolution/udiv.ll
@@ -0,0 +1,125 @@
+; NOTE: Assertions have been autogenerated by utils/update_analyze_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -S -passes='print<scalar-evolution>' -disable-output < %s 2>&1 | FileCheck %s
+
+declare void @noundef(i8 noundef)
+
+define i8 @udiv_zero(i8 %x) {
+; CHECK-LABEL: 'udiv_zero'
+; CHECK-NEXT: Classifying expressions for: @udiv_zero
+; CHECK-NEXT: %div = udiv i8 0, %x
+; CHECK-NEXT: --> 0 U: [0,1) S: [0,1)
+; CHECK-NEXT: Determining loop execution counts for: @udiv_zero
+;
+ %div = udiv i8 0, %x
+ ret i8 %div
+}
+
+define i8 @udiv_by_one(i8 %x) {
+; CHECK-LABEL: 'udiv_by_one'
+; CHECK-NEXT: Classifying expressions for: @udiv_by_one
+; CHECK-NEXT: %div = udiv i8 %x, 1
+; CHECK-NEXT: --> %x U: full-set S: full-set
+; CHECK-NEXT: Determining loop execution counts for: @udiv_by_one
+;
+ %div = udiv i8 %x, 1
+ ret i8 %div
+}
+
+define i8 @udiv_mul_nuw_const_by_const1(i8 range(i8 0, 16) %x) {
+; CHECK-LABEL: 'udiv_mul_nuw_const_by_const1'
+; CHECK-NEXT: Classifying expressions for: @udiv_mul_nuw_const_by_const1
+; CHECK-NEXT: %mul = mul i8 %x, 3
+; CHECK-NEXT: --> (3 * %x)<nuw><nsw> U: [0,46) S: [0,46)
+; CHECK-NEXT: %div = udiv i8 %mul, 3
+; CHECK-NEXT: --> %x U: [0,16) S: [0,16)
+; CHECK-NEXT: Determining loop execution counts for: @udiv_mul_nuw_const_by_const1
+;
+ %mul = mul i8 %x, 3
+ %div = udiv i8 %mul, 3
+ ret i8 %div
+}
+
+define i8 @udiv_mul_nuw_const_by_const2(i8 range(i8 0, 16) %x) {
+; CHECK-LABEL: 'udiv_mul_nuw_const_by_const2'
+; CHECK-NEXT: Classifying expressions for: @udiv_mul_nuw_const_by_const2
+; CHECK-NEXT: %mul = mul i8 %x, 6
+; CHECK-NEXT: --> (6 * %x)<nuw><nsw> U: [0,91) S: [0,91)
+; CHECK-NEXT: %div = udiv i8 %mul, 3
+; CHECK-NEXT: --> (2 * %x)<nuw><nsw> U: [0,31) S: [0,31)
+; CHECK-NEXT: Determining loop execution counts for: @udiv_mul_nuw_const_by_const2
+;
+ %mul = mul i8 %x, 6
+ %div = udiv i8 %mul, 3
+ ret i8 %div
+}
+
+define i8 @udiv_mul_nuw_const_by_const_common_factor(i8 range(i8 0, 16) %x) {
+; CHECK-LABEL: 'udiv_mul_nuw_const_by_const_common_factor'
+; CHECK-NEXT: Classifying expressions for: @udiv_mul_nuw_const_by_const_common_factor
+; CHECK-NEXT: %mul = mul i8 %x, 6
+; CHECK-NEXT: --> (6 * %x)<nuw><nsw> U: [0,91) S: [0,91)
+; CHECK-NEXT: %div = udiv i8 %mul, 4
+; CHECK-NEXT: --> ((3 * %x)<nuw><nsw> /u 2) U: [0,23) S: [0,23)
+; CHECK-NEXT: Determining loop execution counts for: @udiv_mul_nuw_const_by_const_common_factor
+;
+ %mul = mul i8 %x, 6
+ %div = udiv i8 %mul, 4
+ ret i8 %div
+}
+
+define i8 @udiv_mul_nuw_const_by_const_no_common_factor(i8 range(i8 0, 16) %x) {
+; CHECK-LABEL: 'udiv_mul_nuw_const_by_const_no_common_factor'
+; CHECK-NEXT: Classifying expressions for: @udiv_mul_nuw_const_by_const_no_common_factor
+; CHECK-NEXT: %mul = mul i8 %x, 7
+; CHECK-NEXT: --> (7 * %x)<nuw><nsw> U: [0,106) S: [0,106)
+; CHECK-NEXT: %div = udiv i8 %mul, 4
+; CHECK-NEXT: --> ((7 * %x)<nuw><nsw> /u 4) U: [0,27) S: [0,27)
+; CHECK-NEXT: Determining loop execution counts for: @udiv_mul_nuw_const_by_const_no_common_factor
+;
+ %mul = mul i8 %x, 7
+ %div = udiv i8 %mul, 4
+ ret i8 %div
+}
+
+define i8 @udiv_mul_const_by_const_not_nuw(i8 %x) {
+; CHECK-LABEL: 'udiv_mul_const_by_const_not_nuw'
+; CHECK-NEXT: Classifying expressions for: @udiv_mul_const_by_const_not_nuw
+; CHECK-NEXT: %mul = mul i8 %x, 3
+; CHECK-NEXT: --> (3 * %x) U: full-set S: full-set
+; CHECK-NEXT: %div = udiv i8 %mul, 3
+; CHECK-NEXT: --> ((3 * %x) /u 3) U: [0,86) S: [0,86)
+; CHECK-NEXT: Determining loop execution counts for: @udiv_mul_const_by_const_not_nuw
+;
+ %mul = mul i8 %x, 3
+ %div = udiv i8 %mul, 3
+ ret i8 %div
+}
+
+define i8 @udiv_mul_nuw_by_factor(i8 %x, i8 %y) {
+; CHECK-LABEL: 'udiv_mul_nuw_by_factor'
+; CHECK-NEXT: Classifying expressions for: @udiv_mul_nuw_by_factor
+; CHECK-NEXT: %mul = mul nuw i8 %x, %y
+; CHECK-NEXT: --> (%x * %y)<nuw> U: full-set S: full-set
+; CHECK-NEXT: %div = udiv i8 %mul, %y
+; CHECK-NEXT: --> %x U: full-set S: full-set
+; CHECK-NEXT: Determining loop execution counts for: @udiv_mul_nuw_by_factor
+;
+ %mul = mul nuw i8 %x, %y
+ call void @noundef(i8 %mul)
+ %div = udiv i8 %mul, %y
+ ret i8 %div
+}
+
+define i8 @udiv_mul_by_factor_not_nuw(i8 %x, i8 %y) {
+; CHECK-LABEL: 'udiv_mul_by_factor_not_nuw'
+; CHECK-NEXT: Classifying expressions for: @udiv_mul_by_factor_not_nuw
+; CHECK-NEXT: %mul = mul i8 %x, %y
+; CHECK-NEXT: --> (%x * %y) U: full-set S: full-set
+; CHECK-NEXT: %div = udiv i8 %mul, %y
+; CHECK-NEXT: --> ((%x * %y) /u %y) U: full-set S: full-set
+; CHECK-NEXT: Determining loop execution counts for: @udiv_mul_by_factor_not_nuw
+;
+ %mul = mul i8 %x, %y
+ %div = udiv i8 %mul, %y
+ ret i8 %div
+}
More information about the llvm-commits
mailing list