[llvm] Add DebugSSAUpdater class to track debug value liveness (PR #135349)

Stephen Tozer via llvm-commits llvm-commits at lists.llvm.org
Mon Sep 15 03:31:37 PDT 2025


https://github.com/SLTozer updated https://github.com/llvm/llvm-project/pull/135349

>From 685b24ce0f672de743275e731143b13cb70804e1 Mon Sep 17 00:00:00 2001
From: Stephen Tozer <stephen.tozer at sony.com>
Date: Wed, 2 Apr 2025 10:08:29 +0100
Subject: [PATCH 1/3] Add DebugSSAUpdater class to track debug value liveness

This patch adds a class that uses SSA construction, with debug values as
definitions, to determine whether and which debug values for a particular
variable are live at each point in an IR function. This will be used by the
IR reader of llvm-debuginfo-analyzer to compute variable ranges and
coverage, although it may be applicable to other debug info IR analyses.
---
 llvm/include/llvm/IR/DebugInfoMetadata.h      |   1 +
 .../llvm/Transforms/Utils/DebugSSAUpdater.h   | 351 +++++++++++++++
 llvm/lib/IR/DebugInfoMetadata.cpp             |   4 +
 llvm/lib/Transforms/Utils/CMakeLists.txt      |   1 +
 llvm/lib/Transforms/Utils/DebugSSAUpdater.cpp | 415 ++++++++++++++++++
 .../unittests/Transforms/Utils/CMakeLists.txt |   1 +
 .../Transforms/Utils/DebugSSAUpdaterTest.cpp  | 220 ++++++++++
 .../llvm/lib/Transforms/Utils/BUILD.gn        |   1 +
 8 files changed, 994 insertions(+)
 create mode 100644 llvm/include/llvm/Transforms/Utils/DebugSSAUpdater.h
 create mode 100644 llvm/lib/Transforms/Utils/DebugSSAUpdater.cpp
 create mode 100644 llvm/unittests/Transforms/Utils/DebugSSAUpdaterTest.cpp

diff --git a/llvm/include/llvm/IR/DebugInfoMetadata.h b/llvm/include/llvm/IR/DebugInfoMetadata.h
index a4e9d1639bb2b..6652e303a6648 100644
--- a/llvm/include/llvm/IR/DebugInfoMetadata.h
+++ b/llvm/include/llvm/IR/DebugInfoMetadata.h
@@ -4665,6 +4665,7 @@ template <> struct DenseMapInfo<DebugVariable> {
 /// information).
 class DebugVariableAggregate : public DebugVariable {
 public:
+  LLVM_ABI DebugVariableAggregate(const DbgVariableRecord *DVR);
   DebugVariableAggregate(const DebugVariable &V)
       : DebugVariable(V.getVariable(), std::nullopt, V.getInlinedAt()) {}
 };
diff --git a/llvm/include/llvm/Transforms/Utils/DebugSSAUpdater.h b/llvm/include/llvm/Transforms/Utils/DebugSSAUpdater.h
new file mode 100644
index 0000000000000..27a56274cd3d9
--- /dev/null
+++ b/llvm/include/llvm/Transforms/Utils/DebugSSAUpdater.h
@@ -0,0 +1,351 @@
+//===- DebugSSAUpdater.h - Debug SSA Update Tool ----------------*- C++ -*-===//
+//
+// 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 the DebugSSAUpdater class, which is used to evaluate the
+// live values of debug variables in IR. This uses SSA construction, treating
+// debug value records as definitions, to determine at each point in the program
+// which definition(s) are live at a given point. This is useful for analysis of
+// the state of debug variables, such as measuring the change in values of a
+// variable over time, or calculating coverage stats.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_TRANSFORMS_UTILS_DEBUGSSAUPDATER_H
+#define LLVM_TRANSFORMS_UTILS_DEBUGSSAUPDATER_H
+
+#include "llvm/IR/BasicBlock.h"
+#include "llvm/IR/CFG.h"
+#include "llvm/IR/DebugInfoMetadata.h"
+#include "llvm/IR/DebugProgramInstruction.h"
+#include "llvm/IR/Instruction.h"
+
+namespace llvm {
+
+////////////////////////////////////////
+// SSAUpdater specialization classes
+
+class DbgSSAPhi;
+template <typename T> class SmallVectorImpl;
+template <typename T> class SSAUpdaterTraits;
+
+/// A definition of a variable; can represent either a debug value, no
+/// definition (the variable has not yet been defined), or a phi value*.
+/// *Meaning multiple definitions that are live-in to a block from different
+/// predecessors, not a debug value that uses an IR PHINode.
+struct DbgValueDef {
+  DbgSSAPhi *Phi;
+  bool IsUndef;
+  bool IsMemory;
+  Metadata *Locations;
+  DIExpression *Expression;
+
+  DbgValueDef()
+      : Phi(nullptr), IsUndef(true), IsMemory(false), Locations(nullptr),
+        Expression(nullptr) {}
+  DbgValueDef(int)
+      : Phi(nullptr), IsUndef(true), IsMemory(false), Locations(nullptr),
+        Expression(nullptr) {}
+  DbgValueDef(bool IsMemory, Metadata *Locations, DIExpression *Expression)
+      : Phi(nullptr), IsUndef(false), IsMemory(IsMemory), Locations(Locations),
+        Expression(Expression) {}
+  DbgValueDef(DbgVariableRecord *DVR) : Phi(nullptr) {
+    assert(!DVR->isDbgAssign() && "#dbg_assign not yet supported");
+    IsUndef = DVR->isKillLocation();
+    IsMemory = DVR->isAddressOfVariable();
+    Locations = DVR->getRawLocation();
+    Expression = DVR->getExpression();
+  }
+  DbgValueDef(DbgSSAPhi *Phi)
+      : Phi(Phi), IsUndef(false), IsMemory(false), Locations(nullptr),
+        Expression(nullptr) {}
+
+  bool agreesWith(DbgValueDef Other) const {
+    if (IsUndef && Other.IsUndef)
+      return true;
+    return std::tie(Phi, IsUndef, IsMemory, Locations, Expression) ==
+           std::tie(Other.Phi, Other.IsUndef, Other.IsMemory, Other.Locations,
+                    Other.Expression);
+  }
+
+  operator bool() const { return !IsUndef; }
+  bool operator==(DbgValueDef Other) const { return agreesWith(Other); }
+  bool operator!=(DbgValueDef Other) const { return !agreesWith(Other); }
+
+  void print(raw_ostream &OS) const;
+};
+
+class DbgSSABlock;
+class DebugSSAUpdater;
+
+/// Represents the live-in definitions of a variable to a block with multiple
+/// predecessors.
+class DbgSSAPhi {
+public:
+  SmallVector<std::pair<DbgSSABlock *, DbgValueDef>, 4> IncomingValues;
+  DbgSSABlock *ParentBlock;
+  DbgSSAPhi(DbgSSABlock *ParentBlock) : ParentBlock(ParentBlock) {}
+
+  DbgSSABlock *getParent() { return ParentBlock; }
+  unsigned getNumIncomingValues() const { return IncomingValues.size(); }
+  DbgSSABlock *getIncomingBlock(size_t Idx) {
+    return IncomingValues[Idx].first;
+  }
+  DbgValueDef getIncomingValue(size_t Idx) {
+    return IncomingValues[Idx].second;
+  }
+  void addIncoming(DbgSSABlock *BB, DbgValueDef DV) {
+    IncomingValues.push_back({BB, DV});
+  }
+
+  void print(raw_ostream &OS) const;
+};
+
+inline raw_ostream &operator<<(raw_ostream &OS, const DbgValueDef &DV) {
+  DV.print(OS);
+  return OS;
+}
+inline raw_ostream &operator<<(raw_ostream &OS, const DbgSSAPhi &PHI) {
+  PHI.print(OS);
+  return OS;
+}
+
+/// Thin wrapper around a block successor iterator.
+class DbgSSABlockSuccIterator {
+public:
+  succ_iterator SuccIt;
+  DebugSSAUpdater &Updater;
+
+  DbgSSABlockSuccIterator(succ_iterator SuccIt, DebugSSAUpdater &Updater)
+      : SuccIt(SuccIt), Updater(Updater) {}
+
+  bool operator!=(const DbgSSABlockSuccIterator &OtherIt) const {
+    return OtherIt.SuccIt != SuccIt;
+  }
+
+  DbgSSABlockSuccIterator &operator++() {
+    ++SuccIt;
+    return *this;
+  }
+
+  DbgSSABlock *operator*();
+};
+
+/// Thin wrapper around a block successor iterator.
+class DbgSSABlockPredIterator {
+public:
+  pred_iterator PredIt;
+  DebugSSAUpdater &Updater;
+
+  DbgSSABlockPredIterator(pred_iterator PredIt, DebugSSAUpdater &Updater)
+      : PredIt(PredIt), Updater(Updater) {}
+
+  bool operator!=(const DbgSSABlockPredIterator &OtherIt) const {
+    return OtherIt.PredIt != PredIt;
+  }
+
+  DbgSSABlockPredIterator &operator++() {
+    ++PredIt;
+    return *this;
+  }
+
+  DbgSSABlock *operator*();
+};
+
+class DbgSSABlock {
+public:
+  BasicBlock &BB;
+  DebugSSAUpdater &Updater;
+  using PHIListT = SmallVector<DbgSSAPhi, 1>;
+  /// List of PHIs in this block. There should only ever be one, but this needs
+  /// to be a list for the SSAUpdater.
+  PHIListT PHIList;
+
+  DbgSSABlock(BasicBlock &BB, DebugSSAUpdater &Updater)
+      : BB(BB), Updater(Updater) {}
+
+  DbgSSABlockPredIterator pred_begin() {
+    return DbgSSABlockPredIterator(llvm::pred_begin(&BB), Updater);
+  }
+
+  DbgSSABlockPredIterator pred_end() {
+    return DbgSSABlockPredIterator(llvm::pred_end(&BB), Updater);
+  }
+
+  iterator_range<DbgSSABlockPredIterator> predecessors() {
+    return iterator_range(pred_begin(), pred_end());
+  }
+
+  DbgSSABlockSuccIterator succ_begin() {
+    return DbgSSABlockSuccIterator(llvm::succ_begin(&BB), Updater);
+  }
+
+  DbgSSABlockSuccIterator succ_end() {
+    return DbgSSABlockSuccIterator(llvm::succ_end(&BB), Updater);
+  }
+
+  iterator_range<DbgSSABlockSuccIterator> successors() {
+    return iterator_range(succ_begin(), succ_end());
+  }
+
+  /// SSAUpdater has requested a PHI: create that within this block record.
+  DbgSSAPhi *newPHI() {
+    assert(PHIList.empty() &&
+           "Only one PHI should exist per-block per-variable");
+    PHIList.emplace_back(this);
+    return &PHIList.back();
+  }
+
+  /// SSAUpdater wishes to know what PHIs already exist in this block.
+  PHIListT &phis() { return PHIList; }
+};
+
+/// Class used to determine the live ranges of debug variables in IR using
+/// SSA construction (via the SSAUpdaterImpl class), used for analysis purposes.
+class DebugSSAUpdater {
+  friend class SSAUpdaterTraits<DebugSSAUpdater>;
+
+private:
+  /// This keeps track of which value to use on a per-block basis. When we
+  /// insert PHI nodes, we keep track of them here.
+  void *AV = nullptr;
+
+  SmallVectorImpl<DbgSSAPhi *> *InsertedPHIs;
+
+  DenseMap<BasicBlock *, DbgSSABlock *> BlockMap;
+
+public:
+  /// If InsertedPHIs is specified, it will be filled
+  /// in with all PHI Nodes created by rewriting.
+  explicit DebugSSAUpdater(
+      SmallVectorImpl<DbgSSAPhi *> *InsertedPHIs = nullptr);
+  DebugSSAUpdater(const DebugSSAUpdater &) = delete;
+  DebugSSAUpdater &operator=(const DebugSSAUpdater &) = delete;
+  ~DebugSSAUpdater();
+
+  void reset() {
+    for (auto &Block : BlockMap)
+      delete Block.second;
+
+    if (InsertedPHIs)
+      InsertedPHIs->clear();
+    BlockMap.clear();
+  }
+
+  void initialize();
+
+  /// For a given BB, create a wrapper block for it. Stores it in the
+  /// DebugSSAUpdater block map.
+  DbgSSABlock *getDbgSSABlock(BasicBlock *BB) {
+    auto it = BlockMap.find(BB);
+    if (it == BlockMap.end()) {
+      BlockMap[BB] = new DbgSSABlock(*BB, *this);
+      it = BlockMap.find(BB);
+    }
+    return it->second;
+  }
+
+  /// Indicate that a rewritten value is available in the specified block
+  /// with the specified value.
+  void addAvailableValue(DbgSSABlock *BB, DbgValueDef DV);
+
+  /// Return true if the DebugSSAUpdater already has a value for the specified
+  /// block.
+  bool hasValueForBlock(DbgSSABlock *BB) const;
+
+  /// Return the value for the specified block if the DebugSSAUpdater has one,
+  /// otherwise return nullptr.
+  DbgValueDef findValueForBlock(DbgSSABlock *BB) const;
+
+  /// Construct SSA form, materializing a value that is live at the end
+  /// of the specified block.
+  DbgValueDef getValueAtEndOfBlock(DbgSSABlock *BB);
+
+  /// Construct SSA form, materializing a value that is live in the
+  /// middle of the specified block.
+  ///
+  /// \c getValueInMiddleOfBlock is the same as \c GetValueAtEndOfBlock except
+  /// in one important case: if there is a definition of the rewritten value
+  /// after the 'use' in BB.  Consider code like this:
+  ///
+  /// \code
+  ///      X1 = ...
+  ///   SomeBB:
+  ///      use(X)
+  ///      X2 = ...
+  ///      br Cond, SomeBB, OutBB
+  /// \endcode
+  ///
+  /// In this case, there are two values (X1 and X2) added to the AvailableVals
+  /// set by the client of the rewriter, and those values are both live out of
+  /// their respective blocks.  However, the use of X happens in the *middle* of
+  /// a block.  Because of this, we need to insert a new PHI node in SomeBB to
+  /// merge the appropriate values, and this value isn't live out of the block.
+  DbgValueDef getValueInMiddleOfBlock(DbgSSABlock *BB);
+
+private:
+  DbgValueDef getValueAtEndOfBlockInternal(DbgSSABlock *BB);
+};
+
+struct DbgRangeEntry {
+  BasicBlock::iterator Start;
+  BasicBlock::iterator End;
+  // Should be non-PHI.
+  DbgValueDef Value;
+};
+
+class DbgValueRangeTable {
+  DenseMap<DebugVariableAggregate, SmallVector<DbgRangeEntry>>
+      OrigVariableValueRangeTable;
+  DenseMap<DebugVariableAggregate, DbgValueDef> OrigSingleLocVariableValueTable;
+  // For the only initial user of this class, the mappings below are useful and
+  // are used in conjunction with the variable value ranges above, thus we track
+  // them as part of the same class. If we have more uses for variable value
+  // range tracking, then the line/variable name mapping should be moved out to
+  // a separate class.
+  DenseMap<BasicBlock::iterator, uint64_t> LineMapping;
+  DenseMap<uint64_t, std::string> VariableNameMapping;
+
+  using VarMapping = DenseMap<Value *, uint64_t>;
+  VarMapping VariableMapping;
+  uint64_t KeyIndex = 0;
+
+public:
+  void addVariable(Function *F, DebugVariableAggregate DVA);
+  bool hasVariableEntry(DebugVariableAggregate DVA) const {
+    return OrigVariableValueRangeTable.contains(DVA) ||
+           OrigSingleLocVariableValueTable.contains(DVA);
+  }
+  bool hasSingleLocEntry(DebugVariableAggregate DVA) const {
+    return OrigSingleLocVariableValueTable.contains(DVA);
+  }
+  ArrayRef<DbgRangeEntry> getVariableRanges(DebugVariableAggregate DVA) {
+    return OrigVariableValueRangeTable[DVA];
+  }
+  DbgValueDef getSingleLoc(DebugVariableAggregate DVA) {
+    return OrigSingleLocVariableValueTable[DVA];
+  }
+
+  void addLine(BasicBlock::iterator I, uint64_t LineAddr) {
+    LineMapping[I] = LineAddr;
+  }
+  uint64_t getLine(BasicBlock::iterator I) {
+    return LineMapping.contains(I) ? LineMapping[I] : (uint64_t)-1;
+  }
+
+  uint64_t addVariableName(Value *V, uint64_t Size);
+  std::string getVariableName(uint64_t Key) {
+    assert(VariableNameMapping.contains(Key) && "Why not here?");
+    return VariableNameMapping[Key];
+  }
+
+  void printValues(DebugVariableAggregate DVA, raw_ostream &OS);
+};
+
+} // end namespace llvm
+
+#endif // LLVM_TRANSFORMS_UTILS_DEBUGSSAUPDATER_H
diff --git a/llvm/lib/IR/DebugInfoMetadata.cpp b/llvm/lib/IR/DebugInfoMetadata.cpp
index 96065edca9b51..1ededb9e7b3e2 100644
--- a/llvm/lib/IR/DebugInfoMetadata.cpp
+++ b/llvm/lib/IR/DebugInfoMetadata.cpp
@@ -54,6 +54,10 @@ DebugVariable::DebugVariable(const DbgVariableRecord *DVR)
       Fragment(DVR->getExpression()->getFragmentInfo()),
       InlinedAt(DVR->getDebugLoc().getInlinedAt()) {}
 
+DebugVariableAggregate::DebugVariableAggregate(const DbgVariableRecord *DVR)
+    : DebugVariable(DVR->getVariable(), std::nullopt,
+                    DVR->getDebugLoc()->getInlinedAt()) {}
+
 DILocation::DILocation(LLVMContext &C, StorageType Storage, unsigned Line,
                        unsigned Column, uint64_t AtomGroup, uint8_t AtomRank,
                        ArrayRef<Metadata *> MDs, bool ImplicitCode)
diff --git a/llvm/lib/Transforms/Utils/CMakeLists.txt b/llvm/lib/Transforms/Utils/CMakeLists.txt
index e411d68570096..f367ca2fdf56b 100644
--- a/llvm/lib/Transforms/Utils/CMakeLists.txt
+++ b/llvm/lib/Transforms/Utils/CMakeLists.txt
@@ -20,6 +20,7 @@ add_llvm_component_library(LLVMTransformUtils
   CtorUtils.cpp
   CountVisits.cpp
   Debugify.cpp
+  DebugSSAUpdater.cpp
   DeclareRuntimeLibcalls.cpp
   DemoteRegToStack.cpp
   DXILUpgrade.cpp
diff --git a/llvm/lib/Transforms/Utils/DebugSSAUpdater.cpp b/llvm/lib/Transforms/Utils/DebugSSAUpdater.cpp
new file mode 100644
index 0000000000000..f646fdea90968
--- /dev/null
+++ b/llvm/lib/Transforms/Utils/DebugSSAUpdater.cpp
@@ -0,0 +1,415 @@
+//===- DebugSSAUpdater.cpp - Debug Variable SSA Update Tool ---------------===//
+//
+// 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 implements the DebugSSAUpdater class.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Transforms/Utils/DebugSSAUpdater.h"
+#include "llvm/IR/CFG.h"
+#include "llvm/IR/DebugInfo.h"
+#include "llvm/IR/DebugInfoMetadata.h"
+#include "llvm/Transforms/Utils/SSAUpdaterImpl.h"
+
+using namespace llvm;
+
+#define DEBUG_TYPE "debug-ssa-updater"
+
+void DbgValueDef::print(raw_ostream &OS) const {
+  OS << "DbgVal{ ";
+  if (IsUndef) {
+    OS << "undef }";
+    return;
+  }
+  if (Phi) {
+    OS << *Phi << "}";
+    return;
+  }
+  OS << (IsMemory ? "Mem: " : "Def: ") << *Locations << " - " << *Expression
+     << " }";
+}
+
+void DbgSSAPhi::print(raw_ostream &OS) const {
+  OS << "DbgPhi ";
+  for (auto &[BB, DV] : IncomingValues)
+    OS << "[" << BB->BB.getName() << ", " << DV << "] ";
+}
+
+using AvailableValsTy = DenseMap<DbgSSABlock *, DbgValueDef>;
+
+static AvailableValsTy &getAvailableVals(void *AV) {
+  return *static_cast<AvailableValsTy *>(AV);
+}
+
+DebugSSAUpdater::DebugSSAUpdater(SmallVectorImpl<DbgSSAPhi *> *NewPHI)
+    : InsertedPHIs(NewPHI) {}
+
+DebugSSAUpdater::~DebugSSAUpdater() {
+  delete static_cast<AvailableValsTy *>(AV);
+}
+
+void DebugSSAUpdater::initialize() {
+  if (!AV)
+    AV = new AvailableValsTy();
+  else
+    getAvailableVals(AV).clear();
+}
+
+bool DebugSSAUpdater::hasValueForBlock(DbgSSABlock *BB) const {
+  return getAvailableVals(AV).count(BB);
+}
+
+DbgValueDef DebugSSAUpdater::findValueForBlock(DbgSSABlock *BB) const {
+  return getAvailableVals(AV).lookup(BB);
+}
+
+void DebugSSAUpdater::addAvailableValue(DbgSSABlock *BB, DbgValueDef DV) {
+  getAvailableVals(AV)[BB] = DV;
+}
+
+DbgValueDef DebugSSAUpdater::getValueAtEndOfBlock(DbgSSABlock *BB) {
+  DbgValueDef Res = getValueAtEndOfBlockInternal(BB);
+  return Res;
+}
+
+DbgValueDef DebugSSAUpdater::getValueInMiddleOfBlock(DbgSSABlock *BB) {
+  // If there is no definition of the renamed variable in this block, just use
+  // 'getValueAtEndOfBlock' to do our work.
+  if (!hasValueForBlock(BB))
+    return getValueAtEndOfBlock(BB);
+
+  // Otherwise, we have the hard case. Get the live-in values for each
+  // predecessor.
+  SmallVector<std::pair<DbgSSABlock *, DbgValueDef>, 8> PredValues;
+  DbgValueDef SingularValue;
+
+  bool IsFirstPred = true;
+  for (DbgSSABlock *PredBB : BB->predecessors()) {
+    DbgValueDef PredVal = getValueAtEndOfBlock(PredBB);
+    PredValues.push_back(std::make_pair(PredBB, PredVal));
+
+    // Compute SingularValue.
+    if (IsFirstPred) {
+      SingularValue = PredVal;
+      IsFirstPred = false;
+    } else if (!PredVal.agreesWith(SingularValue))
+      SingularValue = DbgValueDef();
+  }
+
+  // If there are no predecessors, just return undef.
+  if (PredValues.empty())
+    return DbgValueDef();
+
+  // Otherwise, if all the merged values are the same, just use it.
+  if (!SingularValue.IsUndef)
+    return SingularValue;
+
+  // Ok, we have no way out, insert a new one now.
+  DbgSSAPhi *InsertedPHI = BB->newPHI();
+
+  // Fill in all the predecessors of the PHI.
+  for (const auto &PredValue : PredValues)
+    InsertedPHI->addIncoming(PredValue.first, PredValue.second);
+
+  // See if the PHI node can be merged to a single value. This can happen in
+  // loop cases when we get a PHI of itself and one other value.
+
+  // If the client wants to know about all new instructions, tell it.
+  if (InsertedPHIs)
+    InsertedPHIs->push_back(InsertedPHI);
+
+  LLVM_DEBUG(dbgs() << "  Inserted PHI: " << *InsertedPHI << "\n");
+  return InsertedPHI;
+}
+
+DbgSSABlock *DbgSSABlockSuccIterator::operator*() {
+  return Updater.getDbgSSABlock(*SuccIt);
+}
+DbgSSABlock *DbgSSABlockPredIterator::operator*() {
+  return Updater.getDbgSSABlock(*PredIt);
+}
+
+namespace llvm {
+
+template <> class SSAUpdaterTraits<DebugSSAUpdater> {
+public:
+  using BlkT = DbgSSABlock;
+  using ValT = DbgValueDef;
+  using PhiT = DbgSSAPhi;
+  using BlkSucc_iterator = DbgSSABlockSuccIterator;
+
+  static BlkSucc_iterator BlkSucc_begin(BlkT *BB) { return BB->succ_begin(); }
+  static BlkSucc_iterator BlkSucc_end(BlkT *BB) { return BB->succ_end(); }
+
+  class PHI_iterator {
+  private:
+    DbgSSAPhi *PHI;
+    unsigned Idx;
+
+  public:
+    explicit PHI_iterator(DbgSSAPhi *P) // begin iterator
+        : PHI(P), Idx(0) {}
+    PHI_iterator(DbgSSAPhi *P, bool) // end iterator
+        : PHI(P), Idx(PHI->getNumIncomingValues()) {}
+
+    PHI_iterator &operator++() {
+      ++Idx;
+      return *this;
+    }
+    bool operator==(const PHI_iterator &X) const { return Idx == X.Idx; }
+    bool operator!=(const PHI_iterator &X) const { return !operator==(X); }
+
+    DbgValueDef getIncomingValue() { return PHI->getIncomingValue(Idx); }
+    DbgSSABlock *getIncomingBlock() { return PHI->getIncomingBlock(Idx); }
+  };
+
+  static PHI_iterator PHI_begin(PhiT *PHI) { return PHI_iterator(PHI); }
+  static PHI_iterator PHI_end(PhiT *PHI) { return PHI_iterator(PHI, true); }
+
+  /// FindPredecessorBlocks - Put the predecessors of Info->BB into the Preds
+  /// vector, set Info->NumPreds, and allocate space in Info->Preds.
+  static void FindPredecessorBlocks(DbgSSABlock *BB,
+                                    SmallVectorImpl<DbgSSABlock *> *Preds) {
+    for (auto PredIt = BB->pred_begin(); PredIt != BB->pred_end(); ++PredIt)
+      Preds->push_back(*PredIt);
+  }
+
+  /// GetPoisonVal - Get an undefined value of the same type as the value
+  /// being handled.
+  static DbgValueDef GetPoisonVal(DbgSSABlock *BB, DebugSSAUpdater *Updater) {
+    return DbgValueDef();
+  }
+
+  /// CreateEmptyPHI - Create a new PHI instruction in the specified block.
+  /// Reserve space for the operands (?) but do not fill them in yet.
+  static DbgSSAPhi *CreateEmptyPHI(DbgSSABlock *BB, unsigned NumPreds,
+                                   DebugSSAUpdater *Updater) {
+    DbgSSAPhi *PHI = BB->newPHI();
+    return PHI;
+  }
+
+  /// AddPHIOperand - Add the specified value as an operand of the PHI for
+  /// the specified predecessor block.
+  static void AddPHIOperand(DbgSSAPhi *PHI, DbgValueDef Val,
+                            DbgSSABlock *Pred) {
+    PHI->addIncoming(Pred, Val);
+  }
+
+  /// ValueIsPHI - Check if a value is a PHI.
+  static DbgSSAPhi *ValueIsPHI(DbgValueDef Val, DebugSSAUpdater *Updater) {
+    return Val.Phi;
+  }
+
+  /// ValueIsNewPHI - Like ValueIsPHI but also check if the PHI has no source
+  /// operands, i.e., it was just added.
+  static DbgSSAPhi *ValueIsNewPHI(DbgValueDef Val, DebugSSAUpdater *Updater) {
+    DbgSSAPhi *PHI = ValueIsPHI(Val, Updater);
+    if (PHI && PHI->getNumIncomingValues() == 0)
+      return PHI;
+    return nullptr;
+  }
+
+  /// GetPHIValue - For the specified PHI instruction, return the value
+  /// that it defines.
+  static DbgValueDef GetPHIValue(DbgSSAPhi *PHI) { return PHI; }
+};
+
+} // end namespace llvm
+
+/// Check to see if AvailableVals has an entry for the specified BB and if so,
+/// return it. If not, construct SSA form by first calculating the required
+/// placement of PHIs and then inserting new PHIs where needed.
+DbgValueDef DebugSSAUpdater::getValueAtEndOfBlockInternal(DbgSSABlock *BB) {
+  AvailableValsTy &AvailableVals = getAvailableVals(AV);
+  if (AvailableVals.contains(BB))
+    return AvailableVals[BB];
+
+  SSAUpdaterImpl<DebugSSAUpdater> Impl(this, &AvailableVals, InsertedPHIs);
+  return Impl.GetValue(BB);
+}
+
+bool isContained(DIScope *Inner, DIScope *Outer) {
+  if (Inner == Outer)
+    return true;
+  if (!Inner->getScope())
+    return false;
+  return isContained(Inner->getScope(), Outer);
+}
+
+void DbgValueRangeTable::addVariable(Function *F, DebugVariableAggregate DVA) {
+  const DILocalVariable *Var = DVA.getVariable();
+  const DILocation *InlinedAt = DVA.getInlinedAt();
+
+  DenseMap<BasicBlock *, SmallVector<DbgVariableRecord *>> BlockDbgRecordValues;
+  DenseSet<BasicBlock *> HasAnyInstructionsInScope;
+  int NumRecordsFound = 0;
+  DbgVariableRecord *LastRecordFound = nullptr;
+  bool DeclareRecordFound = false;
+
+  LLVM_DEBUG(dbgs() << "Finding variable info for " << *Var << " at "
+                    << InlinedAt << "\n");
+
+  for (auto &BB : *F) {
+    auto &DbgRecordValues = BlockDbgRecordValues[&BB];
+    bool FoundInstructionInScope = false;
+    for (auto &I : BB) {
+      LLVM_DEBUG(dbgs() << "Instruction: '" << I << "'\n");
+
+      for (DbgVariableRecord &DVR : filterDbgVars(I.getDbgRecordRange())) {
+        if (DVR.getVariable() == Var &&
+            DVR.getDebugLoc().getInlinedAt() == InlinedAt) {
+          assert(!DVR.isDbgAssign() && "No support for #dbg_declare yet.");
+          if (DVR.isDbgDeclare())
+            DeclareRecordFound = true;
+          ++NumRecordsFound;
+          LastRecordFound = &DVR;
+          DbgRecordValues.push_back(&DVR);
+        }
+        if (!FoundInstructionInScope && I.getDebugLoc()) {
+          if (I.getDebugLoc().getInlinedAt() == InlinedAt &&
+              isContained(cast<DILocalScope>(I.getDebugLoc().getScope()),
+                          Var->getScope())) {
+            FoundInstructionInScope = true;
+            HasAnyInstructionsInScope.insert(&BB);
+          }
+        }
+      }
+      if (!FoundInstructionInScope && I.getDebugLoc()) {
+        if (I.getDebugLoc().getInlinedAt() == InlinedAt &&
+            isContained(cast<DILocalScope>(I.getDebugLoc().getScope()),
+                        Var->getScope())) {
+          FoundInstructionInScope = true;
+          HasAnyInstructionsInScope.insert(&BB);
+        }
+      }
+    }
+    LLVM_DEBUG(dbgs() << "DbgRecordValues found in '" << BB.getName() << "':\n";
+               for_each(DbgRecordValues, [](auto *DV) { DV->dump(); }));
+  }
+
+  if (!NumRecordsFound) {
+    LLVM_DEBUG(dbgs() << "No dbg_records found for variable!\n");
+    return;
+  }
+
+  // Now that we have all the DbgValues, we can start defining available values
+  // for each block. The end goal is to have, for every block with any
+  // instructions in scope, a LiveIn value.
+  // Currently we anticipate that either a variable has a set of #dbg_values, in
+  // which case we need a complete SSA liveness analysis to determine live-in
+  // values per-block, or a variable has a single #dbg_declare.
+  if (DeclareRecordFound) {
+    // FIXME: This should be changed for fragments!
+    LLVM_DEBUG(dbgs() << "Single location found for variable!\n");
+    assert(NumRecordsFound == 1 &&
+           "Found multiple records for a #dbg_declare variable!");
+    OrigSingleLocVariableValueTable[DVA] = DbgValueDef(LastRecordFound);
+    return;
+  }
+
+  // We don't have a single location, so let's have fun with liveness.
+  DenseMap<BasicBlock *, DbgValueDef> LiveInMap;
+  SmallVector<DbgSSAPhi *> HypotheticalPHIs;
+  DebugSSAUpdater SSAUpdater(&HypotheticalPHIs);
+  SSAUpdater.initialize();
+  for (auto &[BB, DVs] : BlockDbgRecordValues) {
+    auto *DbgBB = SSAUpdater.getDbgSSABlock(BB);
+    if (DVs.empty())
+      continue;
+    auto *LastValueInBlock = DVs.back();
+    LLVM_DEBUG(dbgs() << "Last value in " << BB->getName() << ": "
+                      << *LastValueInBlock << "\n");
+    SSAUpdater.addAvailableValue(DbgBB, DbgValueDef(LastValueInBlock));
+  }
+
+  for (BasicBlock &BB : *F) {
+    if (!HasAnyInstructionsInScope.contains(&BB)) {
+      LLVM_DEBUG(dbgs() << "Skipping finding debug ranges for '" << BB.getName()
+                        << "' due to no in-scope instructions.\n");
+      continue;
+    }
+    LLVM_DEBUG(dbgs() << "Finding live-in value for '" << BB.getName()
+                      << "'...\n");
+    DbgValueDef LiveValue =
+        SSAUpdater.getValueInMiddleOfBlock(SSAUpdater.getDbgSSABlock(&BB));
+    LLVM_DEBUG(dbgs() << "Found live-in: " << LiveValue << "\n");
+    auto HasValidValue = [](DbgValueDef DV) {
+      return !DV.IsUndef && DV.Phi == nullptr;
+    };
+
+    SmallVector<DbgRangeEntry> BlockDbgRanges;
+    BasicBlock::iterator LastIt = BB.begin();
+    for (auto *DVR : BlockDbgRecordValues[&BB]) {
+      // Create a range that ends as of DVR.
+      BasicBlock::iterator DVRStartIt =
+          const_cast<Instruction *>(DVR->getInstruction())->getIterator();
+      if (HasValidValue(LiveValue))
+        BlockDbgRanges.push_back({LastIt, DVRStartIt, LiveValue});
+      LiveValue = DbgValueDef(DVR);
+      LastIt = DVRStartIt;
+    }
+
+    // After considering all in-block debug values, if any, create a range
+    // covering the remainder of the block.
+    if (HasValidValue(LiveValue))
+      BlockDbgRanges.push_back({LastIt, BB.end(), LiveValue});
+    LLVM_DEBUG(dbgs() << "Create set of ranges with " << BlockDbgRanges.size()
+                      << " entries!\n");
+    if (!BlockDbgRanges.empty())
+      OrigVariableValueRangeTable[DVA].append(BlockDbgRanges);
+  }
+}
+
+void DbgValueRangeTable::printValues(DebugVariableAggregate DVA,
+                                     raw_ostream &OS) {
+  OS << "Variable Table for '" << DVA.getVariable()->getName() << "' (at "
+     << DVA.getInlinedAt() << "):\n";
+  if (!hasVariableEntry(DVA)) {
+    OS << "  Empty!\n";
+    return;
+  }
+  if (hasSingleLocEntry(DVA)) {
+    OS << "  SingleLoc: " << OrigSingleLocVariableValueTable[DVA] << "\n";
+    return;
+  }
+  OS << "  LocRange:\n";
+  for (DbgRangeEntry RangeEntry : OrigVariableValueRangeTable[DVA]) {
+    OS << "    (";
+    if (RangeEntry.Start == RangeEntry.Start->getParent()->begin() &&
+        RangeEntry.End == RangeEntry.Start->getParent()->end()) {
+      OS << RangeEntry.Start->getParent()->getName();
+    } else {
+      OS << RangeEntry.Start->getParent()->getName() << ": "
+         << *RangeEntry.Start << ", ";
+      if (RangeEntry.End == RangeEntry.Start->getParent()->end())
+        OS << "..";
+      else
+        OS << *RangeEntry.End;
+    }
+    OS << ") [" << RangeEntry.Value << "]\n";
+  }
+}
+
+uint64_t DbgValueRangeTable::addVariableName(Value *V, uint64_t Size) {
+  uint64_t Key = 0;
+  auto I = VariableMapping.find(V);
+  if (I == VariableMapping.end()) {
+    Key = KeyIndex;
+    VariableMapping.try_emplace(V, Key);
+    std::string &ValueText = VariableNameMapping[Key];
+    raw_string_ostream Stream(ValueText);
+    Stream << " ";
+    V->printAsOperand(Stream, true);
+    KeyIndex += Size;
+  } else {
+    Key = I->second;
+  }
+  LLVM_DEBUG(dbgs() << "Stashing Value: " << Key << " - " << *V << "\n");
+
+  return Key;
+}
diff --git a/llvm/unittests/Transforms/Utils/CMakeLists.txt b/llvm/unittests/Transforms/Utils/CMakeLists.txt
index 5c7ec28709c16..7d649f25c830a 100644
--- a/llvm/unittests/Transforms/Utils/CMakeLists.txt
+++ b/llvm/unittests/Transforms/Utils/CMakeLists.txt
@@ -19,6 +19,7 @@ add_llvm_unittest(UtilsTests
   CodeLayoutTest.cpp
   CodeMoverUtilsTest.cpp
   DebugifyTest.cpp
+  DebugSSAUpdaterTest.cpp
   FunctionComparatorTest.cpp
   IntegerDivisionTest.cpp
   LocalTest.cpp
diff --git a/llvm/unittests/Transforms/Utils/DebugSSAUpdaterTest.cpp b/llvm/unittests/Transforms/Utils/DebugSSAUpdaterTest.cpp
new file mode 100644
index 0000000000000..038a06a40242a
--- /dev/null
+++ b/llvm/unittests/Transforms/Utils/DebugSSAUpdaterTest.cpp
@@ -0,0 +1,220 @@
+//===- DebugSSAUpdater.cpp - Unit tests for debug variable tracking -------===//
+//
+// 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/Transforms/Utils/DebugSSAUpdater.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/AsmParser/Parser.h"
+#include "llvm/IR/BasicBlock.h"
+#include "llvm/IR/DebugInfoMetadata.h"
+#include "llvm/IR/DebugProgramInstruction.h"
+#include "llvm/IR/Function.h"
+#include "llvm/IR/GlobalValue.h"
+#include "llvm/IR/Module.h"
+#include "llvm/Support/Debug.h"
+#include "llvm/Support/SourceMgr.h"
+#include "gtest/gtest.h"
+
+using namespace llvm;
+
+static std::unique_ptr<Module> parseIR(LLVMContext &C, const char *IR) {
+  SMDiagnostic Err;
+  std::unique_ptr<Module> Mod = parseAssemblyString(IR, Err, C);
+  if (!Mod)
+    Err.print("DebugSSAUpdaterTests", errs());
+  return Mod;
+}
+
+namespace {
+
+// Verify that two conflicting live-in values result in no live-in range for a
+// block.
+TEST(DebugSSAUpdater, EmptyPHIRange) {
+  LLVMContext C;
+
+  std::unique_ptr<Module> M =
+      parseIR(C,
+              R"(define i32 @foo(i32 %a, i1 %b) !dbg !7 {
+entry:
+    #dbg_value(i32 %a, !6, !DIExpression(), !10)
+  br i1 %b, label %if.then, label %if.else, !dbg !11
+
+if.then:
+  %c = add i32 %a, 10, !dbg !12
+    #dbg_value(i32 %c, !6, !DIExpression(), !13)
+  br label %exit, !dbg !14
+
+if.else:
+  %d = mul i32 %a, 3, !dbg !15
+    #dbg_value(i32 %d, !6, !DIExpression(), !16)
+  br label %exit, !dbg !17
+
+exit:
+  %res = phi i32 [ %c, %if.then ], [ %d, %if.else ], !dbg !18
+  ret i32 %res, !dbg !19
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!4}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_17, file: !1, producer: "clang version 20.0.0")
+!1 = !DIFile(filename: "test.cpp", directory: ".")
+!2 = !{}
+!3 = !{i32 7, !"Dwarf Version", i32 4}
+!4 = !{i32 2, !"Debug Info Version", i32 3}
+!5 = !{!"clang version 20.0.0"}
+!6 = !DILocalVariable(name: "a", scope: !7, file: !1, line: 11, type: !8)
+!7 = distinct !DISubprogram(name: "foo", linkageName: "_Z3foov", scope: !1, file: !1, line: 10, type: !9, scopeLine: 10, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !2)
+!8 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!9 = !DISubroutineType(types: !2)
+!10 = !DILocation(line: 10, scope: !7)
+!11 = !DILocation(line: 11, scope: !7)
+!12 = !DILocation(line: 12, scope: !7)
+!13 = !DILocation(line: 13, scope: !7)
+!14 = !DILocation(line: 14, scope: !7)
+!15 = !DILocation(line: 15, scope: !7)
+!16 = !DILocation(line: 16, scope: !7)
+!17 = !DILocation(line: 17, scope: !7)
+!18 = !DILocation(line: 18, scope: !7)
+!19 = !DILocation(line: 19, scope: !7)
+)");
+
+  Function *Foo = &*M->begin();
+  DebugVariableAggregate VarA(cast<DbgVariableRecord>(
+      Foo->begin()->begin()->getDbgRecordRange().begin()));
+  DbgValueRangeTable DbgValueRanges;
+  DbgValueRanges.addVariable(Foo, VarA);
+  BasicBlock *ExitBlock = &Foo->back();
+  // We should have 5 ranges: 1 in the entry block, and 2 in each `if` block,
+  // while there should be no range for the exit block.
+  EXPECT_EQ(DbgValueRanges.getVariableRanges(VarA).size(), 5);
+  EXPECT_TRUE(none_of(DbgValueRanges.getVariableRanges(VarA),
+                      [&](DbgRangeEntry VarRange) {
+                        return VarRange.Start->getParent() == ExitBlock;
+                      }));
+}
+
+// Verify that we correctly set live-in variable values through loops.
+TEST(DebugSSAUpdater, LoopPHI) {
+  LLVMContext C;
+
+  std::unique_ptr<Module> M =
+      parseIR(C,
+              R"(define i32 @foo(i32 %a, i32 %max) !dbg !7 {
+entry:
+    #dbg_value(i32 %a, !6, !DIExpression(), !10)
+  %cond.entry = icmp slt i32 %a, %max, !dbg !11
+  br i1 %cond.entry, label %loop, label %exit, !dbg !12
+
+loop:
+  %loop.a = phi i32 [ %a, %entry ], [ %inc, %loop ]
+  %inc = add i32 %loop.a, 1, !dbg !13
+  %cond.loop = icmp slt i32 %inc, %max, !dbg !14
+  br i1 %cond.loop, label %loop, label %exit, !dbg !15
+
+exit:
+  %res = phi i32 [ %a, %entry ], [ %loop.a, %loop ]
+  ret i32 %res, !dbg !16
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!4}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_17, file: !1, producer: "clang version 20.0.0")
+!1 = !DIFile(filename: "test.cpp", directory: ".")
+!2 = !{}
+!3 = !{i32 7, !"Dwarf Version", i32 4}
+!4 = !{i32 2, !"Debug Info Version", i32 3}
+!5 = !{!"clang version 20.0.0"}
+!6 = !DILocalVariable(name: "a", scope: !7, file: !1, line: 11, type: !8)
+!7 = distinct !DISubprogram(name: "foo", linkageName: "_Z3foov", scope: !1, file: !1, line: 10, type: !9, scopeLine: 10, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !2)
+!8 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!9 = !DISubroutineType(types: !2)
+!10 = !DILocation(line: 10, scope: !7)
+!11 = !DILocation(line: 11, scope: !7)
+!12 = !DILocation(line: 12, scope: !7)
+!13 = !DILocation(line: 13, scope: !7)
+!14 = !DILocation(line: 14, scope: !7)
+!15 = !DILocation(line: 15, scope: !7)
+!16 = !DILocation(line: 16, scope: !7)
+)");
+
+  Function *Foo = &*M->begin();
+  DebugVariableAggregate VarA(cast<DbgVariableRecord>(
+      Foo->begin()->begin()->getDbgRecordRange().begin()));
+  DbgValueRangeTable DbgValueRanges;
+  DbgValueRanges.addVariable(Foo, VarA);
+  // We should have 3 ranges: 1 in the entry block, and 1 live-in entry for each
+  // of the loops.
+  EXPECT_EQ(DbgValueRanges.getVariableRanges(VarA).size(), 3);
+  EXPECT_TRUE(
+      all_of(DbgValueRanges.getVariableRanges(VarA),
+             [&](DbgRangeEntry VarRange) { return !VarRange.Value.IsUndef; }));
+}
+
+// Verify that when a variable has only undef debug values, it has no live
+// ranges.
+TEST(DebugSSAUpdater, AllUndefVar) {
+  LLVMContext C;
+
+  std::unique_ptr<Module> M =
+      parseIR(C,
+              R"(define i32 @foo(i32 %a, i1 %b) !dbg !7 {
+entry:
+    #dbg_value(i32 poison, !6, !DIExpression(), !10)
+  br i1 %b, label %if.then, label %if.else, !dbg !11
+
+if.then:
+  %c = add i32 %a, 10, !dbg !12
+    #dbg_value(i32 poison, !6, !DIExpression(), !13)
+  br label %exit, !dbg !14
+
+if.else:
+  %d = mul i32 %a, 3, !dbg !15
+    #dbg_value(i32 poison, !6, !DIExpression(), !16)
+  br label %exit, !dbg !17
+
+exit:
+  %res = phi i32 [ %c, %if.then ], [ %d, %if.else ], !dbg !18
+  ret i32 %res, !dbg !19
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!4}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_17, file: !1, producer: "clang version 20.0.0")
+!1 = !DIFile(filename: "test.cpp", directory: ".")
+!2 = !{}
+!3 = !{i32 7, !"Dwarf Version", i32 4}
+!4 = !{i32 2, !"Debug Info Version", i32 3}
+!5 = !{!"clang version 20.0.0"}
+!6 = !DILocalVariable(name: "a", scope: !7, file: !1, line: 11, type: !8)
+!7 = distinct !DISubprogram(name: "foo", linkageName: "_Z3foov", scope: !1, file: !1, line: 10, type: !9, scopeLine: 10, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !2)
+!8 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!9 = !DISubroutineType(types: !2)
+!10 = !DILocation(line: 10, scope: !7)
+!11 = !DILocation(line: 11, scope: !7)
+!12 = !DILocation(line: 12, scope: !7)
+!13 = !DILocation(line: 13, scope: !7)
+!14 = !DILocation(line: 14, scope: !7)
+!15 = !DILocation(line: 15, scope: !7)
+!16 = !DILocation(line: 16, scope: !7)
+!17 = !DILocation(line: 17, scope: !7)
+!18 = !DILocation(line: 18, scope: !7)
+!19 = !DILocation(line: 19, scope: !7)
+)");
+
+  Function *Foo = &*M->begin();
+  DebugVariableAggregate VarA(cast<DbgVariableRecord>(
+      Foo->begin()->begin()->getDbgRecordRange().begin()));
+  DbgValueRangeTable DbgValueRanges;
+  DbgValueRanges.addVariable(Foo, VarA);
+  // There should be no variable ranges emitted for a variable that has only
+  // undef dbg_values.
+  EXPECT_EQ(DbgValueRanges.getVariableRanges(VarA).size(), 0);
+}
+} // namespace
diff --git a/llvm/utils/gn/secondary/llvm/lib/Transforms/Utils/BUILD.gn b/llvm/utils/gn/secondary/llvm/lib/Transforms/Utils/BUILD.gn
index bf650c38e150a..186d2ef96c19b 100644
--- a/llvm/utils/gn/secondary/llvm/lib/Transforms/Utils/BUILD.gn
+++ b/llvm/utils/gn/secondary/llvm/lib/Transforms/Utils/BUILD.gn
@@ -29,6 +29,7 @@ static_library("Utils") {
     "CtorUtils.cpp",
     "DXILUpgrade.cpp",
     "Debugify.cpp",
+    "DebugSSAUpdater.cpp",
     "DeclareRuntimeLibcalls.cpp",
     "DemoteRegToStack.cpp",
     "EntryExitInstrumenter.cpp",

>From 46c45a9e8ff082a15f4c0ee34c2a76bd4a7a5d40 Mon Sep 17 00:00:00 2001
From: Stephen Tozer <stephen.tozer at sony.com>
Date: Thu, 21 Aug 2025 16:55:56 +0100
Subject: [PATCH 2/3] Address review comments, split out value-name mapping to
 separate class, remove line tracking

---
 .../llvm/Transforms/Utils/DebugSSAUpdater.h   | 63 ++++++++-------
 llvm/lib/Transforms/Utils/DebugSSAUpdater.cpp | 76 +++++++------------
 .../Transforms/Utils/DebugSSAUpdaterTest.cpp  |  1 -
 3 files changed, 62 insertions(+), 78 deletions(-)

diff --git a/llvm/include/llvm/Transforms/Utils/DebugSSAUpdater.h b/llvm/include/llvm/Transforms/Utils/DebugSSAUpdater.h
index 27a56274cd3d9..85ed3e240af24 100644
--- a/llvm/include/llvm/Transforms/Utils/DebugSSAUpdater.h
+++ b/llvm/include/llvm/Transforms/Utils/DebugSSAUpdater.h
@@ -13,16 +13,23 @@
 // the state of debug variables, such as measuring the change in values of a
 // variable over time, or calculating coverage stats.
 //
+// NB: This is an expensive analysis that is generally not suitable for use in
+// LLVM passes, but may be useful for standalone tools.
+//
 //===----------------------------------------------------------------------===//
 
 #ifndef LLVM_TRANSFORMS_UTILS_DEBUGSSAUPDATER_H
 #define LLVM_TRANSFORMS_UTILS_DEBUGSSAUPDATER_H
 
+#include "llvm/ADT/SmallVector.h"
 #include "llvm/IR/BasicBlock.h"
 #include "llvm/IR/CFG.h"
 #include "llvm/IR/DebugInfoMetadata.h"
 #include "llvm/IR/DebugProgramInstruction.h"
 #include "llvm/IR/Instruction.h"
+#include "llvm/IR/ValueHandle.h"
+#include "llvm/IR/ValueMap.h"
+#include <cstdint>
 
 namespace llvm {
 
@@ -30,7 +37,6 @@ namespace llvm {
 // SSAUpdater specialization classes
 
 class DbgSSAPhi;
-template <typename T> class SmallVectorImpl;
 template <typename T> class SSAUpdaterTraits;
 
 /// A definition of a variable; can represent either a debug value, no
@@ -208,12 +214,15 @@ class DbgSSABlock {
 /// SSA construction (via the SSAUpdaterImpl class), used for analysis purposes.
 class DebugSSAUpdater {
   friend class SSAUpdaterTraits<DebugSSAUpdater>;
+  using AvailableValsTy = DenseMap<DbgSSABlock *, DbgValueDef>;
 
 private:
   /// This keeps track of which value to use on a per-block basis. When we
   /// insert PHI nodes, we keep track of them here.
-  void *AV = nullptr;
+  AvailableValsTy AV;
 
+  /// Pointer to an optionally-passed vector into which, if it is non-null,
+  /// the PHIs that describe ambiguous variable locations will be inserted.
   SmallVectorImpl<DbgSSAPhi *> *InsertedPHIs;
 
   DenseMap<BasicBlock *, DbgSSABlock *> BlockMap;
@@ -225,7 +234,6 @@ class DebugSSAUpdater {
       SmallVectorImpl<DbgSSAPhi *> *InsertedPHIs = nullptr);
   DebugSSAUpdater(const DebugSSAUpdater &) = delete;
   DebugSSAUpdater &operator=(const DebugSSAUpdater &) = delete;
-  ~DebugSSAUpdater();
 
   void reset() {
     for (auto &Block : BlockMap)
@@ -298,21 +306,35 @@ struct DbgRangeEntry {
   DbgValueDef Value;
 };
 
+/// Utility class used to store the names of SSA values after their owning
+/// modules have been destroyed. Values are added via \c addValue to receive a
+/// corresponding ID, which can then be used to retrieve the name of the SSA
+/// value via \c getName at any point. Adding the same value multiple times
+/// returns the same ID, making \c addValue idempotent.
+class SSAValueNameMap {
+  struct Config : ValueMapConfig<Value *> {
+    enum { FollowRAUW = false };
+  };
+public:
+  using ValueID = uint64_t;
+  ValueID addValue(Value *V);
+  std::string getName(ValueID ID) {
+    return ValueIDToNameMap[ID];
+  }
+private:
+  DenseMap<ValueID, std::string> ValueIDToNameMap;
+  ValueMap<Value*, ValueID, Config> ValueToIDMap;
+  ValueID NextID = 0;
+};
+
+/// Utility class used to find and store the live debug ranges for variables in
+/// a module. This class uses the DebugSSAUpdater for each variable added with
+/// \c addVariable to find either a single-location value, e.g. #dbg_declare, or
+/// a set of live value ranges corresponding to the set of #dbg_value records.
 class DbgValueRangeTable {
   DenseMap<DebugVariableAggregate, SmallVector<DbgRangeEntry>>
       OrigVariableValueRangeTable;
   DenseMap<DebugVariableAggregate, DbgValueDef> OrigSingleLocVariableValueTable;
-  // For the only initial user of this class, the mappings below are useful and
-  // are used in conjunction with the variable value ranges above, thus we track
-  // them as part of the same class. If we have more uses for variable value
-  // range tracking, then the line/variable name mapping should be moved out to
-  // a separate class.
-  DenseMap<BasicBlock::iterator, uint64_t> LineMapping;
-  DenseMap<uint64_t, std::string> VariableNameMapping;
-
-  using VarMapping = DenseMap<Value *, uint64_t>;
-  VarMapping VariableMapping;
-  uint64_t KeyIndex = 0;
 
 public:
   void addVariable(Function *F, DebugVariableAggregate DVA);
@@ -330,19 +352,6 @@ class DbgValueRangeTable {
     return OrigSingleLocVariableValueTable[DVA];
   }
 
-  void addLine(BasicBlock::iterator I, uint64_t LineAddr) {
-    LineMapping[I] = LineAddr;
-  }
-  uint64_t getLine(BasicBlock::iterator I) {
-    return LineMapping.contains(I) ? LineMapping[I] : (uint64_t)-1;
-  }
-
-  uint64_t addVariableName(Value *V, uint64_t Size);
-  std::string getVariableName(uint64_t Key) {
-    assert(VariableNameMapping.contains(Key) && "Why not here?");
-    return VariableNameMapping[Key];
-  }
-
   void printValues(DebugVariableAggregate DVA, raw_ostream &OS);
 };
 
diff --git a/llvm/lib/Transforms/Utils/DebugSSAUpdater.cpp b/llvm/lib/Transforms/Utils/DebugSSAUpdater.cpp
index f646fdea90968..c8d51edff9dc7 100644
--- a/llvm/lib/Transforms/Utils/DebugSSAUpdater.cpp
+++ b/llvm/lib/Transforms/Utils/DebugSSAUpdater.cpp
@@ -42,34 +42,23 @@ void DbgSSAPhi::print(raw_ostream &OS) const {
 
 using AvailableValsTy = DenseMap<DbgSSABlock *, DbgValueDef>;
 
-static AvailableValsTy &getAvailableVals(void *AV) {
-  return *static_cast<AvailableValsTy *>(AV);
-}
-
 DebugSSAUpdater::DebugSSAUpdater(SmallVectorImpl<DbgSSAPhi *> *NewPHI)
     : InsertedPHIs(NewPHI) {}
 
-DebugSSAUpdater::~DebugSSAUpdater() {
-  delete static_cast<AvailableValsTy *>(AV);
-}
-
 void DebugSSAUpdater::initialize() {
-  if (!AV)
-    AV = new AvailableValsTy();
-  else
-    getAvailableVals(AV).clear();
+  AV.clear();
 }
 
 bool DebugSSAUpdater::hasValueForBlock(DbgSSABlock *BB) const {
-  return getAvailableVals(AV).count(BB);
+  return AV.count(BB);
 }
 
 DbgValueDef DebugSSAUpdater::findValueForBlock(DbgSSABlock *BB) const {
-  return getAvailableVals(AV).lookup(BB);
+  return AV.lookup(BB);
 }
 
 void DebugSSAUpdater::addAvailableValue(DbgSSABlock *BB, DbgValueDef DV) {
-  getAvailableVals(AV)[BB] = DV;
+  AV[BB] = DV;
 }
 
 DbgValueDef DebugSSAUpdater::getValueAtEndOfBlock(DbgSSABlock *BB) {
@@ -171,8 +160,8 @@ template <> class SSAUpdaterTraits<DebugSSAUpdater> {
   static PHI_iterator PHI_begin(PhiT *PHI) { return PHI_iterator(PHI); }
   static PHI_iterator PHI_end(PhiT *PHI) { return PHI_iterator(PHI, true); }
 
-  /// FindPredecessorBlocks - Put the predecessors of Info->BB into the Preds
-  /// vector, set Info->NumPreds, and allocate space in Info->Preds.
+  /// FindPredecessorBlocks - Put the predecessors of BB into the Preds
+  /// vector.
   static void FindPredecessorBlocks(DbgSSABlock *BB,
                                     SmallVectorImpl<DbgSSABlock *> *Preds) {
     for (auto PredIt = BB->pred_begin(); PredIt != BB->pred_end(); ++PredIt)
@@ -185,8 +174,7 @@ template <> class SSAUpdaterTraits<DebugSSAUpdater> {
     return DbgValueDef();
   }
 
-  /// CreateEmptyPHI - Create a new PHI instruction in the specified block.
-  /// Reserve space for the operands (?) but do not fill them in yet.
+  /// CreateEmptyPHI - Create a new debug PHI entry for the specified block.
   static DbgSSAPhi *CreateEmptyPHI(DbgSSABlock *BB, unsigned NumPreds,
                                    DebugSSAUpdater *Updater) {
     DbgSSAPhi *PHI = BB->newPHI();
@@ -225,11 +213,10 @@ template <> class SSAUpdaterTraits<DebugSSAUpdater> {
 /// return it. If not, construct SSA form by first calculating the required
 /// placement of PHIs and then inserting new PHIs where needed.
 DbgValueDef DebugSSAUpdater::getValueAtEndOfBlockInternal(DbgSSABlock *BB) {
-  AvailableValsTy &AvailableVals = getAvailableVals(AV);
-  if (AvailableVals.contains(BB))
-    return AvailableVals[BB];
+  if (AV.contains(BB))
+    return AV[BB];
 
-  SSAUpdaterImpl<DebugSSAUpdater> Impl(this, &AvailableVals, InsertedPHIs);
+  SSAUpdaterImpl<DebugSSAUpdater> Impl(this, &AV, InsertedPHIs);
   return Impl.GetValue(BB);
 }
 
@@ -263,21 +250,13 @@ void DbgValueRangeTable::addVariable(Function *F, DebugVariableAggregate DVA) {
       for (DbgVariableRecord &DVR : filterDbgVars(I.getDbgRecordRange())) {
         if (DVR.getVariable() == Var &&
             DVR.getDebugLoc().getInlinedAt() == InlinedAt) {
-          assert(!DVR.isDbgAssign() && "No support for #dbg_declare yet.");
+          assert(!DVR.isDbgAssign() && "No support for #dbg_assign yet.");
           if (DVR.isDbgDeclare())
             DeclareRecordFound = true;
           ++NumRecordsFound;
           LastRecordFound = &DVR;
           DbgRecordValues.push_back(&DVR);
         }
-        if (!FoundInstructionInScope && I.getDebugLoc()) {
-          if (I.getDebugLoc().getInlinedAt() == InlinedAt &&
-              isContained(cast<DILocalScope>(I.getDebugLoc().getScope()),
-                          Var->getScope())) {
-            FoundInstructionInScope = true;
-            HasAnyInstructionsInScope.insert(&BB);
-          }
-        }
       }
       if (!FoundInstructionInScope && I.getDebugLoc()) {
         if (I.getDebugLoc().getInlinedAt() == InlinedAt &&
@@ -312,7 +291,8 @@ void DbgValueRangeTable::addVariable(Function *F, DebugVariableAggregate DVA) {
     return;
   }
 
-  // We don't have a single location, so let's have fun with liveness.
+  // We don't have a single location for the variable's entire scope, so instead
+  // we must now perform a liveness analysis to create a location list.
   DenseMap<BasicBlock *, DbgValueDef> LiveInMap;
   SmallVector<DbgSSAPhi *> HypotheticalPHIs;
   DebugSSAUpdater SSAUpdater(&HypotheticalPHIs);
@@ -395,21 +375,17 @@ void DbgValueRangeTable::printValues(DebugVariableAggregate DVA,
   }
 }
 
-uint64_t DbgValueRangeTable::addVariableName(Value *V, uint64_t Size) {
-  uint64_t Key = 0;
-  auto I = VariableMapping.find(V);
-  if (I == VariableMapping.end()) {
-    Key = KeyIndex;
-    VariableMapping.try_emplace(V, Key);
-    std::string &ValueText = VariableNameMapping[Key];
-    raw_string_ostream Stream(ValueText);
-    Stream << " ";
-    V->printAsOperand(Stream, true);
-    KeyIndex += Size;
-  } else {
-    Key = I->second;
-  }
-  LLVM_DEBUG(dbgs() << "Stashing Value: " << Key << " - " << *V << "\n");
-
-  return Key;
+SSAValueNameMap::ValueID SSAValueNameMap::addValue(Value *V) {
+  auto ExistingID = ValueToIDMap.find(V);
+  if (ExistingID != ValueToIDMap.end())
+    return ExistingID->second;
+  // First, get a new ID and Map V to it.
+  ValueID NewID = NextID++;
+  ValueToIDMap.insert({V, NewID});
+  // Then, get the name string for V and map NewID to it.
+  assert(!ValueIDToNameMap.contains(NewID) && "New value ID already maps to a name?");
+  std::string &ValueText = ValueIDToNameMap[NewID];
+  raw_string_ostream Stream(ValueText);
+  V->printAsOperand(Stream, true);
+  return NewID;
 }
diff --git a/llvm/unittests/Transforms/Utils/DebugSSAUpdaterTest.cpp b/llvm/unittests/Transforms/Utils/DebugSSAUpdaterTest.cpp
index 038a06a40242a..9341153ed0690 100644
--- a/llvm/unittests/Transforms/Utils/DebugSSAUpdaterTest.cpp
+++ b/llvm/unittests/Transforms/Utils/DebugSSAUpdaterTest.cpp
@@ -15,7 +15,6 @@
 #include "llvm/IR/Function.h"
 #include "llvm/IR/GlobalValue.h"
 #include "llvm/IR/Module.h"
-#include "llvm/Support/Debug.h"
 #include "llvm/Support/SourceMgr.h"
 #include "gtest/gtest.h"
 

>From 05a23fb63082bfccfe3f22568100d7ed5082a076 Mon Sep 17 00:00:00 2001
From: Stephen Tozer <stephen.tozer at sony.com>
Date: Thu, 21 Aug 2025 18:33:52 +0100
Subject: [PATCH 3/3] clang-format

---
 llvm/include/llvm/Transforms/Utils/DebugSSAUpdater.h | 8 ++++----
 llvm/lib/Transforms/Utils/DebugSSAUpdater.cpp        | 7 +++----
 2 files changed, 7 insertions(+), 8 deletions(-)

diff --git a/llvm/include/llvm/Transforms/Utils/DebugSSAUpdater.h b/llvm/include/llvm/Transforms/Utils/DebugSSAUpdater.h
index 85ed3e240af24..90899c86f5c3b 100644
--- a/llvm/include/llvm/Transforms/Utils/DebugSSAUpdater.h
+++ b/llvm/include/llvm/Transforms/Utils/DebugSSAUpdater.h
@@ -315,15 +315,15 @@ class SSAValueNameMap {
   struct Config : ValueMapConfig<Value *> {
     enum { FollowRAUW = false };
   };
+
 public:
   using ValueID = uint64_t;
   ValueID addValue(Value *V);
-  std::string getName(ValueID ID) {
-    return ValueIDToNameMap[ID];
-  }
+  std::string getName(ValueID ID) { return ValueIDToNameMap[ID]; }
+
 private:
   DenseMap<ValueID, std::string> ValueIDToNameMap;
-  ValueMap<Value*, ValueID, Config> ValueToIDMap;
+  ValueMap<Value *, ValueID, Config> ValueToIDMap;
   ValueID NextID = 0;
 };
 
diff --git a/llvm/lib/Transforms/Utils/DebugSSAUpdater.cpp b/llvm/lib/Transforms/Utils/DebugSSAUpdater.cpp
index c8d51edff9dc7..c0e7609176a83 100644
--- a/llvm/lib/Transforms/Utils/DebugSSAUpdater.cpp
+++ b/llvm/lib/Transforms/Utils/DebugSSAUpdater.cpp
@@ -45,9 +45,7 @@ using AvailableValsTy = DenseMap<DbgSSABlock *, DbgValueDef>;
 DebugSSAUpdater::DebugSSAUpdater(SmallVectorImpl<DbgSSAPhi *> *NewPHI)
     : InsertedPHIs(NewPHI) {}
 
-void DebugSSAUpdater::initialize() {
-  AV.clear();
-}
+void DebugSSAUpdater::initialize() { AV.clear(); }
 
 bool DebugSSAUpdater::hasValueForBlock(DbgSSABlock *BB) const {
   return AV.count(BB);
@@ -383,7 +381,8 @@ SSAValueNameMap::ValueID SSAValueNameMap::addValue(Value *V) {
   ValueID NewID = NextID++;
   ValueToIDMap.insert({V, NewID});
   // Then, get the name string for V and map NewID to it.
-  assert(!ValueIDToNameMap.contains(NewID) && "New value ID already maps to a name?");
+  assert(!ValueIDToNameMap.contains(NewID) &&
+         "New value ID already maps to a name?");
   std::string &ValueText = ValueIDToNameMap[NewID];
   raw_string_ostream Stream(ValueText);
   V->printAsOperand(Stream, true);



More information about the llvm-commits mailing list