[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