[llvm] [SCEV] Remove udiv exact logic (PR #195840)

Nikita Popov via llvm-commits llvm-commits at lists.llvm.org
Tue May 5 08:47:42 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/3] [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/3] 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/3] 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



More information about the llvm-commits mailing list