[llvm] [SCEV] Updates for getUDivExactExpr() to improve handling of MulExpr numerators/denominators (PR #195704)

Ryan Buchner via llvm-commits llvm-commits at lists.llvm.org
Tue May 5 10:49:17 PDT 2026


https://github.com/bababuck updated https://github.com/llvm/llvm-project/pull/195704

>From 856962bb6d969c84acb99a23bcddc885babe59a7 Mon Sep 17 00:00:00 2001
From: bababuck <buchner.ryan at gmail.com>
Date: Thu, 30 Apr 2026 15:54:22 -0700
Subject: [PATCH 1/6] [SCEV] Preserve NUW flags of MulExpr numerators in
 getUDivExactExpr()

---
 llvm/lib/Analysis/ScalarEvolution.cpp         |  6 +-
 .../Analysis/ScalarEvolutionTest.cpp          | 72 +++++++++++++++++++
 2 files changed, 76 insertions(+), 2 deletions(-)

diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index 676292ebe0346..9e280209813a0 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -3755,7 +3755,9 @@ const SCEV *ScalarEvolution::getUDivExactExpr(SCEVUse LHS, SCEVUse RHS) {
     if (const auto *LHSCst = dyn_cast<SCEVConstant>(Mul->getOperand(0))) {
       if (LHSCst == RHSCst) {
         SmallVector<SCEVUse, 2> Operands(drop_begin(Mul->operands()));
-        return getMulExpr(Operands);
+        return getMulExpr(
+            Operands, LHSCst->isZero() ? SCEVNoWrapFlags::FlagAnyWrap
+                                       : Mul->getNoWrapFlags() & SCEV::FlagNUW);
       }
 
       // We can't just assume that LHSCst divides RHSCst cleanly, it could be
@@ -3770,7 +3772,7 @@ const SCEV *ScalarEvolution::getUDivExactExpr(SCEVUse LHS, SCEVUse RHS) {
         SmallVector<SCEVUse, 2> Operands;
         Operands.push_back(LHSCst);
         append_range(Operands, Mul->operands().drop_front());
-        LHS = getMulExpr(Operands);
+        LHS = getMulExpr(Operands, Mul->getNoWrapFlags() & SCEV::FlagNUW);
         RHS = RHSCst;
         Mul = dyn_cast<SCEVMulExpr>(LHS);
         if (!Mul)
diff --git a/llvm/unittests/Analysis/ScalarEvolutionTest.cpp b/llvm/unittests/Analysis/ScalarEvolutionTest.cpp
index 8504534693281..d9d38e31b8570 100644
--- a/llvm/unittests/Analysis/ScalarEvolutionTest.cpp
+++ b/llvm/unittests/Analysis/ScalarEvolutionTest.cpp
@@ -1579,6 +1579,78 @@ TEST_F(ScalarEvolutionsTest, SCEVUDivFloorCeiling) {
   });
 }
 
+TEST_F(ScalarEvolutionsTest, SCEVUDivExactPreserveNUW) {
+  LLVMContext C;
+  SMDiagnostic Err;
+  std::unique_ptr<Module> M =
+      parseAssemblyString("define void @foo(i32 %a, i32 %b) {"
+                          "  ret void"
+                          "}",
+                          Err, C);
+
+  ASSERT_TRUE(M && "Could not parse module?");
+  ASSERT_TRUE(!verifyModule(*M) && "Must have been well formed!");
+
+  runWithSE(*M, "foo", [](Function &F, LoopInfo &LI, ScalarEvolution &SE) {
+    const SCEV *A = SE.getSCEV(getArgByName(F, "a"));
+    const SCEV *B = SE.getSCEV(getArgByName(F, "b"));
+    const SCEV *Two = SE.getConstant(A->getType(), 2);
+    const SCEV *Three = SE.getConstant(A->getType(), 3);
+    const SCEV *Four = SE.getConstant(A->getType(), 4);
+    const SCEV *Six = SE.getConstant(A->getType(), 6);
+    const SCEV *Nine = SE.getConstant(A->getType(), 9);
+    const SCEV *Twelve = SE.getConstant(A->getType(), 12);
+    const SCEV *TwentyFour = SE.getConstant(A->getType(), 24);
+
+    SmallVector<SCEVUse, 3> LHSMulOps = {A, B, Twelve};
+    const SCEVMulExpr *LHSMulNUW =
+        cast<const SCEVMulExpr>(SE.getMulExpr(LHSMulOps, SCEV::FlagNUW));
+    // UDivExactExpr where the divisor is a constant that appears in dividend
+    // MulExpr
+    const SCEVMulExpr *EqualConsts =
+        cast<const SCEVMulExpr>(SE.getUDivExactExpr(LHSMulNUW, Twelve));
+    SmallVector<SCEVUse, 2> EqualConstsResOps = {A, B};
+    EXPECT_TRUE(EqualConsts->hasNoUnsignedWrap());
+    EXPECT_EQ(EqualConsts, SE.getMulExpr(EqualConstsResOps, SCEV::FlagNUW));
+
+    // UDivExactExpr where the divisor is a constant multiple of the dividend
+    // constant
+    const SCEV *DividendSmaller = SE.getUDivExactExpr(LHSMulNUW, TwentyFour);
+    SmallVector<SCEVUse, 2> DividendSmallerResOps = {A, B};
+    EXPECT_TRUE(cast<const SCEVMulExpr>(DividendSmaller->operands().front())
+                    ->hasNoUnsignedWrap());
+    EXPECT_EQ(DividendSmaller,
+              SE.getUDivExpr(
+                  SE.getMulExpr(DividendSmallerResOps, SCEV::FlagNUW), Two));
+
+    // UDivExactExpr where the dividend constant is a constant multiple of the
+    // divisor
+    const SCEVMulExpr *DivisorSmaller =
+        cast<const SCEVMulExpr>(SE.getUDivExactExpr(LHSMulNUW, Six));
+    SmallVector<SCEVUse, 2> DivisorSmallerResOps = {A, B, Two};
+    EXPECT_TRUE(DivisorSmaller->hasNoUnsignedWrap());
+    EXPECT_EQ(DivisorSmaller,
+              SE.getMulExpr(DivisorSmallerResOps, SCEV::FlagNUW));
+
+    // UDivExactExpr where the divisor and dividend constant,are not factors of
+    // each other
+    const SCEV *NoFactor = SE.getUDivExactExpr(LHSMulNUW, Nine);
+    SmallVector<SCEVUse, 2> NoFactorResOps = {A, B, Four};
+    EXPECT_TRUE(cast<const SCEVMulExpr>(NoFactor->operands().front())
+                    ->hasNoUnsignedWrap());
+    EXPECT_EQ(
+        NoFactor,
+        SE.getUDivExpr(SE.getMulExpr(NoFactorResOps, SCEV::FlagNUW), Three));
+
+    // UDivExactExpr where the divisor is contained in the divident MulExpr
+    const SCEVMulExpr *NonConstFactor =
+        cast<const SCEVMulExpr>(SE.getUDivExactExpr(LHSMulNUW, B));
+    SmallVector<SCEVUse, 2> NonConstFactorResOps = {A, Twelve};
+    EXPECT_FALSE(NonConstFactor->hasNoUnsignedWrap());
+    EXPECT_EQ(NonConstFactor, SE.getMulExpr(NonConstFactorResOps));
+  });
+}
+
 TEST_F(ScalarEvolutionsTest, CheckGetPowerOfTwo) {
   Module M("CheckGetPowerOfTwo", Context);
   FunctionType *FTy = FunctionType::get(Type::getVoidTy(Context), {}, false);

>From 23e2f52bb8ed1e3c0b8102774b052c55e7f465de Mon Sep 17 00:00:00 2001
From: bababuck <buchner.ryan at gmail.com>
Date: Thu, 30 Apr 2026 15:54:27 -0700
Subject: [PATCH 2/6] [SCEV] Improve getUDivExactExpr() for cases where the
 denominator is a MulExpr

---
 llvm/lib/Analysis/ScalarEvolution.cpp         |  33 ++++
 .../Analysis/ScalarEvolutionTest.cpp          | 154 ++++++++++++++++++
 2 files changed, 187 insertions(+)

diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index 9e280209813a0..56ac8b8451262 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -3745,6 +3745,39 @@ const SCEV *ScalarEvolution::getUDivExactExpr(SCEVUse LHS, SCEVUse RHS) {
   // just deal with u/exact (multiply, constant). See SCEVDivision towards the
   // end of this file for inspiration.
 
+  if (LHS == RHS)
+    return getConstant(LHS->getType(), 1);
+
+  if (const SCEVMulExpr *RHSMul = dyn_cast<SCEVMulExpr>(RHS);
+      RHSMul && RHSMul->hasNoUnsignedWrap() &&
+      isa<SCEVMulExpr, SCEVConstant, SCEVUnknown>(LHS)) {
+    const SCEV *CurrSCEV = LHS;
+    SmallVector<SCEVUse, 2> Operands;
+    bool OnlyNonZero = true;
+    for (SCEVUse Op : RHSMul->operands()) {
+      if (!OnlyNonZero) {
+        Operands.push_back(Op);
+        continue;
+      }
+      const SCEV *NewSCEV = getUDivExactExpr(CurrSCEV, Op);
+      if (isa<SCEVUDivExpr>(NewSCEV)) {
+        Operands.push_back(Op);
+        continue;
+      }
+      if (!isa<SCEVConstant>(Op) || Op->isZero())
+        OnlyNonZero = false;
+      CurrSCEV = NewSCEV;
+    }
+    if (Operands.size())
+      return getUDivExpr(
+          CurrSCEV,
+          getMulExpr(Operands, OnlyNonZero
+                                   ? RHSMul->getNoWrapFlags() & SCEV::FlagNUW
+                                   : SCEVNoWrapFlags::FlagAnyWrap));
+    else
+      return CurrSCEV;
+  }
+
   const SCEVMulExpr *Mul = dyn_cast<SCEVMulExpr>(LHS);
   if (!Mul || !Mul->hasNoUnsignedWrap())
     return getUDivExpr(LHS, RHS);
diff --git a/llvm/unittests/Analysis/ScalarEvolutionTest.cpp b/llvm/unittests/Analysis/ScalarEvolutionTest.cpp
index d9d38e31b8570..a89880e743b1f 100644
--- a/llvm/unittests/Analysis/ScalarEvolutionTest.cpp
+++ b/llvm/unittests/Analysis/ScalarEvolutionTest.cpp
@@ -1651,6 +1651,160 @@ TEST_F(ScalarEvolutionsTest, SCEVUDivExactPreserveNUW) {
   });
 }
 
+TEST_F(ScalarEvolutionsTest, SCEVUDivExactNUWMulDivisor) {
+  LLVMContext C;
+  SMDiagnostic Err;
+  std::unique_ptr<Module> M = parseAssemblyString(
+      "define void @foo(i32 %a, i32 %b, i32 %c, i32 %d, i32 %e) {"
+      "  ret void"
+      "}",
+      Err, C);
+
+  ASSERT_TRUE(M && "Could not parse module?");
+  ASSERT_TRUE(!verifyModule(*M) && "Must have been well formed!");
+
+  runWithSE(*M, "foo", [](Function &F, LoopInfo &LI, ScalarEvolution &SE) {
+    const SCEV *A = SE.getSCEV(getArgByName(F, "a"));
+    const SCEV *B = SE.getSCEV(getArgByName(F, "b"));
+    const SCEV *C = SE.getSCEV(getArgByName(F, "c"));
+    const SCEV *D = SE.getSCEV(getArgByName(F, "d"));
+    const SCEV *E = SE.getSCEV(getArgByName(F, "e"));
+    const SCEV *One = SE.getConstant(A->getType(), 1);
+    const SCEV *Two = SE.getConstant(A->getType(), 2);
+    const SCEV *Three = SE.getConstant(A->getType(), 3);
+    const SCEV *Six = SE.getConstant(A->getType(), 6);
+
+    SmallVector<SCEVUse, 2> BDOps = {B, D};
+    const SCEV *BD = SE.getMulExpr(BDOps, SCEV::FlagNUW);
+
+    SmallVector<SCEVUse, 2> CEOps = {C, E};
+    const SCEV *CE = SE.getMulExpr(CEOps, SCEV::FlagNUW);
+
+    SmallVector<SCEVUse, 2> CDEOps = {C, D, E};
+    const SCEV *CDE = SE.getMulExpr(CDEOps, SCEV::FlagNUW);
+
+    SmallVector<SCEVUse, 3> ABCOps = {A, B, C};
+    const SCEV *ABC = SE.getMulExpr(ABCOps, SCEV::FlagNUW);
+
+    SmallVector<SCEVUse, 3> ABEOps = {A, B, E};
+    const SCEV *ABE = SE.getMulExpr(ABEOps, SCEV::FlagNUW);
+
+    SmallVector<SCEVUse, 3> BCEOps = {B, C, E};
+    const SCEV *BCE = SE.getMulExpr(BCEOps, SCEV::FlagNUW);
+
+    SmallVector<SCEVUse, 2> A3Ops = {A, Three};
+    const SCEV *A3 = SE.getMulExpr(A3Ops, SCEV::FlagNUW);
+
+    SmallVector<SCEVUse, 2> B3Ops = {B, Three};
+    const SCEV *B3 = SE.getMulExpr(B3Ops, SCEV::FlagNUW);
+
+    SmallVector<SCEVUse, 2> D6Ops = {D, Six};
+    const SCEV *D6 = SE.getMulExpr(D6Ops, SCEV::FlagNUW);
+
+    SmallVector<SCEVUse, 4> ABCDOps = {A, B, C, D};
+    const SCEV *ABCDNoNUW = SE.getMulExpr(ABCDOps);
+
+    SmallVector<SCEVUse, 4> ABCE3Ops = {A, B, C, E, Three};
+    const SCEV *ABCE3 = SE.getMulExpr(ABCE3Ops, SCEV::FlagNUW);
+
+    // B / (B * D)
+    EXPECT_EQ(SE.getUDivExactExpr(B, BD), SE.getUDivExpr(One, D));
+
+    // A / (A * B * C)
+    {
+      auto *A_ABC = SE.getUDivExactExpr(A, ABC);
+      auto *Denom = dyn_cast<const SCEVMulExpr>(A_ABC->operands().back());
+      EXPECT_TRUE(Denom);
+      EXPECT_FALSE(!Denom || Denom->hasNoUnsignedWrap());
+      SmallVector<SCEVUse, 2> BCOps = {B, C};
+      const SCEV *BCNoNUW = SE.getMulExpr(BCOps);
+      EXPECT_EQ(A_ABC, SE.getUDivExpr(One, BCNoNUW));
+    }
+
+    // B / (A * B * C)
+    {
+      auto *B_ABC = SE.getUDivExactExpr(B, ABC);
+      auto *Denom = dyn_cast<const SCEVMulExpr>(B_ABC->operands().back());
+      EXPECT_TRUE(Denom);
+      EXPECT_FALSE(!Denom || Denom->hasNoUnsignedWrap());
+      SmallVector<SCEVUse, 2> ACOps = {A, C};
+      const SCEV *ACNoNUW = SE.getMulExpr(ACOps);
+      EXPECT_EQ(B_ABC, SE.getUDivExpr(One, ACNoNUW));
+    }
+
+    // C / (A * B * C)
+    {
+      auto *C_ABC = SE.getUDivExactExpr(C, ABC);
+      auto *Denom = dyn_cast<const SCEVMulExpr>(C_ABC->operands().back());
+      EXPECT_TRUE(Denom);
+      EXPECT_FALSE(!Denom || Denom->hasNoUnsignedWrap());
+      SmallVector<SCEVUse, 2> ABOps = {A, B};
+      const SCEV *ABNoNUW = SE.getMulExpr(ABOps);
+      EXPECT_EQ(C_ABC, SE.getUDivExpr(One, ABNoNUW));
+    }
+
+    // (C * E) / (C * D * E)
+    {
+      auto *CE_CDE = SE.getUDivExactExpr(CE, CDE);
+      auto *Denom = dyn_cast<const SCEVMulExpr>(CE_CDE->operands().back());
+      EXPECT_TRUE(Denom);
+      EXPECT_FALSE(!Denom || Denom->hasNoUnsignedWrap());
+      SmallVector<SCEVUse, 2> DEOps = {D, E};
+      const SCEV *DENoNUW = SE.getMulExpr(DEOps);
+      SmallVector<SCEVUse, 2> CDOps = {C, D};
+      const SCEV *CDNoNUW = SE.getMulExpr(CDOps);
+      EXPECT_TRUE(CE_CDE == SE.getUDivExpr(E, DENoNUW) ||
+                  CE_CDE == SE.getUDivExpr(C, CDNoNUW));
+    }
+
+    // (B * D) / (A * B * E)
+    {
+      auto *BD_ABE = SE.getUDivExactExpr(BD, ABE);
+      auto *Denom = dyn_cast<const SCEVMulExpr>(BD_ABE->operands().back());
+      EXPECT_TRUE(Denom);
+      EXPECT_FALSE(!Denom || Denom->hasNoUnsignedWrap());
+      SmallVector<SCEVUse, 2> AEOps = {A, E};
+      const SCEV *AENoNUW = SE.getMulExpr(AEOps);
+      EXPECT_EQ(BD_ABE, SE.getUDivExpr(D, AENoNUW));
+    }
+
+    // (3 * A) / (3 * B)
+    EXPECT_EQ(SE.getUDivExactExpr(A3, B3), SE.getUDivExpr(A, B));
+
+    // (B * D) / (B * D)
+    EXPECT_EQ(SE.getUDivExactExpr(BD, BD), One);
+
+    // (3 * A) / (3 * A)
+    EXPECT_EQ(SE.getUDivExactExpr(A3, A3), One);
+
+    // (D) / (B * C * E)
+    EXPECT_EQ(SE.getUDivExactExpr(D, BCE), SE.getUDivExpr(D, BCE));
+
+    // (D * 6) / (3 * A * B * C * E)
+    {
+      auto *D6_ABCE3 = SE.getUDivExactExpr(D6, ABCE3);
+      EXPECT_TRUE(cast<const SCEVMulExpr>(D6_ABCE3->operands().front())
+                      ->hasNoUnsignedWrap());
+      EXPECT_TRUE(cast<const SCEVMulExpr>(D6_ABCE3->operands().back())
+                      ->hasNoUnsignedWrap());
+      auto *Num = dyn_cast<const SCEVMulExpr>(D6_ABCE3->operands().front());
+      auto *Denom = dyn_cast<const SCEVMulExpr>(D6_ABCE3->operands().back());
+      EXPECT_TRUE(Num);
+      EXPECT_TRUE(Num && Num->hasNoUnsignedWrap());
+      EXPECT_TRUE(Denom);
+      EXPECT_TRUE(Denom && Denom->hasNoUnsignedWrap());
+      SmallVector<SCEVUse, 4> ABCEOps = {A, B, C, E};
+      const SCEV *ABCE = SE.getMulExpr(ABCEOps, SCEV::FlagNUW);
+      SmallVector<SCEVUse, 2> D2Ops = {D, Two};
+      const SCEV *D2 = SE.getMulExpr(D2Ops, SCEV::FlagNUW);
+      EXPECT_EQ(D6_ABCE3, SE.getUDivExpr(D2, ABCE));
+      // No NUW
+      EXPECT_EQ(SE.getUDivExactExpr(A, ABCDNoNUW),
+                SE.getUDivExpr(A, ABCDNoNUW));
+    }
+  });
+}
+
 TEST_F(ScalarEvolutionsTest, CheckGetPowerOfTwo) {
   Module M("CheckGetPowerOfTwo", Context);
   FunctionType *FTy = FunctionType::get(Type::getVoidTy(Context), {}, false);

>From 9547752db8940cb2dde47018e5e799da1851c72d Mon Sep 17 00:00:00 2001
From: bababuck <buchner.ryan at gmail.com>
Date: Mon, 4 May 2026 16:17:45 -0700
Subject: [PATCH 3/6] Remove isZero() check from constants in MulExpr's

MulExpr cannot have 0 constants by construction.
---
 llvm/lib/Analysis/ScalarEvolution.cpp | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index 56ac8b8451262..395ccf0010bbe 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -3764,7 +3764,7 @@ const SCEV *ScalarEvolution::getUDivExactExpr(SCEVUse LHS, SCEVUse RHS) {
         Operands.push_back(Op);
         continue;
       }
-      if (!isa<SCEVConstant>(Op) || Op->isZero())
+      if (!isa<SCEVConstant>(Op)) // Constant is guaranteed non-zero is MulExpr
         OnlyNonZero = false;
       CurrSCEV = NewSCEV;
     }
@@ -3788,9 +3788,7 @@ const SCEV *ScalarEvolution::getUDivExactExpr(SCEVUse LHS, SCEVUse RHS) {
     if (const auto *LHSCst = dyn_cast<SCEVConstant>(Mul->getOperand(0))) {
       if (LHSCst == RHSCst) {
         SmallVector<SCEVUse, 2> Operands(drop_begin(Mul->operands()));
-        return getMulExpr(
-            Operands, LHSCst->isZero() ? SCEVNoWrapFlags::FlagAnyWrap
-                                       : Mul->getNoWrapFlags() & SCEV::FlagNUW);
+        return getMulExpr(Operands, Mul->getNoWrapFlags() & SCEV::FlagNUW);
       }
 
       // We can't just assume that LHSCst divides RHSCst cleanly, it could be

>From 93b6da33d4ac20fb7ad41744e35aba2dfffd5707 Mon Sep 17 00:00:00 2001
From: bababuck <buchner.ryan at gmail.com>
Date: Mon, 4 May 2026 17:26:26 -0700
Subject: [PATCH 4/6] [SCEV] Simplify X udiv X -> 1 in getUDivExpr()

---
 llvm/lib/Analysis/ScalarEvolution.cpp         |  8 ++++---
 .../Analysis/ScalarEvolutionTest.cpp          | 24 +++++++++++++++++++
 2 files changed, 29 insertions(+), 3 deletions(-)

diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index 395ccf0010bbe..b053e3122cf10 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -3561,6 +3561,11 @@ const SCEV *ScalarEvolution::getUDivExpr(SCEVUse LHS, SCEVUse RHS) {
   if (match(LHS, m_scev_Zero()))
     return LHS;
 
+  // X udiv X == 1
+  if (LHS == RHS)
+    return getConstant(LHS->getType(), 1);
+
+
   if (const SCEVConstant *RHSC = dyn_cast<SCEVConstant>(RHS)) {
     if (RHSC->getValue()->isOne())
       return LHS;                               // X udiv 1 --> x
@@ -3745,9 +3750,6 @@ const SCEV *ScalarEvolution::getUDivExactExpr(SCEVUse LHS, SCEVUse RHS) {
   // just deal with u/exact (multiply, constant). See SCEVDivision towards the
   // end of this file for inspiration.
 
-  if (LHS == RHS)
-    return getConstant(LHS->getType(), 1);
-
   if (const SCEVMulExpr *RHSMul = dyn_cast<SCEVMulExpr>(RHS);
       RHSMul && RHSMul->hasNoUnsignedWrap() &&
       isa<SCEVMulExpr, SCEVConstant, SCEVUnknown>(LHS)) {
diff --git a/llvm/unittests/Analysis/ScalarEvolutionTest.cpp b/llvm/unittests/Analysis/ScalarEvolutionTest.cpp
index a89880e743b1f..2c2e2a0c03a69 100644
--- a/llvm/unittests/Analysis/ScalarEvolutionTest.cpp
+++ b/llvm/unittests/Analysis/ScalarEvolutionTest.cpp
@@ -1579,6 +1579,30 @@ TEST_F(ScalarEvolutionsTest, SCEVUDivFloorCeiling) {
   });
 }
 
+TEST_F(ScalarEvolutionsTest, SCEVUDivEqual) {
+  LLVMContext C;
+  SMDiagnostic Err;
+  std::unique_ptr<Module> M =
+      parseAssemblyString("define void @foo(i32 %a) {"
+                          "  ret void"
+                          "}",
+                          Err, C);
+
+  ASSERT_TRUE(M && "Could not parse module?");
+  ASSERT_TRUE(!verifyModule(*M) && "Must have been well formed!");
+
+  runWithSE(*M, "foo", [](Function &F, LoopInfo &LI, ScalarEvolution &SE) {
+    const SCEV *A = SE.getSCEV(getArgByName(F, "a"));
+    const SCEV *One = SE.getConstant(A->getType(), 1);
+    const SCEV *Two = SE.getConstant(A->getType(), 2);
+    EXPECT_EQ(One, SE.getUDivExpr(A, A));
+    EXPECT_EQ(One, SE.getUDivExpr(Two, Two));
+    EXPECT_EQ(One, SE.getUDivExactExpr(A, A));
+    EXPECT_EQ(One, SE.getUDivExactExpr(Two, Two));
+  });
+}
+
+
 TEST_F(ScalarEvolutionsTest, SCEVUDivExactPreserveNUW) {
   LLVMContext C;
   SMDiagnostic Err;

>From d03ffa51a12239874c79209ba4e3171bf73d7a29 Mon Sep 17 00:00:00 2001
From: bababuck <buchner.ryan at gmail.com>
Date: Tue, 5 May 2026 10:38:57 -0700
Subject: [PATCH 5/6] [SCEV] Refactor with NUW propogation and other fixes

---
 llvm/lib/Analysis/ScalarEvolution.cpp         | 108 +++++++-----------
 .../Analysis/ScalarEvolutionTest.cpp          |  45 +++-----
 2 files changed, 59 insertions(+), 94 deletions(-)

diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index b053e3122cf10..52e08a25f38b8 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -3750,80 +3750,56 @@ const SCEV *ScalarEvolution::getUDivExactExpr(SCEVUse LHS, SCEVUse RHS) {
   // just deal with u/exact (multiply, constant). See SCEVDivision towards the
   // end of this file for inspiration.
 
+  SmallVector<SCEVUse> RHSOperands, LHSOperands;
   if (const SCEVMulExpr *RHSMul = dyn_cast<SCEVMulExpr>(RHS);
-      RHSMul && RHSMul->hasNoUnsignedWrap() &&
-      isa<SCEVMulExpr, SCEVConstant, SCEVUnknown>(LHS)) {
-    const SCEV *CurrSCEV = LHS;
-    SmallVector<SCEVUse, 2> Operands;
-    bool OnlyNonZero = true;
-    for (SCEVUse Op : RHSMul->operands()) {
-      if (!OnlyNonZero) {
-        Operands.push_back(Op);
-        continue;
-      }
-      const SCEV *NewSCEV = getUDivExactExpr(CurrSCEV, Op);
-      if (isa<SCEVUDivExpr>(NewSCEV)) {
-        Operands.push_back(Op);
-        continue;
-      }
-      if (!isa<SCEVConstant>(Op)) // Constant is guaranteed non-zero is MulExpr
-        OnlyNonZero = false;
-      CurrSCEV = NewSCEV;
-    }
-    if (Operands.size())
-      return getUDivExpr(
-          CurrSCEV,
-          getMulExpr(Operands, OnlyNonZero
-                                   ? RHSMul->getNoWrapFlags() & SCEV::FlagNUW
-                                   : SCEVNoWrapFlags::FlagAnyWrap));
-    else
-      return CurrSCEV;
-  }
-
-  const SCEVMulExpr *Mul = dyn_cast<SCEVMulExpr>(LHS);
-  if (!Mul || !Mul->hasNoUnsignedWrap())
-    return getUDivExpr(LHS, RHS);
+      RHSMul && RHSMul->hasNoUnsignedWrap())
+    RHSOperands = to_vector(RHSMul->operands());
+  else
+    RHSOperands.push_back(RHS);
+  if (const SCEVMulExpr *LHSMul = dyn_cast<SCEVMulExpr>(LHS);
+      LHSMul && LHSMul->hasNoUnsignedWrap())
+    LHSOperands = to_vector(LHSMul->operands());
+  else
+    LHSOperands.push_back(LHS);
 
-  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, Mul->getNoWrapFlags() & SCEV::FlagNUW);
-      }
+  for (unsigned RHSIdx = 0; RHSIdx < RHSOperands.size(); ++RHSIdx) {
+    SCEVUse RHSOp = RHSOperands[RHSIdx];
+    if (RHSOp->isOne())
+      continue;
+    if (const SCEVConstant *RHSCst = dyn_cast<SCEVConstant>(RHSOp)) {
+      // 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>(LHSOperands[0])) {
+        if (LHSCst == RHSCst) {
+          RHSOperands[RHSIdx] = getConstant(RHSOp->getType(), 1);
+          LHSOperands[0] = getConstant(RHSOp->getType(), 1);
+          continue;
+        }
 
-      // 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, Mul->getNoWrapFlags() & SCEV::FlagNUW);
-        RHS = RHSCst;
-        Mul = dyn_cast<SCEVMulExpr>(LHS);
-        if (!Mul)
-          return getUDivExactExpr(LHS, RHS);
+        // 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)) {
+          LHSOperands[0] =
+              cast<SCEVConstant>(getConstant(LHSCst->getAPInt().udiv(Factor)));
+          RHSOperands[RHSIdx] =
+              cast<SCEVConstant>(getConstant(RHSCst->getAPInt().udiv(Factor)));
+        }
+        continue;
       }
     }
-  }
 
-  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);
+    for (unsigned LHSIdx = 0; LHSIdx < LHSOperands.size(); ++LHSIdx) {
+      if (LHSOperands[LHSIdx] == RHSOp) {
+        RHSOperands[RHSIdx] = getConstant(RHSOp->getType(), 1);
+        LHSOperands[LHSIdx] = getConstant(LHSOperands[LHSIdx]->getType(), 1);
+        break;
+      }
     }
   }
-
-  return getUDivExpr(LHS, RHS);
+  return getUDivExpr(getMulExpr(LHSOperands, SCEV::FlagNUW),
+                     getMulExpr(RHSOperands, SCEV::FlagNUW));
 }
 
 /// Get an add recurrence expression for the specified loop.  Simplify the
