[llvm] [IR] Simplify DIAssignID by storing its links in the node. NFC (PR #223324)

Fangrui Song via llvm-commits llvm-commits at lists.llvm.org
Mon Sep 14 01:38:20 PDT 2026


https://github.com/MaskRay updated https://github.com/llvm/llvm-project/pull/223324

>From 5804cbbd91023b7da6fb5387cde6cc602456a990 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Sun, 13 Sep 2026 23:00:39 -0700
Subject: [PATCH 1/2] [IR] Simplify DIAssignID by storing its links in the
 node. NFC

A DIAssignID links the instructions it is attached to, kept in
LLVMContextImpl::AssignmentIDToInstrs, with the dbg_assign records that
refer to it, found through a permanent use list (#78300 made DIAssignID
the only always-replaceable MDNode for this). Keep both lists in the
node as TinyPtrVector members instead (98% of instruction lists and
88-96% of record lists hold exactly one pointer), and drop the map and
`isAlwaysReplaceable()`.

Aided by Opus 5
---
 llvm/include/llvm/IR/DebugInfo.h         | 15 ++++--
 llvm/include/llvm/IR/DebugInfoMetadata.h | 13 +++--
 llvm/include/llvm/IR/Metadata.h          |  3 +-
 llvm/lib/IR/DebugInfo.cpp                | 15 ++----
 llvm/lib/IR/LLVMContextImpl.h            |  5 --
 llvm/lib/IR/Metadata.cpp                 | 62 +++++++++---------------
 llvm/lib/IR/Verifier.cpp                 |  8 ++-
 7 files changed, 50 insertions(+), 71 deletions(-)

diff --git a/llvm/include/llvm/IR/DebugInfo.h b/llvm/include/llvm/IR/DebugInfo.h
index 18b94f8f66e48..5f1c49703040a 100644
--- a/llvm/include/llvm/IR/DebugInfo.h
+++ b/llvm/include/llvm/IR/DebugInfo.h
@@ -185,13 +185,14 @@ namespace at {
 // Utilities for enumerating storing instructions from an assignment ID.
 //
 /// A range of instructions.
-using AssignmentInstRange =
-    iterator_range<SmallVectorImpl<Instruction *>::iterator>;
+using AssignmentInstRange = ArrayRef<Instruction *>;
 /// Return a range of instructions (typically just one) that have \p ID
 /// as an attachment.
 /// Iterators invalidated by adding or removing DIAssignID metadata to/from any
 /// instruction (including by deleting or cloning instructions).
-LLVM_ABI AssignmentInstRange getAssignmentInsts(DIAssignID *ID);
+inline AssignmentInstRange getAssignmentInsts(DIAssignID *ID) {
+  return ID->getInstructions();
+}
 
 inline AssignmentInstRange getAssignmentInsts(const DbgVariableRecord *DVR) {
   assert(DVR->isDbgAssign() &&
@@ -199,12 +200,18 @@ inline AssignmentInstRange getAssignmentInsts(const DbgVariableRecord *DVR) {
   return getAssignmentInsts(DVR->getAssignID());
 }
 
+// Return the dbg_assign records linked to ID, most recently linked first. A
+// copy, as callers unlink or relink records while iterating.
+inline SmallVector<DbgVariableRecord *> getAssignmentMarkers(DIAssignID *ID) {
+  return SmallVector<DbgVariableRecord *>(llvm::reverse(ID->getRecords()));
+}
+
 /// Return a range of dbg_assign records for which \p Inst performs the
 /// assignment they encode.
 inline SmallVector<DbgVariableRecord *>
 getDVRAssignmentMarkers(const Instruction *Inst) {
   if (auto *ID = Inst->getMetadata(LLVMContext::MD_DIAssignID))
-    return cast<DIAssignID>(ID)->getAllDbgVariableRecordUsers();
+    return getAssignmentMarkers(cast<DIAssignID>(ID));
   return {};
 }
 
diff --git a/llvm/include/llvm/IR/DebugInfoMetadata.h b/llvm/include/llvm/IR/DebugInfoMetadata.h
index 3327471f461b6..aca4949bd3f94 100644
--- a/llvm/include/llvm/IR/DebugInfoMetadata.h
+++ b/llvm/include/llvm/IR/DebugInfoMetadata.h
@@ -19,6 +19,7 @@
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/TinyPtrVector.h"
 #include "llvm/ADT/iterator_range.h"
 #include "llvm/IR/Constants.h"
 #include "llvm/IR/DbgVariableFragmentInfo.h"
@@ -320,6 +321,13 @@ class GenericDINode : public DINode {
 class DIAssignID : public MDNode {
   friend class LLVMContextImpl;
   friend class MDNode;
+  friend class Instruction;
+  friend class DebugValueUser;
+
+  /// The instructions this ID is attached to and the dbg_assign records that
+  /// refer to it, maintained by Instruction and DebugValueUser.
+  TinyPtrVector<Instruction *> Instrs;
+  TinyPtrVector<DbgVariableRecord *> Records;
 
   DIAssignID(LLVMContext &C, StorageType Storage)
       : MDNode(C, DIAssignIDKind, Storage, {}) {}
@@ -335,9 +343,8 @@ class DIAssignID : public MDNode {
   // This node has no operands to replace.
   void replaceOperandWith(unsigned I, Metadata *New) = delete;
 
-  SmallVector<DbgVariableRecord *> getAllDbgVariableRecordUsers() {
-    return Context.getReplaceableUses()->getAllDbgVariableRecordUsers();
-  }
+  ArrayRef<Instruction *> getInstructions() const { return Instrs; }
+  ArrayRef<DbgVariableRecord *> getRecords() const { return Records; }
 
   static DIAssignID *getDistinct(LLVMContext &Context) {
     return getImpl(Context, Distinct);
diff --git a/llvm/include/llvm/IR/Metadata.h b/llvm/include/llvm/IR/Metadata.h
index c677f9cc61769..51af66ec1809e 100644
--- a/llvm/include/llvm/IR/Metadata.h
+++ b/llvm/include/llvm/IR/Metadata.h
@@ -1262,8 +1262,7 @@ class MDNode : public Metadata {
   bool isDistinct() const { return Storage == Distinct; }
   bool isTemporary() const { return Storage == Temporary; }
 
-  bool isReplaceable() const { return isTemporary() || isAlwaysReplaceable(); }
-  bool isAlwaysReplaceable() const { return getMetadataID() == DIAssignIDKind; }
+  bool isReplaceable() const { return isTemporary(); }
 
   unsigned getNumTemporaryUses() const {
     assert(isTemporary() && "Only for temporaries");
diff --git a/llvm/lib/IR/DebugInfo.cpp b/llvm/lib/IR/DebugInfo.cpp
index 38959b61148fe..d0f75fcf34002 100644
--- a/llvm/lib/IR/DebugInfo.cpp
+++ b/llvm/lib/IR/DebugInfo.cpp
@@ -2021,17 +2021,6 @@ LLVMMetadataKind LLVMGetMetadataKind(LLVMMetadataRef Metadata) {
   }
 }
 
-AssignmentInstRange at::getAssignmentInsts(DIAssignID *ID) {
-  assert(ID && "Expected non-null ID");
-  LLVMContext &Ctx = ID->getContext();
-  auto &Map = Ctx.pImpl->AssignmentIDToInstrs;
-
-  auto MapIt = Map.find(ID);
-  if (MapIt == Map.end())
-    return make_range(nullptr, nullptr);
-
-  return make_range(MapIt->second.begin(), MapIt->second.end());
-}
 
 void at::deleteAssignmentMarkers(const Instruction *Inst) {
   for (auto *DVR : getDVRAssignmentMarkers(Inst))
@@ -2048,7 +2037,9 @@ void at::RAUW(DIAssignID *Old, DIAssignID *New) {
   for (auto *I : InstVec)
     I->setMetadata(LLVMContext::MD_DIAssignID, New);
 
-  Old->replaceAllUsesWith(New);
+  for (DbgVariableRecord *DVR :
+       SmallVector<DbgVariableRecord *>(Old->getRecords()))
+    DVR->setAssignId(New);
 }
 
 void at::deleteAll(Function *F) {
diff --git a/llvm/lib/IR/LLVMContextImpl.h b/llvm/lib/IR/LLVMContextImpl.h
index 49a8a803732a5..ad733db864a8f 100644
--- a/llvm/lib/IR/LLVMContextImpl.h
+++ b/llvm/lib/IR/LLVMContextImpl.h
@@ -1764,11 +1764,6 @@ class LLVMContextImpl {
   /// builds to ensure that all metadata attachments are properly freed.
   unsigned MetadataRecycleSize = 0;
 
-  /// Map DIAssignID -> Instructions with that attachment.
-  /// Managed by Instruction via Instruction::updateDIAssignIDMapping.
-  /// Query using the at:: functions defined in DebugInfo.h.
-  DenseMap<DIAssignID *, SmallVector<Instruction *, 1>> AssignmentIDToInstrs;
-
   /// Collection of per-GlobalObject sections used in this context.
   DenseMap<const GlobalObject *, StringRef> GlobalObjectSections;
 
diff --git a/llvm/lib/IR/Metadata.cpp b/llvm/lib/IR/Metadata.cpp
index 87766f4d47437..117757b9514e8 100644
--- a/llvm/lib/IR/Metadata.cpp
+++ b/llvm/lib/IR/Metadata.cpp
@@ -176,27 +176,31 @@ void DebugValueUser::handleChangedValue(void *Old, Metadata *New) {
 void DebugValueUser::trackDebugValue(size_t Idx) {
   assert(Idx < 3 && "Invalid debug value index.");
   Metadata *&MD = DebugValues[Idx];
-  if (MD)
-    MetadataTracking::track(&MD, *MD, *this);
+  if (!MD)
+    return;
+  MetadataTracking::track(&MD, *MD, *this);
+  if (auto *ID = Idx == 2 ? dyn_cast<DIAssignID>(MD) : nullptr)
+    ID->Records.push_back(getUser());
 }
 
 void DebugValueUser::trackDebugValues() {
-  for (Metadata *&MD : DebugValues)
-    if (MD)
-      MetadataTracking::track(&MD, *MD, *this);
+  for (size_t I = 0, E = DebugValues.size(); I != E; ++I)
+    trackDebugValue(I);
 }
 
 void DebugValueUser::untrackDebugValue(size_t Idx) {
   assert(Idx < 3 && "Invalid debug value index.");
   Metadata *&MD = DebugValues[Idx];
-  if (MD)
-    MetadataTracking::untrack(MD);
+  if (!MD)
+    return;
+  MetadataTracking::untrack(MD);
+  if (auto *ID = Idx == 2 ? dyn_cast<DIAssignID>(MD) : nullptr)
+    ID->Records.erase(llvm::find(ID->Records, getUser()));
 }
 
 void DebugValueUser::untrackDebugValues() {
-  for (Metadata *&MD : DebugValues)
-    if (MD)
-      MetadataTracking::untrack(MD);
+  for (size_t I = 0, E = DebugValues.size(); I != E; ++I)
+    untrackDebugValue(I);
 }
 
 void DebugValueUser::retrackDebugValues(DebugValueUser &X) {
@@ -204,6 +208,8 @@ void DebugValueUser::retrackDebugValues(DebugValueUser &X) {
   for (const auto &[MD, XMD] : zip(DebugValues, X.DebugValues))
     if (XMD)
       MetadataTracking::retrack(XMD, MD);
+  if (auto *ID = dyn_cast_or_null<DIAssignID>(DebugValues[2]))
+    *llvm::find(ID->Records, X.getUser()) = getUser();
   X.DebugValues.fill(nullptr);
 }
 
@@ -459,9 +465,7 @@ void ReplaceableUses::resolveAllUses(bool ResolveUsers) {
 // conditional compilation to avoid a compile time regression.
 ReplaceableUses *ReplaceableUses::getOrCreate(Metadata &MD) {
   if (auto *N = dyn_cast<MDNode>(&MD)) {
-    return !N->isResolved() || N->isAlwaysReplaceable()
-               ? N->Context.getOrCreateReplaceableUses()
-               : nullptr;
+    return N->isResolved() ? nullptr : N->Context.getOrCreateReplaceableUses();
   }
   if (auto ArgList = dyn_cast<DIArgList>(&MD))
     return ArgList;
@@ -470,9 +474,7 @@ ReplaceableUses *ReplaceableUses::getOrCreate(Metadata &MD) {
 
 ReplaceableUses *ReplaceableUses::getIfExists(Metadata &MD) {
   if (auto *N = dyn_cast<MDNode>(&MD)) {
-    return !N->isResolved() || N->isAlwaysReplaceable()
-               ? N->Context.getReplaceableUses()
-               : nullptr;
+    return N->isResolved() ? nullptr : N->Context.getReplaceableUses();
   }
   if (auto ArgList = dyn_cast<DIArgList>(&MD))
     return ArgList;
@@ -481,7 +483,7 @@ ReplaceableUses *ReplaceableUses::getIfExists(Metadata &MD) {
 
 bool ReplaceableUses::isReplaceable(const Metadata &MD) {
   if (auto *N = dyn_cast<MDNode>(&MD))
-    return !N->isResolved() || N->isAlwaysReplaceable();
+    return !N->isResolved();
   return isa<ValueAsMetadata>(&MD) || isa<DIArgList>(&MD);
 }
 
@@ -1748,34 +1750,14 @@ void Instruction::dropUnknownNonDebugMetadata(ArrayRef<unsigned> KnownIDs) {
 }
 
 void Instruction::updateDIAssignIDMapping(DIAssignID *ID) {
-  auto &IDToInstrs = getContext().pImpl->AssignmentIDToInstrs;
-  if (const DIAssignID *CurrentID =
+  if (auto *CurrentID =
           cast_or_null<DIAssignID>(getMetadata(LLVMContext::MD_DIAssignID))) {
-    // Nothing to do if the ID isn't changing.
     if (ID == CurrentID)
       return;
-
-    // Unmap this instruction from its current ID.
-    auto InstrsIt = IDToInstrs.find(CurrentID);
-    assert(InstrsIt != IDToInstrs.end() &&
-           "Expect existing attachment to be mapped");
-
-    auto &InstVec = InstrsIt->second;
-    auto *InstIt = llvm::find(InstVec, this);
-    assert(InstIt != InstVec.end() &&
-           "Expect instruction to be mapped to attachment");
-    // The vector contains a ptr to this. If this is the only element in the
-    // vector, remove the ID:vector entry, otherwise just remove the
-    // instruction from the vector.
-    if (InstVec.size() == 1)
-      IDToInstrs.erase(InstrsIt);
-    else
-      InstVec.erase(InstIt);
+    CurrentID->Instrs.erase(llvm::find(CurrentID->Instrs, this));
   }
-
-  // Map this instruction to the new ID.
   if (ID)
-    IDToInstrs[ID].push_back(this);
+    ID->Instrs.push_back(this);
 }
 
 void Instruction::setMetadata(unsigned KindID, MDNode *Node) {
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index c60c1b6ac11b6..dcebdfa795697 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -5562,13 +5562,9 @@ void Verifier::visitDIAssignIDMetadata(Instruction &I, MDNode *MD) {
                 "dbg.assign not in same function as inst", DAI, &I);
     }
   }
-  for (DbgVariableRecord *DVR :
-       cast<DIAssignID>(MD)->getAllDbgVariableRecordUsers()) {
-    CheckDI(DVR->isDbgAssign(),
-            "!DIAssignID should only be used by Assign DVRs.", MD, DVR);
+  for (DbgVariableRecord *DVR : at::getAssignmentMarkers(cast<DIAssignID>(MD)))
     CheckDI(DVR->getFunction() == I.getFunction(),
             "DVRAssign not in same function as inst", DVR, &I);
-  }
 }
 
 void Verifier::visitMMRAMetadata(Instruction &I, MDNode *MD) {
@@ -7391,6 +7387,8 @@ void Verifier::visit(DbgVariableRecord &DVR) {
   CheckDI(MD && (isa<ValueAsMetadata>(MD) || isa<DIArgList>(MD) ||
                  (isa<MDNode>(MD) && !cast<MDNode>(MD)->getNumOperands())),
           "invalid #dbg record address/value", &DVR, MD, BB, F);
+  CheckDI(DVR.isDbgAssign() || !isa<DIAssignID>(MD),
+          "!DIAssignID should only be used by Assign DVRs.", MD, &DVR);
   if (auto *VAM = dyn_cast<ValueAsMetadata>(MD)) {
     visitValueAsMetadata(*VAM, F);
     if (DVR.isDbgDeclare()) {

>From 5920897998324c2a8467bff6e8d692832b6c5e61 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Mon, 14 Sep 2026 01:38:09 -0700
Subject: [PATCH 2/2] 2 -> AssignIDIdx

---
 llvm/include/llvm/IR/DebugProgramInstruction.h | 2 +-
 llvm/include/llvm/IR/Metadata.h                | 2 ++
 llvm/lib/IR/DebugProgramInstruction.cpp        | 4 ++--
 llvm/lib/IR/Metadata.cpp                       | 6 +++---
 4 files changed, 8 insertions(+), 6 deletions(-)

diff --git a/llvm/include/llvm/IR/DebugProgramInstruction.h b/llvm/include/llvm/IR/DebugProgramInstruction.h
index 9402889eb45d0..1acb8838bc1fe 100644
--- a/llvm/include/llvm/IR/DebugProgramInstruction.h
+++ b/llvm/include/llvm/IR/DebugProgramInstruction.h
@@ -527,7 +527,7 @@ class DbgVariableRecord : public DbgRecord, protected DebugValueUser {
   Metadata *getRawAddress() const {
     return isDbgAssign() ? DebugValues[1] : DebugValues[0];
   }
-  Metadata *getRawAssignID() const { return DebugValues[2]; }
+  Metadata *getRawAssignID() const { return DebugValues[AssignIDIdx]; }
   LLVM_ABI DIAssignID *getAssignID() const;
   DIExpression *getAddressExpression() const { return AddressExpression.get(); }
   MDNode *getRawAddressExpression() const {
diff --git a/llvm/include/llvm/IR/Metadata.h b/llvm/include/llvm/IR/Metadata.h
index 51af66ec1809e..017fd98e704ea 100644
--- a/llvm/include/llvm/IR/Metadata.h
+++ b/llvm/include/llvm/IR/Metadata.h
@@ -225,6 +225,8 @@ class DebugValueUser {
   // restructure the DbgVariableRecord class then we can template parameterize
   // this array size.
   std::array<Metadata *, 3> DebugValues;
+  // The slot holding the DIAssignID of a dbg_assign record.
+  static constexpr size_t AssignIDIdx = 2;
 
   ArrayRef<Metadata *> getDebugValues() const { return DebugValues; }
 
diff --git a/llvm/lib/IR/DebugProgramInstruction.cpp b/llvm/lib/IR/DebugProgramInstruction.cpp
index fe25cd3959bfb..4df30bedf588b 100644
--- a/llvm/lib/IR/DebugProgramInstruction.cpp
+++ b/llvm/lib/IR/DebugProgramInstruction.cpp
@@ -505,11 +505,11 @@ Value *DbgVariableRecord::getAddress() const {
 }
 
 DIAssignID *DbgVariableRecord::getAssignID() const {
-  return cast<DIAssignID>(DebugValues[2]);
+  return cast<DIAssignID>(DebugValues[AssignIDIdx]);
 }
 
 void DbgVariableRecord::setAssignId(DIAssignID *New) {
-  resetDebugValue(2, New);
+  resetDebugValue(AssignIDIdx, New);
 }
 
 void DbgVariableRecord::setKillAddress() {
diff --git a/llvm/lib/IR/Metadata.cpp b/llvm/lib/IR/Metadata.cpp
index 117757b9514e8..5021b205f1dcd 100644
--- a/llvm/lib/IR/Metadata.cpp
+++ b/llvm/lib/IR/Metadata.cpp
@@ -179,7 +179,7 @@ void DebugValueUser::trackDebugValue(size_t Idx) {
   if (!MD)
     return;
   MetadataTracking::track(&MD, *MD, *this);
-  if (auto *ID = Idx == 2 ? dyn_cast<DIAssignID>(MD) : nullptr)
+  if (auto *ID = Idx == AssignIDIdx ? dyn_cast<DIAssignID>(MD) : nullptr)
     ID->Records.push_back(getUser());
 }
 
@@ -194,7 +194,7 @@ void DebugValueUser::untrackDebugValue(size_t Idx) {
   if (!MD)
     return;
   MetadataTracking::untrack(MD);
-  if (auto *ID = Idx == 2 ? dyn_cast<DIAssignID>(MD) : nullptr)
+  if (auto *ID = Idx == AssignIDIdx ? dyn_cast<DIAssignID>(MD) : nullptr)
     ID->Records.erase(llvm::find(ID->Records, getUser()));
 }
 
@@ -208,7 +208,7 @@ void DebugValueUser::retrackDebugValues(DebugValueUser &X) {
   for (const auto &[MD, XMD] : zip(DebugValues, X.DebugValues))
     if (XMD)
       MetadataTracking::retrack(XMD, MD);
-  if (auto *ID = dyn_cast_or_null<DIAssignID>(DebugValues[2]))
+  if (auto *ID = dyn_cast_or_null<DIAssignID>(DebugValues[AssignIDIdx]))
     *llvm::find(ID->Records, X.getUser()) = getUser();
   X.DebugValues.fill(nullptr);
 }



More information about the llvm-commits mailing list