[llvm] [SCEV] Fix infinite recursion in expensive range sharpening (PR #215029)

via llvm-commits llvm-commits at lists.llvm.org
Sat Aug 8 15:29:50 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-analysis

Author: Kacper Doga (varev-dev)

<details>
<summary>Changes</summary>

`getRangeForAffineNoSelfWrappingAR()` collects loop guards, which queries ranges of other expressions and can end up back in the range computation for the same AddRec. Neither the guard collection depth nor `getRangeRef()` depth is carried across that boundary, so this recurses until the stack overflows.

Mark AddRecs currently being sharpened and return a full range on re-entry, following the existing Pending* idiom for recursive SCEV queries.

Fixes #<!-- -->158255 

---
Full diff: https://github.com/llvm/llvm-project/pull/215029.diff


3 Files Affected:

- (modified) llvm/include/llvm/Analysis/ScalarEvolution.h (+4) 
- (modified) llvm/lib/Analysis/ScalarEvolution.cpp (+9) 
- (added) llvm/test/Analysis/ScalarEvolution/range-sharpening-recursion.ll (+43) 


``````````diff
diff --git a/llvm/include/llvm/Analysis/ScalarEvolution.h b/llvm/include/llvm/Analysis/ScalarEvolution.h
index 0d7f9ae298e2a..bea83b6165e0e 100644
--- a/llvm/include/llvm/Analysis/ScalarEvolution.h
+++ b/llvm/include/llvm/Analysis/ScalarEvolution.h
@@ -1716,6 +1716,10 @@ class ScalarEvolution {
   // Mark SCEVUnknown Phis currently being processed by isImpliedViaMerge.
   SmallPtrSet<const PHINode *, 6> PendingMerges;
 
+  /// Mark AddRecs currently being processed by
+  /// getRangeForAffineNoSelfWrappingAR.
+  SmallPtrSet<const SCEVAddRecExpr *, 4> PendingRangeSharpening;
+
   /// Set to true by isLoopBackedgeGuardedByCond when we're walking the set of
   /// conditions dominating the backedge of a loop.
   bool WalkingBEDominatingConds = false;
diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index 27a1a20bcdf79..c941338aa1aaa 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -7261,6 +7261,12 @@ ConstantRange ScalarEvolution::getRangeForAffineNoSelfWrappingAR(
                                          MaxItersWithoutWrap))
     return ConstantRange::getFull(BitWidth);
 
+  // Collecting loop guards below queries ranges of other expressions, which can
+  // recursively end up back here for the same AddRec.
+  if (!PendingRangeSharpening.insert(AddRec).second)
+    return ConstantRange::getFull(BitWidth);
+  llvm::scope_exit ClearOnExit([&]() { PendingRangeSharpening.erase(AddRec); });
+
   ICmpInst::Predicate LEPred =
       IsSigned ? ICmpInst::ICMP_SLE : ICmpInst::ICMP_ULE;
   ICmpInst::Predicate GEPred =
@@ -14112,6 +14118,7 @@ ScalarEvolution::ScalarEvolution(ScalarEvolution &&Arg)
       ValueExprMap(std::move(Arg.ValueExprMap)),
       PendingLoopPredicates(std::move(Arg.PendingLoopPredicates)),
       PendingMerges(std::move(Arg.PendingMerges)),
+      PendingRangeSharpening(std::move(Arg.PendingRangeSharpening)),
       ConstantMultipleCache(std::move(Arg.ConstantMultipleCache)),
       BackedgeTakenCounts(std::move(Arg.BackedgeTakenCounts)),
       PredicatedBackedgeTakenCounts(
@@ -14155,6 +14162,8 @@ ScalarEvolution::~ScalarEvolution() {
 
   assert(PendingLoopPredicates.empty() && "isImpliedCond garbage");
   assert(PendingMerges.empty() && "isImpliedViaMerge garbage");
+  assert(PendingRangeSharpening.empty() &&
+         "getRangeForAffineNoSelfWrappingAR garbage");
   assert(!WalkingBEDominatingConds && "isLoopBackedgeGuardedByCond garbage!");
   assert(!ProvingSplitPredicate && "ProvingSplitPredicate garbage!");
 }
diff --git a/llvm/test/Analysis/ScalarEvolution/range-sharpening-recursion.ll b/llvm/test/Analysis/ScalarEvolution/range-sharpening-recursion.ll
new file mode 100644
index 0000000000000..e6a007c58a76f
--- /dev/null
+++ b/llvm/test/Analysis/ScalarEvolution/range-sharpening-recursion.ll
@@ -0,0 +1,43 @@
+; NOTE: Assertions have been autogenerated by utils/update_analyze_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -disable-output -scalar-evolution-use-expensive-range-sharpening -passes='print<scalar-evolution>' 2>&1 | FileCheck %s
+
+; Sharpening the range of %iv collects the loop guards of %inner, which queries
+; the range of %iv again. Check we do not recurse infinitely.
+
+define void @test(i1 %c) {
+; CHECK-LABEL: 'test'
+; CHECK-NEXT:  Classifying expressions for: @test
+; CHECK-NEXT:    %p = phi i32 [ 0, %inner.exit ], [ 0, %other ], [ 0, %entry ]
+; CHECK-NEXT:    --> 0 U: [0,1) S: [0,1) Exits: 0 LoopDispositions: { %outer: Invariant, %inner: Invariant }
+; CHECK-NEXT:    %iv = phi i32 [ %iv.next, %inner ], [ 0, %outer ]
+; CHECK-NEXT:    --> {0,+,1}<nuw><nsw><%inner> U: [0,2) S: [0,2) Exits: 1 LoopDispositions: { %inner: Computable, %outer: Uniform }
+; CHECK-NEXT:    %iv.next = add i32 %iv, 1
+; CHECK-NEXT:    --> {1,+,1}<nuw><nsw><%inner> U: [1,3) S: [1,3) Exits: 2 LoopDispositions: { %inner: Computable, %outer: Uniform }
+; CHECK-NEXT:  Determining loop execution counts for: @test
+; CHECK-NEXT:  Loop %inner: backedge-taken count is i32 1
+; CHECK-NEXT:  Loop %inner: constant max backedge-taken count is i32 1
+; CHECK-NEXT:  Loop %inner: symbolic max backedge-taken count is i32 1
+; CHECK-NEXT:  Loop %inner: Trip multiple is 2
+; CHECK-NEXT:  Loop %outer: <multiple exits> Unpredictable backedge-taken count.
+; CHECK-NEXT:  Loop %outer: Unpredictable constant max backedge-taken count.
+; CHECK-NEXT:  Loop %outer: Unpredictable symbolic max backedge-taken count.
+;
+entry:
+  br i1 %c, label %other, label %outer
+
+other:
+  br label %outer
+
+outer:
+  %p = phi i32 [ 0, %inner.exit ], [ 0, %other ], [ 0, %entry ]
+  br label %inner
+
+inner:
+  %iv = phi i32 [ %iv.next, %inner ], [ 0, %outer ]
+  %iv.next = add i32 %iv, 1
+  %cmp = icmp ult i32 %iv, 1
+  br i1 %cmp, label %inner, label %inner.exit
+
+inner.exit:
+  br label %outer
+}

``````````

</details>


https://github.com/llvm/llvm-project/pull/215029


More information about the llvm-commits mailing list