[llvm] [SCEV] Cache recursive complexity comparisons (PR #223723)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Sep 17 04:47:00 PDT 2026
https://github.com/ErikHogeman updated https://github.com/llvm/llvm-project/pull/223723
>From 64655cfd80771ecd193b2a92a91ffadb2685ffad Mon Sep 17 00:00:00 2001
From: Erik Hogeman <erik.hogeman at arm.com>
Date: Tue, 15 Sep 2026 16:21:28 +0200
Subject: [PATCH] [SCEV] Cache recursive complexity comparisons
Comparisons of SCEVs with shared subexpressions can recursively revisit
the same pairs, causing exponential work during IndVarSimplify.
Memoize results within each top-level comparison. Include recursion depth
in the cache key because it can affect the comparison result.
Add a regression with interdependent multiplication chains that expose
the compile-time blowup.
Assisted-by: OpenAI Codex
---
llvm/lib/Analysis/ScalarEvolution.cpp | 51 +++++--
.../scev-complexity-exponential.ll | 133 ++++++++++++++++++
2 files changed, 171 insertions(+), 13 deletions(-)
create mode 100644 llvm/test/Transforms/IndVarSimplify/scev-complexity-exponential.ll
diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index 39cd6ff1ec473..7b8960177264a 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -654,9 +654,14 @@ static int CompareValueComplexity(const LoopInfo *const LI, Value *LV,
// more efficient.
// If the max analysis depth was reached, return std::nullopt, assuming we do
// not know if they are equivalent for sure.
+using SCEVComplexityCache =
+ SmallDenseMap<std::tuple<const SCEV *, const SCEV *, unsigned>,
+ std::optional<int>, 8>;
+
static std::optional<int>
-CompareSCEVComplexity(const LoopInfo *const LI, const SCEV *LHS,
- const SCEV *RHS, DominatorTree &DT, unsigned Depth = 0) {
+CompareSCEVComplexityImpl(SCEVComplexityCache &Cache, const LoopInfo *const LI,
+ const SCEV *LHS, const SCEV *RHS, DominatorTree &DT,
+ unsigned Depth) {
// Fast-path: SCEVs are uniqued so we can do a quick equality check.
if (LHS == RHS)
return 0;
@@ -669,6 +674,18 @@ CompareSCEVComplexity(const LoopInfo *const LI, const SCEV *LHS,
if (Depth > MaxSCEVCompareDepth)
return std::nullopt;
+ // Include Depth in the Key because a comparison can produce a different
+ // result at different Depth levels.
+ auto Key = std::make_tuple(LHS, RHS, Depth);
+ auto It = Cache.find(Key);
+ if (It != Cache.end())
+ return It->second;
+
+ auto CacheResult = [&Cache, &Key](std::optional<int> Result) {
+ Cache.try_emplace(Key, Result);
+ return Result;
+ };
+
// Aside from the getSCEVType() ordering, the particular ordering
// isn't very important except that it's beneficial to be consistent,
// so that (a + b) and (b + a) don't end up as different expressions.
@@ -679,7 +696,7 @@ CompareSCEVComplexity(const LoopInfo *const LI, const SCEV *LHS,
int X =
CompareValueComplexity(LI, LU->getValue(), RU->getValue(), Depth + 1);
- return X;
+ return CacheResult(X);
}
case scConstant: {
@@ -691,14 +708,14 @@ CompareSCEVComplexity(const LoopInfo *const LI, const SCEV *LHS,
const APInt &RA = RC->getAPInt();
unsigned LBitWidth = LA.getBitWidth(), RBitWidth = RA.getBitWidth();
if (LBitWidth != RBitWidth)
- return (int)LBitWidth - (int)RBitWidth;
- return LA.ult(RA) ? -1 : 1;
+ return CacheResult((int)LBitWidth - (int)RBitWidth);
+ return CacheResult(LA.ult(RA) ? -1 : 1);
}
case scVScale: {
const auto *LTy = cast<IntegerType>(cast<SCEVVScale>(LHS)->getType());
const auto *RTy = cast<IntegerType>(cast<SCEVVScale>(RHS)->getType());
- return LTy->getBitWidth() - RTy->getBitWidth();
+ return CacheResult(LTy->getBitWidth() - RTy->getBitWidth());
}
case scAddRecExpr: {
@@ -713,10 +730,10 @@ CompareSCEVComplexity(const LoopInfo *const LI, const SCEV *LHS,
const BasicBlock *LHead = LLoop->getHeader(), *RHead = RLoop->getHeader();
assert(LHead != RHead && "Two loops share the same header?");
if (DT.dominates(LHead, RHead))
- return 1;
+ return CacheResult(1);
assert(DT.dominates(RHead, LHead) &&
"No dominance between recurrences used by one SCEV?");
- return -1;
+ return CacheResult(-1);
}
[[fallthrough]];
@@ -740,15 +757,15 @@ CompareSCEVComplexity(const LoopInfo *const LI, const SCEV *LHS,
// Lexicographically compare n-ary-like expressions.
unsigned LNumOps = LOps.size(), RNumOps = ROps.size();
if (LNumOps != RNumOps)
- return (int)LNumOps - (int)RNumOps;
+ return CacheResult((int)LNumOps - (int)RNumOps);
for (unsigned i = 0; i != LNumOps; ++i) {
- auto X = CompareSCEVComplexity(LI, LOps[i].getPointer(),
- ROps[i].getPointer(), DT, Depth + 1);
+ auto X = CompareSCEVComplexityImpl(Cache, LI, LOps[i].getPointer(),
+ ROps[i].getPointer(), DT, Depth + 1);
if (X != 0)
- return X;
+ return CacheResult(X);
}
- return 0;
+ return CacheResult(0);
}
case scCouldNotCompute:
@@ -757,6 +774,14 @@ CompareSCEVComplexity(const LoopInfo *const LI, const SCEV *LHS,
llvm_unreachable("Unknown SCEV kind!");
}
+static std::optional<int> CompareSCEVComplexity(const LoopInfo *const LI,
+ const SCEV *LHS,
+ const SCEV *RHS,
+ DominatorTree &DT) {
+ SCEVComplexityCache Cache;
+ return CompareSCEVComplexityImpl(Cache, LI, LHS, RHS, DT, 0);
+}
+
/// Given a list of SCEV objects, order them by their complexity, and group
/// objects of the same complexity together by value. When this routine is
/// finished, we know that any duplicates in the vector are consecutive and that
diff --git a/llvm/test/Transforms/IndVarSimplify/scev-complexity-exponential.ll b/llvm/test/Transforms/IndVarSimplify/scev-complexity-exponential.ll
new file mode 100644
index 0000000000000..9a54b879a0f6a
--- /dev/null
+++ b/llvm/test/Transforms/IndVarSimplify/scev-complexity-exponential.ll
@@ -0,0 +1,133 @@
+; RUN: opt -passes=indvars -disable-output %s
+
+; Check that SCEV complexity comparisons in indvars avoid exponential work.
+; The three multiplication chains repeatedly reuse each other's previous values,
+; forming shared subexpressions. Without caching comparison results, recursive
+; comparisons revisit the same pairs of SCEVs many times.
+;
+; This is a compile-time regression test: indvars should complete quickly.
+; No particular transformed IR is required.
+
+define void @test(i32 %a, i32 %b) {
+entry:
+ br label %loop
+
+loop:
+ %iv = phi i32 [ %next, %loop ], [ %a, %entry ]
+ %a0 = mul i32 %iv, %b
+ %b0 = mul i32 %a, %iv
+ %c0 = mul i32 %b, %a
+ %a1 = mul i32 %a0, %c0
+ %b1 = mul i32 %b0, %a0
+ %c1 = mul i32 %c0, %b0
+ %a2 = mul i32 %a1, %c1
+ %b2 = mul i32 %b1, %a1
+ %c2 = mul i32 %c1, %b1
+ %a3 = mul i32 %a2, %c2
+ %b3 = mul i32 %b2, %a2
+ %c3 = mul i32 %c2, %b2
+ %a4 = mul i32 %a3, %c3
+ %b4 = mul i32 %b3, %a3
+ %c4 = mul i32 %c3, %b3
+ %a5 = mul i32 %a4, %c4
+ %b5 = mul i32 %b4, %a4
+ %c5 = mul i32 %c4, %b4
+ %a6 = mul i32 %a5, %c5
+ %b6 = mul i32 %b5, %a5
+ %c6 = mul i32 %c5, %b5
+ %a7 = mul i32 %a6, %c6
+ %b7 = mul i32 %b6, %a6
+ %c7 = mul i32 %c6, %b6
+ %a8 = mul i32 %a7, %c7
+ %b8 = mul i32 %b7, %a7
+ %c8 = mul i32 %c7, %b7
+ %a9 = mul i32 %a8, %c8
+ %b9 = mul i32 %b8, %a8
+ %c9 = mul i32 %c8, %b8
+ %a10 = mul i32 %a9, %c9
+ %b10 = mul i32 %b9, %a9
+ %c10 = mul i32 %c9, %b9
+ %a11 = mul i32 %a10, %c10
+ %b11 = mul i32 %b10, %a10
+ %c11 = mul i32 %c10, %b10
+ %a12 = mul i32 %a11, %c11
+ %b12 = mul i32 %b11, %a11
+ %c12 = mul i32 %c11, %b11
+ %a13 = mul i32 %a12, %c12
+ %b13 = mul i32 %b12, %a12
+ %c13 = mul i32 %c12, %b12
+ %a14 = mul i32 %a13, %c13
+ %b14 = mul i32 %b13, %a13
+ %c14 = mul i32 %c13, %b13
+ %a15 = mul i32 %a14, %c14
+ %b15 = mul i32 %b14, %a14
+ %c15 = mul i32 %c14, %b14
+ %a16 = mul i32 %a15, %c15
+ %b16 = mul i32 %b15, %a15
+ %c16 = mul i32 %c15, %b15
+ %a17 = mul i32 %a16, %c16
+ %b17 = mul i32 %b16, %a16
+ %c17 = mul i32 %c16, %b16
+ %a18 = mul i32 %a17, %c17
+ %b18 = mul i32 %b17, %a17
+ %c18 = mul i32 %c17, %b17
+ %a19 = mul i32 %a18, %c18
+ %b19 = mul i32 %b18, %a18
+ %c19 = mul i32 %c18, %b18
+ %a20 = mul i32 %a19, %c19
+ %b20 = mul i32 %b19, %a19
+ %c20 = mul i32 %c19, %b19
+ %a21 = mul i32 %a20, %c20
+ %b21 = mul i32 %b20, %a20
+ %c21 = mul i32 %c20, %b20
+ %a22 = mul i32 %a21, %c21
+ %b22 = mul i32 %b21, %a21
+ %c22 = mul i32 %c21, %b21
+ %a23 = mul i32 %a22, %c22
+ %b23 = mul i32 %b22, %a22
+ %c23 = mul i32 %c22, %b22
+ %a24 = mul i32 %a23, %c23
+ %b24 = mul i32 %b23, %a23
+ %c24 = mul i32 %c23, %b23
+ %a25 = mul i32 %a24, %c24
+ %b25 = mul i32 %b24, %a24
+ %c25 = mul i32 %c24, %b24
+ %a26 = mul i32 %a25, %c25
+ %b26 = mul i32 %b25, %a25
+ %c26 = mul i32 %c25, %b25
+ %a27 = mul i32 %a26, %c26
+ %b27 = mul i32 %b26, %a26
+ %c27 = mul i32 %c26, %b26
+ %a28 = mul i32 %a27, %c27
+ %b28 = mul i32 %b27, %a27
+ %c28 = mul i32 %c27, %b27
+ %a29 = mul i32 %a28, %c28
+ %b29 = mul i32 %b28, %a28
+ %c29 = mul i32 %c28, %b28
+ %a30 = mul i32 %a29, %c29
+ %b30 = mul i32 %b29, %a29
+ %c30 = mul i32 %c29, %b29
+ %a31 = mul i32 %a30, %c30
+ %b31 = mul i32 %b30, %a30
+ %c31 = mul i32 %c30, %b30
+ %a32 = mul i32 %a31, %c31
+ %b32 = mul i32 %b31, %a31
+ %c32 = mul i32 %c31, %b31
+ %a33 = mul i32 %a32, %c32
+ %b33 = mul i32 %b32, %a32
+ %c33 = mul i32 %c32, %b32
+ %a34 = mul i32 %a33, %c33
+ %b34 = mul i32 %b33, %a33
+ %c34 = mul i32 %c33, %b33
+ %a35 = mul i32 %a34, %c34
+ %b35 = mul i32 %b34, %a34
+ %c35 = mul i32 %c34, %b34
+ %a36 = mul i32 %a35, %c35
+ %c36 = mul i32 %c35, %b35
+ %next = mul i32 %a36, %c36
+ ; The untaken backedge still forms a loop that indvars analyzes.
+ br i1 false, label %loop, label %exit
+
+exit:
+ ret void
+}
More information about the llvm-commits
mailing list