diff --git a/llvm/unittests/Analysis/ScalarEvolutionTest.cpp b/llvm/unittests/Analysis/ScalarEvolutionTest.cpp
index 2c2e2a0c03a69..c92620904f7e3 100644
--- a/llvm/unittests/Analysis/ScalarEvolutionTest.cpp
+++ b/llvm/unittests/Analysis/ScalarEvolutionTest.cpp
@@ -1670,7 +1670,7 @@ TEST_F(ScalarEvolutionsTest, SCEVUDivExactPreserveNUW) {
     const SCEVMulExpr *NonConstFactor =
         cast<const SCEVMulExpr>(SE.getUDivExactExpr(LHSMulNUW, B));
     SmallVector<SCEVUse, 2> NonConstFactorResOps = {A, Twelve};
-    EXPECT_FALSE(NonConstFactor->hasNoUnsignedWrap());
+    EXPECT_TRUE(NonConstFactor->hasNoUnsignedWrap());
     EXPECT_EQ(NonConstFactor, SE.getMulExpr(NonConstFactorResOps));
   });
 }
@@ -1739,10 +1739,10 @@ TEST_F(ScalarEvolutionsTest, SCEVUDivExactNUWMulDivisor) {
       auto *A_ABC = SE.getUDivExactExpr(A, ABC);
       auto *Denom = dyn_cast<const SCEVMulExpr>(A_ABC->operands().back());
       EXPECT_TRUE(Denom);
-      EXPECT_FALSE(!Denom || Denom->hasNoUnsignedWrap());
+      EXPECT_TRUE(Denom && Denom->hasNoUnsignedWrap());
       SmallVector<SCEVUse, 2> BCOps = {B, C};
-      const SCEV *BCNoNUW = SE.getMulExpr(BCOps);
-      EXPECT_EQ(A_ABC, SE.getUDivExpr(One, BCNoNUW));
+      const SCEV *BC = SE.getMulExpr(BCOps, SCEV::FlagNUW);
+      EXPECT_EQ(A_ABC, SE.getUDivExpr(One, BC));
     }
 
     // B / (A * B * C)
