[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