[llvm] 3946c50 - Add DebugSSAUpdater class to track debug value liveness (#135349)

via llvm-commits llvm-commits at lists.llvm.org
Tue Sep 16 03:22:05 PDT 2025


Author: Stephen Tozer
Date: 2025-09-16T11:22:02+01:00
New Revision: 3946c5061d708b1e7604db63e9b604cb0fce7dd6

URL: https://github.com/llvm/llvm-project/commit/3946c5061d708b1e7604db63e9b604cb0fce7dd6
DIFF: https://github.com/llvm/llvm-project/commit/3946c5061d708b1e7604db63e9b604cb0fce7dd6.diff

LOG: Add DebugSSAUpdater class to track debug value liveness (#135349)

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.

Added: 
    llvm/include/llvm/Transforms/Utils/DebugSSAUpdater.h
    llvm/lib/Transforms/Utils/DebugSSAUpdater.cpp
    llvm/unittests/Transforms/Utils/DebugSSAUpdaterTest.cpp

Modified: 
    llvm/include/llvm/IR/DebugInfoMetadata.h
    llvm/lib/IR/DebugInfoMetadata.cpp
    llvm/lib/Transforms/Utils/CMakeLists.txt
    llvm/unittests/Transforms/Utils/CMakeLists.txt
    llvm/utils/gn/secondary/llvm/lib/Transforms/Utils/BUILD.gn

Removed: 
    


################################################################################
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..90899c86f5c3b
--- /dev/null
+++ b/llvm/include/llvm/Transforms/Utils/DebugSSAUpdater.h
@@ -0,0 +1,360 @@
+//===- 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.
+//
+// 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 {
+
+////////////////////////////////////////
+// SSAUpdater specialization classes
+
+class DbgSSAPhi;
+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 
diff erent
+/// 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>;
+  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.
+  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;
+
+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;
+
+  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;
+};
+
+/// 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;
+
+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 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..c0e7609176a83
--- /dev/null
+++ b/llvm/lib/Transforms/Utils/DebugSSAUpdater.cpp
@@ -0,0 +1,390 @@
+//===- 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>;
+
+DebugSSAUpdater::DebugSSAUpdater(SmallVectorImpl<DbgSSAPhi *> *NewPHI)
+    : InsertedPHIs(NewPHI) {}
+
+void DebugSSAUpdater::initialize() { AV.clear(); }
+
+bool DebugSSAUpdater::hasValueForBlock(DbgSSABlock *BB) const {
+  return AV.count(BB);
+}
+
+DbgValueDef DebugSSAUpdater::findValueForBlock(DbgSSABlock *BB) const {
+  return AV.lookup(BB);
+}
+
+void DebugSSAUpdater::addAvailableValue(DbgSSABlock *BB, DbgValueDef DV) {
+  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 BB into the Preds
+  /// vector.
+  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 debug PHI entry for the specified block.
+  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) {
+  if (AV.contains(BB))
+    return AV[BB];
+
+  SSAUpdaterImpl<DebugSSAUpdater> Impl(this, &AV, 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_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);
+        }
+      }
+    }
+    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 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);
+  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";
+  }
+}
+
+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/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..cc20aec580591
--- /dev/null
+++ b/llvm/unittests/Transforms/Utils/DebugSSAUpdaterTest.cpp
@@ -0,0 +1,219 @@
+//===- 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/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(), 5u);
+  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(), 3u);
+  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(), 0u);
+}
+} // 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",


        


More information about the llvm-commits mailing list