-
Notifications
You must be signed in to change notification settings - Fork 15k
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
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 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>; | ||
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; | ||
jmorse marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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 { | ||
jmorse marked this conversation as resolved.
Show resolved
Hide resolved
|
||
DenseMap<DebugVariableAggregate, SmallVector<DbgRangeEntry>> | ||
OrigVariableValueRangeTable; | ||
jmorse marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.