@@ -1750,10 +1750,10 @@ TEST_F(ScalarEvolutionsTest, SCEVUDivExactNUWMulDivisor) {
       auto *B_ABC = SE.getUDivExactExpr(B, ABC);
       auto *Denom = dyn_cast<const SCEVMulExpr>(B_ABC->operands().back());
       EXPECT_TRUE(Denom);
-      EXPECT_FALSE(!Denom || Denom->hasNoUnsignedWrap());
+      EXPECT_TRUE(Denom && Denom->hasNoUnsignedWrap());
       SmallVector<SCEVUse, 2> ACOps = {A, C};
-      const SCEV *ACNoNUW = SE.getMulExpr(ACOps);
-      EXPECT_EQ(B_ABC, SE.getUDivExpr(One, ACNoNUW));
+      const SCEV *AC = SE.getMulExpr(ACOps, SCEV::FlagNUW);
+      EXPECT_EQ(B_ABC, SE.getUDivExpr(One, AC));
     }
 
     // C / (A * B * C)
@@ -1761,35 +1761,24 @@ TEST_F(ScalarEvolutionsTest, SCEVUDivExactNUWMulDivisor) {
       auto *C_ABC = SE.getUDivExactExpr(C, ABC);
       auto *Denom = dyn_cast<const SCEVMulExpr>(C_ABC->operands().back());
       EXPECT_TRUE(Denom);
-      EXPECT_FALSE(!Denom || Denom->hasNoUnsignedWrap());
+      EXPECT_TRUE(Denom && Denom->hasNoUnsignedWrap());
       SmallVector<SCEVUse, 2> ABOps = {A, B};
-      const SCEV *ABNoNUW = SE.getMulExpr(ABOps);
-      EXPECT_EQ(C_ABC, SE.getUDivExpr(One, ABNoNUW));
+      const SCEV *AB = SE.getMulExpr(ABOps, SCEV::FlagNUW);
+      EXPECT_EQ(C_ABC, SE.getUDivExpr(One, AB));
     }
 
     // (C * E) / (C * D * E)
