[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