diff --git a/llvm/include/llvm/IR/DebugInfoMetadata.h b/llvm/include/llvm/IR/DebugInfoMetadata.h index 02f0a9f677db3..30cbf1daf78ef 100644 --- a/llvm/include/llvm/IR/DebugInfoMetadata.h +++ b/llvm/include/llvm/IR/DebugInfoMetadata.h @@ -4406,6 +4406,7 @@ template <> struct DenseMapInfo { class DebugVariableAggregate : public DebugVariable { public: LLVM_ABI DebugVariableAggregate(const DbgVariableIntrinsic *DVI); + 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 class SmallVectorImpl; +template 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, 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; + /// 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 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 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; + +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 *InsertedPHIs; + + DenseMap BlockMap; + +public: + /// If InsertedPHIs is specified, it will be filled + /// in with all PHI Nodes created by rewriting. + explicit DebugSSAUpdater( + SmallVectorImpl *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> + OrigVariableValueRangeTable; + DenseMap 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 LineMapping; + DenseMap VariableNameMapping; + + using VarMapping = DenseMap; + 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 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 473114b99225b..58c119515798a 100644 --- a/llvm/lib/IR/DebugInfoMetadata.cpp +++ b/llvm/lib/IR/DebugInfoMetadata.cpp @@ -63,6 +63,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, uint64_t AtomGroup, uint8_t AtomRank, ArrayRef MDs, bool ImplicitCode) diff --git a/llvm/lib/Transforms/Utils/CMakeLists.txt b/llvm/lib/Transforms/Utils/CMakeLists.txt index 78cad0d253be8..7e61f47094151 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 DemoteRegToStack.cpp DXILUpgrade.cpp EntryExitInstrumenter.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; + +static AvailableValsTy &getAvailableVals(void *AV) { + return *static_cast(AV); +} + +DebugSSAUpdater::DebugSSAUpdater(SmallVectorImpl *NewPHI) + : InsertedPHIs(NewPHI) {} + +DebugSSAUpdater::~DebugSSAUpdater() { + delete static_cast(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, 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 { +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 *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 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> BlockDbgRecordValues; + DenseSet 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(I.getDebugLoc().getScope()), + Var->getScope())) { + FoundInstructionInScope = true; + HasAnyInstructionsInScope.insert(&BB); + } + } + } + if (!FoundInstructionInScope && I.getDebugLoc()) { + if (I.getDebugLoc().getInlinedAt() == InlinedAt && + isContained(cast(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 LiveInMap; + SmallVector 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 BlockDbgRanges; + BasicBlock::iterator LastIt = BB.begin(); + for (auto *DVR : BlockDbgRecordValues[&BB]) { + // Create a range that ends as of DVR. + BasicBlock::iterator DVRStartIt = + const_cast(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 parseIR(LLVMContext &C, const char *IR) { + SMDiagnostic Err; + std::unique_ptr 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 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( + 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 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( + 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 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( + 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 b16fe19bddfd1..2e3cee8519bb0 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", "DemoteRegToStack.cpp", "EntryExitInstrumenter.cpp", "EscapeEnumerator.cpp",