[llvm] [Analysis] Replace SCEVCallbackVH with per-function InstructionListener (PR #196485)
Pankaj Dwivedi via llvm-commits
llvm-commits at lists.llvm.org
Fri May 8 01:59:16 PDT 2026
https://github.com/PankajDwivedi-25 updated https://github.com/llvm/llvm-project/pull/196485
>From df6e1f485683dc9050d853bef510da012816b4a7 Mon Sep 17 00:00:00 2001
From: padivedi <padivedi at amd.com>
Date: Fri, 8 May 2026 14:25:19 +0530
Subject: [PATCH 1/2] [IR] InstructionListener: per-function instruction
removal/RAUW notifications
Introduce InstructionListener, a per-function callback mechanism that
notifies analyses when an Instruction is removed from a Function or
RAUW'd. Think of it as "a value handle to many values".
---
llvm/include/llvm/IR/Function.h | 19 +
llvm/include/llvm/IR/Instruction.h | 4 +
llvm/include/llvm/IR/InstructionListener.h | 69 ++++
llvm/lib/IR/BasicBlock.cpp | 6 +
llvm/lib/IR/Function.cpp | 31 ++
llvm/lib/IR/Instruction.cpp | 10 +
llvm/lib/IR/Value.cpp | 8 +
llvm/unittests/IR/CMakeLists.txt | 1 +
llvm/unittests/IR/InstructionListenerTest.cpp | 380 ++++++++++++++++++
9 files changed, 528 insertions(+)
create mode 100644 llvm/include/llvm/IR/InstructionListener.h
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 f39fe509a49a4..c846fd5d55f31 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 InstructionListener;
class LLVM_ABI Function : public GlobalObject, public ilist_node<Function> {
public:
@@ -112,7 +114,24 @@ class LLVM_ABI Function : public GlobalObject, public ilist_node<Function> {
friend class SymbolTableListTraits<Function>;
+ friend class InstructionListener;
+ SmallVector<InstructionListener *, 0> InstructionListeners;
+
+ void addInstructionListener(InstructionListener *L);
+ 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);
+
+ /// 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
/// 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..7420e6d0cd357 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
+ /// InstructionListeners 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/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 da97b26f7cec5..c2afe8139e078 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->hasInstructionListeners()) {
+ 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..281e9719f2bbd 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/InstructionListener.h"
#include "llvm/IR/IntrinsicInst.h"
#include "llvm/IR/Intrinsics.h"
#include "llvm/IR/LLVMContext.h"
@@ -62,6 +63,36 @@ 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,
+ RAUWCallbackT RAUWCB)
+ : F(F), Callback(CB), RAUWCallback(RAUWCB) {
+ F.addInstructionListener(this);
+}
+
+InstructionListener::~InstructionListener() {
+ F.removeInstructionListener(this);
+}
+
+void Function::addInstructionListener(InstructionListener *L) {
+ assert(!llvm::is_contained(InstructionListeners, L) &&
+ "Listener already registered");
+ InstructionListeners.push_back(L);
+}
+
+void Function::removeInstructionListener(InstructionListener *L) {
+ InstructionListeners.erase(llvm::find(InstructionListeners, L));
+}
+
+void Function::notifyInstructionRemoved(Instruction *I) {
+ 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/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/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 d62ce66ef9d34..92b082f82690e 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
+ InstructionListenerTest.cpp
InstructionsTest.cpp
IntrinsicsTest.cpp
LegacyPassManagerTest.cpp
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 c0b055259f914d992a996c7e9dbafac5129b27a7 Mon Sep 17 00:00:00 2001
From: padivedi <padivedi at amd.com>
Date: Fri, 8 May 2026 14:28:16 +0530
Subject: [PATCH 2/2] [Analysis] Replace SCEVCallbackVH with
InstructionListener
---
llvm/include/llvm/Analysis/ScalarEvolution.h | 21 +++++----
llvm/include/llvm/IR/InstructionListener.h | 8 +++-
llvm/lib/Analysis/ScalarEvolution.cpp | 49 +++++++++-----------
llvm/lib/IR/Function.cpp | 11 ++++-
4 files changed, 48 insertions(+), 41 deletions(-)
diff --git a/llvm/include/llvm/Analysis/ScalarEvolution.h b/llvm/include/llvm/Analysis/ScalarEvolution.h
index 5c01da0855f66..0926418af4bae 100644
--- a/llvm/include/llvm/Analysis/ScalarEvolution.h
+++ b/llvm/include/llvm/Analysis/ScalarEvolution.h
@@ -31,6 +31,7 @@
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/IR/ConstantRange.h"
+#include "llvm/IR/InstructionListener.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/PassManager.h"
#include "llvm/IR/ValueHandle.h"
@@ -1619,19 +1620,19 @@ class ScalarEvolution {
};
private:
- /// A CallbackVH to arrange for ScalarEvolution to be notified whenever a
- /// Value is deleted.
- class LLVM_ABI SCEVCallbackVH final : public CallbackVH {
+ /// A per-function listener that invalidates SCEV caches when an instruction
+ /// is removed or RAUW'd. Replaces per-value SCEVCallbackVH handles.
+ class SCEVInstructionListener : public InstructionListener {
ScalarEvolution *SE;
- void deleted() override;
- void allUsesReplacedWith(Value *New) override;
+ static void onRemoved(InstructionListener *Self, Instruction *I);
+ static void onRAUW(InstructionListener *Self, Instruction *Old, Value *New);
public:
- SCEVCallbackVH(Value *V, ScalarEvolution *SE = nullptr);
+ SCEVInstructionListener(Function &F, ScalarEvolution *SE)
+ : InstructionListener(F, &onRemoved, &onRAUW), SE(SE) {}
};
- friend class SCEVCallbackVH;
friend class SCEVExpander;
friend class SCEVUnknown;
@@ -1676,12 +1677,14 @@ class ScalarEvolution {
ExprValueMapType ExprValueMap;
/// The type for ValueExprMap.
- using ValueExprMapType =
- DenseMap<SCEVCallbackVH, const SCEV *, DenseMapInfo<Value *>>;
+ using ValueExprMapType = DenseMap<Value *, const SCEV *>;
/// This is a cache of the values we have analyzed so far.
ValueExprMapType ValueExprMap;
+ /// Listener that invalidates SCEV caches on instruction removal/RAUW.
+ SCEVInstructionListener InstListener;
+
/// This is a cache for expressions that got folded to a different existing
/// SCEV.
DenseMap<FoldID, const SCEV *> FoldCache;
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/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index 9f362deb7cca9..6b57a0dd53a15 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -4695,7 +4695,7 @@ ArrayRef<Value *> ScalarEvolution::getSCEVValues(const SCEV *S) {
/// cannot be used separately. eraseValueFromMap should be used to remove
/// V from ValueExprMap and ExprValueMap at the same time.
void ScalarEvolution::eraseValueFromMap(Value *V) {
- ValueExprMapType::iterator I = ValueExprMap.find_as(V);
+ ValueExprMapType::iterator I = ValueExprMap.find(V);
if (I != ValueExprMap.end()) {
auto EVIt = ExprValueMap.find(I->second);
bool Removed = EVIt->second.remove(V);
@@ -4709,9 +4709,9 @@ void ScalarEvolution::insertValueToMap(Value *V, const SCEV *S) {
// A recursive query may have already computed the SCEV. It should be
// equivalent, but may not necessarily be exactly the same, e.g. due to lazily
// inferred nowrap flags.
- auto It = ValueExprMap.find_as(V);
+ auto It = ValueExprMap.find(V);
if (It == ValueExprMap.end()) {
- ValueExprMap.insert({SCEVCallbackVH(V, this), S});
+ ValueExprMap.insert({V, S});
ExprValueMap[S].insert(V);
}
}
@@ -4729,7 +4729,7 @@ const SCEV *ScalarEvolution::getSCEV(Value *V) {
const SCEV *ScalarEvolution::getExistingSCEV(Value *V) {
assert(isSCEVable(V->getType()) && "Value is not SCEVable!");
- ValueExprMapType::iterator I = ValueExprMap.find_as(V);
+ ValueExprMapType::iterator I = ValueExprMap.find(V);
if (I != ValueExprMap.end()) {
const SCEV *S = I->second;
assert(checkValidity(S) &&
@@ -5966,7 +5966,7 @@ const SCEV *ScalarEvolution::createAddRecFromPHI(PHINode *PN) {
if (!BEValueV || !StartValueV)
return nullptr;
- assert(ValueExprMap.find_as(PN) == ValueExprMap.end() &&
+ assert(ValueExprMap.find(PN) == ValueExprMap.end() &&
"PHI node already processed?");
// First, try to find AddRec expression without creating a fictituos symbolic
@@ -8752,8 +8752,7 @@ void ScalarEvolution::visitAndClearUsers(
if (!isSCEVable(I->getType()) && !isa<WithOverflowInst>(I))
continue;
- ValueExprMapType::iterator It =
- ValueExprMap.find_as(static_cast<Value *>(I));
+ ValueExprMapType::iterator It = ValueExprMap.find(static_cast<Value *>(I));
if (It != ValueExprMap.end()) {
eraseValueFromMap(It->first);
ToForget.push_back(It->second);
@@ -13941,30 +13940,24 @@ const SCEV *ScalarEvolution::getElementSize(Instruction *Inst) {
}
//===----------------------------------------------------------------------===//
-// SCEVCallbackVH Class Implementation
+// SCEVInstructionListener Implementation
//===----------------------------------------------------------------------===//
-void ScalarEvolution::SCEVCallbackVH::deleted() {
- assert(SE && "SCEVCallbackVH called with a null ScalarEvolution!");
- if (PHINode *PN = dyn_cast<PHINode>(getValPtr()))
+void ScalarEvolution::SCEVInstructionListener::onRemoved(
+ InstructionListener *Self, Instruction *I) {
+ ScalarEvolution *SE = static_cast<SCEVInstructionListener *>(Self)->SE;
+ if (PHINode *PN = dyn_cast<PHINode>(I))
SE->ConstantEvolutionLoopExitValue.erase(PN);
- SE->eraseValueFromMap(getValPtr());
- // this now dangles!
+ SE->eraseValueFromMap(I);
}
-void ScalarEvolution::SCEVCallbackVH::allUsesReplacedWith(Value *V) {
- assert(SE && "SCEVCallbackVH called with a null ScalarEvolution!");
-
- // Forget all the expressions associated with users of the old value,
- // so that future queries will recompute the expressions using the new
- // value.
- SE->forgetValue(getValPtr());
- // this now dangles!
+void ScalarEvolution::SCEVInstructionListener::onRAUW(InstructionListener *Self,
+ Instruction *Old,
+ Value *) {
+ ScalarEvolution *SE = static_cast<SCEVInstructionListener *>(Self)->SE;
+ SE->forgetValue(Old);
}
-ScalarEvolution::SCEVCallbackVH::SCEVCallbackVH(Value *V, ScalarEvolution *se)
- : CallbackVH(V), SE(se) {}
-
//===----------------------------------------------------------------------===//
// ScalarEvolution Class Implementation
//===----------------------------------------------------------------------===//
@@ -13973,8 +13966,8 @@ ScalarEvolution::ScalarEvolution(Function &F, TargetLibraryInfo &TLI,
AssumptionCache &AC, DominatorTree &DT,
LoopInfo &LI)
: F(F), DL(F.getDataLayout()), TLI(TLI), AC(AC), DT(DT), LI(LI),
- CouldNotCompute(new SCEVCouldNotCompute()), ValuesAtScopes(64),
- LoopDispositions(64), BlockDispositions(64) {
+ CouldNotCompute(new SCEVCouldNotCompute()), InstListener(F, this),
+ ValuesAtScopes(64), LoopDispositions(64), BlockDispositions(64) {
// To use guards for proving predicates, we need to scan every instruction in
// relevant basic blocks, and not just terminators. Doing this is a waste of
// time if the IR does not actually contain any calls to
@@ -13993,7 +13986,7 @@ ScalarEvolution::ScalarEvolution(Function &F, TargetLibraryInfo &TLI,
ScalarEvolution::ScalarEvolution(ScalarEvolution &&Arg)
: F(Arg.F), DL(Arg.DL), HasGuards(Arg.HasGuards), TLI(Arg.TLI), AC(Arg.AC),
DT(Arg.DT), LI(Arg.LI), CouldNotCompute(std::move(Arg.CouldNotCompute)),
- ValueExprMap(std::move(Arg.ValueExprMap)),
+ ValueExprMap(std::move(Arg.ValueExprMap)), InstListener(F, this),
PendingLoopPredicates(std::move(Arg.PendingLoopPredicates)),
PendingMerges(std::move(Arg.PendingMerges)),
ConstantMultipleCache(std::move(Arg.ConstantMultipleCache)),
@@ -14585,7 +14578,7 @@ void ScalarEvolution::forgetMemoizedResultsImpl(const SCEV *S) {
auto ExprIt = ExprValueMap.find(S);
if (ExprIt != ExprValueMap.end()) {
for (Value *V : ExprIt->second) {
- auto ValueIt = ValueExprMap.find_as(V);
+ auto ValueIt = ValueExprMap.find(V);
if (ValueIt != ValueExprMap.end())
ValueExprMap.erase(ValueIt);
}
diff --git a/llvm/lib/IR/Function.cpp b/llvm/lib/IR/Function.cpp
index 281e9719f2bbd..68c1d10f1d40e 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) {
@@ -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.
More information about the llvm-commits
mailing list