[llvm] [SCEV] Cancel common addend/multiplier in SimplifyICmpOperands (PR #205014)

Aleksandr Popov via llvm-commits llvm-commits at lists.llvm.org
Tue Jun 30 07:46:47 PDT 2026


https://github.com/aleks-tmb updated https://github.com/llvm/llvm-project/pull/205014

>From 3569b3009518503c61428c5c49050467520df041 Mon Sep 17 00:00:00 2001
From: Aleksandr Popov <apopov at azul.com>
Date: Sun, 21 Jun 2026 21:23:10 +0000
Subject: [PATCH 1/2] [SCEV] Cancel common addend/multiplier in
 SimplifyICmpOperands

Simplify:
  (K + A) pred (K + B)  ->  A pred B
  (C * A) pred (C * B)  ->  A pred B

when no-wrap flags allow it: NSW for signed predicates, NUW for
unsigned. Also requires C > 0 for signed and C != 0 for unsigned
in the multiplication case.

Adds commutative NUW/NSW pattern matchers for Add and Mul to
ScalarEvolutionPatternMatch.
---
 .../Analysis/ScalarEvolutionPatternMatch.h    | 21 +++++++++++
 llvm/lib/Analysis/ScalarEvolution.cpp         | 35 +++++++++++++++++++
 .../Analysis/ScalarEvolution/add-like-or.ll   |  8 ++---
 .../LoopUnroll/X86/high-cost-expansion.ll     | 33 +++++++++++++++--
 4 files changed, 90 insertions(+), 7 deletions(-)

diff --git a/llvm/include/llvm/Analysis/ScalarEvolutionPatternMatch.h b/llvm/include/llvm/Analysis/ScalarEvolutionPatternMatch.h
index 5aa54e1d7fb37..26cc0609241f3 100644
--- a/llvm/include/llvm/Analysis/ScalarEvolutionPatternMatch.h
+++ b/llvm/include/llvm/Analysis/ScalarEvolutionPatternMatch.h
@@ -263,6 +263,27 @@ m_scev_c_NUWMul(const Op0_t &Op0, const Op1_t &Op1) {
                                                                        Op1);
 }
 
