[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