Skip to content

[Analysis] Replace SCEVCallbackVH with per-function InstructionListener - #196485

Open
PankajDwivedi-25 wants to merge 3 commits into
llvm:mainfrom
PankajDwivedi-25:perf/scev-instruction-listener
Open

[Analysis] Replace SCEVCallbackVH with per-function InstructionListener#196485
PankajDwivedi-25 wants to merge 3 commits into
llvm:mainfrom
PankajDwivedi-25:perf/scev-instruction-listener

Conversation

@PankajDwivedi-25

@PankajDwivedi-25 PankajDwivedi-25 commented May 8, 2026

Copy link
Copy Markdown
Contributor

This replaces the per-value SCEVCallbackVH mechanism in ScalarEvolution
with a single InstructionListener registration per function.

Instead of creating one CallbackVH per cached value in ValueExprMap
(O(N) handles, each with linked-list and DenseMap entries in LLVMContext),
a single listener handles deletion and RAUW notifications for all
instructions in the function.

Changes:

  • Remove SCEVCallbackVH class
  • Change ValueExprMap key from SCEVCallbackVH to plain Value*
  • Add SCEVInstructionListener that calls eraseValueFromMap() on
    deletion and forgetValue() on RAUW
  • Add detach() in ~Function() to handle safe destruction ordering
    when module passes delete functions

SCEVUnknown retains its CallbackVH (wraps arbitrary Values including
Arguments, outside InstructionListener's scope).

Built on the InstructionListener infrastructure introduced in PR #193198.

NOTE: This is an experimental patch as per discussion in RFC: https://discourse.llvm.org/t/rfc-valuedeletionlistener-context-level-value-deletion-notifications/90624/23

CTT: https://llvm-compile-time-tracker.com/compare.php?stat=instructions%3Au&from=df6e1f485683dc9050d853bef510da012816b4a7&to=c0b055259f914d992a996c7e9dbafac5129b27a7

@PankajDwivedi-25
PankajDwivedi-25 requested a review from nikic as a code owner May 8, 2026 08:19
@llvmorg-github-actions llvmorg-github-actions Bot added llvm:ir llvm:analysis Includes value tracking, cost tables and constant folding labels May 8, 2026
@llvmorg-github-actions

llvmorg-github-actions Bot commented May 8, 2026

Copy link
Copy Markdown

@llvm/pr-subscribers-llvm-analysis

@llvm/pr-subscribers-llvm-ir

Author: Pankaj Dwivedi (PankajDwivedi-25)

Changes

This replaces the per-value SCEVCallbackVH mechanism in ScalarEvolution
with a single InstructionListener registration per function.

Instead of creating one CallbackVH per cached value in ValueExprMap
(O(N) handles, each with linked-list and DenseMap entries in LLVMContext),
a single listener handles deletion and RAUW notifications for all
instructions in the function.

Changes:

  • Remove SCEVCallbackVH class
  • Change ValueExprMap key from SCEVCallbackVH to plain Value*
  • Add SCEVInstructionListener that calls eraseValueFromMap() on
    deletion and forgetValue() on RAUW
  • Add detach() in ~Function() to handle safe destruction ordering
    when module passes delete functions

SCEVUnknown retains its CallbackVH (wraps arbitrary Values including
Arguments, outside InstructionListener's scope).

Built on the InstructionListener infrastructure introduced in PR #193198.

This is an experimental patch as per discussion in RFC: https://discourse.llvm.org/t/rfc-valuedeletionlistener-context-level-value-deletion-notifications/90624/23


Patch is 30.80 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/196485.diff

11 Files Affected:

  • (modified) llvm/include/llvm/Analysis/ScalarEvolution.h (+12-9)
  • (modified) llvm/include/llvm/IR/Function.h (+19)
  • (modified) llvm/include/llvm/IR/Instruction.h (+4)
  • (added) llvm/include/llvm/IR/InstructionListener.h (+73)
  • (modified) llvm/lib/Analysis/ScalarEvolution.cpp (+21-28)
  • (modified) llvm/lib/IR/BasicBlock.cpp (+6)
  • (modified) llvm/lib/IR/Function.cpp (+38)
  • (modified) llvm/lib/IR/Instruction.cpp (+10)
  • (modified) llvm/lib/IR/Value.cpp (+8)
  • (modified) llvm/unittests/IR/CMakeLists.txt (+1)
  • (added) llvm/unittests/IR/InstructionListenerTest.cpp (+380)
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/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..093bace4c1bef
--- /dev/null
+++ b/llvm/include/llvm/IR/InstructionListener.h
@@ -0,0 +1,73 @@
+//===- 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;
+
+  /// 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; }
+};
+
+} // namespace llvm
+
+#endif // LLVM_IR_INSTRUCTIONLISTENER_H
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/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..68c1d10f1d40e 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,37 @@ 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() {
+  if (F)
+    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."));
@@ -516,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/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(InstructionList...
[truncated]

@PankajDwivedi-25

Copy link
Copy Markdown
Contributor Author

@nikic, could you please add my fork to the compile-time tracker?

@PankajDwivedi-25
PankajDwivedi-25 force-pushed the perf/scev-instruction-listener branch from 8fd5c75 to c0b0552 Compare May 8, 2026 08:59
@PankajDwivedi-25
PankajDwivedi-25 requested a review from ssahasra May 11, 2026 07:43
@nikic

nikic commented May 11, 2026

Copy link
Copy Markdown
Contributor

@nikic, could you please add my fork to the compile-time tracker?

Done.

Note that it will test all commits on the branch, so it's usually best to not use perf/ branches for PRs, otherwise all fixups based on review feedback will also get tested.

@PankajDwivedi-25

Copy link
Copy Markdown
Contributor Author

@nikic, could you please add my fork to the compile-time tracker?

Done.

Note that it will test all commits on the branch, so it's usually best to not use perf/ branches for PRs, otherwise all fixups based on review feedback will also get tested.

thanks, Yes this is just experimental branch.

@PankajDwivedi-25
PankajDwivedi-25 force-pushed the perf/scev-instruction-listener branch from fb72448 to 8faa457 Compare May 15, 2026 15:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

llvm:analysis Includes value tracking, cost tables and constant folding llvm:ir

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants