Skip to content

Add DebugSSAUpdater class to track debug value liveness #135349

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions llvm/include/llvm/IR/DebugInfoMetadata.h
Original file line number Diff line number Diff line change
Expand Up @@ -4349,6 +4349,7 @@ template <> struct DenseMapInfo<DebugVariable> {
class DebugVariableAggregate : public DebugVariable {
public:
DebugVariableAggregate(const DbgVariableIntrinsic *DVI);
DebugVariableAggregate(const DbgVariableRecord *DVR);
DebugVariableAggregate(const DebugVariable &V)
: DebugVariable(V.getVariable(), std::nullopt, V.getInlinedAt()) {}
};
Expand Down
351 changes: 351 additions & 0 deletions llvm/include/llvm/Transforms/Utils/DebugSSAUpdater.h
Original file line number Diff line number Diff line change
@@ -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.
//
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like we should add a rider/warning about how "If you're using this in an optimisation pass you're doing it wrong". i.e., computing the dominance frontiers of the debug variables is expensive and shouldn't be done lightly. Adding such a warning will discourage people from submitting patches to optimisations using DebugSSAUpdater that we then have to reject on the basis of the compile-time cost.

//===----------------------------------------------------------------------===//

#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;
Comment on lines +33 to +34
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How come the fwd-decl is needed instead of just including SmallVector.h?


/// 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;
Comment on lines +213 to +215
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're deliberately skipping type-checking by this being a void pointer, with some casting happening in the .cpp implementation -- why's this necessary?


SmallVectorImpl<DbgSSAPhi *> *InsertedPHIs;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wants a docu-comment as a pointer-to-a-vector is moderately abnormal


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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wants a docu-comment as to the purpose

DenseMap<DebugVariableAggregate, SmallVector<DbgRangeEntry>>
OrigVariableValueRangeTable;
Comment on lines +302 to +303
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A map containing a vector will be large; this might be alright as it's not designed to happen during optimisation passes.

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;
Comment on lines +313 to +315
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the uint64_t is a key of somethingorother -- any chance of using a typedef of the uint64_t instead? It will help to visually distinguish it from the other user of uint64_t here, the line-number-mapping.


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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd much prefer returning the UINT64_MAX macro-value, makes it clear what's going on.

}

uint64_t addVariableName(Value *V, uint64_t Size);
std::string getVariableName(uint64_t Key) {
assert(VariableNameMapping.contains(Key) && "Why not here?");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assertion-failure comment (sadly) probably wants to be more descriptive.

return VariableNameMapping[Key];
}

void printValues(DebugVariableAggregate DVA, raw_ostream &OS);
};

} // end namespace llvm

#endif // LLVM_TRANSFORMS_UTILS_DEBUGSSAUPDATER_H
4 changes: 4 additions & 0 deletions llvm/lib/IR/DebugInfoMetadata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ DebugVariableAggregate::DebugVariableAggregate(const DbgVariableIntrinsic *DVI)
: DebugVariable(DVI->getVariable(), std::nullopt,
DVI->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, ArrayRef<Metadata *> MDs,
bool ImplicitCode)
Expand Down
1 change: 1 addition & 0 deletions llvm/lib/Transforms/Utils/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ add_llvm_component_library(LLVMTransformUtils
CtorUtils.cpp
CountVisits.cpp
Debugify.cpp
DebugSSAUpdater.cpp
DemoteRegToStack.cpp
DXILUpgrade.cpp
EntryExitInstrumenter.cpp
Expand Down
Loading
Loading