[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