-    {
-      auto *CE_CDE = SE.getUDivExactExpr(CE, CDE);
-      auto *Denom = dyn_cast<const SCEVMulExpr>(CE_CDE->operands().back());
-      EXPECT_TRUE(Denom);
-      EXPECT_FALSE(!Denom || Denom->hasNoUnsignedWrap());
-      SmallVector<SCEVUse, 2> DEOps = {D, E};
-      const SCEV *DENoNUW = SE.getMulExpr(DEOps);
-      SmallVector<SCEVUse, 2> CDOps = {C, D};
-      const SCEV *CDNoNUW = SE.getMulExpr(CDOps);
-      EXPECT_TRUE(CE_CDE == SE.getUDivExpr(E, DENoNUW) ||
-                  CE_CDE == SE.getUDivExpr(C, CDNoNUW));
-    }
+    EXPECT_TRUE(SE.getUDivExactExpr(CE, CDE) == SE.getUDivExpr(One, D));
 
     // (B * D) / (A * B * E)
     {
       auto *BD_ABE = SE.getUDivExactExpr(BD, ABE);
       auto *Denom = dyn_cast<const SCEVMulExpr>(BD_ABE->operands().back());
       EXPECT_TRUE(Denom);
-      EXPECT_FALSE(!Denom || Denom->hasNoUnsignedWrap());
+      EXPECT_TRUE(Denom && Denom->hasNoUnsignedWrap());
       SmallVector<SCEVUse, 2> AEOps = {A, E};
-      const SCEV *AENoNUW = SE.getMulExpr(AEOps);
-      EXPECT_EQ(BD_ABE, SE.getUDivExpr(D, AENoNUW));
+      const SCEV *AE = SE.getMulExpr(AEOps, SCEV::FlagNUW);
+      EXPECT_EQ(BD_ABE, SE.getUDivExpr(D, AE));
     }
 
     // (3 * A) / (3 * B)
