[llvm] [RFC][IR] InstructionListener: per-function instruction removal/RAUW notifications (PR #193198)

Pankaj Dwivedi via llvm-commits llvm-commits at lists.llvm.org
Wed May 13 01:26:37 PDT 2026


https://github.com/PankajDwivedi-25 updated https://github.com/llvm/llvm-project/pull/193198

>From 5d24beb632dc4691ee16b2d50e2bf6658353a592 Mon Sep 17 00:00:00 2001
From: padivedi <padivedi at amd.com>
Date: Tue, 21 Apr 2026 16:55:36 +0530
Subject: [PATCH 1/9] [IR] Add ValueDeletionListener for context-level Value
 deletion notifications

---
 llvm/include/llvm/IR/LLVMContext.h            |  10 ++
 llvm/include/llvm/IR/ValueDeletionListener.h  |  58 ++++++++
 llvm/lib/IR/LLVMContext.cpp                   |  18 +++
 llvm/lib/IR/LLVMContextImpl.h                 |   6 +
 llvm/lib/IR/Value.cpp                         |   6 +
 llvm/unittests/IR/CMakeLists.txt              |   1 +
 .../IR/ValueDeletionListenerTest.cpp          | 132 ++++++++++++++++++
 7 files changed, 231 insertions(+)
 create mode 100644 llvm/include/llvm/IR/ValueDeletionListener.h
 create mode 100644 llvm/unittests/IR/ValueDeletionListenerTest.cpp

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

>From df53d34b020c642fa294fc3b1a28a3531bf58340 Mon Sep 17 00:00:00 2001
From: padivedi <padivedi at amd.com>
Date: Wed, 22 Apr 2026 10:53:33 +0530
Subject: [PATCH 2/9] review: address suggestion

---
 llvm/include/llvm/IR/LLVMContext.h | 5 +++++
 llvm/lib/IR/LLVMContext.cpp        | 5 +++++
 llvm/lib/IR/Value.cpp              | 4 +---
 3 files changed, 11 insertions(+), 3 deletions(-)

diff --git a/llvm/include/llvm/IR/LLVMContext.h b/llvm/include/llvm/IR/LLVMContext.h
index 1452822dec430..0acdd8cbc2c7e 100644
--- a/llvm/include/llvm/IR/LLVMContext.h
+++ b/llvm/include/llvm/IR/LLVMContext.h
@@ -37,6 +37,7 @@ template <typename T> class SmallVectorImpl;
 template <typename T> class StringMapEntry;
 class StringRef;
 class Twine;
+class Value;
 class LLVMRemarkStreamer;
 
 namespace remarks {
@@ -319,6 +320,10 @@ class LLVMContext {
   /// the ValueDeletionListener destructor.
   LLVM_ABI void removeValueDeletionListener(ValueDeletionListener *L);
 
+  /// Notify all registered ValueDeletionListeners that \p V is being deleted.
+  /// Called from ~Value().
+  LLVM_ABI void notifyValueDeleted(Value *V);
+
   /// 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/lib/IR/LLVMContext.cpp b/llvm/lib/IR/LLVMContext.cpp
index c756d9ef3e12c..165c82272f691 100644
--- a/llvm/lib/IR/LLVMContext.cpp
+++ b/llvm/lib/IR/LLVMContext.cpp
@@ -225,6 +225,11 @@ void LLVMContext::removeValueDeletionListener(ValueDeletionListener *L) {
   pImpl->ValueDeletionListeners.erase(L);
 }
 
+void LLVMContext::notifyValueDeleted(Value *V) {
+  for (ValueDeletionListener *L : pImpl->ValueDeletionListeners)
+    L->valueDeleted(V);
+}
+
 void LLVMContext::emitError(const Twine &ErrorStr) {
   diagnose(DiagnosticInfoGeneric(ErrorStr));
 }
diff --git a/llvm/lib/IR/Value.cpp b/llvm/lib/IR/Value.cpp
index 69a85f423679e..32e355f20566e 100644
--- a/llvm/lib/IR/Value.cpp
+++ b/llvm/lib/IR/Value.cpp
@@ -27,7 +27,6 @@
 #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"
@@ -80,8 +79,7 @@ Value::~Value() {
     ValueHandleBase::ValueIsDeleted(this);
 
   // Notify context-level deletion listeners (e.g. analyses tracking Value*).
-  for (ValueDeletionListener *L : getContext().pImpl->ValueDeletionListeners)
-    L->valueDeleted(this);
+  getContext().notifyValueDeleted(this);
 
   if (isUsedByMetadata())
     ValueAsMetadata::handleDeletion(this);

>From 2af998bd9f35f4696a05e65bd5d846ae4e4f58e1 Mon Sep 17 00:00:00 2001
From: padivedi <padivedi at amd.com>
Date: Wed, 22 Apr 2026 12:27:58 +0530
Subject: [PATCH 3/9] review: make listener api's private

---
 llvm/include/llvm/IR/LLVMContext.h           | 22 ++++++++------------
 llvm/include/llvm/IR/ValueDeletionListener.h |  6 +++---
 2 files changed, 12 insertions(+), 16 deletions(-)

diff --git a/llvm/include/llvm/IR/LLVMContext.h b/llvm/include/llvm/IR/LLVMContext.h
index 0acdd8cbc2c7e..c3be962c14cb8 100644
--- a/llvm/include/llvm/IR/LLVMContext.h
+++ b/llvm/include/llvm/IR/LLVMContext.h
@@ -311,19 +311,6 @@ 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);
-
-  /// Notify all registered ValueDeletionListeners that \p V is being deleted.
-  /// Called from ~Value().
-  LLVM_ABI void notifyValueDeleted(Value *V);
-
   /// 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".
@@ -377,6 +364,15 @@ class LLVMContext {
 
   /// removeModule - Unregister a module from this context.
   void removeModule(Module *);
+
+  // ValueDeletionListener registers/deregisters via its constructor/destructor.
+  friend class ValueDeletionListener;
+  // Value calls notifyValueDeleted from ~Value().
+  friend class Value;
+
+  LLVM_ABI void addValueDeletionListener(ValueDeletionListener *L);
+  LLVM_ABI void removeValueDeletionListener(ValueDeletionListener *L);
+  LLVM_ABI void notifyValueDeleted(Value *V);
 };
 
 // Create wrappers for C Binding types (see CBindingWrapping.h).
diff --git a/llvm/include/llvm/IR/ValueDeletionListener.h b/llvm/include/llvm/IR/ValueDeletionListener.h
index 340b081325453..465ca2b5b9597 100644
--- a/llvm/include/llvm/IR/ValueDeletionListener.h
+++ b/llvm/include/llvm/IR/ValueDeletionListener.h
@@ -11,8 +11,8 @@
 // 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.
+// Registration and deregistration happen automatically via RAII: the
+// constructor registers with an LLVMContext, and the destructor deregisters.
 //
 //===----------------------------------------------------------------------===//
 
@@ -34,7 +34,7 @@ class Value;
 /// the point of registration.
 ///
 /// Lifetime is managed via RAII: the constructor registers with the
-/// LLVMContext, and the destructor unregisters.
+/// LLVMContext, and the destructor deregisters.
 class ValueDeletionListener {
 public:
   using CallbackT = void (*)(ValueDeletionListener *, const Value *);

>From 10d637ff875bb440fe33e2060e4d07f5a1786c72 Mon Sep 17 00:00:00 2001
From: padivedi <padivedi at amd.com>
Date: Wed, 22 Apr 2026 14:20:36 +0530
Subject: [PATCH 4/9] review: address suggestion

---
 llvm/lib/IR/LLVMContext.cpp   | 7 +++++--
 llvm/lib/IR/LLVMContextImpl.h | 2 +-
 2 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/llvm/lib/IR/LLVMContext.cpp b/llvm/lib/IR/LLVMContext.cpp
index 165c82272f691..78a99dfb46630 100644
--- a/llvm/lib/IR/LLVMContext.cpp
+++ b/llvm/lib/IR/LLVMContext.cpp
@@ -218,11 +218,14 @@ ValueDeletionListener::~ValueDeletionListener() {
 }
 
 void LLVMContext::addValueDeletionListener(ValueDeletionListener *L) {
-  pImpl->ValueDeletionListeners.insert(L);
+  assert(!llvm::is_contained(pImpl->ValueDeletionListeners, L) &&
+         "Listener already registered");
+  pImpl->ValueDeletionListeners.push_back(L);
 }
 
 void LLVMContext::removeValueDeletionListener(ValueDeletionListener *L) {
-  pImpl->ValueDeletionListeners.erase(L);
+  pImpl->ValueDeletionListeners.erase(
+      llvm::find(pImpl->ValueDeletionListeners, L));
 }
 
 void LLVMContext::notifyValueDeleted(Value *V) {
diff --git a/llvm/lib/IR/LLVMContextImpl.h b/llvm/lib/IR/LLVMContextImpl.h
index 09d31a0da58af..8c7604ad86c21 100644
--- a/llvm/lib/IR/LLVMContextImpl.h
+++ b/llvm/lib/IR/LLVMContextImpl.h
@@ -1751,7 +1751,7 @@ class LLVMContextImpl {
   /// 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;
+  SmallVector<ValueDeletionListener *, 2> ValueDeletionListeners;
 
   /// CustomMDKindNames - Map to hold the metadata string to ID mapping.
   StringMap<unsigned> CustomMDKindNames;

>From 81ac3923683e36d0f59d48fddc77a762228a1274 Mon Sep 17 00:00:00 2001
From: padivedi <padivedi at amd.com>
Date: Thu, 23 Apr 2026 16:02:24 +0530
Subject: [PATCH 5/9] change the listener design from ~value per context to
 instructiper function

---
 llvm/include/llvm/IR/Function.h               |  17 ++
 llvm/include/llvm/IR/Instruction.h            |   4 +
 .../llvm/IR/InstructionDeletionListener.h     |  61 ++++++
 llvm/include/llvm/IR/LLVMContext.h            |  11 -
 llvm/include/llvm/IR/ValueDeletionListener.h  |  58 ------
 llvm/lib/IR/BasicBlock.cpp                    |   6 +
 llvm/lib/IR/Function.cpp                      |  28 +++
 llvm/lib/IR/Instruction.cpp                   |  10 +
 llvm/lib/IR/LLVMContext.cpp                   |  26 ---
 llvm/lib/IR/LLVMContextImpl.h                 |   6 -
 llvm/lib/IR/Value.cpp                         |   4 -
 llvm/unittests/IR/CMakeLists.txt              |   2 +-
 .../IR/InstructionDeletionListenerTest.cpp    | 189 ++++++++++++++++++
 .../IR/ValueDeletionListenerTest.cpp          | 132 ------------
 14 files changed, 316 insertions(+), 238 deletions(-)
 create mode 100644 llvm/include/llvm/IR/InstructionDeletionListener.h
 delete mode 100644 llvm/include/llvm/IR/ValueDeletionListener.h
 create mode 100644 llvm/unittests/IR/InstructionDeletionListenerTest.cpp
 delete mode 100644 llvm/unittests/IR/ValueDeletionListenerTest.cpp

diff --git a/llvm/include/llvm/IR/Function.h b/llvm/include/llvm/IR/Function.h
index f39fe509a49a4..a6010a90d8869 100644
--- a/llvm/include/llvm/IR/Function.h
+++ b/llvm/include/llvm/IR/Function.h
@@ -18,6 +18,7 @@
 #define LLVM_IR_FUNCTION_H
 
 #include "llvm/ADT/DenseSet.h"
+#include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/ADT/Twine.h"
 #include "llvm/ADT/ilist_node.h"
@@ -61,6 +62,7 @@ class Type;
 class User;
 class BranchProbabilityInfo;
 class BlockFrequencyInfo;
+class InstructionDeletionListener;
 
 class LLVM_ABI Function : public GlobalObject, public ilist_node<Function> {
 public:
@@ -112,7 +114,22 @@ class LLVM_ABI Function : public GlobalObject, public ilist_node<Function> {
 
   friend class SymbolTableListTraits<Function>;
 
+  friend class InstructionDeletionListener;
+  SmallVector<InstructionDeletionListener *, 0> InstructionDeletionListeners;
+
+  void addInstructionDeletionListener(InstructionDeletionListener *L);
+  void removeInstructionDeletionListener(InstructionDeletionListener *L);
+
 public:
+  /// Notify all registered listeners that an instruction is being removed
+  /// from this function. Called from Instruction::setParent and
+  /// BasicBlock::setParent when the parent is being set to null.
+  LLVM_ABI void notifyInstructionRemoved(Instruction *I);
+
+  bool hasInstructionDeletionListeners() const {
+    return !InstructionDeletionListeners.empty();
+  }
+
   /// hasLazyArguments/CheckLazyArguments - The argument list of a function is
   /// built on demand, so that the list isn't allocated until the first client
   /// needs it.  The hasLazyArguments predicate returns true if the arg list
diff --git a/llvm/include/llvm/IR/Instruction.h b/llvm/include/llvm/IR/Instruction.h
index 0b57ad4d0a379..a2661702d35a1 100644
--- a/llvm/include/llvm/IR/Instruction.h
+++ b/llvm/include/llvm/IR/Instruction.h
@@ -1081,6 +1081,10 @@ class Instruction : public User,
                                      ilist_parent<BasicBlock>>;
   friend class BasicBlock; // For renumbering.
 
+  /// Overrides ilist-provided setParent to notify per-function
+  /// InstructionDeletionListeners when this instruction is removed.
+  void setParent(BasicBlock *P);
+
   // Shadow Value::setValueSubclassData with a private forwarding method so that
   // subclasses cannot accidentally use it.
   void setValueSubclassData(unsigned short D) {
diff --git a/llvm/include/llvm/IR/InstructionDeletionListener.h b/llvm/include/llvm/IR/InstructionDeletionListener.h
new file mode 100644
index 0000000000000..c96b374a0ae51
--- /dev/null
+++ b/llvm/include/llvm/IR/InstructionDeletionListener.h
@@ -0,0 +1,61 @@
+//===- llvm/IR/InstructionDeletionListener.h - Instruction removal hook
+//----===//
+//
+// 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 InstructionDeletionListener, an interface that allows
+// analyses to be notified when an Instruction is removed from a Function.
+// Think of an analysis that uses this as "a value handle to many values" —
+// a single per-function registration replaces one handle per tracked value.
+//
+// Registration and deregistration happen automatically via RAII: the
+// constructor registers with a Function, and the destructor deregisters.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_IR_INSTRUCTIONDELETIONLISTENER_H
+#define LLVM_IR_INSTRUCTIONDELETIONLISTENER_H
+
+#include "llvm/Support/Compiler.h"
+
+namespace llvm {
+
+class Function;
+class Instruction;
+
+/// A per-function listener notified when an Instruction is removed from its
+/// parent BasicBlock.
+///
+/// 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.
+///
+/// Lifetime is managed via RAII: the constructor registers with the
+/// Function, and the destructor deregisters.
+class InstructionDeletionListener {
+public:
+  using CallbackT = void (*)(InstructionDeletionListener *, Instruction *);
+
+private:
+  Function &F;
+  CallbackT Callback;
+
+public:
+  LLVM_ABI InstructionDeletionListener(Function &F, CallbackT CB);
+  LLVM_ABI ~InstructionDeletionListener();
+
+  InstructionDeletionListener(const InstructionDeletionListener &) = delete;
+  InstructionDeletionListener &
+  operator=(const InstructionDeletionListener &) = delete;
+
+  void instructionRemoved(Instruction *I) { Callback(this, I); }
+  Function &getFunction() const { return F; }
+};
+
+} // namespace llvm
+
+#endif // LLVM_IR_INSTRUCTIONDELETIONLISTENER_H
diff --git a/llvm/include/llvm/IR/LLVMContext.h b/llvm/include/llvm/IR/LLVMContext.h
index c3be962c14cb8..646a04673dbd0 100644
--- a/llvm/include/llvm/IR/LLVMContext.h
+++ b/llvm/include/llvm/IR/LLVMContext.h
@@ -32,12 +32,10 @@ class Instruction;
 class LLVMContextImpl;
 class Module;
 class OptPassGate;
-class ValueDeletionListener;
 template <typename T> class SmallVectorImpl;
 template <typename T> class StringMapEntry;
 class StringRef;
 class Twine;
-class Value;
 class LLVMRemarkStreamer;
 
 namespace remarks {
@@ -364,15 +362,6 @@ class LLVMContext {
 
   /// removeModule - Unregister a module from this context.
   void removeModule(Module *);
-
-  // ValueDeletionListener registers/deregisters via its constructor/destructor.
-  friend class ValueDeletionListener;
-  // Value calls notifyValueDeleted from ~Value().
-  friend class Value;
-
-  LLVM_ABI void addValueDeletionListener(ValueDeletionListener *L);
-  LLVM_ABI void removeValueDeletionListener(ValueDeletionListener *L);
-  LLVM_ABI void notifyValueDeleted(Value *V);
 };
 
 // Create wrappers for C Binding types (see CBindingWrapping.h).
diff --git a/llvm/include/llvm/IR/ValueDeletionListener.h b/llvm/include/llvm/IR/ValueDeletionListener.h
deleted file mode 100644
index 465ca2b5b9597..0000000000000
--- a/llvm/include/llvm/IR/ValueDeletionListener.h
+++ /dev/null
@@ -1,58 +0,0 @@
-//===- 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 deregistration happen automatically via RAII: the
-// constructor registers with an LLVMContext, and the destructor deregisters.
-//
-//===----------------------------------------------------------------------===//
-
-#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 deregisters.
-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/BasicBlock.cpp b/llvm/lib/IR/BasicBlock.cpp
index da97b26f7cec5..85e0762f161f2 100644
--- a/llvm/lib/IR/BasicBlock.cpp
+++ b/llvm/lib/IR/BasicBlock.cpp
@@ -17,6 +17,7 @@
 #include "llvm/IR/CFG.h"
 #include "llvm/IR/Constants.h"
 #include "llvm/IR/DebugProgramInstruction.h"
+#include "llvm/IR/Function.h"
 #include "llvm/IR/Instructions.h"
 #include "llvm/IR/IntrinsicInst.h"
 #include "llvm/IR/LLVMContext.h"
@@ -195,6 +196,11 @@ BasicBlock::~BasicBlock() {
 }
 
 void BasicBlock::setParent(Function *parent) {
+  // Notify per-function listeners when BB is removed from its parent function.
+  if (!parent && Parent && Parent->hasInstructionDeletionListeners()) {
+    for (Instruction &I : *this)
+      Parent->notifyInstructionRemoved(&I);
+  }
   // Set Parent=parent, updating instruction symtab entries as appropriate.
   if (Parent != parent)
     Number = parent ? parent->NextBlockNum++ : -1u;
diff --git a/llvm/lib/IR/Function.cpp b/llvm/lib/IR/Function.cpp
index a6568bb50f0c8..f3ba502dd3029 100644
--- a/llvm/lib/IR/Function.cpp
+++ b/llvm/lib/IR/Function.cpp
@@ -30,6 +30,7 @@
 #include "llvm/IR/GlobalValue.h"
 #include "llvm/IR/InstIterator.h"
 #include "llvm/IR/Instruction.h"
+#include "llvm/IR/InstructionDeletionListener.h"
 #include "llvm/IR/IntrinsicInst.h"
 #include "llvm/IR/Intrinsics.h"
 #include "llvm/IR/LLVMContext.h"
@@ -62,6 +63,33 @@ using ProfileCount = Function::ProfileCount;
 // are not in the public header file...
 template class LLVM_EXPORT_TEMPLATE llvm::SymbolTableListTraits<BasicBlock>;
 
+InstructionDeletionListener::InstructionDeletionListener(Function &F,
+                                                         CallbackT CB)
+    : F(F), Callback(CB) {
+  F.addInstructionDeletionListener(this);
+}
+
+InstructionDeletionListener::~InstructionDeletionListener() {
+  F.removeInstructionDeletionListener(this);
+}
+
+void Function::addInstructionDeletionListener(InstructionDeletionListener *L) {
+  assert(!llvm::is_contained(InstructionDeletionListeners, L) &&
+         "Listener already registered");
+  InstructionDeletionListeners.push_back(L);
+}
+
+void Function::removeInstructionDeletionListener(
+    InstructionDeletionListener *L) {
+  InstructionDeletionListeners.erase(
+      llvm::find(InstructionDeletionListeners, L));
+}
+
+void Function::notifyInstructionRemoved(Instruction *I) {
+  for (InstructionDeletionListener *L : InstructionDeletionListeners)
+    L->instructionRemoved(I);
+}
+
 static cl::opt<int> NonGlobalValueMaxNameSize(
     "non-global-value-max-name-size", cl::Hidden, cl::init(1024),
     cl::desc("Maximum size for the name of non-global values."));
diff --git a/llvm/lib/IR/Instruction.cpp b/llvm/lib/IR/Instruction.cpp
index 8aa19a436a157..7547640552895 100644
--- a/llvm/lib/IR/Instruction.cpp
+++ b/llvm/lib/IR/Instruction.cpp
@@ -16,6 +16,7 @@
 #include "llvm/IR/AttributeMask.h"
 #include "llvm/IR/Attributes.h"
 #include "llvm/IR/Constants.h"
+#include "llvm/IR/Function.h"
 #include "llvm/IR/InstrTypes.h"
 #include "llvm/IR/Instructions.h"
 #include "llvm/IR/IntrinsicInst.h"
@@ -95,6 +96,15 @@ const DataLayout &Instruction::getDataLayout() const {
   return getModule()->getDataLayout();
 }
 
+void Instruction::setParent(BasicBlock *P) {
+  if (!P && getParent() && getParent()->getParent())
+    getParent()->getParent()->notifyInstructionRemoved(this);
+  using Base =
+      ilist_node_with_parent<Instruction, BasicBlock, ilist_iterator_bits<true>,
+                             ilist_parent<BasicBlock>>;
+  Base::setParent(P);
+}
+
 void Instruction::removeFromParent() {
   // Perform any debug-info maintenence required.
   handleMarkerRemoval();
diff --git a/llvm/lib/IR/LLVMContext.cpp b/llvm/lib/IR/LLVMContext.cpp
index 78a99dfb46630..10aba759185a7 100644
--- a/llvm/lib/IR/LLVMContext.cpp
+++ b/llvm/lib/IR/LLVMContext.cpp
@@ -20,7 +20,6 @@
 #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"
@@ -208,31 +207,6 @@ 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) {
-  assert(!llvm::is_contained(pImpl->ValueDeletionListeners, L) &&
-         "Listener already registered");
-  pImpl->ValueDeletionListeners.push_back(L);
-}
-
-void LLVMContext::removeValueDeletionListener(ValueDeletionListener *L) {
-  pImpl->ValueDeletionListeners.erase(
-      llvm::find(pImpl->ValueDeletionListeners, L));
-}
-
-void LLVMContext::notifyValueDeleted(Value *V) {
-  for (ValueDeletionListener *L : pImpl->ValueDeletionListeners)
-    L->valueDeleted(V);
-}
-
 void LLVMContext::emitError(const Twine &ErrorStr) {
   diagnose(DiagnosticInfoGeneric(ErrorStr));
 }
diff --git a/llvm/lib/IR/LLVMContextImpl.h b/llvm/lib/IR/LLVMContextImpl.h
index 8c7604ad86c21..11245b4ea7803 100644
--- a/llvm/lib/IR/LLVMContextImpl.h
+++ b/llvm/lib/IR/LLVMContextImpl.h
@@ -73,7 +73,6 @@ class RemarkStreamer;
 template <typename T> class StringMapEntry;
 class StringRef;
 class TypedPointerType;
-class ValueDeletionListener;
 class ValueHandleBase;
 
 template <> struct DenseMapInfo<APFloat> {
@@ -1748,11 +1747,6 @@ 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.
-  SmallVector<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 32e355f20566e..360bf0f8fc47f 100644
--- a/llvm/lib/IR/Value.cpp
+++ b/llvm/lib/IR/Value.cpp
@@ -77,10 +77,6 @@ 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*).
-  getContext().notifyValueDeleted(this);
-
   if (isUsedByMetadata())
     ValueAsMetadata::handleDeletion(this);
 
diff --git a/llvm/unittests/IR/CMakeLists.txt b/llvm/unittests/IR/CMakeLists.txt
index 3684f3b699594..b31996f5f69fa 100644
--- a/llvm/unittests/IR/CMakeLists.txt
+++ b/llvm/unittests/IR/CMakeLists.txt
@@ -31,6 +31,7 @@ add_llvm_unittest(IRTests
   GlobalObjectTest.cpp
   PassBuilderCallbacksTest.cpp
   IRBuilderTest.cpp
+  InstructionDeletionListenerTest.cpp
   InstructionsTest.cpp
   IntrinsicsTest.cpp
   LegacyPassManagerTest.cpp
@@ -49,7 +50,6 @@ add_llvm_unittest(IRTests
   TypesTest.cpp
   UseTest.cpp
   UserTest.cpp
-  ValueDeletionListenerTest.cpp
   ValueHandleTest.cpp
   ValueMapTest.cpp
   ValueTest.cpp
diff --git a/llvm/unittests/IR/InstructionDeletionListenerTest.cpp b/llvm/unittests/IR/InstructionDeletionListenerTest.cpp
new file mode 100644
index 0000000000000..53e3a7e5afee0
--- /dev/null
+++ b/llvm/unittests/IR/InstructionDeletionListenerTest.cpp
@@ -0,0 +1,189 @@
+//===- InstructionDeletionListenerTest.cpp - per-Function listener tests --===//
+//
+// 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/InstructionDeletionListener.h"
+#include "llvm/ADT/DenseSet.h"
+#include "llvm/IR/BasicBlock.h"
+#include "llvm/IR/DerivedTypes.h"
+#include "llvm/IR/Function.h"
+#include "llvm/IR/IRBuilder.h"
+#include "llvm/IR/LLVMContext.h"
+#include "llvm/IR/Module.h"
+#include "llvm/IR/Type.h"
+#include "gtest/gtest.h"
+
+using namespace llvm;
+
+namespace {
+
+class TrackingListener : public InstructionDeletionListener {
+  DenseSet<const Value *> &UniformValuesRef;
+
+  static void callback(InstructionDeletionListener *Self, Instruction *I) {
+    static_cast<TrackingListener *>(Self)->UniformValuesRef.erase(I);
+  }
+
+public:
+  TrackingListener(Function &F, DenseSet<const Value *> &UniformValues)
+      : InstructionDeletionListener(F, &callback),
+        UniformValuesRef(UniformValues) {}
+};
+
+static Function *createFunction(Module &M, const char *Name) {
+  Type *I32Ty = Type::getInt32Ty(M.getContext());
+  FunctionType *FTy = FunctionType::get(I32Ty, {I32Ty, I32Ty}, false);
+  return Function::Create(FTy, GlobalValue::ExternalLinkage, Name, &M);
+}
+
+TEST(InstructionDeletionListenerTest, EraseFromParent) {
+  LLVMContext C;
+  Module M("test", C);
+  Function *F = createFunction(M, "f");
+  BasicBlock *BB = BasicBlock::Create(C, "entry", F);
+  IRBuilder<> Builder(BB);
+
+  Instruction *Add =
+      cast<Instruction>(Builder.CreateAdd(F->getArg(0), F->getArg(1)));
+  Builder.CreateRet(F->getArg(0));
+
+  DenseSet<const Value *> UniformValues;
+  UniformValues.insert(Add);
+
+  TrackingListener Listener(*F, UniformValues);
+
+  EXPECT_TRUE(UniformValues.contains(Add));
+  Add->eraseFromParent();
+  EXPECT_FALSE(UniformValues.contains(Add));
+}
+
+TEST(InstructionDeletionListenerTest, RemoveAndDelete) {
+  LLVMContext C;
+  Module M("test", C);
+  Function *F = createFunction(M, "f");
+  BasicBlock *BB = BasicBlock::Create(C, "entry", F);
+  IRBuilder<> Builder(BB);
+
+  Instruction *Add =
+      cast<Instruction>(Builder.CreateAdd(F->getArg(0), F->getArg(1)));
+  Builder.CreateRet(F->getArg(0));
+
+  DenseSet<const Value *> UniformValues;
+  UniformValues.insert(Add);
+
+  TrackingListener Listener(*F, UniformValues);
+
+  EXPECT_TRUE(UniformValues.contains(Add));
+  Add->removeFromParent();
+  EXPECT_FALSE(UniformValues.contains(Add));
+  Add->deleteValue();
+}
+
+TEST(InstructionDeletionListenerTest, BasicBlockErasure) {
+  LLVMContext C;
+  Module M("test", C);
+  Function *F = createFunction(M, "f");
+  BasicBlock *Entry = BasicBlock::Create(C, "entry", F);
+  BasicBlock *Dead = BasicBlock::Create(C, "dead", F);
+  IRBuilder<> Builder(Entry);
+  Builder.CreateRet(F->getArg(0));
+
+  Builder.SetInsertPoint(Dead);
+  Instruction *Add =
+      cast<Instruction>(Builder.CreateAdd(F->getArg(0), F->getArg(1)));
+  Builder.CreateUnreachable();
+
+  DenseSet<const Value *> UniformValues;
+  UniformValues.insert(Add);
+
+  TrackingListener Listener(*F, UniformValues);
+
+  EXPECT_TRUE(UniformValues.contains(Add));
+  Dead->eraseFromParent();
+  EXPECT_FALSE(UniformValues.contains(Add));
+}
+
+TEST(InstructionDeletionListenerTest, ListenerScopeRAII) {
+  LLVMContext C;
+  Module M("test", C);
+  Function *F = createFunction(M, "f");
+  BasicBlock *BB = BasicBlock::Create(C, "entry", F);
+  IRBuilder<> Builder(BB);
+  Builder.CreateRet(F->getArg(0));
+
+  EXPECT_FALSE(F->hasInstructionDeletionListeners());
+  {
+    DenseSet<const Value *> UniformValues;
+    TrackingListener Listener(*F, UniformValues);
+    EXPECT_TRUE(F->hasInstructionDeletionListeners());
+  }
+  EXPECT_FALSE(F->hasInstructionDeletionListeners());
+}
+
+TEST(InstructionDeletionListenerTest, MultipleListeners) {
+  LLVMContext C;
+  Module M("test", C);
+  Function *F = createFunction(M, "f");
+  BasicBlock *BB = BasicBlock::Create(C, "entry", F);
+  IRBuilder<> Builder(BB);
+
+  Instruction *Add =
+      cast<Instruction>(Builder.CreateAdd(F->getArg(0), F->getArg(1)));
+  Builder.CreateRet(F->getArg(0));
+
+  DenseSet<const Value *> UniformValues1;
+  DenseSet<const Value *> UniformValues2;
+  UniformValues1.insert(Add);
+  UniformValues2.insert(Add);
+
+  TrackingListener L1(*F, UniformValues1);
+  TrackingListener L2(*F, UniformValues2);
+
+  EXPECT_TRUE(UniformValues1.contains(Add));
+  EXPECT_TRUE(UniformValues2.contains(Add));
+  Add->eraseFromParent();
+  EXPECT_FALSE(UniformValues1.contains(Add));
+  EXPECT_FALSE(UniformValues2.contains(Add));
+}
+
+TEST(InstructionDeletionListenerTest, PerFunctionIsolation) {
+  LLVMContext C;
+  Module M("test", C);
+  Function *F1 = createFunction(M, "f1");
+  Function *F2 = createFunction(M, "f2");
+
+  BasicBlock *BB1 = BasicBlock::Create(C, "entry", F1);
+  BasicBlock *BB2 = BasicBlock::Create(C, "entry", F2);
+
+  IRBuilder<> B1(BB1);
+  Instruction *Add1 =
+      cast<Instruction>(B1.CreateAdd(F1->getArg(0), F1->getArg(1)));
+  B1.CreateRet(F1->getArg(0));
+
+  IRBuilder<> B2(BB2);
+  Instruction *Add2 =
+      cast<Instruction>(B2.CreateAdd(F2->getArg(0), F2->getArg(1)));
+  B2.CreateRet(F2->getArg(0));
+
+  DenseSet<const Value *> UniformValues1;
+  DenseSet<const Value *> UniformValues2;
+  UniformValues1.insert(Add1);
+  UniformValues2.insert(Add2);
+
+  TrackingListener L1(*F1, UniformValues1);
+  TrackingListener L2(*F2, UniformValues2);
+
+  // Erasing from F1 should not affect F2's listener.
+  Add1->eraseFromParent();
+  EXPECT_FALSE(UniformValues1.contains(Add1));
+  EXPECT_TRUE(UniformValues2.contains(Add2));
+
+  Add2->eraseFromParent();
+  EXPECT_FALSE(UniformValues2.contains(Add2));
+}
+
+} // end anonymous namespace
diff --git a/llvm/unittests/IR/ValueDeletionListenerTest.cpp b/llvm/unittests/IR/ValueDeletionListenerTest.cpp
deleted file mode 100644
index 0332b95550a82..0000000000000
--- a/llvm/unittests/IR/ValueDeletionListenerTest.cpp
+++ /dev/null
@@ -1,132 +0,0 @@
-//===- 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

>From 45cd4c14018c08753f4b9ba88a7854d40e938d10 Mon Sep 17 00:00:00 2001
From: padivedi <padivedi at amd.com>
Date: Wed, 6 May 2026 19:54:06 +0530
Subject: [PATCH 6/9] add RAUW's support

---
 llvm/include/llvm/IR/Function.h               |  18 +-
 llvm/include/llvm/IR/Instruction.h            |   2 +-
 .../llvm/IR/InstructionDeletionListener.h     |  61 ---
 llvm/include/llvm/IR/InstructionListener.h    |  69 ++++
 llvm/lib/IR/BasicBlock.cpp                    |   2 +-
 llvm/lib/IR/Function.cpp                      |  33 +-
 llvm/lib/IR/Value.cpp                         |   8 +
 llvm/unittests/IR/CMakeLists.txt              |   2 +-
 .../IR/InstructionDeletionListenerTest.cpp    | 189 ---------
 llvm/unittests/IR/InstructionListenerTest.cpp | 380 ++++++++++++++++++
 10 files changed, 488 insertions(+), 276 deletions(-)
 delete mode 100644 llvm/include/llvm/IR/InstructionDeletionListener.h
 create mode 100644 llvm/include/llvm/IR/InstructionListener.h
 delete mode 100644 llvm/unittests/IR/InstructionDeletionListenerTest.cpp
 create mode 100644 llvm/unittests/IR/InstructionListenerTest.cpp

diff --git a/llvm/include/llvm/IR/Function.h b/llvm/include/llvm/IR/Function.h
index a6010a90d8869..c846fd5d55f31 100644
--- a/llvm/include/llvm/IR/Function.h
+++ b/llvm/include/llvm/IR/Function.h
@@ -62,7 +62,7 @@ class Type;
 class User;
 class BranchProbabilityInfo;
 class BlockFrequencyInfo;
-class InstructionDeletionListener;
+class InstructionListener;
 
 class LLVM_ABI Function : public GlobalObject, public ilist_node<Function> {
 public:
@@ -114,11 +114,11 @@ class LLVM_ABI Function : public GlobalObject, public ilist_node<Function> {
 
   friend class SymbolTableListTraits<Function>;
 
-  friend class InstructionDeletionListener;
-  SmallVector<InstructionDeletionListener *, 0> InstructionDeletionListeners;
+  friend class InstructionListener;
+  SmallVector<InstructionListener *, 0> InstructionListeners;
 
-  void addInstructionDeletionListener(InstructionDeletionListener *L);
-  void removeInstructionDeletionListener(InstructionDeletionListener *L);
+  void addInstructionListener(InstructionListener *L);
+  void removeInstructionListener(InstructionListener *L);
 
 public:
   /// Notify all registered listeners that an instruction is being removed
@@ -126,9 +126,11 @@ class LLVM_ABI Function : public GlobalObject, public ilist_node<Function> {
   /// BasicBlock::setParent when the parent is being set to null.
   LLVM_ABI void notifyInstructionRemoved(Instruction *I);
 
-  bool hasInstructionDeletionListeners() const {
-    return !InstructionDeletionListeners.empty();
-  }
+  /// Notify all registered listeners that an instruction in this function is
+  /// being RAUW'd (replaced with another value). Called from Value::doRAUW().
+  LLVM_ABI void notifyInstructionRAUW(Instruction *Old, Value *New);
+
+  bool hasInstructionListeners() const { return !InstructionListeners.empty(); }
 
   /// hasLazyArguments/CheckLazyArguments - The argument list of a function is
   /// built on demand, so that the list isn't allocated until the first client
diff --git a/llvm/include/llvm/IR/Instruction.h b/llvm/include/llvm/IR/Instruction.h
index a2661702d35a1..7420e6d0cd357 100644
--- a/llvm/include/llvm/IR/Instruction.h
+++ b/llvm/include/llvm/IR/Instruction.h
@@ -1082,7 +1082,7 @@ class Instruction : public User,
   friend class BasicBlock; // For renumbering.
 
   /// Overrides ilist-provided setParent to notify per-function
-  /// InstructionDeletionListeners when this instruction is removed.
+  /// InstructionListeners when this instruction is removed.
   void setParent(BasicBlock *P);
 
   // Shadow Value::setValueSubclassData with a private forwarding method so that
diff --git a/llvm/include/llvm/IR/InstructionDeletionListener.h b/llvm/include/llvm/IR/InstructionDeletionListener.h
deleted file mode 100644
index c96b374a0ae51..0000000000000
--- a/llvm/include/llvm/IR/InstructionDeletionListener.h
+++ /dev/null
@@ -1,61 +0,0 @@
-//===- llvm/IR/InstructionDeletionListener.h - Instruction removal hook
-//----===//
-//
-// 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 InstructionDeletionListener, an interface that allows
-// analyses to be notified when an Instruction is removed from a Function.
-// Think of an analysis that uses this as "a value handle to many values" —
-// a single per-function registration replaces one handle per tracked value.
-//
-// Registration and deregistration happen automatically via RAII: the
-// constructor registers with a Function, and the destructor deregisters.
-//
-//===----------------------------------------------------------------------===//
-
-#ifndef LLVM_IR_INSTRUCTIONDELETIONLISTENER_H
-#define LLVM_IR_INSTRUCTIONDELETIONLISTENER_H
-
-#include "llvm/Support/Compiler.h"
-
-namespace llvm {
-
-class Function;
-class Instruction;
-
-/// A per-function listener notified when an Instruction is removed from its
-/// parent BasicBlock.
-///
-/// 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.
-///
-/// Lifetime is managed via RAII: the constructor registers with the
-/// Function, and the destructor deregisters.
-class InstructionDeletionListener {
-public:
-  using CallbackT = void (*)(InstructionDeletionListener *, Instruction *);
-
-private:
-  Function &F;
-  CallbackT Callback;
-
-public:
-  LLVM_ABI InstructionDeletionListener(Function &F, CallbackT CB);
-  LLVM_ABI ~InstructionDeletionListener();
-
-  InstructionDeletionListener(const InstructionDeletionListener &) = delete;
-  InstructionDeletionListener &
-  operator=(const InstructionDeletionListener &) = delete;
-
-  void instructionRemoved(Instruction *I) { Callback(this, I); }
-  Function &getFunction() const { return F; }
-};
-
-} // namespace llvm
-
-#endif // LLVM_IR_INSTRUCTIONDELETIONLISTENER_H
diff --git a/llvm/include/llvm/IR/InstructionListener.h b/llvm/include/llvm/IR/InstructionListener.h
new file mode 100644
index 0000000000000..a0d01a075ee41
--- /dev/null
+++ b/llvm/include/llvm/IR/InstructionListener.h
@@ -0,0 +1,69 @@
+//===- llvm/IR/InstructionListener.h - Per-function instruction listener --===//
+//
+// 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 InstructionListener, a per-function interface that
+// notifies analyses when an Instruction is removed from a Function or
+// RAUW'd (replaced with another value). Think of it as "a value handle to
+// many values" — a single per-function registration replaces one handle per
+// tracked value.
+//
+// Registration and deregistration happen automatically via RAII: the
+// constructor registers with a Function, and the destructor deregisters.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_IR_INSTRUCTIONLISTENER_H
+#define LLVM_IR_INSTRUCTIONLISTENER_H
+
+#include "llvm/Support/Compiler.h"
+
+namespace llvm {
+
+class Function;
+class Instruction;
+class Value;
+
+/// A per-function listener notified when an Instruction is removed from its
+/// parent BasicBlock or when an Instruction is RAUW'd.
+///
+/// Subclasses implement the callbacks by passing static functions to the
+/// constructor, which static_casts the listener back to the derived type.
+/// This avoids virtual dispatch overhead while preserving type safety.
+///
+/// Lifetime is managed via RAII: the constructor registers with the
+/// Function, and the destructor deregisters.
+class InstructionListener {
+public:
+  using CallbackT = void (*)(InstructionListener *, Instruction *);
+  using RAUWCallbackT = void (*)(InstructionListener *, Instruction *Old,
+                                 Value *New);
+
+private:
+  Function &F;
+  CallbackT Callback;
+  RAUWCallbackT RAUWCallback;
+
+public:
+  LLVM_ABI InstructionListener(Function &F, CallbackT CB,
+                               RAUWCallbackT RAUWCB = nullptr);
+  LLVM_ABI ~InstructionListener();
+
+  InstructionListener(const InstructionListener &) = delete;
+  InstructionListener &operator=(const InstructionListener &) = delete;
+
+  void instructionRemoved(Instruction *I) { Callback(this, I); }
+  void instructionRAUW(Instruction *Old, Value *New) {
+    if (RAUWCallback)
+      RAUWCallback(this, Old, New);
+  }
+  Function &getFunction() const { return F; }
+};
+
+} // namespace llvm
+
+#endif // LLVM_IR_INSTRUCTIONLISTENER_H
diff --git a/llvm/lib/IR/BasicBlock.cpp b/llvm/lib/IR/BasicBlock.cpp
index 85e0762f161f2..c2afe8139e078 100644
--- a/llvm/lib/IR/BasicBlock.cpp
+++ b/llvm/lib/IR/BasicBlock.cpp
@@ -197,7 +197,7 @@ BasicBlock::~BasicBlock() {
 
 void BasicBlock::setParent(Function *parent) {
   // Notify per-function listeners when BB is removed from its parent function.
-  if (!parent && Parent && Parent->hasInstructionDeletionListeners()) {
+  if (!parent && Parent && Parent->hasInstructionListeners()) {
     for (Instruction &I : *this)
       Parent->notifyInstructionRemoved(&I);
   }
diff --git a/llvm/lib/IR/Function.cpp b/llvm/lib/IR/Function.cpp
index f3ba502dd3029..281e9719f2bbd 100644
--- a/llvm/lib/IR/Function.cpp
+++ b/llvm/lib/IR/Function.cpp
@@ -30,7 +30,7 @@
 #include "llvm/IR/GlobalValue.h"
 #include "llvm/IR/InstIterator.h"
 #include "llvm/IR/Instruction.h"
-#include "llvm/IR/InstructionDeletionListener.h"
+#include "llvm/IR/InstructionListener.h"
 #include "llvm/IR/IntrinsicInst.h"
 #include "llvm/IR/Intrinsics.h"
 #include "llvm/IR/LLVMContext.h"
@@ -63,33 +63,36 @@ using ProfileCount = Function::ProfileCount;
 // are not in the public header file...
 template class LLVM_EXPORT_TEMPLATE llvm::SymbolTableListTraits<BasicBlock>;
 
-InstructionDeletionListener::InstructionDeletionListener(Function &F,
-                                                         CallbackT CB)
-    : F(F), Callback(CB) {
-  F.addInstructionDeletionListener(this);
+InstructionListener::InstructionListener(Function &F, CallbackT CB,
+                                         RAUWCallbackT RAUWCB)
+    : F(F), Callback(CB), RAUWCallback(RAUWCB) {
+  F.addInstructionListener(this);
 }
 
-InstructionDeletionListener::~InstructionDeletionListener() {
-  F.removeInstructionDeletionListener(this);
+InstructionListener::~InstructionListener() {
+  F.removeInstructionListener(this);
 }
 
-void Function::addInstructionDeletionListener(InstructionDeletionListener *L) {
-  assert(!llvm::is_contained(InstructionDeletionListeners, L) &&
+void Function::addInstructionListener(InstructionListener *L) {
+  assert(!llvm::is_contained(InstructionListeners, L) &&
          "Listener already registered");
-  InstructionDeletionListeners.push_back(L);
+  InstructionListeners.push_back(L);
 }
 
-void Function::removeInstructionDeletionListener(
-    InstructionDeletionListener *L) {
-  InstructionDeletionListeners.erase(
-      llvm::find(InstructionDeletionListeners, L));
+void Function::removeInstructionListener(InstructionListener *L) {
+  InstructionListeners.erase(llvm::find(InstructionListeners, L));
 }
 
 void Function::notifyInstructionRemoved(Instruction *I) {
-  for (InstructionDeletionListener *L : InstructionDeletionListeners)
+  for (InstructionListener *L : InstructionListeners)
     L->instructionRemoved(I);
 }
 
+void Function::notifyInstructionRAUW(Instruction *Old, Value *New) {
+  for (InstructionListener *L : InstructionListeners)
+    L->instructionRAUW(Old, New);
+}
+
 static cl::opt<int> NonGlobalValueMaxNameSize(
     "non-global-value-max-name-size", cl::Hidden, cl::init(1024),
     cl::desc("Maximum size for the name of non-global values."));
diff --git a/llvm/lib/IR/Value.cpp b/llvm/lib/IR/Value.cpp
index 360bf0f8fc47f..3ea087692a777 100644
--- a/llvm/lib/IR/Value.cpp
+++ b/llvm/lib/IR/Value.cpp
@@ -525,6 +525,14 @@ void Value::doRAUW(Value *New, ReplaceMetadataUses ReplaceMetaUses) {
   // Notify all ValueHandles (if present) that this value is going away.
   if (HasValueHandle)
     ValueHandleBase::ValueIsRAUWd(this, New);
+
+  // Notify per-function listeners when an instruction is RAUW'd.
+  if (Instruction *I = dyn_cast<Instruction>(this))
+    if (BasicBlock *BB = I->getParent())
+      if (Function *F = BB->getParent())
+        if (F->hasInstructionListeners())
+          F->notifyInstructionRAUW(I, New);
+
   if (ReplaceMetaUses == ReplaceMetadataUses::Yes && isUsedByMetadata())
     ValueAsMetadata::handleRAUW(this, New);
 
diff --git a/llvm/unittests/IR/CMakeLists.txt b/llvm/unittests/IR/CMakeLists.txt
index b31996f5f69fa..92b082f82690e 100644
--- a/llvm/unittests/IR/CMakeLists.txt
+++ b/llvm/unittests/IR/CMakeLists.txt
@@ -31,7 +31,7 @@ add_llvm_unittest(IRTests
   GlobalObjectTest.cpp
   PassBuilderCallbacksTest.cpp
   IRBuilderTest.cpp
-  InstructionDeletionListenerTest.cpp
+  InstructionListenerTest.cpp
   InstructionsTest.cpp
   IntrinsicsTest.cpp
   LegacyPassManagerTest.cpp
diff --git a/llvm/unittests/IR/InstructionDeletionListenerTest.cpp b/llvm/unittests/IR/InstructionDeletionListenerTest.cpp
deleted file mode 100644
index 53e3a7e5afee0..0000000000000
--- a/llvm/unittests/IR/InstructionDeletionListenerTest.cpp
+++ /dev/null
@@ -1,189 +0,0 @@
-//===- InstructionDeletionListenerTest.cpp - per-Function listener tests --===//
-//
-// 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/InstructionDeletionListener.h"
-#include "llvm/ADT/DenseSet.h"
-#include "llvm/IR/BasicBlock.h"
-#include "llvm/IR/DerivedTypes.h"
-#include "llvm/IR/Function.h"
-#include "llvm/IR/IRBuilder.h"
-#include "llvm/IR/LLVMContext.h"
-#include "llvm/IR/Module.h"
-#include "llvm/IR/Type.h"
-#include "gtest/gtest.h"
-
-using namespace llvm;
-
-namespace {
-
-class TrackingListener : public InstructionDeletionListener {
-  DenseSet<const Value *> &UniformValuesRef;
-
-  static void callback(InstructionDeletionListener *Self, Instruction *I) {
-    static_cast<TrackingListener *>(Self)->UniformValuesRef.erase(I);
-  }
-
-public:
-  TrackingListener(Function &F, DenseSet<const Value *> &UniformValues)
-      : InstructionDeletionListener(F, &callback),
-        UniformValuesRef(UniformValues) {}
-};
-
-static Function *createFunction(Module &M, const char *Name) {
-  Type *I32Ty = Type::getInt32Ty(M.getContext());
-  FunctionType *FTy = FunctionType::get(I32Ty, {I32Ty, I32Ty}, false);
-  return Function::Create(FTy, GlobalValue::ExternalLinkage, Name, &M);
-}
-
-TEST(InstructionDeletionListenerTest, EraseFromParent) {
-  LLVMContext C;
-  Module M("test", C);
-  Function *F = createFunction(M, "f");
-  BasicBlock *BB = BasicBlock::Create(C, "entry", F);
-  IRBuilder<> Builder(BB);
-
-  Instruction *Add =
-      cast<Instruction>(Builder.CreateAdd(F->getArg(0), F->getArg(1)));
-  Builder.CreateRet(F->getArg(0));
-
-  DenseSet<const Value *> UniformValues;
-  UniformValues.insert(Add);
-
-  TrackingListener Listener(*F, UniformValues);
-
-  EXPECT_TRUE(UniformValues.contains(Add));
-  Add->eraseFromParent();
-  EXPECT_FALSE(UniformValues.contains(Add));
-}
-
-TEST(InstructionDeletionListenerTest, RemoveAndDelete) {
-  LLVMContext C;
-  Module M("test", C);
-  Function *F = createFunction(M, "f");
-  BasicBlock *BB = BasicBlock::Create(C, "entry", F);
-  IRBuilder<> Builder(BB);
-
-  Instruction *Add =
-      cast<Instruction>(Builder.CreateAdd(F->getArg(0), F->getArg(1)));
-  Builder.CreateRet(F->getArg(0));
-
-  DenseSet<const Value *> UniformValues;
-  UniformValues.insert(Add);
-
-  TrackingListener Listener(*F, UniformValues);
-
-  EXPECT_TRUE(UniformValues.contains(Add));
-  Add->removeFromParent();
-  EXPECT_FALSE(UniformValues.contains(Add));
-  Add->deleteValue();
-}
-
-TEST(InstructionDeletionListenerTest, BasicBlockErasure) {
-  LLVMContext C;
-  Module M("test", C);
-  Function *F = createFunction(M, "f");
-  BasicBlock *Entry = BasicBlock::Create(C, "entry", F);
-  BasicBlock *Dead = BasicBlock::Create(C, "dead", F);
-  IRBuilder<> Builder(Entry);
-  Builder.CreateRet(F->getArg(0));
-
-  Builder.SetInsertPoint(Dead);
-  Instruction *Add =
-      cast<Instruction>(Builder.CreateAdd(F->getArg(0), F->getArg(1)));
-  Builder.CreateUnreachable();
-
-  DenseSet<const Value *> UniformValues;
-  UniformValues.insert(Add);
-
-  TrackingListener Listener(*F, UniformValues);
-
-  EXPECT_TRUE(UniformValues.contains(Add));
-  Dead->eraseFromParent();
-  EXPECT_FALSE(UniformValues.contains(Add));
-}
-
-TEST(InstructionDeletionListenerTest, ListenerScopeRAII) {
-  LLVMContext C;
-  Module M("test", C);
-  Function *F = createFunction(M, "f");
-  BasicBlock *BB = BasicBlock::Create(C, "entry", F);
-  IRBuilder<> Builder(BB);
-  Builder.CreateRet(F->getArg(0));
-
-  EXPECT_FALSE(F->hasInstructionDeletionListeners());
-  {
-    DenseSet<const Value *> UniformValues;
-    TrackingListener Listener(*F, UniformValues);
-    EXPECT_TRUE(F->hasInstructionDeletionListeners());
-  }
-  EXPECT_FALSE(F->hasInstructionDeletionListeners());
-}
-
-TEST(InstructionDeletionListenerTest, MultipleListeners) {
-  LLVMContext C;
-  Module M("test", C);
-  Function *F = createFunction(M, "f");
-  BasicBlock *BB = BasicBlock::Create(C, "entry", F);
-  IRBuilder<> Builder(BB);
-
-  Instruction *Add =
-      cast<Instruction>(Builder.CreateAdd(F->getArg(0), F->getArg(1)));
-  Builder.CreateRet(F->getArg(0));
-
-  DenseSet<const Value *> UniformValues1;
-  DenseSet<const Value *> UniformValues2;
-  UniformValues1.insert(Add);
-  UniformValues2.insert(Add);
-
-  TrackingListener L1(*F, UniformValues1);
-  TrackingListener L2(*F, UniformValues2);
-
-  EXPECT_TRUE(UniformValues1.contains(Add));
-  EXPECT_TRUE(UniformValues2.contains(Add));
-  Add->eraseFromParent();
-  EXPECT_FALSE(UniformValues1.contains(Add));
-  EXPECT_FALSE(UniformValues2.contains(Add));
-}
-
-TEST(InstructionDeletionListenerTest, PerFunctionIsolation) {
-  LLVMContext C;
-  Module M("test", C);
-  Function *F1 = createFunction(M, "f1");
-  Function *F2 = createFunction(M, "f2");
-
-  BasicBlock *BB1 = BasicBlock::Create(C, "entry", F1);
-  BasicBlock *BB2 = BasicBlock::Create(C, "entry", F2);
-
-  IRBuilder<> B1(BB1);
-  Instruction *Add1 =
-      cast<Instruction>(B1.CreateAdd(F1->getArg(0), F1->getArg(1)));
-  B1.CreateRet(F1->getArg(0));
-
-  IRBuilder<> B2(BB2);
-  Instruction *Add2 =
-      cast<Instruction>(B2.CreateAdd(F2->getArg(0), F2->getArg(1)));
-  B2.CreateRet(F2->getArg(0));
-
-  DenseSet<const Value *> UniformValues1;
-  DenseSet<const Value *> UniformValues2;
-  UniformValues1.insert(Add1);
-  UniformValues2.insert(Add2);
-
-  TrackingListener L1(*F1, UniformValues1);
-  TrackingListener L2(*F2, UniformValues2);
-
-  // Erasing from F1 should not affect F2's listener.
-  Add1->eraseFromParent();
-  EXPECT_FALSE(UniformValues1.contains(Add1));
-  EXPECT_TRUE(UniformValues2.contains(Add2));
-
-  Add2->eraseFromParent();
-  EXPECT_FALSE(UniformValues2.contains(Add2));
-}
-
-} // end anonymous namespace
diff --git a/llvm/unittests/IR/InstructionListenerTest.cpp b/llvm/unittests/IR/InstructionListenerTest.cpp
new file mode 100644
index 0000000000000..a6b4f53b31580
--- /dev/null
+++ b/llvm/unittests/IR/InstructionListenerTest.cpp
@@ -0,0 +1,380 @@
+//===- InstructionListenerTest.cpp - per-Function instruction listener ----===//
+//
+// 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/InstructionListener.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/DenseSet.h"
+#include "llvm/IR/BasicBlock.h"
+#include "llvm/IR/DerivedTypes.h"
+#include "llvm/IR/Function.h"
+#include "llvm/IR/IRBuilder.h"
+#include "llvm/IR/LLVMContext.h"
+#include "llvm/IR/Module.h"
+#include "llvm/IR/Type.h"
+#include "gtest/gtest.h"
+
+using namespace llvm;
+
+namespace {
+
+class TrackingListener : public InstructionListener {
+  DenseSet<const Value *> &UniformValuesRef;
+
+  static void onRemoved(InstructionListener *Self, Instruction *I) {
+    static_cast<TrackingListener *>(Self)->UniformValuesRef.erase(I);
+  }
+
+  static void onRAUW(InstructionListener *Self, Instruction *Old, Value *New) {
+    TrackingListener *This = static_cast<TrackingListener *>(Self);
+    This->UniformValuesRef.erase(Old);
+  }
+
+public:
+  TrackingListener(Function &F, DenseSet<const Value *> &UniformValues)
+      : InstructionListener(F, &onRemoved, &onRAUW),
+        UniformValuesRef(UniformValues) {}
+};
+
+static Function *createFunction(Module &M, const char *Name) {
+  Type *I32Ty = Type::getInt32Ty(M.getContext());
+  FunctionType *FTy = FunctionType::get(I32Ty, {I32Ty, I32Ty}, false);
+  return Function::Create(FTy, GlobalValue::ExternalLinkage, Name, &M);
+}
+
+TEST(InstructionListenerTest, EraseFromParent) {
+  LLVMContext C;
+  Module M("test", C);
+  Function *F = createFunction(M, "f");
+  BasicBlock *BB = BasicBlock::Create(C, "entry", F);
+  IRBuilder<> Builder(BB);
+
+  Instruction *Add =
+      cast<Instruction>(Builder.CreateAdd(F->getArg(0), F->getArg(1)));
+  Builder.CreateRet(F->getArg(0));
+
+  DenseSet<const Value *> UniformValues;
+  UniformValues.insert(Add);
+
+  TrackingListener Listener(*F, UniformValues);
+
+  EXPECT_TRUE(UniformValues.contains(Add));
+  Add->eraseFromParent();
+  EXPECT_FALSE(UniformValues.contains(Add));
+}
+
+TEST(InstructionListenerTest, RemoveAndDelete) {
+  LLVMContext C;
+  Module M("test", C);
+  Function *F = createFunction(M, "f");
+  BasicBlock *BB = BasicBlock::Create(C, "entry", F);
+  IRBuilder<> Builder(BB);
+
+  Instruction *Add =
+      cast<Instruction>(Builder.CreateAdd(F->getArg(0), F->getArg(1)));
+  Builder.CreateRet(F->getArg(0));
+
+  DenseSet<const Value *> UniformValues;
+  UniformValues.insert(Add);
+
+  TrackingListener Listener(*F, UniformValues);
+
+  EXPECT_TRUE(UniformValues.contains(Add));
+  Add->removeFromParent();
+  EXPECT_FALSE(UniformValues.contains(Add));
+  Add->deleteValue();
+}
+
+TEST(InstructionListenerTest, BasicBlockErasure) {
+  LLVMContext C;
+  Module M("test", C);
+  Function *F = createFunction(M, "f");
+  BasicBlock *Entry = BasicBlock::Create(C, "entry", F);
+  BasicBlock *Dead = BasicBlock::Create(C, "dead", F);
+  IRBuilder<> Builder(Entry);
+  Builder.CreateRet(F->getArg(0));
+
+  Builder.SetInsertPoint(Dead);
+  Instruction *Add =
+      cast<Instruction>(Builder.CreateAdd(F->getArg(0), F->getArg(1)));
+  Builder.CreateUnreachable();
+
+  DenseSet<const Value *> UniformValues;
+  UniformValues.insert(Add);
+
+  TrackingListener Listener(*F, UniformValues);
+
+  EXPECT_TRUE(UniformValues.contains(Add));
+  Dead->eraseFromParent();
+  EXPECT_FALSE(UniformValues.contains(Add));
+}
+
+TEST(InstructionListenerTest, ListenerScopeRAII) {
+  LLVMContext C;
+  Module M("test", C);
+  Function *F = createFunction(M, "f");
+  BasicBlock *BB = BasicBlock::Create(C, "entry", F);
+  IRBuilder<> Builder(BB);
+  Builder.CreateRet(F->getArg(0));
+
+  EXPECT_FALSE(F->hasInstructionListeners());
+  {
+    DenseSet<const Value *> UniformValues;
+    TrackingListener Listener(*F, UniformValues);
+    EXPECT_TRUE(F->hasInstructionListeners());
+  }
+  EXPECT_FALSE(F->hasInstructionListeners());
+}
+
+TEST(InstructionListenerTest, MultipleListeners) {
+  LLVMContext C;
+  Module M("test", C);
+  Function *F = createFunction(M, "f");
+  BasicBlock *BB = BasicBlock::Create(C, "entry", F);
+  IRBuilder<> Builder(BB);
+
+  Instruction *Add =
+      cast<Instruction>(Builder.CreateAdd(F->getArg(0), F->getArg(1)));
+  Builder.CreateRet(F->getArg(0));
+
+  DenseSet<const Value *> UniformValues1;
+  DenseSet<const Value *> UniformValues2;
+  UniformValues1.insert(Add);
+  UniformValues2.insert(Add);
+
+  TrackingListener L1(*F, UniformValues1);
+  TrackingListener L2(*F, UniformValues2);
+
+  EXPECT_TRUE(UniformValues1.contains(Add));
+  EXPECT_TRUE(UniformValues2.contains(Add));
+  Add->eraseFromParent();
+  EXPECT_FALSE(UniformValues1.contains(Add));
+  EXPECT_FALSE(UniformValues2.contains(Add));
+}
+
+TEST(InstructionListenerTest, PerFunctionIsolation) {
+  LLVMContext C;
+  Module M("test", C);
+  Function *F1 = createFunction(M, "f1");
+  Function *F2 = createFunction(M, "f2");
+
+  BasicBlock *BB1 = BasicBlock::Create(C, "entry", F1);
+  BasicBlock *BB2 = BasicBlock::Create(C, "entry", F2);
+
+  IRBuilder<> B1(BB1);
+  Instruction *Add1 =
+      cast<Instruction>(B1.CreateAdd(F1->getArg(0), F1->getArg(1)));
+  B1.CreateRet(F1->getArg(0));
+
+  IRBuilder<> B2(BB2);
+  Instruction *Add2 =
+      cast<Instruction>(B2.CreateAdd(F2->getArg(0), F2->getArg(1)));
+  B2.CreateRet(F2->getArg(0));
+
+  DenseSet<const Value *> UniformValues1;
+  DenseSet<const Value *> UniformValues2;
+  UniformValues1.insert(Add1);
+  UniformValues2.insert(Add2);
+
+  TrackingListener L1(*F1, UniformValues1);
+  TrackingListener L2(*F2, UniformValues2);
+
+  // Erasing from F1 should not affect F2's listener.
+  Add1->eraseFromParent();
+  EXPECT_FALSE(UniformValues1.contains(Add1));
+  EXPECT_TRUE(UniformValues2.contains(Add2));
+
+  Add2->eraseFromParent();
+  EXPECT_FALSE(UniformValues2.contains(Add2));
+}
+
+TEST(InstructionListenerTest, RAUWBasic) {
+  LLVMContext C;
+  Module M("test", C);
+  Function *F = createFunction(M, "f");
+  BasicBlock *BB = BasicBlock::Create(C, "entry", F);
+  IRBuilder<> Builder(BB);
+
+  Instruction *Add =
+      cast<Instruction>(Builder.CreateAdd(F->getArg(0), F->getArg(1)));
+  Builder.CreateRet(Add);
+
+  DenseSet<const Value *> UniformValues;
+  UniformValues.insert(Add);
+
+  TrackingListener Listener(*F, UniformValues);
+
+  EXPECT_TRUE(UniformValues.contains(Add));
+  Add->replaceAllUsesWith(F->getArg(0));
+  EXPECT_FALSE(UniformValues.contains(Add));
+}
+
+TEST(InstructionListenerTest, RAUWReceivesNewValue) {
+  LLVMContext C;
+  Module M("test", C);
+  Function *F = createFunction(M, "f");
+  BasicBlock *BB = BasicBlock::Create(C, "entry", F);
+  IRBuilder<> Builder(BB);
+
+  Instruction *Add =
+      cast<Instruction>(Builder.CreateAdd(F->getArg(0), F->getArg(1)));
+  Builder.CreateRet(Add);
+
+  Value *ReceivedOld = nullptr;
+  Value *ReceivedNew = nullptr;
+
+  class CapturingListener : public InstructionListener {
+    Value *&OldRef;
+    Value *&NewRef;
+
+    static void onRemoved(InstructionListener *, Instruction *) {}
+    static void onRAUW(InstructionListener *Self, Instruction *Old,
+                       Value *New) {
+      CapturingListener *This = static_cast<CapturingListener *>(Self);
+      This->OldRef = Old;
+      This->NewRef = New;
+    }
+
+  public:
+    CapturingListener(Function &F, Value *&Old, Value *&New)
+        : InstructionListener(F, &onRemoved, &onRAUW), OldRef(Old),
+          NewRef(New) {}
+  };
+
+  CapturingListener Listener(*F, ReceivedOld, ReceivedNew);
+
+  Value *Replacement = F->getArg(0);
+  Add->replaceAllUsesWith(Replacement);
+
+  EXPECT_EQ(ReceivedOld, Add);
+  EXPECT_EQ(ReceivedNew, Replacement);
+}
+
+TEST(InstructionListenerTest, RAUWWithoutCallback) {
+  LLVMContext C;
+  Module M("test", C);
+  Function *F = createFunction(M, "f");
+  BasicBlock *BB = BasicBlock::Create(C, "entry", F);
+  IRBuilder<> Builder(BB);
+
+  Instruction *Add =
+      cast<Instruction>(Builder.CreateAdd(F->getArg(0), F->getArg(1)));
+  Builder.CreateRet(Add);
+
+  DenseSet<const Value *> UniformValues;
+  UniformValues.insert(Add);
+
+  class DeletionOnlyListener : public InstructionListener {
+    DenseSet<const Value *> &Ref;
+
+    static void onRemoved(InstructionListener *Self, Instruction *I) {
+      static_cast<DeletionOnlyListener *>(Self)->Ref.erase(I);
+    }
+
+  public:
+    DeletionOnlyListener(Function &F, DenseSet<const Value *> &S)
+        : InstructionListener(F, &onRemoved), Ref(S) {}
+  };
+
+  DeletionOnlyListener Listener(*F, UniformValues);
+
+  // RAUW should not crash even without a RAUW callback.
+  EXPECT_TRUE(UniformValues.contains(Add));
+  Add->replaceAllUsesWith(F->getArg(0));
+  // Set not updated since no RAUW callback — only deletion updates it.
+  EXPECT_TRUE(UniformValues.contains(Add));
+}
+
+TEST(InstructionListenerTest, RAUWPerFunctionIsolation) {
+  LLVMContext C;
+  Module M("test", C);
+  Function *F1 = createFunction(M, "f1");
+  Function *F2 = createFunction(M, "f2");
+
+  BasicBlock *BB1 = BasicBlock::Create(C, "entry", F1);
+  BasicBlock *BB2 = BasicBlock::Create(C, "entry", F2);
+
+  IRBuilder<> B1(BB1);
+  Instruction *Add1 =
+      cast<Instruction>(B1.CreateAdd(F1->getArg(0), F1->getArg(1)));
+  B1.CreateRet(Add1);
+
+  IRBuilder<> B2(BB2);
+  Instruction *Add2 =
+      cast<Instruction>(B2.CreateAdd(F2->getArg(0), F2->getArg(1)));
+  B2.CreateRet(Add2);
+
+  DenseSet<const Value *> UniformValues1;
+  DenseSet<const Value *> UniformValues2;
+  UniformValues1.insert(Add1);
+  UniformValues2.insert(Add2);
+
+  TrackingListener L1(*F1, UniformValues1);
+  TrackingListener L2(*F2, UniformValues2);
+
+  // RAUW in F1 should not affect F2's listener.
+  Add1->replaceAllUsesWith(F1->getArg(0));
+  EXPECT_FALSE(UniformValues1.contains(Add1));
+  EXPECT_TRUE(UniformValues2.contains(Add2));
+}
+
+/// Demonstrates replacing per-value CallbackVH with a single listener.
+/// This mirrors the pattern used by LazyValueInfo (LVIValueHandle) and
+/// similar analyses that cache per-value results in a DenseMap.
+TEST(InstructionListenerTest, CacheInvalidationPattern) {
+  LLVMContext C;
+  Module M("test", C);
+  Function *F = createFunction(M, "f");
+  BasicBlock *BB = BasicBlock::Create(C, "entry", F);
+  IRBuilder<> Builder(BB);
+
+  Instruction *Add1 =
+      cast<Instruction>(Builder.CreateAdd(F->getArg(0), F->getArg(1)));
+  Instruction *Add2 = cast<Instruction>(Builder.CreateAdd(F->getArg(0), Add1));
+  Builder.CreateRet(F->getArg(0));
+
+  // Simulate an analysis cache: maps Instruction* -> some cached result.
+  DenseMap<const Value *, int> Cache;
+  Cache[Add1] = 42;
+  Cache[Add2] = 99;
+
+  class CacheListener : public InstructionListener {
+    DenseMap<const Value *, int> &CacheRef;
+
+    static void onRemoved(InstructionListener *Self, Instruction *I) {
+      static_cast<CacheListener *>(Self)->CacheRef.erase(I);
+    }
+
+    static void onRAUW(InstructionListener *Self, Instruction *Old, Value *) {
+      static_cast<CacheListener *>(Self)->CacheRef.erase(Old);
+    }
+
+  public:
+    CacheListener(Function &F, DenseMap<const Value *, int> &C)
+        : InstructionListener(F, &onRemoved, &onRAUW), CacheRef(C) {}
+  };
+
+  CacheListener Listener(*F, Cache);
+
+  EXPECT_EQ(Cache.size(), 2u);
+
+  // RAUW Add1 with arg0 — cache entry for Add1 is invalidated.
+  Add1->replaceAllUsesWith(F->getArg(0));
+  EXPECT_EQ(Cache.size(), 1u);
+  EXPECT_FALSE(Cache.contains(Add1));
+  EXPECT_TRUE(Cache.contains(Add2));
+
+  // Erase Add1 — already removed from cache by RAUW, no effect.
+  Add1->eraseFromParent();
+  EXPECT_EQ(Cache.size(), 1u);
+
+  // Erase Add2 — triggers deletion callback, removes from cache.
+  Add2->eraseFromParent();
+  EXPECT_TRUE(Cache.empty());
+}
+
+} // end anonymous namespace

>From d61179176fe600b1dd5559d0a890543dd765785b Mon Sep 17 00:00:00 2001
From: padivedi <pankajkumar.divedi at amd.com>
Date: Tue, 12 May 2026 15:22:59 +0530
Subject: [PATCH 7/9] refactoring

---
 llvm/include/llvm/IR/Function.h            | 22 ++++++++++++++--------
 llvm/include/llvm/IR/InstructionListener.h |  8 ++++++--
 llvm/lib/IR/Function.cpp                   | 15 +++++++++++----
 llvm/lib/IR/Value.cpp                      |  4 +---
 4 files changed, 32 insertions(+), 17 deletions(-)

diff --git a/llvm/include/llvm/IR/Function.h b/llvm/include/llvm/IR/Function.h
index c846fd5d55f31..be0b7ae7f849c 100644
--- a/llvm/include/llvm/IR/Function.h
+++ b/llvm/include/llvm/IR/Function.h
@@ -121,17 +121,23 @@ class LLVM_ABI Function : public GlobalObject, public ilist_node<Function> {
   void removeInstructionListener(InstructionListener *L);
 
 public:
-  /// Notify all registered listeners that an instruction is being removed
-  /// from this function. Called from Instruction::setParent and
-  /// BasicBlock::setParent when the parent is being set to null.
-  LLVM_ABI void notifyInstructionRemoved(Instruction *I);
+  bool hasInstructionListeners() const { return !InstructionListeners.empty(); }
 
-  /// Notify all registered listeners that an instruction in this function is
-  /// being RAUW'd (replaced with another value). Called from Value::doRAUW().
-  LLVM_ABI void notifyInstructionRAUW(Instruction *Old, Value *New);
+  void notifyInstructionRemoved(Instruction *I) {
+    if (hasInstructionListeners())
+      notifyInstructionRemovedImpl(I);
+  }
 
-  bool hasInstructionListeners() const { return !InstructionListeners.empty(); }
+  void notifyInstructionRAUW(Instruction *Old, Value *New) {
+    if (hasInstructionListeners())
+      notifyInstructionRAUWImpl(Old, New);
+  }
 
+private:
+  LLVM_ABI void notifyInstructionRemovedImpl(Instruction *I);
+  LLVM_ABI void notifyInstructionRAUWImpl(Instruction *Old, Value *New);
+
+public:
   /// hasLazyArguments/CheckLazyArguments - The argument list of a function is
   /// built on demand, so that the list isn't allocated until the first client
   /// needs it.  The hasLazyArguments predicate returns true if the arg list
diff --git a/llvm/include/llvm/IR/InstructionListener.h b/llvm/include/llvm/IR/InstructionListener.h
index a0d01a075ee41..093bace4c1bef 100644
--- a/llvm/include/llvm/IR/InstructionListener.h
+++ b/llvm/include/llvm/IR/InstructionListener.h
@@ -44,7 +44,7 @@ class InstructionListener {
                                  Value *New);
 
 private:
-  Function &F;
+  Function *F;
   CallbackT Callback;
   RAUWCallbackT RAUWCallback;
 
@@ -56,12 +56,16 @@ class InstructionListener {
   InstructionListener(const InstructionListener &) = delete;
   InstructionListener &operator=(const InstructionListener &) = delete;
 
+  /// Called by ~Function() to detach the listener before the Function is
+  /// destroyed. After this call, the destructor becomes a no-op.
+  void detach() { F = nullptr; }
+
   void instructionRemoved(Instruction *I) { Callback(this, I); }
   void instructionRAUW(Instruction *Old, Value *New) {
     if (RAUWCallback)
       RAUWCallback(this, Old, New);
   }
-  Function &getFunction() const { return F; }
+  Function *getFunction() const { return F; }
 };
 
 } // namespace llvm
diff --git a/llvm/lib/IR/Function.cpp b/llvm/lib/IR/Function.cpp
index 281e9719f2bbd..021cde27cdb1a 100644
--- a/llvm/lib/IR/Function.cpp
+++ b/llvm/lib/IR/Function.cpp
@@ -65,12 +65,13 @@ template class LLVM_EXPORT_TEMPLATE llvm::SymbolTableListTraits<BasicBlock>;
 
 InstructionListener::InstructionListener(Function &F, CallbackT CB,
                                          RAUWCallbackT RAUWCB)
-    : F(F), Callback(CB), RAUWCallback(RAUWCB) {
+    : F(&F), Callback(CB), RAUWCallback(RAUWCB) {
   F.addInstructionListener(this);
 }
 
 InstructionListener::~InstructionListener() {
-  F.removeInstructionListener(this);
+  if (F)
+    F->removeInstructionListener(this);
 }
 
 void Function::addInstructionListener(InstructionListener *L) {
@@ -83,12 +84,12 @@ void Function::removeInstructionListener(InstructionListener *L) {
   InstructionListeners.erase(llvm::find(InstructionListeners, L));
 }
 
-void Function::notifyInstructionRemoved(Instruction *I) {
+void Function::notifyInstructionRemovedImpl(Instruction *I) {
   for (InstructionListener *L : InstructionListeners)
     L->instructionRemoved(I);
 }
 
-void Function::notifyInstructionRAUW(Instruction *Old, Value *New) {
+void Function::notifyInstructionRAUWImpl(Instruction *Old, Value *New) {
   for (InstructionListener *L : InstructionListeners)
     L->instructionRAUW(Old, New);
 }
@@ -547,6 +548,12 @@ Function::Function(FunctionType *Ty, LinkageTypes Linkage, unsigned AddrSpace,
 }
 
 Function::~Function() {
+  // Detach all instruction listeners before destruction so their destructors
+  // don't try to call back into this Function.
+  for (InstructionListener *L : InstructionListeners)
+    L->detach();
+  InstructionListeners.clear();
+
   validateBlockNumbers();
 
   dropAllReferences();    // After this it is safe to delete instructions.
diff --git a/llvm/lib/IR/Value.cpp b/llvm/lib/IR/Value.cpp
index 3ea087692a777..1c752940347d3 100644
--- a/llvm/lib/IR/Value.cpp
+++ b/llvm/lib/IR/Value.cpp
@@ -526,12 +526,10 @@ void Value::doRAUW(Value *New, ReplaceMetadataUses ReplaceMetaUses) {
   if (HasValueHandle)
     ValueHandleBase::ValueIsRAUWd(this, New);
 
-  // Notify per-function listeners when an instruction is RAUW'd.
   if (Instruction *I = dyn_cast<Instruction>(this))
     if (BasicBlock *BB = I->getParent())
       if (Function *F = BB->getParent())
-        if (F->hasInstructionListeners())
-          F->notifyInstructionRAUW(I, New);
+        F->notifyInstructionRAUW(I, New);
 
   if (ReplaceMetaUses == ReplaceMetadataUses::Yes && isUsedByMetadata())
     ValueAsMetadata::handleRAUW(this, New);

>From a63cd0cbe4b9799a7279cc15237e93e2e07f27b0 Mon Sep 17 00:00:00 2001
From: Pankaj Dwivedi <pankajkumar.divedi at amd.com>
Date: Tue, 12 May 2026 22:04:35 +0530
Subject: [PATCH 8/9] Update llvm/lib/IR/Instruction.cpp

Co-authored-by: Alexis Engelke <engelke at in.tum.de>
---
 llvm/lib/IR/Instruction.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/lib/IR/Instruction.cpp b/llvm/lib/IR/Instruction.cpp
index 7547640552895..2684d2b806d83 100644
--- a/llvm/lib/IR/Instruction.cpp
+++ b/llvm/lib/IR/Instruction.cpp
@@ -97,7 +97,7 @@ const DataLayout &Instruction::getDataLayout() const {
 }
 
 void Instruction::setParent(BasicBlock *P) {
-  if (!P && getParent() && getParent()->getParent())
+  if (getParent() && getParent()->getParent() != P)
     getParent()->getParent()->notifyInstructionRemoved(this);
   using Base =
       ilist_node_with_parent<Instruction, BasicBlock, ilist_iterator_bits<true>,

>From add71ae70fef14fa7c27c03fdf7d1ceccf200831 Mon Sep 17 00:00:00 2001
From: padivedi <pankajkumar.divedi at amd.com>
Date: Wed, 13 May 2026 13:55:17 +0530
Subject: [PATCH 9/9] review

---
 llvm/include/llvm/IR/Function.h               | 19 +++----------------
 llvm/include/llvm/IR/InstructionListener.h    | 11 +++++++----
 llvm/lib/IR/BasicBlock.cpp                    | 15 ++++++++-------
 llvm/lib/IR/Function.cpp                      | 14 ++------------
 llvm/lib/IR/Instruction.cpp                   | 10 ++++++++--
 llvm/lib/IR/Value.cpp                         |  5 ++++-
 llvm/unittests/IR/InstructionListenerTest.cpp | 16 ++++++++++++++++
 7 files changed, 48 insertions(+), 42 deletions(-)

diff --git a/llvm/include/llvm/IR/Function.h b/llvm/include/llvm/IR/Function.h
index be0b7ae7f849c..db1426004bfb0 100644
--- a/llvm/include/llvm/IR/Function.h
+++ b/llvm/include/llvm/IR/Function.h
@@ -115,6 +115,9 @@ class LLVM_ABI Function : public GlobalObject, public ilist_node<Function> {
   friend class SymbolTableListTraits<Function>;
 
   friend class InstructionListener;
+  friend class BasicBlock;
+  friend class Instruction;
+  friend class Value;
   SmallVector<InstructionListener *, 0> InstructionListeners;
 
   void addInstructionListener(InstructionListener *L);
@@ -122,22 +125,6 @@ class LLVM_ABI Function : public GlobalObject, public ilist_node<Function> {
 
 public:
   bool hasInstructionListeners() const { return !InstructionListeners.empty(); }
-
-  void notifyInstructionRemoved(Instruction *I) {
-    if (hasInstructionListeners())
-      notifyInstructionRemovedImpl(I);
-  }
-
-  void notifyInstructionRAUW(Instruction *Old, Value *New) {
-    if (hasInstructionListeners())
-      notifyInstructionRAUWImpl(Old, New);
-  }
-
-private:
-  LLVM_ABI void notifyInstructionRemovedImpl(Instruction *I);
-  LLVM_ABI void notifyInstructionRAUWImpl(Instruction *Old, Value *New);
-
-public:
   /// hasLazyArguments/CheckLazyArguments - The argument list of a function is
   /// built on demand, so that the list isn't allocated until the first client
   /// needs it.  The hasLazyArguments predicate returns true if the arg list
diff --git a/llvm/include/llvm/IR/InstructionListener.h b/llvm/include/llvm/IR/InstructionListener.h
index 093bace4c1bef..1a6297b385f00 100644
--- a/llvm/include/llvm/IR/InstructionListener.h
+++ b/llvm/include/llvm/IR/InstructionListener.h
@@ -39,17 +39,17 @@ class Value;
 /// Function, and the destructor deregisters.
 class InstructionListener {
 public:
-  using CallbackT = void (*)(InstructionListener *, Instruction *);
+  using RemoveCallbackT = void (*)(InstructionListener *, Instruction *);
   using RAUWCallbackT = void (*)(InstructionListener *, Instruction *Old,
                                  Value *New);
 
 private:
   Function *F;
-  CallbackT Callback;
+  RemoveCallbackT RemoveCallback;
   RAUWCallbackT RAUWCallback;
 
 public:
-  LLVM_ABI InstructionListener(Function &F, CallbackT CB,
+  LLVM_ABI InstructionListener(Function &F, RemoveCallbackT REMCB,
                                RAUWCallbackT RAUWCB = nullptr);
   LLVM_ABI ~InstructionListener();
 
@@ -60,7 +60,10 @@ class InstructionListener {
   /// destroyed. After this call, the destructor becomes a no-op.
   void detach() { F = nullptr; }
 
-  void instructionRemoved(Instruction *I) { Callback(this, I); }
+  void instructionRemoved(Instruction *I) {
+    if (RemoveCallback)
+      RemoveCallback(this, I);
+  }
   void instructionRAUW(Instruction *Old, Value *New) {
     if (RAUWCallback)
       RAUWCallback(this, Old, New);
diff --git a/llvm/lib/IR/BasicBlock.cpp b/llvm/lib/IR/BasicBlock.cpp
index c2afe8139e078..64c37d3089ae5 100644
--- a/llvm/lib/IR/BasicBlock.cpp
+++ b/llvm/lib/IR/BasicBlock.cpp
@@ -18,6 +18,7 @@
 #include "llvm/IR/Constants.h"
 #include "llvm/IR/DebugProgramInstruction.h"
 #include "llvm/IR/Function.h"
+#include "llvm/IR/InstructionListener.h"
 #include "llvm/IR/Instructions.h"
 #include "llvm/IR/IntrinsicInst.h"
 #include "llvm/IR/LLVMContext.h"
@@ -196,14 +197,14 @@ BasicBlock::~BasicBlock() {
 }
 
 void BasicBlock::setParent(Function *parent) {
-  // Notify per-function listeners when BB is removed from its parent function.
-  if (!parent && Parent && Parent->hasInstructionListeners()) {
-    for (Instruction &I : *this)
-      Parent->notifyInstructionRemoved(&I);
-  }
-  // Set Parent=parent, updating instruction symtab entries as appropriate.
-  if (Parent != parent)
+  if (Parent != parent) {
+    if (Parent && Parent->hasInstructionListeners()) {
+      for (Instruction &I : *this)
+        for (InstructionListener *L : Parent->InstructionListeners)
+          L->instructionRemoved(&I);
+    }
     Number = parent ? parent->NextBlockNum++ : -1u;
+  }
   InstList.setSymTabObject(&Parent, parent);
 }
 
diff --git a/llvm/lib/IR/Function.cpp b/llvm/lib/IR/Function.cpp
index 021cde27cdb1a..4c7daa6d4fbb8 100644
--- a/llvm/lib/IR/Function.cpp
+++ b/llvm/lib/IR/Function.cpp
@@ -63,9 +63,9 @@ using ProfileCount = Function::ProfileCount;
 // are not in the public header file...
 template class LLVM_EXPORT_TEMPLATE llvm::SymbolTableListTraits<BasicBlock>;
 
-InstructionListener::InstructionListener(Function &F, CallbackT CB,
+InstructionListener::InstructionListener(Function &F, RemoveCallbackT REMCB,
                                          RAUWCallbackT RAUWCB)
-    : F(&F), Callback(CB), RAUWCallback(RAUWCB) {
+    : F(&F), RemoveCallback(REMCB), RAUWCallback(RAUWCB) {
   F.addInstructionListener(this);
 }
 
@@ -84,16 +84,6 @@ void Function::removeInstructionListener(InstructionListener *L) {
   InstructionListeners.erase(llvm::find(InstructionListeners, L));
 }
 
-void Function::notifyInstructionRemovedImpl(Instruction *I) {
-  for (InstructionListener *L : InstructionListeners)
-    L->instructionRemoved(I);
-}
-
-void Function::notifyInstructionRAUWImpl(Instruction *Old, Value *New) {
-  for (InstructionListener *L : InstructionListeners)
-    L->instructionRAUW(Old, New);
-}
-
 static cl::opt<int> NonGlobalValueMaxNameSize(
     "non-global-value-max-name-size", cl::Hidden, cl::init(1024),
     cl::desc("Maximum size for the name of non-global values."));
diff --git a/llvm/lib/IR/Instruction.cpp b/llvm/lib/IR/Instruction.cpp
index 2684d2b806d83..ed61eaabaf638 100644
--- a/llvm/lib/IR/Instruction.cpp
+++ b/llvm/lib/IR/Instruction.cpp
@@ -18,6 +18,7 @@
 #include "llvm/IR/Constants.h"
 #include "llvm/IR/Function.h"
 #include "llvm/IR/InstrTypes.h"
+#include "llvm/IR/InstructionListener.h"
 #include "llvm/IR/Instructions.h"
 #include "llvm/IR/IntrinsicInst.h"
 #include "llvm/IR/Intrinsics.h"
@@ -97,8 +98,13 @@ const DataLayout &Instruction::getDataLayout() const {
 }
 
 void Instruction::setParent(BasicBlock *P) {
-  if (getParent() && getParent()->getParent() != P)
-    getParent()->getParent()->notifyInstructionRemoved(this);
+  if (BasicBlock *OldBB = getParent()) {
+    Function *OldF = OldBB->getParent();
+    Function *NewF = P ? P->getParent() : nullptr;
+    if (OldF && OldF != NewF && OldF->hasInstructionListeners())
+      for (InstructionListener *L : OldF->InstructionListeners)
+        L->instructionRemoved(this);
+  }
   using Base =
       ilist_node_with_parent<Instruction, BasicBlock, ilist_iterator_bits<true>,
                              ilist_parent<BasicBlock>>;
diff --git a/llvm/lib/IR/Value.cpp b/llvm/lib/IR/Value.cpp
index 1c752940347d3..5069a02398e94 100644
--- a/llvm/lib/IR/Value.cpp
+++ b/llvm/lib/IR/Value.cpp
@@ -22,6 +22,7 @@
 #include "llvm/IR/DerivedUser.h"
 #include "llvm/IR/GetElementPtrTypeIterator.h"
 #include "llvm/IR/InstrTypes.h"
+#include "llvm/IR/InstructionListener.h"
 #include "llvm/IR/Instructions.h"
 #include "llvm/IR/IntrinsicInst.h"
 #include "llvm/IR/Module.h"
@@ -529,7 +530,9 @@ void Value::doRAUW(Value *New, ReplaceMetadataUses ReplaceMetaUses) {
   if (Instruction *I = dyn_cast<Instruction>(this))
     if (BasicBlock *BB = I->getParent())
       if (Function *F = BB->getParent())
-        F->notifyInstructionRAUW(I, New);
+        if (F->hasInstructionListeners())
+          for (InstructionListener *L : F->InstructionListeners)
+            L->instructionRAUW(I, New);
 
   if (ReplaceMetaUses == ReplaceMetadataUses::Yes && isUsedByMetadata())
     ValueAsMetadata::handleRAUW(this, New);
diff --git a/llvm/unittests/IR/InstructionListenerTest.cpp b/llvm/unittests/IR/InstructionListenerTest.cpp
index a6b4f53b31580..c4d29ca4dfc07 100644
--- a/llvm/unittests/IR/InstructionListenerTest.cpp
+++ b/llvm/unittests/IR/InstructionListenerTest.cpp
@@ -377,4 +377,20 @@ TEST(InstructionListenerTest, CacheInvalidationPattern) {
   EXPECT_TRUE(Cache.empty());
 }
 
+TEST(InstructionListenerTest, FunctionErasedBeforeListener) {
+  LLVMContext C;
+  Module M("test", C);
+  Function *F = createFunction(M, "f");
+  BasicBlock *BB = BasicBlock::Create(C, "entry", F);
+  IRBuilder<> Builder(BB);
+  Builder.CreateRet(F->getArg(0));
+
+  DenseSet<const Value *> UniformValues;
+  TrackingListener Listener(*F, UniformValues);
+
+  EXPECT_TRUE(F->hasInstructionListeners());
+  F->eraseFromParent();
+  // Listener outlives the Function. Its destructor must not crash.
+}
+
 } // end anonymous namespace



More information about the llvm-commits mailing list