[llvm] 9ed81e3 - Reland "[AssumptionCache] Limit the number of assumptions inspected per value" (#218438)

via llvm-commits llvm-commits at lists.llvm.org
Tue Aug 25 12:13:51 PDT 2026


Author: Matsu
Date: 2026-08-25T12:13:45-07:00
New Revision: 9ed81e3f037565d69c90e2f0f372eb2a8c5e1fb7

URL: https://github.com/llvm/llvm-project/commit/9ed81e3f037565d69c90e2f0f372eb2a8c5e1fb7
DIFF: https://github.com/llvm/llvm-project/commit/9ed81e3f037565d69c90e2f0f372eb2a8c5e1fb7.diff

LOG: Reland "[AssumptionCache] Limit the number of assumptions inspected per value" (#218438)

Reland #217525

The reverted commit clamped in assumptionsFor(), which added a load and
a compare to every query and grew the inlined code at each of its call
sites. That cost 0.02% of the instructions compiling sqlite3 at -O3, on
code where no value comes near the limit.

Cap the list in updateAffectedValues() instead, where it is built. A
query is then bounded by the list itself, so assumptionsFor() is
unchanged and the objects for ValueTracking.cpp and
AssumeBundleQueries.cpp are byte-identical to before the original
commit. The assumptions kept are the same, the oldest
-max-assumes-per-value of them.

Added: 
    llvm/test/Analysis/AssumptionCache/max-assumes-per-value.ll

Modified: 
    llvm/include/llvm/Analysis/AssumptionCache.h
    llvm/lib/Analysis/AssumptionCache.cpp

Removed: 
    


################################################################################
diff  --git a/llvm/include/llvm/Analysis/AssumptionCache.h b/llvm/include/llvm/Analysis/AssumptionCache.h
index ed335c084fde8..e35c22fca68b9 100644
--- a/llvm/include/llvm/Analysis/AssumptionCache.h
+++ b/llvm/include/llvm/Analysis/AssumptionCache.h
@@ -163,6 +163,8 @@ class AssumptionCache {
   }
 
   /// Access the list of assumptions which affect this value.
+  ///
+  /// No more than -max-assumes-per-value of them are cached.
   MutableArrayRef<ResultElem> assumptionsFor(const Value *V) {
     if (!Scanned)
       scanFunction();

diff  --git a/llvm/lib/Analysis/AssumptionCache.cpp b/llvm/lib/Analysis/AssumptionCache.cpp
index 87e63e95a46d6..a65dc5c4ae87f 100644
--- a/llvm/lib/Analysis/AssumptionCache.cpp
+++ b/llvm/lib/Analysis/AssumptionCache.cpp
@@ -34,6 +34,7 @@
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/raw_ostream.h"
 #include <cassert>
+#include <limits>
 
 using namespace llvm;
 using namespace llvm::PatternMatch;
@@ -43,6 +44,10 @@ static cl::opt<bool>
                           cl::desc("Enable verification of assumption cache"),
                           cl::init(false));
 
+static cl::opt<unsigned> MaxAssumesPerValue(
+    "max-assumes-per-value", cl::Hidden, cl::init(1024),
+    cl::desc("Maximum number of assumptions to cache for a single value"));
+
 SmallVector<AssumptionCache::ResultElem, 1> &
 AssumptionCache::getOrInsertAffectedValues(Value *V) {
   // Try using find_as first to avoid creating extra value handles just for the
@@ -110,6 +115,13 @@ void AssumptionCache::updateAffectedValues(AssumeInst *CI) {
 
   for (auto &AV : Affected) {
     auto &AVV = getOrInsertAffectedValues(AV.Assume);
+
+    // Callers walk every entry cached for a value, including the ones left
+    // behind by erased assumptions, so cache no more of them than an analysis
+    // should walk.
+    if (AVV.size() >= MaxAssumesPerValue)
+      continue;
+
     if (llvm::none_of(AVV, [&](ResultElem &Elem) {
           return Elem.Assume == CI && Elem.Index == AV.Index;
         }))
@@ -172,9 +184,12 @@ void AssumptionCache::transferAffectedValuesInCache(Value *OV, Value *NV) {
   if (AVI == AffectedValues.end())
     return;
 
-  for (auto &A : AVI->second)
+  for (auto &A : AVI->second) {
+    if (NAVV.size() >= MaxAssumesPerValue)
+      break;
     if (!llvm::is_contained(NAVV, A))
       NAVV.push_back(A);
+  }
   AffectedValues.erase(OV);
 }
 
@@ -209,6 +224,27 @@ void AssumptionCache::scanFunction() {
     updateAffectedValues(cast<AssumeInst>(A));
 }
 
+/// Check the assumptions cached for \p F, collecting them in \p Cached. Returns
+/// a description of the first invariant violated, or nullptr if there is none.
+static const char *
+findCacheViolation(const Function &F, ArrayRef<WeakVH> Assumptions,
+                   SmallPtrSetImpl<const CallInst *> &Cached) {
+  for (const WeakVH &VH : Assumptions) {
+    if (!VH)
+      continue;
+
+    const auto *CI = cast<CallInst>(VH);
+    if (CI->getFunction() != &F)
+      return "Cached assumption not inside this function";
+    if (!match(CI, m_Intrinsic<Intrinsic::assume>()))
+      return "Cached something other than a call to @llvm.assume";
+    if (!Cached.insert(CI).second)
+      return "Cache contains multiple copies of a call";
+  }
+
+  return nullptr;
+}
+
 void AssumptionCache::registerAssumption(AssumeInst *CI) {
   // If we haven't scanned the function yet, just drop this assumption. It will
   // be found when we scan later.
@@ -225,18 +261,19 @@ void AssumptionCache::registerAssumption(AssumeInst *CI) {
 
   // We expect the number of assumptions to be small, so in an asserts build
   // check that we don't accumulate duplicates and that all assumptions point
-  // to the same function.
-  SmallPtrSet<Value *, 16> AssumptionSet;
-  for (auto &VH : AssumeHandles) {
-    if (!VH)
-      continue;
-
-    assert(&F == cast<Instruction>(VH)->getParent()->getParent() &&
-           "Cached assumption not inside this function!");
-    assert(match(cast<CallInst>(VH), m_Intrinsic<Intrinsic::assume>()) &&
-           "Cached something other than a call to @llvm.assume!");
-    assert(AssumptionSet.insert(VH).second &&
-           "Cache contains multiple copies of a call!");
+  // to the same function. Scanning the whole cache on every registration is
+  // quadratic, so stop once it outgrows that expectation unless expensive
+  // checks are enabled. Larger caches are checked by
+  // AssumptionCacheTracker::verifyAnalysis() instead.
+#ifdef EXPENSIVE_CHECKS
+  constexpr unsigned MaxAssumesToVerify = std::numeric_limits<unsigned>::max();
+#else
+  constexpr unsigned MaxAssumesToVerify = 64;
+#endif
+  if (AssumeHandles.size() <= MaxAssumesToVerify) {
+    SmallPtrSet<const CallInst *, 16> Cached;
+    if (const char *Violation = findCacheViolation(F, AssumeHandles, Cached))
+      llvm_unreachable(Violation);
   }
 #endif
 
@@ -324,16 +361,18 @@ void AssumptionCacheTracker::verifyAnalysis() const {
   if (!VerifyAssumptionCache)
     return;
 
-  SmallPtrSet<const CallInst *, 4> AssumptionSet;
   for (const auto &I : AssumptionCaches) {
-    for (auto &VH : I.second->assumptions())
-      if (VH)
-        AssumptionSet.insert(cast<CallInst>(VH));
+    const Function &F = cast<Function>(*I.first);
+
+    SmallPtrSet<const CallInst *, 4> Cached;
+    if (const char *Violation =
+            findCacheViolation(F, I.second->assumptions(), Cached))
+      report_fatal_error(Violation);
 
-    for (const BasicBlock &B : cast<Function>(*I.first))
+    for (const BasicBlock &B : F)
       for (const Instruction &II : B)
         if (match(&II, m_Intrinsic<Intrinsic::assume>()) &&
-            !AssumptionSet.count(cast<CallInst>(&II)))
+            !Cached.count(cast<CallInst>(&II)))
           report_fatal_error("Assumption in scanned function not in cache");
   }
 }

diff  --git a/llvm/test/Analysis/AssumptionCache/max-assumes-per-value.ll b/llvm/test/Analysis/AssumptionCache/max-assumes-per-value.ll
new file mode 100644
index 0000000000000..98070fb4fa7ee
--- /dev/null
+++ b/llvm/test/Analysis/AssumptionCache/max-assumes-per-value.ll
@@ -0,0 +1,37 @@
+; RUN: opt < %s -S -passes=instsimplify -max-assumes-per-value=3 | FileCheck %s --check-prefixes=CHECK,USED
+; RUN: opt < %s -S -passes=instsimplify -max-assumes-per-value=2 | FileCheck %s --check-prefixes=CHECK,IGNORED
+
+; Analyses inspect every assumption cached for a value, so only the first
+; -max-assumes-per-value assumptions affecting it are cached. Here the
+; assumption that proves the comparison is the third one.
+
+declare void @llvm.assume(i1)
+
+define i1 @assumes_within_limit(i32 %x) {
+; CHECK-LABEL: define i1 @assumes_within_limit(
+; USED: ret i1 true
+; IGNORED: ret i1 %cmp
+  %u1 = icmp ne i32 %x, 1234
+  call void @llvm.assume(i1 %u1)
+  %u2 = icmp ne i32 %x, 5678
+  call void @llvm.assume(i1 %u2)
+  %c = icmp sgt i32 %x, 41
+  call void @llvm.assume(i1 %c)
+  %cmp = icmp sgt i32 %x, 0
+  ret i1 %cmp
+}
+
+; The limit applies per value, so assumptions about %y are still cached when %x
+; is affected by more of them than the limit allows.
+define i1 @limit_is_per_value(i32 %x, i32 %y) {
+; CHECK-LABEL: define i1 @limit_is_per_value(
+; CHECK: ret i1 true
+  %cx = icmp sgt i32 %x, 41
+  call void @llvm.assume(i1 %cx)
+  call void @llvm.assume(i1 %cx)
+  call void @llvm.assume(i1 %cx)
+  %cy = icmp sgt i32 %y, 41
+  call void @llvm.assume(i1 %cy)
+  %cmp = icmp sgt i32 %y, 0
+  ret i1 %cmp
+}


        


More information about the llvm-commits mailing list