[llvm] [SCEV] Merge udiv exact logic into general udiv logic (PR #195840)
Nikita Popov via llvm-commits
llvm-commits at lists.llvm.org
Wed May 6 01:20:47 PDT 2026
https://github.com/nikic updated https://github.com/llvm/llvm-project/pull/195840
>From e19b1719f0865c5398196efd1911c20b0012ed22 Mon Sep 17 00:00:00 2001
From: Nikita Popov <npopov at redhat.com>
Date: Tue, 5 May 2026 12:48:46 +0200
Subject: [PATCH 1/4] [SCEV] Remove udiv exact logic
I believe our udiv exact handling is a strict subset of generic
udiv handling:
https://github.com/llvm/llvm-project/blob/8a0c5d3f43b27c8e2895c6106d6b551d626979fd/llvm/lib/Analysis/ScalarEvolution.cpp#L3641-L3657
In particular, 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.
---
llvm/lib/Analysis/ScalarEvolution.cpp | 50 ++-------------------------
1 file changed, 2 insertions(+), 48 deletions(-)
diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index 676292ebe0346..1e754c46bda4f 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -3738,55 +3738,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);
}
>From f6d97ce5e84d77b7905b3479a917746dffbed4ed Mon Sep 17 00:00:00 2001
From: Nikita Popov <npopov at redhat.com>
Date: Tue, 5 May 2026 17:44:01 +0200
Subject: [PATCH 2/4] Add some udiv tests
---
llvm/test/Analysis/ScalarEvolution/udiv.ll | 111 +++++++++++++++++++++
1 file changed, 111 insertions(+)
create mode 100644 llvm/test/Analysis/ScalarEvolution/udiv.ll
diff --git a/llvm/test/Analysis/ScalarEvolution/udiv.ll b/llvm/test/Analysis/ScalarEvolution/udiv.ll
new file mode 100644
index 0000000000000..156269951eeed
--- /dev/null
+++ b/llvm/test/Analysis/ScalarEvolution/udiv.ll
@@ -0,0 +1,111 @@
+; 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_not_divisible(i8 range(i8 0, 16) %x) {
+; CHECK-LABEL: 'udiv_mul_nuw_const_by_const_not_divisible'
+; CHECK-NEXT: Classifying expressions for: @udiv_mul_nuw_const_by_const_not_divisible
+; 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: --> ((6 * %x)<nuw><nsw> /u 4) U: [0,23) S: [0,23)
+; CHECK-NEXT: Determining loop execution counts for: @udiv_mul_nuw_const_by_const_not_divisible
+;
+ %mul = mul i8 %x, 6
+ %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 * %y)<nuw> /u %y) 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
+}
>From 14ab411871ab2de4d013f8a5faef3b2bc2dd7d84 Mon Sep 17 00:00:00 2001
From: Nikita Popov <npopov at redhat.com>
Date: Tue, 5 May 2026 17:46:01 +0200
Subject: [PATCH 3/4] Restore non-constant factor fold
---
llvm/lib/Analysis/ScalarEvolution.cpp | 13 +++++++++++++
llvm/test/Analysis/ScalarEvolution/udiv.ll | 2 +-
2 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index 1e754c46bda4f..60d96682240ce 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -3703,6 +3703,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;
diff --git a/llvm/test/Analysis/ScalarEvolution/udiv.ll b/llvm/test/Analysis/ScalarEvolution/udiv.ll
index 156269951eeed..a8b72e6dd9341 100644
--- a/llvm/test/Analysis/ScalarEvolution/udiv.ll
+++ b/llvm/test/Analysis/ScalarEvolution/udiv.ll
@@ -87,7 +87,7 @@ define i8 @udiv_mul_nuw_by_factor(i8 %x, i8 %y) {
; 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 * %y)<nuw> /u %y) U: full-set S: full-set
+; 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
>From 0bcc0d60bce01dcdbc24cedd956f16907da11e47 Mon Sep 17 00:00:00 2001
From: Nikita Popov <npopov at redhat.com>
Date: Wed, 6 May 2026 10:09:03 +0200
Subject: [PATCH 4/4] Restore constant GCD fold
---
llvm/lib/Analysis/ScalarEvolution.cpp | 17 ++++++++++++-
.../ScalarEvolution/mul-udiv-folds.ll | 2 +-
llvm/test/Analysis/ScalarEvolution/udiv.ll | 24 +++++++++++++++----
3 files changed, 36 insertions(+), 7 deletions(-)
diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index 60d96682240ce..f3615aff2197c 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.
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
index a8b72e6dd9341..7defe258b29de 100644
--- a/llvm/test/Analysis/ScalarEvolution/udiv.ll
+++ b/llvm/test/Analysis/ScalarEvolution/udiv.ll
@@ -53,20 +53,34 @@ define i8 @udiv_mul_nuw_const_by_const2(i8 range(i8 0, 16) %x) {
ret i8 %div
}
-define i8 @udiv_mul_nuw_const_by_const_not_divisible(i8 range(i8 0, 16) %x) {
-; CHECK-LABEL: 'udiv_mul_nuw_const_by_const_not_divisible'
-; CHECK-NEXT: Classifying expressions for: @udiv_mul_nuw_const_by_const_not_divisible
+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: --> ((6 * %x)<nuw><nsw> /u 4) U: [0,23) S: [0,23)
-; CHECK-NEXT: Determining loop execution counts for: @udiv_mul_nuw_const_by_const_not_divisible
+; 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
More information about the llvm-commits
mailing list