@@ -1822,10 +1811,10 @@ TEST_F(ScalarEvolutionsTest, SCEVUDivExactNUWMulDivisor) {
       SmallVector<SCEVUse, 2> D2Ops = {D, Two};
       const SCEV *D2 = SE.getMulExpr(D2Ops, SCEV::FlagNUW);
       EXPECT_EQ(D6_ABCE3, SE.getUDivExpr(D2, ABCE));
-      // No NUW
-      EXPECT_EQ(SE.getUDivExactExpr(A, ABCDNoNUW),
-                SE.getUDivExpr(A, ABCDNoNUW));
     }
+
+    // No NUW
+    EXPECT_EQ(SE.getUDivExactExpr(A, ABCDNoNUW), SE.getUDivExpr(A, ABCDNoNUW));
   });
 }
 

>From 31ba6621cba890921f44ce8e4233558f1b7ee7ae Mon Sep 17 00:00:00 2001
From: bababuck <buchner.ryan at gmail.com>
Date: Tue, 5 May 2026 10:48:07 -0700
Subject: [PATCH 6/6] Lint fixes

---
 llvm/lib/Analysis/ScalarEvolution.cpp           | 1 -
 llvm/unittests/Analysis/ScalarEvolutionTest.cpp | 1 -
 2 files changed, 2 deletions(-)

diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index 52e08a25f38b8..716346a7f73db 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -3565,7 +3565,6 @@ const SCEV *ScalarEvolution::getUDivExpr(SCEVUse LHS, SCEVUse RHS) {
   if (LHS == RHS)
     return getConstant(LHS->getType(), 1);
 
-
   if (const SCEVConstant *RHSC = dyn_cast<SCEVConstant>(RHS)) {
     if (RHSC->getValue()->isOne())
       return LHS;                               // X udiv 1 --> x
diff --git a/llvm/unittests/Analysis/ScalarEvolutionTest.cpp b/llvm/unittests/Analysis/ScalarEvolutionTest.cpp
index c92620904f7e3..b3cc2991104b8 100644
--- a/llvm/unittests/Analysis/ScalarEvolutionTest.cpp
+++ b/llvm/unittests/Analysis/ScalarEvolutionTest.cpp
@@ -1602,7 +1602,6 @@ TEST_F(ScalarEvolutionsTest, SCEVUDivEqual) {
   });
 }
 
-
 TEST_F(ScalarEvolutionsTest, SCEVUDivExactPreserveNUW) {
   LLVMContext C;
   SMDiagnostic Err;



More information about the llvm-commits mailing list