+template <typename Op0_t, typename Op1_t>
+inline SCEVBinaryExpr_match<SCEVMulExpr, Op0_t, Op1_t, SCEV::FlagNSW, true>
+m_scev_c_NSWMul(const Op0_t &Op0, const Op1_t &Op1) {
+  return m_scev_Binary<SCEVMulExpr, Op0_t, Op1_t, SCEV::FlagNSW, true>(Op0,
+                                                                       Op1);
+}
+
+template <typename Op0_t, typename Op1_t>
+inline SCEVBinaryExpr_match<SCEVAddExpr, Op0_t, Op1_t, SCEV::FlagNUW, true>
+m_scev_c_NUWAdd(const Op0_t &Op0, const Op1_t &Op1) {
+  return m_scev_Binary<SCEVAddExpr, Op0_t, Op1_t, SCEV::FlagNUW, true>(Op0,
+                                                                       Op1);
+}
+
+template <typename Op0_t, typename Op1_t>
+inline SCEVBinaryExpr_match<SCEVAddExpr, Op0_t, Op1_t, SCEV::FlagNSW, true>
+m_scev_c_NSWAdd(const Op0_t &Op0, const Op1_t &Op1) {
+  return m_scev_Binary<SCEVAddExpr, Op0_t, Op1_t, SCEV::FlagNSW, true>(Op0,
+                                                                       Op1);
+}
+
 template <typename Op0_t, typename Op1_t>
 inline SCEVBinaryExpr_match<SCEVUDivExpr, Op0_t, Op1_t>
 m_scev_UDiv(const Op0_t &Op0, const Op1_t &Op1) {
diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index c0cdce982e623..afb52c2120501 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -11123,6 +11123,41 @@ bool ScalarEvolution::SimplifyICmpOperands(CmpPredicate &Pred, SCEVUse &LHS,
     Changed = true;
   }
 
+  // (K + A) pred (K + B) --> A pred B
+  // when both adds have the appropriate no-wrap flag.
+  {
+    const SCEVConstant *C = nullptr;
+    if ((ICmpInst::isSigned(Pred) &&
+         match(LHS, m_scev_c_NSWAdd(m_SCEVConstant(C), m_SCEV(NewLHS))) &&
+         match(RHS, m_scev_c_NSWAdd(m_scev_Specific(C), m_SCEV(NewRHS)))) ||
+        (ICmpInst::isUnsigned(Pred) &&
+         match(LHS, m_scev_c_NUWAdd(m_SCEVConstant(C), m_SCEV(NewLHS))) &&
+         match(RHS, m_scev_c_NUWAdd(m_scev_Specific(C), m_SCEV(NewRHS))))) {
+      LHS = NewLHS;
+      RHS = NewRHS;
+      Changed = true;
+    }
+  }
+
+  // (C * A) pred (C * B) --> A pred B
+  // For signed predicates, C must be positive and both muls must be NSW.
+  // For unsigned predicates, C must be non-zero and both muls must be NUW.
+  {
+    const SCEVConstant *C = nullptr;
+    if ((ICmpInst::isSigned(Pred) &&
+         match(LHS, m_scev_c_NSWMul(m_SCEVConstant(C), m_SCEV(NewLHS))) &&
+         match(RHS, m_scev_c_NSWMul(m_scev_Specific(C), m_SCEV(NewRHS))) &&
+         C->getAPInt().isStrictlyPositive()) ||
+        (ICmpInst::isUnsigned(Pred) &&
+         match(LHS, m_scev_c_NUWMul(m_SCEVConstant(C), m_SCEV(NewLHS))) &&
+         match(RHS, m_scev_c_NUWMul(m_scev_Specific(C), m_SCEV(NewRHS))) &&
+         !C->getAPInt().isZero())) {
+      LHS = NewLHS;
+      RHS = NewRHS;
+      Changed = true;
+    }
+  }
+
   // If we're comparing an addrec with a value which is loop-invariant in the
   // addrec's loop, put the addrec on the left. Also make a dominance check,
   // as both operands could be addrecs loop-invariant in each other's loop.
diff --git a/llvm/test/Analysis/ScalarEvolution/add-like-or.ll b/llvm/test/Analysis/ScalarEvolution/add-like-or.ll
index 69059d5494e41..2da4fac0cbd87 100644
--- a/llvm/test/Analysis/ScalarEvolution/add-like-or.ll
+++ b/llvm/test/Analysis/ScalarEvolution/add-like-or.ll
@@ -90,13 +90,13 @@ define void @mask-high(i64 %arg, ptr dereferenceable(4) %arg1) {
 ; CHECK-NEXT:    %i4 = or disjoint i64 1, %i3
 ; CHECK-NEXT:    --> (1 + (16 * (%arg /u 16))<nuw>)<nuw><nsw> U: [1,-14) S: [-9223372036854775807,9223372036854775794)
 ; CHECK-NEXT:    %i7 = phi i64 [ %i4, %bb ], [ %i8, %bb6 ]
-; CHECK-NEXT:    --> {(1 + (16 * (%arg /u 16))<nuw>)<nuw><nsw>,+,1}<%bb6> U: full-set S: full-set Exits: ((sext i32 %i to i64) smax (1 + (16 * (%arg /u 16))<nuw>)<nuw><nsw>) LoopDispositions: { %bb6: Computable }
+; CHECK-NEXT:    --> {(1 + (16 * (%arg /u 16))<nuw>)<nuw><nsw>,+,1}<%bb6> U: full-set S: full-set Exits: (sext i32 %i to i64) LoopDispositions: { %bb6: Computable }
 ; CHECK-NEXT:    %i8 = add i64 %i7, 1
-; CHECK-NEXT:    --> {(2 + (16 * (%arg /u 16))<nuw>)<nuw><nsw>,+,1}<%bb6> U: full-set S: full-set Exits: (1 + ((sext i32 %i to i64) smax (1 + (16 * (%arg /u 16))<nuw>)<nuw><nsw>))<nsw> LoopDispositions: { %bb6: Computable }
+; CHECK-NEXT:    --> {(2 + (16 * (%arg /u 16))<nuw>)<nuw><nsw>,+,1}<%bb6> U: full-set S: full-set Exits: (1 + (sext i32 %i to i64))<nsw> LoopDispositions: { %bb6: Computable }
 ; CHECK-NEXT:  Determining loop execution counts for: @mask-high
-; CHECK-NEXT:  Loop %bb6: backedge-taken count is (-1 + (-16 * (%arg /u 16)) + ((sext i32 %i to i64) smax (1 + (16 * (%arg /u 16))<nuw>)<nuw><nsw>))
+; CHECK-NEXT:  Loop %bb6: backedge-taken count is (-1 + (sext i32 %i to i64) + (-16 * (%arg /u 16)))
 ; CHECK-NEXT:  Loop %bb6: constant max backedge-taken count is i64 -9223372034707292162
-; CHECK-NEXT:  Loop %bb6: symbolic max backedge-taken count is (-1 + (-16 * (%arg /u 16)) + ((sext i32 %i to i64) smax (1 + (16 * (%arg /u 16))<nuw>)<nuw><nsw>))
+; CHECK-NEXT:  Loop %bb6: symbolic max backedge-taken count is (-1 + (sext i32 %i to i64) + (-16 * (%arg /u 16)))
 ; CHECK-NEXT:  Loop %bb6: Trip multiple is 1
 ;
 bb:
diff --git a/llvm/test/Transforms/LoopUnroll/X86/high-cost-expansion.ll b/llvm/test/Transforms/LoopUnroll/X86/high-cost-expansion.ll
index d5d4319b041a2..245b241dcf811 100644
--- a/llvm/test/Transforms/LoopUnroll/X86/high-cost-expansion.ll
+++ b/llvm/test/Transforms/LoopUnroll/X86/high-cost-expansion.ll
@@ -11,12 +11,39 @@ define void @mask-high(i64 %arg, ptr dereferenceable(4) %arg1) {
 ; CHECK-NEXT:    [[I5:%.*]] = icmp sgt i64 [[I4]], [[I2]]
 ; CHECK-NEXT:    br i1 [[I5]], label [[BB10:%.*]], label [[BB6_PREHEADER:%.*]]
 ; CHECK:       bb6.preheader:
+; CHECK-NEXT:    [[TMP0:%.*]] = sub i64 [[I2]], [[I3]]
+; CHECK-NEXT:    [[TMP1:%.*]] = add i64 [[I2]], -1
+; CHECK-NEXT:    [[TMP2:%.*]] = sub i64 [[TMP1]], [[I3]]
+; CHECK-NEXT:    [[XTRAITER:%.*]] = and i64 [[TMP0]], 7
+; CHECK-NEXT:    [[LCMP_MOD:%.*]] = icmp ne i64 [[XTRAITER]], 0
+; CHECK-NEXT:    br i1 [[LCMP_MOD]], label [[BB6_PROL_PREHEADER:%.*]], label [[BB6_PROL_LOOPEXIT:%.*]]
+; CHECK:       bb6.prol.preheader:
 ; CHECK-NEXT:    br label [[BB6:%.*]]
-; CHECK:       bb6:
-; CHECK-NEXT:    [[I7:%.*]] = phi i64 [ [[I8:%.*]], [[BB6]] ], [ [[I4]], [[BB6_PREHEADER]] ]
+; CHECK:       bb6.prol:
+; CHECK-NEXT:    [[I7:%.*]] = phi i64 [ [[I8:%.*]], [[BB6]] ], [ [[I4]], [[BB6_PROL_PREHEADER]] ]
+; CHECK-NEXT:    [[PROL_ITER:%.*]] = phi i64 [ 0, [[BB6_PROL_PREHEADER]] ], [ [[PROL_ITER_NEXT:%.*]], [[BB6]] ]
 ; CHECK-NEXT:    [[I8]] = add i64 [[I7]], 1
 ; CHECK-NEXT:    [[I9:%.*]] = icmp slt i64 [[I7]], [[I2]]
-; CHECK-NEXT:    br i1 [[I9]], label [[BB6]], label [[BB10_LOOPEXIT:%.*]]
+; CHECK-NEXT:    [[PROL_ITER_NEXT]] = add i64 [[PROL_ITER]], 1
+; CHECK-NEXT:    [[PROL_ITER_CMP:%.*]] = icmp ne i64 [[PROL_ITER_NEXT]], [[XTRAITER]]
+; CHECK-NEXT:    br i1 [[PROL_ITER_CMP]], label [[BB6]], label [[BB6_PROL_LOOPEXIT_UNR_LCSSA:%.*]], !llvm.loop [[LOOP0:![0-9]+]]
+; CHECK:       bb6.prol.loopexit.unr-lcssa:
+; CHECK-NEXT:    [[I7_UNR_PH:%.*]] = phi i64 [ [[I8]], [[BB6]] ]
+; CHECK-NEXT:    br label [[BB6_PROL_LOOPEXIT]]
+; CHECK:       bb6.prol.loopexit:
+; CHECK-NEXT:    [[I7_UNR:%.*]] = phi i64 [ [[I4]], [[BB6_PREHEADER]] ], [ [[I7_UNR_PH]], [[BB6_PROL_LOOPEXIT_UNR_LCSSA]] ]
+; CHECK-NEXT:    [[TMP3:%.*]] = icmp ult i64 [[TMP2]], 7
+; CHECK-NEXT:    br i1 [[TMP3]], label [[BB10_LOOPEXIT:%.*]], label [[BB6_PREHEADER_NEW:%.*]]
+; CHECK:       bb6.preheader.new:
+; CHECK-NEXT:    br label [[BB7:%.*]]
+; CHECK:       bb6:
+; CHECK-NEXT:    [[I10:%.*]] = phi i64 [ [[I7_UNR]], [[BB6_PREHEADER_NEW]] ], [ [[I8_7:%.*]], [[BB7]] ]
+; CHECK-NEXT:    [[I8_6:%.*]] = add i64 [[I10]], 7
+; CHECK-NEXT:    [[I8_7]] = add i64 [[I10]], 8
+; CHECK-NEXT:    [[I9_7:%.*]] = icmp slt i64 [[I8_6]], [[I2]]
+; CHECK-NEXT:    br i1 [[I9_7]], label [[BB7]], label [[BB10_LOOPEXIT_UNR_LCSSA:%.*]]
+; CHECK:       bb10.loopexit.unr-lcssa:
+; CHECK-NEXT:    br label [[BB10_LOOPEXIT]]
 ; CHECK:       bb10.loopexit:
 ; CHECK-NEXT:    br label [[BB10]]
 ; CHECK:       bb10:

>From f9977f967f81037c8e5aaf837c75c4ac8a13f42f Mon Sep 17 00:00:00 2001
From: Aleksandr Popov <apopov at azul.com>
Date: Tue, 30 Jun 2026 14:44:54 +0000
Subject: [PATCH 2/2] Add unit test

---
 .../Analysis/ScalarEvolutionTest.cpp          | 88 +++++++++++++++++++
 1 file changed, 88 insertions(+)

diff --git a/llvm/unittests/Analysis/ScalarEvolutionTest.cpp b/llvm/unittests/Analysis/ScalarEvolutionTest.cpp
index dcffed39d9381..c41360a930976 100644
--- a/llvm/unittests/Analysis/ScalarEvolutionTest.cpp
+++ b/llvm/unittests/Analysis/ScalarEvolutionTest.cpp
@@ -1940,6 +1940,94 @@ TEST_F(ScalarEvolutionsTest, SimplifyICmpOperands) {
       EXPECT_FALSE(SE.SimplifyICmpOperands(NewPred, NewLHS, NewRHS));
     }
   });
+
+  // Cancel common constant addend: (K + A) pred (K + B) --> A pred B
+  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 *K1 = SE.getConstant(A->getType(), 42);
+    const SCEV *K2 = SE.getConstant(A->getType(), 99);
+
+    // (42 + %a)<nsw> slt (42 + %b)<nsw>  -->  %a slt %b
+    {
+      const SCEV *K1pA = SE.getAddExpr(K1, A, SCEV::FlagNSW);
+      const SCEV *K1pB = SE.getAddExpr(K1, B, SCEV::FlagNSW);
+      CmpPredicate NewPred = ICmpInst::ICMP_SLT;
+      SCEVUse NewLHS = K1pA;
+      SCEVUse NewRHS = K1pB;
+      EXPECT_TRUE(SE.SimplifyICmpOperands(NewPred, NewLHS, NewRHS));
+      EXPECT_EQ(NewPred, ICmpInst::ICMP_SLT);
+      EXPECT_EQ(NewLHS, A);
+      EXPECT_EQ(NewRHS, B);
+    }
+
+    // (42 + %a)<nuw> ult (42 + %b)<nuw>  -->  %a ult %b
+    {
+      const SCEV *K1pA = SE.getAddExpr(K1, A, SCEV::FlagNUW);
+      const SCEV *K1pB = SE.getAddExpr(K1, B, SCEV::FlagNUW);
+      CmpPredicate NewPred = ICmpInst::ICMP_ULT;
+      SCEVUse NewLHS = K1pA;
+      SCEVUse NewRHS = K1pB;
+      EXPECT_TRUE(SE.SimplifyICmpOperands(NewPred, NewLHS, NewRHS));
+      EXPECT_EQ(NewPred, ICmpInst::ICMP_ULT);
+      EXPECT_EQ(NewLHS, A);
+      EXPECT_EQ(NewRHS, B);
+    }
+
+    // (42 + %a)<nsw> slt (99 + %b)<nsw>  -->  no simplification (K mismatch)
+    {
+      const SCEV *K1pA = SE.getAddExpr(K1, A, SCEV::FlagNSW);
+      const SCEV *K2pB = SE.getAddExpr(K2, B, SCEV::FlagNSW);
+      CmpPredicate NewPred = ICmpInst::ICMP_SLT;
+      SCEVUse NewLHS = K1pA;
+      SCEVUse NewRHS = K2pB;
+      EXPECT_FALSE(SE.SimplifyICmpOperands(NewPred, NewLHS, NewRHS));
+    }
+  });
+
+  // Cancel common constant multiplier: (C * A) pred (C * B) --> A pred B
+  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 *PosC = SE.getConstant(A->getType(), 3);
+    const SCEV *NegC = SE.getConstant(A->getType(), -3, /*isSigned=*/true);
+
+    // (3 * %a)<nsw> slt (3 * %b)<nsw>  -->  %a slt %b  (C > 0)
+    {
+      const SCEV *PosCA = SE.getMulExpr(PosC, A, SCEV::FlagNSW);
+      const SCEV *PosCB = SE.getMulExpr(PosC, B, SCEV::FlagNSW);
+      CmpPredicate NewPred = ICmpInst::ICMP_SLT;
+      SCEVUse NewLHS = PosCA;
+      SCEVUse NewRHS = PosCB;
+      EXPECT_TRUE(SE.SimplifyICmpOperands(NewPred, NewLHS, NewRHS));
+      EXPECT_EQ(NewPred, ICmpInst::ICMP_SLT);
+      EXPECT_EQ(NewLHS, A);
+      EXPECT_EQ(NewRHS, B);
+    }
+
+    // (3 * %a)<nuw> ult (3 * %b)<nuw>  -->  %a ult %b  (C != 0)
+    {
+      const SCEV *PosCA = SE.getMulExpr(PosC, A, SCEV::FlagNUW);
+      const SCEV *PosCB = SE.getMulExpr(PosC, B, SCEV::FlagNUW);
+      CmpPredicate NewPred = ICmpInst::ICMP_ULT;
+      SCEVUse NewLHS = PosCA;
+      SCEVUse NewRHS = PosCB;
+      EXPECT_TRUE(SE.SimplifyICmpOperands(NewPred, NewLHS, NewRHS));
+      EXPECT_EQ(NewPred, ICmpInst::ICMP_ULT);
+      EXPECT_EQ(NewLHS, A);
+      EXPECT_EQ(NewRHS, B);
+    }
+
+    // (-3 * %a)<nsw> slt (-3 * %b)<nsw>  -->  no simplification (C < 0)
+    {
+      const SCEV *NegCA = SE.getMulExpr(NegC, A, SCEV::FlagNSW);
+      const SCEV *NegCB = SE.getMulExpr(NegC, B, SCEV::FlagNSW);
+      CmpPredicate NewPred = ICmpInst::ICMP_SLT;
+      SCEVUse NewLHS = NegCA;
+      SCEVUse NewRHS = NegCB;
+      EXPECT_FALSE(SE.SimplifyICmpOperands(NewPred, NewLHS, NewRHS));
+    }
+  });
 }
 
 }  // end namespace llvm



More information about the llvm-commits mailing list