[llvm] [IR] Add ValueDeletionListener for context-level Value deletion notifications (PR #193198)
via llvm-commits
llvm-commits at lists.llvm.org
Tue Apr 21 04:33:24 PDT 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-ir
Author: Pankaj Dwivedi (PankajDwivedi-25)
<details>
<summary>Changes</summary>
This introduces `ValueDeletionListener`, a lightweight mechanism for analyses to be notified when any Value in an LLVMContext is deleted.
Think of an analysis that uses this as **a value handle to many values** Instead of creating _one ValueHandle per tracked value_ (O(N) space and registration), an analysis registers a single listener with the LLVMContext (O(1)). When ~Value() runs, it notifies all registered listeners with the deleted Value*.
This is motivated by analyses like UniformityAnalysis that store raw Value pointers in a DenseSet. If a downstream pass deletes a value and the allocator reuses the address, the stale pointer causes the analysis to silently return wrong results. The listener lets the analysis clean up on deletion without per-value handle overhead.
Design:
- ValueDeletionListener stores a function pointer and uses RAII for registration/unregistration.
- Listeners are stored in a SmallPtrSet<ValueDeletionListener*, 2> in LLVMContextImpl.
- ~Value() iterates this set.
This patch adds only the core infrastructure. A follow-up patch will wire UniformityAnalysis to use it, replacing the current CallbackVH-based approach.
For discussion reference: https://github.com/llvm/llvm-project/pull/187658
---
Full diff: https://github.com/llvm/llvm-project/pull/193198.diff
7 Files Affected:
- (modified) llvm/include/llvm/IR/LLVMContext.h (+10)
- (added) llvm/include/llvm/IR/ValueDeletionListener.h (+58)
- (modified) llvm/lib/IR/LLVMContext.cpp (+18)
- (modified) llvm/lib/IR/LLVMContextImpl.h (+6)
- (modified) llvm/lib/IR/Value.cpp (+6)
- (modified) llvm/unittests/IR/CMakeLists.txt (+1)
- (added) llvm/unittests/IR/ValueDeletionListenerTest.cpp (+132)
``````````diff
diff --git a/llvm/include/llvm/IR/LLVMContext.h b/llvm/include/llvm/IR/LLVMContext.h
index 646a04673dbd0..1452822dec430 100644
--- a/llvm/include/llvm/IR/LLVMContext.h
+++ b/llvm/include/llvm/IR/LLVMContext.h
@@ -32,6 +32,7 @@ class Instruction;
class LLVMContextImpl;
class Module;
class OptPassGate;
+class ValueDeletionListener;
template <typename T> class SmallVectorImpl;
template <typename T> class StringMapEntry;
class StringRef;
@@ -309,6 +310,15 @@ class LLVMContext {
/// any global mutex or cannot block the execution in another LLVM context.
LLVM_ABI void yield();
+ /// Register a listener that will be notified whenever a Value in this
+ /// context is deleted. This is typically called from the
+ /// ValueDeletionListener constructor.
+ LLVM_ABI void addValueDeletionListener(ValueDeletionListener *L);
+
+ /// Remove a previously registered listener. This is typically called from
+ /// the ValueDeletionListener destructor.
+ LLVM_ABI void removeValueDeletionListener(ValueDeletionListener *L);
+
/// emitError - Emit an error message to the currently installed error handler
/// with optional location information. This function returns, so code should
/// be prepared to drop the erroneous construct on the floor and "not crash".
diff --git a/llvm/include/llvm/IR/ValueDeletionListener.h b/llvm/include/llvm/IR/ValueDeletionListener.h
new file mode 100644
index 0000000000000..340b081325453
--- /dev/null
+++ b/llvm/include/llvm/IR/ValueDeletionListener.h
@@ -0,0 +1,58 @@
+//===- llvm/IR/ValueDeletionListener.h - Callback on Value deletion -------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file declares ValueDeletionListener, an interface that allows analyses
+// to be notified when any Value in a given LLVMContext is deleted. Think of
+// an analysis that uses this as "a value handle to many values" — a single
+// registration replaces one handle per tracked value.
+//
+// Registration and unregistration happen automatically via RAII: the
+// constructor registers with an LLVMContext, and the destructor unregisters.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_IR_VALUEDELETIONLISTENER_H
+#define LLVM_IR_VALUEDELETIONLISTENER_H
+
+#include "llvm/Support/Compiler.h"
+
+namespace llvm {
+
+class LLVMContext;
+class Value;
+
+/// A listener that is notified whenever a Value is deleted in its LLVMContext.
+///
+/// Subclasses implement the callback by passing a static function to the
+/// constructor, which static_casts the listener back to the derived type.
+/// This avoids virtual dispatch overhead while preserving type safety at
+/// the point of registration.
+///
+/// Lifetime is managed via RAII: the constructor registers with the
+/// LLVMContext, and the destructor unregisters.
+class ValueDeletionListener {
+public:
+ using CallbackT = void (*)(ValueDeletionListener *, const Value *);
+
+private:
+ LLVMContext &Ctx;
+ CallbackT Callback;
+
+public:
+ LLVM_ABI ValueDeletionListener(LLVMContext &C, CallbackT CB);
+ LLVM_ABI ~ValueDeletionListener();
+
+ ValueDeletionListener(const ValueDeletionListener &) = delete;
+ ValueDeletionListener &operator=(const ValueDeletionListener &) = delete;
+
+ void valueDeleted(const Value *V) { Callback(this, V); }
+};
+
+} // namespace llvm
+
+#endif // LLVM_IR_VALUEDELETIONLISTENER_H
diff --git a/llvm/lib/IR/LLVMContext.cpp b/llvm/lib/IR/LLVMContext.cpp
index 10aba759185a7..c756d9ef3e12c 100644
--- a/llvm/lib/IR/LLVMContext.cpp
+++ b/llvm/lib/IR/LLVMContext.cpp
@@ -20,6 +20,7 @@
#include "llvm/IR/DiagnosticInfo.h"
#include "llvm/IR/DiagnosticPrinter.h"
#include "llvm/IR/LLVMRemarkStreamer.h"
+#include "llvm/IR/ValueDeletionListener.h"
#include "llvm/Remarks/RemarkStreamer.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/ErrorHandling.h"
@@ -207,6 +208,23 @@ void LLVMContext::yield() {
pImpl->YieldCallback(this, pImpl->YieldOpaqueHandle);
}
+ValueDeletionListener::ValueDeletionListener(LLVMContext &C, CallbackT CB)
+ : Ctx(C), Callback(CB) {
+ Ctx.addValueDeletionListener(this);
+}
+
+ValueDeletionListener::~ValueDeletionListener() {
+ Ctx.removeValueDeletionListener(this);
+}
+
+void LLVMContext::addValueDeletionListener(ValueDeletionListener *L) {
+ pImpl->ValueDeletionListeners.insert(L);
+}
+
+void LLVMContext::removeValueDeletionListener(ValueDeletionListener *L) {
+ pImpl->ValueDeletionListeners.erase(L);
+}
+
void LLVMContext::emitError(const Twine &ErrorStr) {
diagnose(DiagnosticInfoGeneric(ErrorStr));
}
diff --git a/llvm/lib/IR/LLVMContextImpl.h b/llvm/lib/IR/LLVMContextImpl.h
index 11245b4ea7803..09d31a0da58af 100644
--- a/llvm/lib/IR/LLVMContextImpl.h
+++ b/llvm/lib/IR/LLVMContextImpl.h
@@ -73,6 +73,7 @@ class RemarkStreamer;
template <typename T> class StringMapEntry;
class StringRef;
class TypedPointerType;
+class ValueDeletionListener;
class ValueHandleBase;
template <> struct DenseMapInfo<APFloat> {
@@ -1747,6 +1748,11 @@ class LLVMContextImpl {
using ValueHandlesTy = DenseMap<Value *, ValueHandleBase *>;
ValueHandlesTy ValueHandles;
+ /// Context-level listeners notified when any Value in this context is
+ /// deleted. Used by analyses that track Value pointers (e.g. UniformValues)
+ /// to remove stale entries without per-value handle overhead.
+ SmallPtrSet<ValueDeletionListener *, 2> ValueDeletionListeners;
+
/// CustomMDKindNames - Map to hold the metadata string to ID mapping.
StringMap<unsigned> CustomMDKindNames;
diff --git a/llvm/lib/IR/Value.cpp b/llvm/lib/IR/Value.cpp
index 360bf0f8fc47f..69a85f423679e 100644
--- a/llvm/lib/IR/Value.cpp
+++ b/llvm/lib/IR/Value.cpp
@@ -27,6 +27,7 @@
#include "llvm/IR/Module.h"
#include "llvm/IR/Operator.h"
#include "llvm/IR/TypedPointerType.h"
+#include "llvm/IR/ValueDeletionListener.h"
#include "llvm/IR/ValueHandle.h"
#include "llvm/IR/ValueSymbolTable.h"
#include "llvm/Support/CommandLine.h"
@@ -77,6 +78,11 @@ Value::~Value() {
// Notify all ValueHandles (if present) that this value is going away.
if (HasValueHandle)
ValueHandleBase::ValueIsDeleted(this);
+
+ // Notify context-level deletion listeners (e.g. analyses tracking Value*).
+ for (ValueDeletionListener *L : getContext().pImpl->ValueDeletionListeners)
+ L->valueDeleted(this);
+
if (isUsedByMetadata())
ValueAsMetadata::handleDeletion(this);
diff --git a/llvm/unittests/IR/CMakeLists.txt b/llvm/unittests/IR/CMakeLists.txt
index d62ce66ef9d34..3684f3b699594 100644
--- a/llvm/unittests/IR/CMakeLists.txt
+++ b/llvm/unittests/IR/CMakeLists.txt
@@ -49,6 +49,7 @@ add_llvm_unittest(IRTests
TypesTest.cpp
UseTest.cpp
UserTest.cpp
+ ValueDeletionListenerTest.cpp
ValueHandleTest.cpp
ValueMapTest.cpp
ValueTest.cpp
diff --git a/llvm/unittests/IR/ValueDeletionListenerTest.cpp b/llvm/unittests/IR/ValueDeletionListenerTest.cpp
new file mode 100644
index 0000000000000..0332b95550a82
--- /dev/null
+++ b/llvm/unittests/IR/ValueDeletionListenerTest.cpp
@@ -0,0 +1,132 @@
+//===- ValueDeletionListenerTest.cpp - Tests for ValueDeletionListener ----===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/IR/ValueDeletionListener.h"
+#include "llvm/ADT/DenseSet.h"
+#include "llvm/IR/Constants.h"
+#include "llvm/IR/Instructions.h"
+#include "llvm/IR/LLVMContext.h"
+#include "gtest/gtest.h"
+#include <memory>
+
+using namespace llvm;
+
+namespace {
+
+class TrackingListener : public ValueDeletionListener {
+ DenseSet<const Value *> &Tracked;
+
+ static void callback(ValueDeletionListener *Self, const Value *V) {
+ static_cast<TrackingListener *>(Self)->Tracked.erase(V);
+ }
+
+public:
+ TrackingListener(LLVMContext &C, DenseSet<const Value *> &S)
+ : ValueDeletionListener(C, &callback), Tracked(S) {}
+};
+
+TEST(ValueDeletionListenerTest, BasicDeletion) {
+ LLVMContext C;
+ Constant *ConstantV = ConstantInt::get(Type::getInt32Ty(C), 0);
+
+ DenseSet<const Value *> Tracked;
+ TrackingListener Listener(C, Tracked);
+
+ std::unique_ptr<BitCastInst> V(
+ new BitCastInst(ConstantV, Type::getInt32Ty(C)));
+ Tracked.insert(V.get());
+ EXPECT_TRUE(Tracked.contains(V.get()));
+
+ // Deleting the value triggers the listener, removing it from the set.
+ V.reset();
+ EXPECT_TRUE(Tracked.empty());
+}
+
+TEST(ValueDeletionListenerTest, AddressReuse) {
+ LLVMContext C;
+ Constant *ConstantV = ConstantInt::get(Type::getInt32Ty(C), 0);
+
+ DenseSet<const Value *> Tracked;
+ TrackingListener Listener(C, Tracked);
+
+ // Create, track, and destroy a value.
+ const Value *OldAddr;
+ {
+ std::unique_ptr<BitCastInst> V1(
+ new BitCastInst(ConstantV, Type::getInt32Ty(C)));
+ OldAddr = V1.get();
+ Tracked.insert(OldAddr);
+ EXPECT_TRUE(Tracked.contains(OldAddr));
+ } // V1 destroyed here — listener removes it from Tracked.
+ EXPECT_FALSE(Tracked.contains(OldAddr));
+
+ // Allocate a new instruction. Even if the allocator reuses the same
+ // address, the set must not contain it.
+ std::unique_ptr<BitCastInst> V2(
+ new BitCastInst(ConstantV, Type::getInt32Ty(C)));
+ EXPECT_FALSE(Tracked.contains(V2.get()));
+}
+
+TEST(ValueDeletionListenerTest, ListenerScopeRAII) {
+ LLVMContext C;
+ Constant *ConstantV = ConstantInt::get(Type::getInt32Ty(C), 0);
+
+ DenseSet<const Value *> Tracked;
+
+ std::unique_ptr<BitCastInst> V(
+ new BitCastInst(ConstantV, Type::getInt32Ty(C)));
+ Tracked.insert(V.get());
+
+ {
+ TrackingListener Listener(C, Tracked);
+ EXPECT_TRUE(Tracked.contains(V.get()));
+ }
+ // Listener is destroyed (unregistered). Deleting the value now must not
+ // crash, but the set won't be updated since no listener is active.
+ const Value *Addr = V.get();
+ V.reset();
+ EXPECT_TRUE(Tracked.contains(Addr));
+}
+
+TEST(ValueDeletionListenerTest, MultipleListeners) {
+ LLVMContext C;
+ Constant *ConstantV = ConstantInt::get(Type::getInt32Ty(C), 0);
+
+ DenseSet<const Value *> Set1, Set2;
+ TrackingListener L1(C, Set1);
+ TrackingListener L2(C, Set2);
+
+ std::unique_ptr<BitCastInst> V(
+ new BitCastInst(ConstantV, Type::getInt32Ty(C)));
+ Set1.insert(V.get());
+ Set2.insert(V.get());
+
+ V.reset();
+ EXPECT_TRUE(Set1.empty());
+ EXPECT_TRUE(Set2.empty());
+}
+
+TEST(ValueDeletionListenerTest, UnrelatedValueNotAffected) {
+ LLVMContext C;
+ Constant *ConstantV = ConstantInt::get(Type::getInt32Ty(C), 0);
+
+ DenseSet<const Value *> Tracked;
+ TrackingListener Listener(C, Tracked);
+
+ std::unique_ptr<BitCastInst> V1(
+ new BitCastInst(ConstantV, Type::getInt32Ty(C)));
+ std::unique_ptr<BitCastInst> V2(
+ new BitCastInst(ConstantV, Type::getInt32Ty(C)));
+ Tracked.insert(V1.get());
+
+ // Deleting V2 (not tracked) should not affect V1 in the set.
+ V2.reset();
+ EXPECT_TRUE(Tracked.contains(V1.get()));
+}
+
+} // namespace
``````````
</details>
https://github.com/llvm/llvm-project/pull/193198
More information about the llvm-commits
mailing list