From 1d1de7467c32d52926ca56b9167a2c65c451ecfa Mon Sep 17 00:00:00 2001 From: OCHyams Date: Fri, 9 Dec 2022 15:43:56 +0000 Subject: [PATCH] [Assignment Tracking][Analysis] Add analysis pass The Assignment Tracking debug-info feature is outlined in this RFC: https://discourse.llvm.org/t/ rfc-assignment-tracking-a-better-way-of-specifying-variable-locations-in-ir Add initial revision of assignment tracking analysis pass --------------------------------------------------------- This patch squashes five individually reviewed patches into one: #1 https://reviews.llvm.org/D136320 #2 https://reviews.llvm.org/D136321 #3 https://reviews.llvm.org/D136325 #4 https://reviews.llvm.org/D136331 #5 https://reviews.llvm.org/D136335 Patch #1 introduces 2 new files: AssignmentTrackingAnalysis.h and .cpp. The two subsequent patches modify those files only. Patch #4 plumbs the analysis into SelectionDAG, and patch #5 is a collection of tests for the analysis as a whole. The analysis was broken up into smaller chunks for review purposes but for the most part the tests were written using the whole analysis. It would be possible to break up the tests for patches #1 through #3 for the purpose of landing the patches seperately. However, most them would require an update for each patch. In addition, patch #4 - which connects the analysis to SelectionDAG - is required by all of the tests. If there is build-bot trouble, we might try a different landing sequence. Analysis problem and goal ------------------------- Variables values can be stored in memory, or available as SSA values, or both. Using the Assignment Tracking metadata, it's not possible to determine a variable location just by looking at a debug intrinsic in isolation. Instructions without any metadata can change the location of a variable. The meaning of dbg.assign intrinsics changes depending on whether there are linked instructions, and where they are relative to those instructions. So we need to analyse the IR and convert the embedded information into a form that SelectionDAG can consume to produce debug variable locations in MIR. The solution is a dataflow analysis which, aiming to maximise the memory location coverage for variables, outputs a mapping of instruction positions to variable location definitions. API usage --------- The analysis is named `AssignmentTrackingAnalysis`. It is added as a required pass for SelectionDAGISel when assignment tracking is enabled. The results of the analysis are exposed via `getResults` using the returned `const FunctionVarLocs *`'s const methods: const VarLocInfo *single_locs_begin() const; const VarLocInfo *single_locs_end() const; const VarLocInfo *locs_begin(const Instruction *Before) const; const VarLocInfo *locs_end(const Instruction *Before) const; void print(raw_ostream &OS, const Function &Fn) const; Debug intrinsics can be ignored after running the analysis. Instead, variable location definitions that occur between an instruction `Inst` and its predecessor (or block start) can be found by looping over the range: locs_begin(Inst), locs_end(Inst) Similarly, variables with a memory location that is valid for their lifetime can be iterated over using the range: single_locs_begin(), single_locs_end() Further detail -------------- For an explanation of the dataflow implementation and the integration with SelectionDAG, please see the reviews linked at the top of this commit message. Reviewed By: jmorse --- .../llvm/CodeGen/AssignmentTrackingAnalysis.h | 117 + llvm/include/llvm/CodeGen/SelectionDAG.h | 9 +- llvm/include/llvm/InitializePasses.h | 1 + llvm/lib/CodeGen/AssignmentTrackingAnalysis.cpp | 2418 ++++++++++++++++++++ llvm/lib/CodeGen/CMakeLists.txt | 1 + llvm/lib/CodeGen/CodeGen.cpp | 1 + llvm/lib/CodeGen/SelectionDAG/SelectionDAG.cpp | 11 +- .../CodeGen/SelectionDAG/SelectionDAGBuilder.cpp | 55 +- .../lib/CodeGen/SelectionDAG/SelectionDAGBuilder.h | 69 +- llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp | 120 +- llvm/test/DebugInfo/assignment-tracking/X86/DSE.ll | 72 + .../X86/dbg-phi-produces-undef.ll | 106 + .../DebugInfo/assignment-tracking/X86/diamond-1.ll | 123 + .../DebugInfo/assignment-tracking/X86/diamond-2.ll | 111 + .../DebugInfo/assignment-tracking/X86/diamond-3.ll | 117 + .../assignment-tracking/X86/lit.local.cfg | 2 + .../assignment-tracking/X86/loop-hoist.ll | 118 + .../DebugInfo/assignment-tracking/X86/loop-sink.ll | 114 + .../assignment-tracking/X86/loop-unroll.ll | 88 + .../X86/lower-offset-expression.ll | 68 + .../assignment-tracking/X86/lower-to-value.ll | 114 + .../X86/mem-loc-frag-fill-cfg.ll | 147 ++ .../assignment-tracking/X86/mem-loc-frag-fill.ll | 109 + .../assignment-tracking/X86/nested-loop-frags.ll | 331 +++ .../assignment-tracking/X86/nested-loop-sroa.ll | 403 ++++ .../assignment-tracking/X86/nested-loop.ll | 398 ++++ .../X86/no-redundant-def-after-alloca.ll | 48 + .../assignment-tracking/X86/order-of-defs.ll | 59 + .../remove-redundant-defs-to-prevent-reordering.ll | 108 + .../X86/remove-undef-fragment.ll | 106 + .../X86/sdag-dangling-dbgassign.ll | 218 ++ .../X86/sdag-ir-salvage-assign.ll | 72 + .../X86/sdag-transfer-dbgassign.ll | 77 + .../X86/single-memory-location-2.ll | 111 + .../X86/single-memory-location.ll | 80 + .../assignment-tracking/X86/split-alloca.ll | 58 + .../assignment-tracking/X86/untagged-store-frag.ll | 82 + .../X86/use-known-value-at-early-mem-def-2.ll | 93 + .../X86/use-known-value-at-early-mem-def.ll | 83 + 39 files changed, 6342 insertions(+), 76 deletions(-) create mode 100644 llvm/include/llvm/CodeGen/AssignmentTrackingAnalysis.h create mode 100644 llvm/lib/CodeGen/AssignmentTrackingAnalysis.cpp create mode 100644 llvm/test/DebugInfo/assignment-tracking/X86/DSE.ll create mode 100644 llvm/test/DebugInfo/assignment-tracking/X86/dbg-phi-produces-undef.ll create mode 100644 llvm/test/DebugInfo/assignment-tracking/X86/diamond-1.ll create mode 100644 llvm/test/DebugInfo/assignment-tracking/X86/diamond-2.ll create mode 100644 llvm/test/DebugInfo/assignment-tracking/X86/diamond-3.ll create mode 100644 llvm/test/DebugInfo/assignment-tracking/X86/lit.local.cfg create mode 100644 llvm/test/DebugInfo/assignment-tracking/X86/loop-hoist.ll create mode 100644 llvm/test/DebugInfo/assignment-tracking/X86/loop-sink.ll create mode 100644 llvm/test/DebugInfo/assignment-tracking/X86/loop-unroll.ll create mode 100644 llvm/test/DebugInfo/assignment-tracking/X86/lower-offset-expression.ll create mode 100644 llvm/test/DebugInfo/assignment-tracking/X86/lower-to-value.ll create mode 100644 llvm/test/DebugInfo/assignment-tracking/X86/mem-loc-frag-fill-cfg.ll create mode 100644 llvm/test/DebugInfo/assignment-tracking/X86/mem-loc-frag-fill.ll create mode 100644 llvm/test/DebugInfo/assignment-tracking/X86/nested-loop-frags.ll create mode 100644 llvm/test/DebugInfo/assignment-tracking/X86/nested-loop-sroa.ll create mode 100644 llvm/test/DebugInfo/assignment-tracking/X86/nested-loop.ll create mode 100644 llvm/test/DebugInfo/assignment-tracking/X86/no-redundant-def-after-alloca.ll create mode 100644 llvm/test/DebugInfo/assignment-tracking/X86/order-of-defs.ll create mode 100644 llvm/test/DebugInfo/assignment-tracking/X86/remove-redundant-defs-to-prevent-reordering.ll create mode 100644 llvm/test/DebugInfo/assignment-tracking/X86/remove-undef-fragment.ll create mode 100644 llvm/test/DebugInfo/assignment-tracking/X86/sdag-dangling-dbgassign.ll create mode 100644 llvm/test/DebugInfo/assignment-tracking/X86/sdag-ir-salvage-assign.ll create mode 100644 llvm/test/DebugInfo/assignment-tracking/X86/sdag-transfer-dbgassign.ll create mode 100644 llvm/test/DebugInfo/assignment-tracking/X86/single-memory-location-2.ll create mode 100644 llvm/test/DebugInfo/assignment-tracking/X86/single-memory-location.ll create mode 100644 llvm/test/DebugInfo/assignment-tracking/X86/split-alloca.ll create mode 100644 llvm/test/DebugInfo/assignment-tracking/X86/untagged-store-frag.ll create mode 100644 llvm/test/DebugInfo/assignment-tracking/X86/use-known-value-at-early-mem-def-2.ll create mode 100644 llvm/test/DebugInfo/assignment-tracking/X86/use-known-value-at-early-mem-def.ll diff --git a/llvm/include/llvm/CodeGen/AssignmentTrackingAnalysis.h b/llvm/include/llvm/CodeGen/AssignmentTrackingAnalysis.h new file mode 100644 index 0000000..6e82b2b --- /dev/null +++ b/llvm/include/llvm/CodeGen/AssignmentTrackingAnalysis.h @@ -0,0 +1,117 @@ +#ifndef LLVM_CODEGEN_ASSIGNMENTTRACKINGANALYSIS_H +#define LLVM_CODEGEN_ASSIGNMENTTRACKINGANALYSIS_H + +#include "llvm/IR/DebugInfoMetadata.h" +#include "llvm/IR/DebugLoc.h" +#include "llvm/Pass.h" + +namespace llvm { +class Function; +class Instruction; +class Value; +class raw_ostream; +} // namespace llvm +class FunctionVarLocsBuilder; + +namespace llvm { +/// Type wrapper for integer ID for Variables. 0 is reserved. +enum class VariableID : unsigned { Reserved = 0 }; +/// Variable location definition used by FunctionVarLocs. +struct VarLocInfo { + llvm::VariableID VariableID; + DIExpression *Expr = nullptr; + DebugLoc DL; + Value *V = nullptr; // TODO: Needs to be value_s_ for variadic expressions. +}; + +/// Data structure describing the variable locations in a function. Used as the +/// result of the AssignmentTrackingAnalysis pass. Essentially read-only +/// outside of AssignmentTrackingAnalysis where it is built. +class FunctionVarLocs { + /// Maps VarLocInfo.VariableID to a DebugVariable for VarLocRecords. + SmallVector Variables; + /// List of variable location changes grouped by the instruction the + /// change occurs before (see VarLocsBeforeInst). The elements from + /// zero to SingleVarLocEnd represent variables with a single location. + SmallVector VarLocRecords; + /// End of range of VarLocRecords that represent variables with a single + /// location that is valid for the entire scope. Range starts at 0. + unsigned SingleVarLocEnd = 0; + /// Maps an instruction to a range of VarLocs that start just before it. + DenseMap> + VarLocsBeforeInst; + +public: + /// Return the DILocalVariable for the location definition represented by \p + /// ID. + DILocalVariable *getDILocalVariable(const VarLocInfo *Loc) const { + VariableID VarID = Loc->VariableID; + return getDILocalVariable(VarID); + } + /// Return the DILocalVariable of the variable represented by \p ID. + DILocalVariable *getDILocalVariable(VariableID ID) const { + return const_cast(getVariable(ID).getVariable()); + } + /// Return the DebugVariable represented by \p ID. + const DebugVariable &getVariable(VariableID ID) const { + return Variables[static_cast(ID)]; + } + + ///@name iterators + ///@{ + /// First single-location variable location definition. + const VarLocInfo *single_locs_begin() const { return VarLocRecords.begin(); } + /// One past the last single-location variable location definition. + const VarLocInfo *single_locs_end() const { + const auto *It = VarLocRecords.begin(); + std::advance(It, SingleVarLocEnd); + return It; + } + /// First variable location definition that comes before \p Before. + const VarLocInfo *locs_begin(const Instruction *Before) const { + auto Span = VarLocsBeforeInst.lookup(Before); + const auto *It = VarLocRecords.begin(); + std::advance(It, Span.first); + return It; + } + /// One past the last variable location definition that comes before \p + /// Before. + const VarLocInfo *locs_end(const Instruction *Before) const { + auto Span = VarLocsBeforeInst.lookup(Before); + const auto *It = VarLocRecords.begin(); + std::advance(It, Span.second); + return It; + } + ///@} + + void print(raw_ostream &OS, const Function &Fn) const; + + ///@{ + /// Non-const methods used by AssignmentTrackingAnalysis (which invalidate + /// analysis results if called incorrectly). + void init(FunctionVarLocsBuilder &Builder); + void clear(); + ///@} +}; + +class AssignmentTrackingAnalysis : public FunctionPass { + std::unique_ptr Results; + +public: + static char ID; + + AssignmentTrackingAnalysis(); + + bool runOnFunction(Function &F) override; + + static bool isRequired() { return true; } + + void getAnalysisUsage(AnalysisUsage &AU) const override { + AU.setPreservesAll(); + } + + const FunctionVarLocs *getResults() { return Results.get(); } +}; + +} // end namespace llvm +#endif // LLVM_CODEGEN_ASSIGNMENTTRACKINGANALYSIS_H diff --git a/llvm/include/llvm/CodeGen/SelectionDAG.h b/llvm/include/llvm/CodeGen/SelectionDAG.h index 2af9fe4..bdc4b56 100644 --- a/llvm/include/llvm/CodeGen/SelectionDAG.h +++ b/llvm/include/llvm/CodeGen/SelectionDAG.h @@ -68,6 +68,7 @@ class ConstantInt; class DataLayout; struct fltSemantics; class FunctionLoweringInfo; +class FunctionVarLocs; class GlobalValue; struct KnownBits; class LegacyDivergenceAnalysis; @@ -222,6 +223,7 @@ class SelectionDAG { const SelectionDAGTargetInfo *TSI = nullptr; const TargetLowering *TLI = nullptr; const TargetLibraryInfo *LibInfo = nullptr; + const FunctionVarLocs *FnVarLocs = nullptr; MachineFunction *MF; Pass *SDAGISelPass = nullptr; LLVMContext *Context; @@ -452,8 +454,8 @@ public: /// Prepare this SelectionDAG to process code in the given MachineFunction. void init(MachineFunction &NewMF, OptimizationRemarkEmitter &NewORE, Pass *PassPtr, const TargetLibraryInfo *LibraryInfo, - LegacyDivergenceAnalysis * Divergence, - ProfileSummaryInfo *PSIin, BlockFrequencyInfo *BFIin); + LegacyDivergenceAnalysis *Divergence, ProfileSummaryInfo *PSIin, + BlockFrequencyInfo *BFIin, FunctionVarLocs const *FnVarLocs); void setFunctionLoweringInfo(FunctionLoweringInfo * FuncInfo) { FLI = FuncInfo; @@ -476,6 +478,9 @@ public: const TargetLibraryInfo &getLibInfo() const { return *LibInfo; } const SelectionDAGTargetInfo &getSelectionDAGInfo() const { return *TSI; } const LegacyDivergenceAnalysis *getDivergenceAnalysis() const { return DA; } + /// Returns the result of the AssignmentTrackingAnalysis pass if it's + /// available, otherwise return nullptr. + const FunctionVarLocs *getFunctionVarLocs() const { return FnVarLocs; } LLVMContext *getContext() const { return Context; } OptimizationRemarkEmitter &getORE() const { return *ORE; } ProfileSummaryInfo *getPSI() const { return PSI; } diff --git a/llvm/include/llvm/InitializePasses.h b/llvm/include/llvm/InitializePasses.h index ad5641b..a009183 100644 --- a/llvm/include/llvm/InitializePasses.h +++ b/llvm/include/llvm/InitializePasses.h @@ -54,6 +54,7 @@ void initializeADCELegacyPassPass(PassRegistry&); void initializeAddDiscriminatorsLegacyPassPass(PassRegistry&); void initializeAlignmentFromAssumptionsPass(PassRegistry&); void initializeAlwaysInlinerLegacyPassPass(PassRegistry&); +void initializeAssignmentTrackingAnalysisPass(PassRegistry &); void initializeAssumeSimplifyPassLegacyPassPass(PassRegistry &); void initializeAssumeBuilderPassLegacyPassPass(PassRegistry &); void initializeAnnotation2MetadataLegacyPass(PassRegistry &); diff --git a/llvm/lib/CodeGen/AssignmentTrackingAnalysis.cpp b/llvm/lib/CodeGen/AssignmentTrackingAnalysis.cpp new file mode 100644 index 0000000..6e12876 --- /dev/null +++ b/llvm/lib/CodeGen/AssignmentTrackingAnalysis.cpp @@ -0,0 +1,2418 @@ +#include "llvm/CodeGen/AssignmentTrackingAnalysis.h" +#include "llvm/ADT/DenseMapInfo.h" +#include "llvm/ADT/IntervalMap.h" +#include "llvm/ADT/PostOrderIterator.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallSet.h" +#include "llvm/ADT/Statistic.h" +#include "llvm/ADT/UniqueVector.h" +#include "llvm/Analysis/Interval.h" +#include "llvm/BinaryFormat/Dwarf.h" +#include "llvm/IR/BasicBlock.h" +#include "llvm/IR/DataLayout.h" +#include "llvm/IR/DebugInfo.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/Instruction.h" +#include "llvm/IR/IntrinsicInst.h" +#include "llvm/IR/PassManager.h" +#include "llvm/IR/PrintPasses.h" +#include "llvm/InitializePasses.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/Transforms/Utils/BasicBlockUtils.h" +#include +#include +#include +#include + +using namespace llvm; +#define DEBUG_TYPE "debug-ata" + +STATISTIC(NumDefsScanned, "Number of dbg locs that get scanned for removal"); +STATISTIC(NumDefsRemoved, "Number of dbg locs removed"); +STATISTIC(NumWedgesScanned, "Number of dbg wedges scanned"); +STATISTIC(NumWedgesChanged, "Number of dbg wedges changed"); + +static cl::opt + MaxNumBlocks("debug-ata-max-blocks", cl::init(10000), + cl::desc("Maximum num basic blocks before debug info dropped"), + cl::Hidden); +/// Option for debugging the pass, determines if the memory location fragment +/// filling happens after generating the variable locations. +static cl::opt EnableMemLocFragFill("mem-loc-frag-fill", cl::init(true), + cl::Hidden); +/// Print the results of the analysis. Respects -filter-print-funcs. +static cl::opt PrintResults("print-debug-ata", cl::init(false), + cl::Hidden); + +// Implicit conversions are disabled for enum class types, so unfortunately we +// need to create a DenseMapInfo wrapper around the specified underlying type. +template <> struct llvm::DenseMapInfo { + using Wrapped = DenseMapInfo; + static inline VariableID getEmptyKey() { + return static_cast(Wrapped::getEmptyKey()); + } + static inline VariableID getTombstoneKey() { + return static_cast(Wrapped::getTombstoneKey()); + } + static unsigned getHashValue(const VariableID &Val) { + return Wrapped::getHashValue(static_cast(Val)); + } + static bool isEqual(const VariableID &LHS, const VariableID &RHS) { + return LHS == RHS; + } +}; + +/// Helper class to build FunctionVarLocs, since that class isn't easy to +/// modify. TODO: There's not a great deal of value in the split, it could be +/// worth merging the two classes. +class FunctionVarLocsBuilder { + friend FunctionVarLocs; + UniqueVector Variables; + // Use an unordered_map so we don't invalidate iterators after + // insert/modifications. + std::unordered_map> + VarLocsBeforeInst; + + SmallVector SingleLocVars; + +public: + /// Find or insert \p V and return the ID. + VariableID insertVariable(DebugVariable V) { + return static_cast(Variables.insert(V)); + } + + /// Get a variable from its \p ID. + const DebugVariable &getVariable(VariableID ID) const { + return Variables[static_cast(ID)]; + } + + /// Return ptr to wedge of defs or nullptr if no defs come just before /p + /// Before. + const SmallVectorImpl *getWedge(const Instruction *Before) const { + auto R = VarLocsBeforeInst.find(Before); + if (R == VarLocsBeforeInst.end()) + return nullptr; + return &R->second; + } + + /// Replace the defs that come just before /p Before with /p Wedge. + void setWedge(const Instruction *Before, SmallVector &&Wedge) { + VarLocsBeforeInst[Before] = std::move(Wedge); + } + + /// Add a def for a variable that is valid for its lifetime. + void addSingleLocVar(DebugVariable Var, DIExpression *Expr, DebugLoc DL, + Value *V) { + VarLocInfo VarLoc; + VarLoc.VariableID = insertVariable(Var); + VarLoc.Expr = Expr; + VarLoc.DL = DL; + VarLoc.V = V; + SingleLocVars.emplace_back(VarLoc); + } + + /// Add a def to the wedge of defs just before /p Before. + void addVarLoc(Instruction *Before, DebugVariable Var, DIExpression *Expr, + DebugLoc DL, Value *V) { + VarLocInfo VarLoc; + VarLoc.VariableID = insertVariable(Var); + VarLoc.Expr = Expr; + VarLoc.DL = DL; + VarLoc.V = V; + VarLocsBeforeInst[Before].emplace_back(VarLoc); + } +}; + +void FunctionVarLocs::print(raw_ostream &OS, const Function &Fn) const { + // Print the variable table first. TODO: Sorting by variable could make the + // output more stable? + unsigned Counter = -1; + OS << "=== Variables ===\n"; + for (const DebugVariable &V : Variables) { + ++Counter; + // Skip first entry because it is a dummy entry. + if (Counter == 0) { + continue; + } + OS << "[" << Counter << "] " << V.getVariable()->getName(); + if (auto F = V.getFragment()) + OS << " bits [" << F->OffsetInBits << ", " + << F->OffsetInBits + F->SizeInBits << ")"; + if (const auto *IA = V.getInlinedAt()) + OS << " inlined-at " << *IA; + OS << "\n"; + } + + auto PrintLoc = [&OS](const VarLocInfo &Loc) { + OS << "DEF Var=[" << (unsigned)Loc.VariableID << "]" + << " Expr=" << *Loc.Expr << " V=" << *Loc.V << "\n"; + }; + + // Print the single location variables. + OS << "=== Single location vars ===\n"; + for (auto It = single_locs_begin(), End = single_locs_end(); It != End; + ++It) { + PrintLoc(*It); + } + + // Print the non-single-location defs in line with IR. + OS << "=== In-line variable defs ==="; + for (const BasicBlock &BB : Fn) { + OS << "\n" << BB.getName() << ":\n"; + for (const Instruction &I : BB) { + for (auto It = locs_begin(&I), End = locs_end(&I); It != End; ++It) { + PrintLoc(*It); + } + OS << I << "\n"; + } + } +} + +void FunctionVarLocs::init(FunctionVarLocsBuilder &Builder) { + // Add the single-location variables first. + for (const auto &VarLoc : Builder.SingleLocVars) + VarLocRecords.emplace_back(VarLoc); + // Mark the end of the section. + SingleVarLocEnd = VarLocRecords.size(); + + // Insert a contiguous block of VarLocInfos for each instruction, mapping it + // to the start and end position in the vector with VarLocsBeforeInst. + for (auto &P : Builder.VarLocsBeforeInst) { + unsigned BlockStart = VarLocRecords.size(); + for (const VarLocInfo &VarLoc : P.second) + VarLocRecords.emplace_back(VarLoc); + unsigned BlockEnd = VarLocRecords.size(); + // Record the start and end indices. + if (BlockEnd != BlockStart) + VarLocsBeforeInst[P.first] = {BlockStart, BlockEnd}; + } + + // Copy the Variables vector from the builder's UniqueVector. + assert(Variables.empty() && "Expect clear before init"); + // UniqueVectors IDs are one-based (which means the VarLocInfo VarID values + // are one-based) so reserve an extra and insert a dummy. + Variables.reserve(Builder.Variables.size() + 1); + Variables.push_back(DebugVariable(nullptr, None, nullptr)); + Variables.append(Builder.Variables.begin(), Builder.Variables.end()); +} + +void FunctionVarLocs::clear() { + Variables.clear(); + VarLocRecords.clear(); + VarLocsBeforeInst.clear(); + SingleVarLocEnd = 0; +} + +/// Walk backwards along constant GEPs and bitcasts to the base storage from \p +/// Start as far as possible. Prepend \Expression with the offset and append it +/// with a DW_OP_deref that haes been implicit until now. Returns the walked-to +/// value and modified expression. +static std::pair +walkToAllocaAndPrependOffsetDeref(const DataLayout &DL, Value *Start, + DIExpression *Expression) { + APInt OffsetInBytes(DL.getTypeSizeInBits(Start->getType()), false); + Value *End = + Start->stripAndAccumulateInBoundsConstantOffsets(DL, OffsetInBytes); + SmallVector Ops; + if (OffsetInBytes.getBoolValue()) { + Ops = {dwarf::DW_OP_plus_uconst, OffsetInBytes.getZExtValue()}; + Expression = DIExpression::prependOpcodes( + Expression, Ops, /*StackValue=*/false, /*EntryValue=*/false); + } + Expression = DIExpression::append(Expression, {dwarf::DW_OP_deref}); + return {End, Expression}; +} + +/// Extract the offset used in \p DIExpr. Returns None if the expression +/// doesn't explicitly describe a memory location with DW_OP_deref or if the +/// expression is too complex to interpret. +static Optional getDerefOffsetInBytes(const DIExpression *DIExpr) { + int64_t Offset = 0; + const unsigned NumElements = DIExpr->getNumElements(); + const auto Elements = DIExpr->getElements(); + unsigned NextElement = 0; + // Extract the offset. + if (NumElements > 2 && Elements[0] == dwarf::DW_OP_plus_uconst) { + Offset = Elements[1]; + NextElement = 2; + } else if (NumElements > 3 && Elements[0] == dwarf::DW_OP_constu) { + NextElement = 3; + if (Elements[2] == dwarf::DW_OP_plus) + Offset = Elements[1]; + else if (Elements[2] == dwarf::DW_OP_minus) + Offset = -Elements[1]; + else + return None; + } + + // If that's all there is it means there's no deref. + if (NextElement >= NumElements) + return None; + + // Check the next element is DW_OP_deref - otherwise this is too complex or + // isn't a deref expression. + if (Elements[NextElement] != dwarf::DW_OP_deref) + return None; + + // Check the final operation is either the DW_OP_deref or is a fragment. + if (NumElements == NextElement + 1) + return Offset; // Ends with deref. + else if (NumElements == NextElement + 3 && + Elements[NextElement] == dwarf::DW_OP_LLVM_fragment) + return Offset; // Ends with deref + fragment. + + // Don't bother trying to interpret anything more complex. + return None; +} + +/// A whole (unfragmented) source variable. +using DebugAggregate = std::pair; +static DebugAggregate getAggregate(const DbgVariableIntrinsic *DII) { + return DebugAggregate(DII->getVariable(), DII->getDebugLoc().getInlinedAt()); +} +static DebugAggregate getAggregate(const DebugVariable &Var) { + return DebugAggregate(Var.getVariable(), Var.getInlinedAt()); +} + +/// In dwarf emission, the following sequence +/// 1. dbg.value ... Fragment(0, 64) +/// 2. dbg.value ... Fragment(0, 32) +/// effectively sets Fragment(32, 32) to undef (each def sets all bits not in +/// the intersection of the fragments to having "no location"). This makes +/// sense for implicit location values because splitting the computed values +/// could be troublesome, and is probably quite uncommon. When we convert +/// dbg.assigns to dbg.value+deref this kind of thing is common, and describing +/// a location (memory) rather than a value means we don't need to worry about +/// splitting any values, so we try to recover the rest of the fragment +/// location here. +/// This class performs a(nother) dataflow analysis over the function, adding +/// variable locations so that any bits of a variable with a memory location +/// have that location explicitly reinstated at each subsequent variable +/// location definition that that doesn't overwrite those bits. i.e. after a +/// variable location def, insert new defs for the memory location with +/// fragments for the difference of "all bits currently in memory" and "the +/// fragment of the second def". +class MemLocFragmentFill { + Function &Fn; + FunctionVarLocsBuilder *FnVarLocs; + const DenseSet *VarsWithStackSlot; + + // 0 = no memory location. + using BaseAddress = unsigned; + using OffsetInBitsTy = unsigned; + using FragTraits = IntervalMapHalfOpenInfo; + using FragsInMemMap = IntervalMap< + OffsetInBitsTy, BaseAddress, + IntervalMapImpl::NodeSizer::LeafSize, + FragTraits>; + FragsInMemMap::Allocator IntervalMapAlloc; + using VarFragMap = DenseMap; + + /// IDs for memory location base addresses in maps. Use 0 to indicate that + /// there's no memory location. + UniqueVector Bases; + UniqueVector Aggregates; + DenseMap LiveIn; + DenseMap LiveOut; + + struct FragMemLoc { + unsigned Var; + unsigned Base; + unsigned OffsetInBits; + unsigned SizeInBits; + DebugLoc DL; + }; + using InsertMap = MapVector>; + + /// BBInsertBeforeMap holds a description for the set of location defs to be + /// inserted after the analysis is complete. It is updated during the dataflow + /// and the entry for a block is CLEARED each time it is (re-)visited. After + /// the dataflow is complete, each block entry will contain the set of defs + /// calculated during the final (fixed-point) iteration. + DenseMap BBInsertBeforeMap; + + static bool intervalMapsAreEqual(const FragsInMemMap &A, + const FragsInMemMap &B) { + auto AIt = A.begin(), AEnd = A.end(); + auto BIt = B.begin(), BEnd = B.end(); + for (; AIt != AEnd; ++AIt, ++BIt) { + if (BIt == BEnd) + return false; // B has fewer elements than A. + if (AIt.start() != BIt.start() || AIt.stop() != BIt.stop()) + return false; // Interval is different. + if (AIt.value() != BIt.value()) + return false; // Value at interval is different. + } + // AIt == AEnd. Check BIt is also now at end. + return BIt == BEnd; + } + + static bool varFragMapsAreEqual(const VarFragMap &A, const VarFragMap &B) { + if (A.size() != B.size()) + return false; + for (const auto &APair : A) { + auto BIt = B.find(APair.first); + if (BIt == B.end()) + return false; + if (!intervalMapsAreEqual(APair.second, BIt->second)) + return false; + } + return true; + } + + /// Return a string for the value that \p BaseID represents. + std::string toString(unsigned BaseID) { + if (BaseID) + return Bases[BaseID]->getName().str(); + else + return "None"; + } + + /// Format string describing an FragsInMemMap (IntervalMap) interval. + std::string toString(FragsInMemMap::const_iterator It, bool Newline = true) { + std::string String; + std::stringstream S(String); + if (It.valid()) { + S << "[" << It.start() << ", " << It.stop() + << "): " << toString(It.value()); + } else { + S << "invalid iterator (end)"; + } + if (Newline) + S << "\n"; + return S.str(); + }; + + FragsInMemMap meetFragments(const FragsInMemMap &A, const FragsInMemMap &B) { + FragsInMemMap Result(IntervalMapAlloc); + for (auto AIt = A.begin(), AEnd = A.end(); AIt != AEnd; ++AIt) { + LLVM_DEBUG(dbgs() << "a " << toString(AIt)); + // This is basically copied from process() and inverted (process is + // performing something like a union whereas this is more of an + // intersect). + + // There's no work to do if interval `a` overlaps no fragments in map `B`. + if (!B.overlaps(AIt.start(), AIt.stop())) + continue; + + // Does StartBit intersect an existing fragment? + auto FirstOverlap = B.find(AIt.start()); + assert(FirstOverlap != B.end()); + bool IntersectStart = FirstOverlap.start() < AIt.start(); + LLVM_DEBUG(dbgs() << "- FirstOverlap " << toString(FirstOverlap, false) + << ", IntersectStart: " << IntersectStart << "\n"); + + // Does EndBit intersect an existing fragment? + auto LastOverlap = B.find(AIt.stop()); + bool IntersectEnd = + LastOverlap != B.end() && LastOverlap.start() < AIt.stop(); + LLVM_DEBUG(dbgs() << "- LastOverlap " << toString(LastOverlap, false) + << ", IntersectEnd: " << IntersectEnd << "\n"); + + // Check if both ends of `a` intersect the same interval `b`. + if (IntersectStart && IntersectEnd && FirstOverlap == LastOverlap) { + // Insert `a` (`a` is contained in `b`) if the values match. + // [ a ] + // [ - b - ] + // - + // [ r ] + LLVM_DEBUG(dbgs() << "- a is contained within " + << toString(FirstOverlap)); + if (AIt.value() && AIt.value() == FirstOverlap.value()) + Result.insert(AIt.start(), AIt.stop(), AIt.value()); + } else { + // There's an overlap but `a` is not fully contained within + // `b`. Shorten any end-point intersections. + // [ - a - ] + // [ - b - ] + // - + // [ r ] + auto Next = FirstOverlap; + if (IntersectStart) { + LLVM_DEBUG(dbgs() << "- insert intersection of a and " + << toString(FirstOverlap)); + if (AIt.value() && AIt.value() == FirstOverlap.value()) + Result.insert(AIt.start(), FirstOverlap.stop(), AIt.value()); + ++Next; + } + // [ - a - ] + // [ - b - ] + // - + // [ r ] + if (IntersectEnd) { + LLVM_DEBUG(dbgs() << "- insert intersection of a and " + << toString(LastOverlap)); + if (AIt.value() && AIt.value() == LastOverlap.value()) + Result.insert(LastOverlap.start(), AIt.stop(), AIt.value()); + } + + // Insert all intervals in map `B` that are contained within interval + // `a` where the values match. + // [ - - a - - ] + // [ b1 ] [ b2 ] + // - + // [ r1 ] [ r2 ] + while (Next != B.end() && Next.start() < AIt.stop() && + Next.stop() <= AIt.stop()) { + LLVM_DEBUG(dbgs() + << "- insert intersection of a and " << toString(Next)); + if (AIt.value() && AIt.value() == Next.value()) + Result.insert(Next.start(), Next.stop(), Next.value()); + ++Next; + } + } + } + return Result; + } + + /// Meet \p A and \p B, storing the result in \p A. + void meetVars(VarFragMap &A, const VarFragMap &B) { + // Meet A and B. + // + // Result = meet(a, b) for a in A, b in B where Var(a) == Var(b) + for (auto It = A.begin(), End = A.end(); It != End; ++It) { + unsigned AVar = It->first; + FragsInMemMap &AFrags = It->second; + auto BIt = B.find(AVar); + if (BIt == B.end()) { + A.erase(It); + continue; // Var has no bits defined in B. + } + LLVM_DEBUG(dbgs() << "meet fragment maps for " + << Aggregates[AVar].first->getName() << "\n"); + AFrags = meetFragments(AFrags, BIt->second); + } + } + + bool meet(const BasicBlock &BB, + const SmallPtrSet &Visited) { + LLVM_DEBUG(dbgs() << "meet block info from preds of " << BB.getName() + << "\n"); + + VarFragMap BBLiveIn; + bool FirstMeet = true; + // LiveIn locs for BB is the meet of the already-processed preds' LiveOut + // locs. + for (auto I = pred_begin(&BB), E = pred_end(&BB); I != E; I++) { + // Ignore preds that haven't been processed yet. This is essentially the + // same as initialising all variables to implicit top value (⊤) which is + // the identity value for the meet operation. + const BasicBlock *Pred = *I; + if (!Visited.count(Pred)) + continue; + + auto PredLiveOut = LiveOut.find(Pred); + assert(PredLiveOut != LiveOut.end()); + + if (FirstMeet) { + LLVM_DEBUG(dbgs() << "BBLiveIn = " << Pred->getName() << "\n"); + BBLiveIn = PredLiveOut->second; + FirstMeet = false; + } else { + LLVM_DEBUG(dbgs() << "BBLiveIn = meet BBLiveIn, " << Pred->getName() + << "\n"); + meetVars(BBLiveIn, PredLiveOut->second); + } + + // An empty set is ⊥ for the intersect-like meet operation. If we've + // already got ⊥ there's no need to run the code - we know the result is + // ⊥ since `meet(a, ⊥) = ⊥`. + if (BBLiveIn.size() == 0) + break; + } + + auto CurrentLiveInEntry = LiveIn.find(&BB); + // If there's no LiveIn entry for the block yet, add it. + if (CurrentLiveInEntry == LiveIn.end()) { + LLVM_DEBUG(dbgs() << "change=true (first) on meet on " << BB.getName() + << "\n"); + LiveIn[&BB] = std::move(BBLiveIn); + return /*Changed=*/true; + } + + // If the LiveIn set has changed (expensive check) update it and return + // true. + if (!varFragMapsAreEqual(BBLiveIn, CurrentLiveInEntry->second)) { + LLVM_DEBUG(dbgs() << "change=true on meet on " << BB.getName() << "\n"); + CurrentLiveInEntry->second = std::move(BBLiveIn); + return /*Changed=*/true; + } + + LLVM_DEBUG(dbgs() << "change=false on meet on " << BB.getName() << "\n"); + return /*Changed=*/false; + } + + void insertMemLoc(BasicBlock &BB, Instruction &Before, unsigned Var, + unsigned StartBit, unsigned EndBit, unsigned Base, + DebugLoc DL) { + assert(StartBit < EndBit && "Cannot create fragment of size <= 0"); + if (!Base) + return; + FragMemLoc Loc; + Loc.Var = Var; + Loc.OffsetInBits = StartBit; + Loc.SizeInBits = EndBit - StartBit; + assert(Base && "Expected a non-zero ID for Base address"); + Loc.Base = Base; + Loc.DL = DL; + BBInsertBeforeMap[&BB][&Before].push_back(Loc); + LLVM_DEBUG(dbgs() << "Add mem def for " << Aggregates[Var].first->getName() + << " bits [" << StartBit << ", " << EndBit << ")\n"); + } + + void addDef(const VarLocInfo &VarLoc, Instruction &Before, BasicBlock &BB, + VarFragMap &LiveSet) { + DebugVariable DbgVar = FnVarLocs->getVariable(VarLoc.VariableID); + if (skipVariable(DbgVar.getVariable())) + return; + // Don't bother doing anything for this variables if we know it's fully + // promoted. We're only interested in variables that (sometimes) live on + // the stack here. + if (!VarsWithStackSlot->count(getAggregate(DbgVar))) + return; + unsigned Var = Aggregates.insert( + DebugAggregate(DbgVar.getVariable(), VarLoc.DL.getInlinedAt())); + + // [StartBit: EndBit) are the bits affected by this def. + const DIExpression *DIExpr = VarLoc.Expr; + unsigned StartBit; + unsigned EndBit; + if (auto Frag = DIExpr->getFragmentInfo()) { + StartBit = Frag->OffsetInBits; + EndBit = StartBit + Frag->SizeInBits; + } else { + assert(static_cast(DbgVar.getVariable()->getSizeInBits())); + StartBit = 0; + EndBit = *DbgVar.getVariable()->getSizeInBits(); + } + + // We will only fill fragments for simple memory-describing dbg.value + // intrinsics. If the fragment offset is the same as the offset from the + // base pointer, do The Thing, otherwise fall back to normal dbg.value + // behaviour. AssignmentTrackingLowering has generated DIExpressions + // written in terms of the base pointer. + // TODO: Remove this condition since the fragment offset doesn't always + // equal the offset from base pointer (e.g. for a SROA-split variable). + const auto DerefOffsetInBytes = getDerefOffsetInBytes(DIExpr); + const unsigned Base = + DerefOffsetInBytes && *DerefOffsetInBytes * 8 == StartBit + ? Bases.insert(VarLoc.V) + : 0; + LLVM_DEBUG(dbgs() << "DEF " << DbgVar.getVariable()->getName() << " [" + << StartBit << ", " << EndBit << "): " << toString(Base) + << "\n"); + + // First of all, any locs that use mem that are disrupted need reinstating. + // Unfortunately, IntervalMap doesn't let us insert intervals that overlap + // with existing intervals so this code involves a lot of fiddling around + // with intervals to do that manually. + auto FragIt = LiveSet.find(Var); + + // Check if the variable does not exist in the map. + if (FragIt == LiveSet.end()) { + // Add this variable to the BB map. + auto P = LiveSet.try_emplace(Var, FragsInMemMap(IntervalMapAlloc)); + assert(P.second && "Var already in map?"); + // Add the interval to the fragment map. + P.first->second.insert(StartBit, EndBit, Base); + return; + } + // The variable has an entry in the map. + + FragsInMemMap &FragMap = FragIt->second; + // First check the easy case: the new fragment `f` doesn't overlap with any + // intervals. + if (!FragMap.overlaps(StartBit, EndBit)) { + LLVM_DEBUG(dbgs() << "- No overlaps\n"); + FragMap.insert(StartBit, EndBit, Base); + return; + } + // There is at least one overlap. + + // Does StartBit intersect an existing fragment? + auto FirstOverlap = FragMap.find(StartBit); + assert(FirstOverlap != FragMap.end()); + bool IntersectStart = FirstOverlap.start() < StartBit; + + // Does EndBit intersect an existing fragment? + auto LastOverlap = FragMap.find(EndBit); + bool IntersectEnd = LastOverlap.valid() && LastOverlap.start() < EndBit; + + // Check if both ends of `f` intersect the same interval `i`. + if (IntersectStart && IntersectEnd && FirstOverlap == LastOverlap) { + LLVM_DEBUG(dbgs() << "- Intersect single interval @ both ends\n"); + // Shorten `i` so that there's space to insert `f`. + // [ f ] + // [ - i - ] + // + + // [ i ][ f ][ i ] + auto EndBitOfOverlap = FirstOverlap.stop(); + FirstOverlap.setStop(StartBit); + insertMemLoc(BB, Before, Var, FirstOverlap.start(), StartBit, + FirstOverlap.value(), VarLoc.DL); + + // Insert a new interval to represent the end part. + FragMap.insert(EndBit, EndBitOfOverlap, FirstOverlap.value()); + insertMemLoc(BB, Before, Var, EndBit, EndBitOfOverlap, + FirstOverlap.value(), VarLoc.DL); + + // Insert the new (middle) fragment now there is space. + FragMap.insert(StartBit, EndBit, Base); + } else { + // There's an overlap but `f` may not be fully contained within + // `i`. Shorten any end-point intersections so that we can then + // insert `f`. + // [ - f - ] + // [ - i - ] + // | | + // [ i ] + // Shorten any end-point intersections. + if (IntersectStart) { + LLVM_DEBUG(dbgs() << "- Intersect interval at start\n"); + // Split off at the intersection. + FirstOverlap.setStop(StartBit); + insertMemLoc(BB, Before, Var, FirstOverlap.start(), StartBit, + FirstOverlap.value(), VarLoc.DL); + } + // [ - f - ] + // [ - i - ] + // | | + // [ i ] + if (IntersectEnd) { + LLVM_DEBUG(dbgs() << "- Intersect interval at end\n"); + // Split off at the intersection. + LastOverlap.setStart(EndBit); + insertMemLoc(BB, Before, Var, EndBit, LastOverlap.stop(), + LastOverlap.value(), VarLoc.DL); + } + + LLVM_DEBUG(dbgs() << "- Erase intervals contained within\n"); + // FirstOverlap and LastOverlap have been shortened such that they're + // no longer overlapping with [StartBit, EndBit). Delete any overlaps + // that remain (these will be fully contained within `f`). + // [ - f - ] } + // [ - i - ] } Intersection shortening that has happened above. + // | | } + // [ i ] } + // ----------------- + // [i2 ] } Intervals fully contained within `f` get erased. + // ----------------- + // [ - f - ][ i ] } Completed insertion. + auto It = FirstOverlap; + if (IntersectStart) + ++It; // IntersectStart: first overlap has been shortened. + while (It.valid() && It.start() >= StartBit && It.stop() <= EndBit) { + LLVM_DEBUG(dbgs() << "- Erase " << toString(It)); + It.erase(); // This increments It after removing the interval. + } + // We've dealt with all the overlaps now! + assert(!FragMap.overlaps(StartBit, EndBit)); + LLVM_DEBUG(dbgs() << "- Insert DEF into now-empty space\n"); + FragMap.insert(StartBit, EndBit, Base); + } + } + + bool skipVariable(const DILocalVariable *V) { return !V->getSizeInBits(); } + + void process(BasicBlock &BB, VarFragMap &LiveSet) { + BBInsertBeforeMap[&BB].clear(); + for (auto &I : BB) { + if (const auto *Locs = FnVarLocs->getWedge(&I)) { + for (const VarLocInfo &Loc : *Locs) { + addDef(Loc, I, *I.getParent(), LiveSet); + } + } + } + } + +public: + MemLocFragmentFill(Function &Fn, + const DenseSet *VarsWithStackSlot) + : Fn(Fn), VarsWithStackSlot(VarsWithStackSlot) {} + + /// Add variable locations to \p FnVarLocs so that any bits of a variable + /// with a memory location have that location explicitly reinstated at each + /// subsequent variable location definition that that doesn't overwrite those + /// bits. i.e. after a variable location def, insert new defs for the memory + /// location with fragments for the difference of "all bits currently in + /// memory" and "the fragment of the second def". e.g. + /// + /// Before: + /// + /// var x bits 0 to 63: value in memory + /// more instructions + /// var x bits 0 to 31: value is %0 + /// + /// After: + /// + /// var x bits 0 to 63: value in memory + /// more instructions + /// var x bits 0 to 31: value is %0 + /// var x bits 32 to 61: value in memory ; <-- new loc def + /// + void run(FunctionVarLocsBuilder *FnVarLocs) { + if (!EnableMemLocFragFill) + return; + + this->FnVarLocs = FnVarLocs; + + // Prepare for traversal. + // + ReversePostOrderTraversal RPOT(&Fn); + std::priority_queue, + std::greater> + Worklist; + std::priority_queue, + std::greater> + Pending; + DenseMap OrderToBB; + DenseMap BBToOrder; + { // Init OrderToBB and BBToOrder. + unsigned int RPONumber = 0; + for (auto RI = RPOT.begin(), RE = RPOT.end(); RI != RE; ++RI) { + OrderToBB[RPONumber] = *RI; + BBToOrder[*RI] = RPONumber; + Worklist.push(RPONumber); + ++RPONumber; + } + LiveIn.init(RPONumber); + LiveOut.init(RPONumber); + } + + // Perform the traversal. + // + // This is a standard "intersect of predecessor outs" dataflow problem. To + // solve it, we perform meet() and process() using the two worklist method + // until the LiveIn data for each block becomes unchanging. + // + // This dataflow is essentially working on maps of sets and at each meet we + // intersect the maps and the mapped sets. So, initialized live-in maps + // monotonically decrease in value throughout the dataflow. + SmallPtrSet Visited; + while (!Worklist.empty() || !Pending.empty()) { + // We track what is on the pending worklist to avoid inserting the same + // thing twice. We could avoid this with a custom priority queue, but + // this is probably not worth it. + SmallPtrSet OnPending; + LLVM_DEBUG(dbgs() << "Processing Worklist\n"); + while (!Worklist.empty()) { + BasicBlock *BB = OrderToBB[Worklist.top()]; + LLVM_DEBUG(dbgs() << "\nPop BB " << BB->getName() << "\n"); + Worklist.pop(); + bool InChanged = meet(*BB, Visited); + // Always consider LiveIn changed on the first visit. + InChanged |= Visited.insert(BB).second; + if (InChanged) { + LLVM_DEBUG(dbgs() + << BB->getName() << " has new InLocs, process it\n"); + // Mutate a copy of LiveIn while processing BB. Once we've processed + // the terminator LiveSet is the LiveOut set for BB. + // This is an expensive copy! + VarFragMap LiveSet = LiveIn[BB]; + + // Process the instructions in the block. + process(*BB, LiveSet); + + // Relatively expensive check: has anything changed in LiveOut for BB? + if (!varFragMapsAreEqual(LiveOut[BB], LiveSet)) { + LLVM_DEBUG(dbgs() << BB->getName() + << " has new OutLocs, add succs to worklist: [ "); + LiveOut[BB] = std::move(LiveSet); + for (auto I = succ_begin(BB), E = succ_end(BB); I != E; I++) { + if (OnPending.insert(*I).second) { + LLVM_DEBUG(dbgs() << I->getName() << " "); + Pending.push(BBToOrder[*I]); + } + } + LLVM_DEBUG(dbgs() << "]\n"); + } + } + } + Worklist.swap(Pending); + // At this point, pending must be empty, since it was just the empty + // worklist + assert(Pending.empty() && "Pending should be empty"); + } + + // Insert new location defs. + for (auto Pair : BBInsertBeforeMap) { + InsertMap &Map = Pair.second; + for (auto Pair : Map) { + Instruction *InsertBefore = Pair.first; + assert(InsertBefore && "should never be null"); + auto FragMemLocs = Pair.second; + auto &Ctx = Fn.getContext(); + + for (auto FragMemLoc : FragMemLocs) { + DIExpression *Expr = DIExpression::get(Ctx, None); + Expr = *DIExpression::createFragmentExpression( + Expr, FragMemLoc.OffsetInBits, FragMemLoc.SizeInBits); + Expr = DIExpression::prepend(Expr, DIExpression::DerefAfter, + FragMemLoc.OffsetInBits / 8); + DebugVariable Var(Aggregates[FragMemLoc.Var].first, Expr, + FragMemLoc.DL.getInlinedAt()); + FnVarLocs->addVarLoc(InsertBefore, Var, Expr, FragMemLoc.DL, + Bases[FragMemLoc.Base]); + } + } + } + } +}; + +/// AssignmentTrackingLowering encapsulates a dataflow analysis over a function +/// that interprets assignment tracking debug info metadata and stores in IR to +/// create a map of variable locations. +class AssignmentTrackingLowering { +public: + /// The kind of location in use for a variable, where Mem is the stack home, + /// Val is an SSA value or const, and None means that there is not one single + /// kind (either because there are multiple or because there is none; it may + /// prove useful to split this into two values in the future). + /// + /// LocKind is a join-semilattice with the partial order: + /// None > Mem, Val + /// + /// i.e. + /// join(Mem, Mem) = Mem + /// join(Val, Val) = Val + /// join(Mem, Val) = None + /// join(None, Mem) = None + /// join(None, Val) = None + /// join(None, None) = None + /// + /// Note: the order is not `None > Val > Mem` because we're using DIAssignID + /// to name assignments and are not tracking the actual stored values. + /// Therefore currently there's no way to ensure that Mem values and Val + /// values are the same. This could be a future extension, though it's not + /// clear that many additional locations would be recovered that way in + /// practice as the likelihood of this sitation arising naturally seems + /// incredibly low. + enum class LocKind { Mem, Val, None }; + + /// An abstraction of the assignment of a value to a variable or memory + /// location. + /// + /// An Assignment is Known or NoneOrPhi. A Known Assignment means we have a + /// DIAssignID ptr that represents it. NoneOrPhi means that we don't (or + /// can't) know the ID of the last assignment that took place. + /// + /// The Status of the Assignment (Known or NoneOrPhi) is another + /// join-semilattice. The partial order is: + /// NoneOrPhi > Known {id_0, id_1, ...id_N} + /// + /// i.e. for all values x and y where x != y: + /// join(x, x) = x + /// join(x, y) = NoneOrPhi + struct Assignment { + enum S { Known, NoneOrPhi } Status; + /// ID of the assignment. nullptr if Status is not Known. + DIAssignID *ID; + /// The dbg.assign that marks this dbg-def. Mem-defs don't use this field. + /// May be nullptr. + DbgAssignIntrinsic *Source; + + bool isSameSourceAssignment(const Assignment &Other) const { + // Don't include Source in the equality check. Assignments are + // defined by their ID, not debug intrinsic(s). + return std::tie(Status, ID) == std::tie(Other.Status, Other.ID); + } + void dump(raw_ostream &OS) { + static const char *LUT[] = {"Known", "NoneOrPhi"}; + OS << LUT[Status] << "(id="; + if (ID) + OS << ID; + else + OS << "null"; + OS << ", s="; + if (Source) + OS << *Source; + else + OS << "null"; + OS << ")"; + } + + static Assignment make(DIAssignID *ID, DbgAssignIntrinsic *Source) { + return Assignment(Known, ID, Source); + } + static Assignment makeFromMemDef(DIAssignID *ID) { + return Assignment(Known, ID, nullptr); + } + static Assignment makeNoneOrPhi() { + return Assignment(NoneOrPhi, nullptr, nullptr); + } + // Again, need a Top value? + Assignment() + : Status(NoneOrPhi), ID(nullptr), Source(nullptr) { + } // Can we delete this? + Assignment(S Status, DIAssignID *ID, DbgAssignIntrinsic *Source) + : Status(Status), ID(ID), Source(Source) { + // If the Status is Known then we expect there to be an assignment ID. + assert(Status == NoneOrPhi || ID); + } + }; + + using AssignmentMap = DenseMap; + using LocMap = DenseMap; + using OverlapMap = DenseMap>; + using UntaggedStoreAssignmentMap = + DenseMap>>; + +private: + /// Map a variable to the set of variables that it fully contains. + OverlapMap VarContains; + /// Map untagged stores to the variable fragments they assign to. Used by + /// processUntaggedInstruction. + UntaggedStoreAssignmentMap UntaggedStoreVars; + + // Machinery to defer inserting dbg.values. + using InsertMap = MapVector>; + InsertMap InsertBeforeMap; + /// Clear the location definitions currently cached for insertion after /p + /// After. + void resetInsertionPoint(Instruction &After); + void emitDbgValue(LocKind Kind, const DbgVariableIntrinsic *Source, + Instruction *After); + + static bool mapsAreEqual(const AssignmentMap &A, const AssignmentMap &B) { + if (A.size() != B.size()) + return false; + for (const auto &Pair : A) { + VariableID Var = Pair.first; + const Assignment &AV = Pair.second; + auto R = B.find(Var); + // Check if this entry exists in B, otherwise ret false. + if (R == B.end()) + return false; + // Check that the assignment value is the same. + if (!AV.isSameSourceAssignment(R->second)) + return false; + } + return true; + } + + /// Represents the stack and debug assignments in a block. Used to describe + /// the live-in and live-out values for blocks, as well as the "current" + /// value as we process each instruction in a block. + struct BlockInfo { + /// Dominating assignment to memory for each variable. + AssignmentMap StackHomeValue; + /// Dominating assignemnt to each variable. + AssignmentMap DebugValue; + /// Location kind for each variable. LiveLoc indicates whether the + /// dominating assignment in StackHomeValue (LocKind::Mem), DebugValue + /// (LocKind::Val), or neither (LocKind::None) is valid, in that order of + /// preference. This cannot be derived by inspecting DebugValue and + /// StackHomeValue due to the fact that there's no distinction in + /// Assignment (the class) between whether an assignment is unknown or a + /// merge of multiple assignments (both are Status::NoneOrPhi). In other + /// words, the memory location may well be valid while both DebugValue and + /// StackHomeValue contain Assignments that have a Status of NoneOrPhi. + LocMap LiveLoc; + + /// Compare every element in each map to determine structural equality + /// (slow). + bool operator==(const BlockInfo &Other) const { + return LiveLoc == Other.LiveLoc && + mapsAreEqual(StackHomeValue, Other.StackHomeValue) && + mapsAreEqual(DebugValue, Other.DebugValue); + } + bool operator!=(const BlockInfo &Other) const { return !(*this == Other); } + bool isValid() { + return LiveLoc.size() == DebugValue.size() && + LiveLoc.size() == StackHomeValue.size(); + } + }; + + Function &Fn; + const DataLayout &Layout; + const DenseSet *VarsWithStackSlot; + FunctionVarLocsBuilder *FnVarLocs; + DenseMap LiveIn; + DenseMap LiveOut; + + /// Helper for process methods to track variables touched each frame. + DenseSet VarsTouchedThisFrame; + + /// The set of variables that sometimes are not located in their stack home. + DenseSet NotAlwaysStackHomed; + + VariableID getVariableID(const DebugVariable &Var) { + return static_cast(FnVarLocs->insertVariable(Var)); + } + + /// Join the LiveOut values of preds that are contained in \p Visited into + /// LiveIn[BB]. Return True if LiveIn[BB] has changed as a result. LiveIn[BB] + /// values monotonically increase. See the @link joinMethods join methods + /// @endlink documentation for more info. + bool join(const BasicBlock &BB, const SmallPtrSet &Visited); + ///@name joinMethods + /// Functions that implement `join` (the least upper bound) for the + /// join-semilattice types used in the dataflow. There is an explicit bottom + /// value (⊥) for some types and and explicit top value (⊤) for all types. + /// By definition: + /// + /// Join(A, B) >= A && Join(A, B) >= B + /// Join(A, ⊥) = A + /// Join(A, ⊤) = ⊤ + /// + /// These invariants are important for monotonicity. + /// + /// For the map-type functions, all unmapped keys in an empty map are + /// associated with a bottom value (⊥). This represents their values being + /// unknown. Unmapped keys in non-empty maps (joining two maps with a key + /// only present in one) represents either a variable going out of scope or + /// dropped debug info. It is assumed the key is associated with a top value + /// (⊤) in this case (unknown location / assignment). + ///@{ + static LocKind joinKind(LocKind A, LocKind B); + static LocMap joinLocMap(const LocMap &A, const LocMap &B); + static Assignment joinAssignment(const Assignment &A, const Assignment &B); + static AssignmentMap joinAssignmentMap(const AssignmentMap &A, + const AssignmentMap &B); + static BlockInfo joinBlockInfo(const BlockInfo &A, const BlockInfo &B); + ///@} + + /// Process the instructions in \p BB updating \p LiveSet along the way. \p + /// LiveSet must be initialized with the current live-in locations before + /// calling this. + void process(BasicBlock &BB, BlockInfo *LiveSet); + ///@name processMethods + /// Methods to process instructions in order to update the LiveSet (current + /// location information). + ///@{ + void processNonDbgInstruction(Instruction &I, BlockInfo *LiveSet); + void processDbgInstruction(Instruction &I, BlockInfo *LiveSet); + /// Update \p LiveSet after encountering an instruction with a DIAssignID + /// attachment, \p I. + void processTaggedInstruction(Instruction &I, BlockInfo *LiveSet); + /// Update \p LiveSet after encountering an instruciton without a DIAssignID + /// attachment, \p I. + void processUntaggedInstruction(Instruction &I, BlockInfo *LiveSet); + void processDbgAssign(DbgAssignIntrinsic &DAI, BlockInfo *LiveSet); + void processDbgValue(DbgValueInst &DVI, BlockInfo *LiveSet); + /// Add an assignment to memory for the variable /p Var. + void addMemDef(BlockInfo *LiveSet, VariableID Var, const Assignment &AV); + /// Add an assignment to the variable /p Var. + void addDbgDef(BlockInfo *LiveSet, VariableID Var, const Assignment &AV); + ///@} + + /// Set the LocKind for \p Var. + void setLocKind(BlockInfo *LiveSet, VariableID Var, LocKind K); + /// Get the live LocKind for a \p Var. Requires addMemDef or addDbgDef to + /// have been called for \p Var first. + LocKind getLocKind(BlockInfo *LiveSet, VariableID Var); + /// Return true if \p Var has an assignment in \p M matching \p AV. + bool hasVarWithAssignment(VariableID Var, const Assignment &AV, + const AssignmentMap &M); + + /// Emit info for variables that are fully promoted. + bool emitPromotedVarLocs(FunctionVarLocsBuilder *FnVarLocs); + +public: + AssignmentTrackingLowering(Function &Fn, const DataLayout &Layout, + const DenseSet *VarsWithStackSlot) + : Fn(Fn), Layout(Layout), VarsWithStackSlot(VarsWithStackSlot) {} + /// Run the analysis, adding variable location info to \p FnVarLocs. Returns + /// true if any variable locations have been added to FnVarLocs. + bool run(FunctionVarLocsBuilder *FnVarLocs); +}; + +void AssignmentTrackingLowering::setLocKind(BlockInfo *LiveSet, VariableID Var, + LocKind K) { + auto SetKind = [this](BlockInfo *LiveSet, VariableID Var, LocKind K) { + VarsTouchedThisFrame.insert(Var); + LiveSet->LiveLoc[Var] = K; + }; + SetKind(LiveSet, Var, K); + + // Update the LocKind for all fragments contained within Var. + for (VariableID Frag : VarContains[Var]) + SetKind(LiveSet, Frag, K); +} + +AssignmentTrackingLowering::LocKind +AssignmentTrackingLowering::getLocKind(BlockInfo *LiveSet, VariableID Var) { + auto Pair = LiveSet->LiveLoc.find(Var); + assert(Pair != LiveSet->LiveLoc.end()); + return Pair->second; +} + +void AssignmentTrackingLowering::addMemDef(BlockInfo *LiveSet, VariableID Var, + const Assignment &AV) { + auto AddDef = [](BlockInfo *LiveSet, VariableID Var, Assignment AV) { + LiveSet->StackHomeValue[Var] = AV; + // Add default (Var -> ⊤) to DebugValue if Var isn't in DebugValue yet. + LiveSet->DebugValue.insert({Var, Assignment::makeNoneOrPhi()}); + // Add default (Var -> ⊤) to LiveLocs if Var isn't in LiveLocs yet. Callers + // of addMemDef will call setLocKind to override. + LiveSet->LiveLoc.insert({Var, LocKind::None}); + }; + AddDef(LiveSet, Var, AV); + + // Use this assigment for all fragments contained within Var, but do not + // provide a Source because we cannot convert Var's value to a value for the + // fragment. + Assignment FragAV = AV; + FragAV.Source = nullptr; + for (VariableID Frag : VarContains[Var]) + AddDef(LiveSet, Frag, FragAV); +} + +void AssignmentTrackingLowering::addDbgDef(BlockInfo *LiveSet, VariableID Var, + const Assignment &AV) { + auto AddDef = [](BlockInfo *LiveSet, VariableID Var, Assignment AV) { + LiveSet->DebugValue[Var] = AV; + // Add default (Var -> ⊤) to StackHome if Var isn't in StackHome yet. + LiveSet->StackHomeValue.insert({Var, Assignment::makeNoneOrPhi()}); + // Add default (Var -> ⊤) to LiveLocs if Var isn't in LiveLocs yet. Callers + // of addDbgDef will call setLocKind to override. + LiveSet->LiveLoc.insert({Var, LocKind::None}); + }; + AddDef(LiveSet, Var, AV); + + // Use this assigment for all fragments contained within Var, but do not + // provide a Source because we cannot convert Var's value to a value for the + // fragment. + Assignment FragAV = AV; + FragAV.Source = nullptr; + for (VariableID Frag : VarContains[Var]) + AddDef(LiveSet, Frag, FragAV); +} + +static DIAssignID *getIDFromInst(const Instruction &I) { + return cast(I.getMetadata(LLVMContext::MD_DIAssignID)); +} + +static DIAssignID *getIDFromMarker(const DbgAssignIntrinsic &DAI) { + return cast(DAI.getAssignID()); +} + +/// Return true if \p Var has an assignment in \p M matching \p AV. +bool AssignmentTrackingLowering::hasVarWithAssignment(VariableID Var, + const Assignment &AV, + const AssignmentMap &M) { + auto AssignmentIsMapped = [](VariableID Var, const Assignment &AV, + const AssignmentMap &M) { + auto R = M.find(Var); + if (R == M.end()) + return false; + return AV.isSameSourceAssignment(R->second); + }; + + if (!AssignmentIsMapped(Var, AV, M)) + return false; + + // Check all the frags contained within Var as these will have all been + // mapped to AV at the last store to Var. + for (VariableID Frag : VarContains[Var]) + if (!AssignmentIsMapped(Frag, AV, M)) + return false; + return true; +} + +const char *locStr(AssignmentTrackingLowering::LocKind Loc) { + using LocKind = AssignmentTrackingLowering::LocKind; + switch (Loc) { + case LocKind::Val: + return "Val"; + case LocKind::Mem: + return "Mem"; + case LocKind::None: + return "None"; + }; + llvm_unreachable("unknown LocKind"); +} + +void AssignmentTrackingLowering::emitDbgValue( + AssignmentTrackingLowering::LocKind Kind, + const DbgVariableIntrinsic *Source, Instruction *After) { + + DILocation *DL = Source->getDebugLoc(); + auto Emit = [this, Source, After, DL](Value *Val, DIExpression *Expr) { + assert(Expr); + // It's possible that getVariableLocationOp(0) is null. Occurs in + // llvm/test/DebugInfo/Generic/2010-05-03-OriginDIE.ll Treat it as undef. + if (!Val) + Val = UndefValue::get(Type::getInt1Ty(Source->getContext())); + + // Find a suitable insert point. + Instruction *InsertBefore = After->getNextNode(); + assert(InsertBefore && "Shouldn't be inserting after a terminator"); + + VariableID Var = getVariableID(DebugVariable(Source)); + VarLocInfo VarLoc; + VarLoc.VariableID = static_cast(Var); + VarLoc.Expr = Expr; + VarLoc.V = Val; + VarLoc.DL = DL; + // Insert it into the map for later. + InsertBeforeMap[InsertBefore].push_back(VarLoc); + }; + + // NOTE: This block can mutate Kind. + if (Kind == LocKind::Mem) { + const auto *DAI = cast(Source); + // Check the address hasn't been dropped (e.g. the debug uses may not have + // been replaced before deleting a Value). + if (Value *Val = DAI->getAddress()) { + DIExpression *Expr = DAI->getAddressExpression(); + assert(!Expr->getFragmentInfo() && + "fragment info should be stored in value-expression only"); + // Copy the fragment info over from the value-expression to the new + // DIExpression. + if (auto OptFragInfo = Source->getExpression()->getFragmentInfo()) { + auto FragInfo = OptFragInfo.value(); + Expr = *DIExpression::createFragmentExpression( + Expr, FragInfo.OffsetInBits, FragInfo.SizeInBits); + } + // The address-expression has an implicit deref, add it now. + std::tie(Val, Expr) = + walkToAllocaAndPrependOffsetDeref(Layout, Val, Expr); + Emit(Val, Expr); + return; + } else { + // The address isn't valid so treat this as a non-memory def. + Kind = LocKind::Val; + } + } + + if (Kind == LocKind::Val) { + /// Get the value component, converting to Undef if it is variadic. + Value *Val = + Source->hasArgList() + ? UndefValue::get(Source->getVariableLocationOp(0)->getType()) + : Source->getVariableLocationOp(0); + Emit(Val, Source->getExpression()); + return; + } + + if (Kind == LocKind::None) { + Value *Val = UndefValue::get(Source->getVariableLocationOp(0)->getType()); + Emit(Val, Source->getExpression()); + return; + } +} + +void AssignmentTrackingLowering::processNonDbgInstruction( + Instruction &I, AssignmentTrackingLowering::BlockInfo *LiveSet) { + if (I.hasMetadata(LLVMContext::MD_DIAssignID)) + processTaggedInstruction(I, LiveSet); + else + processUntaggedInstruction(I, LiveSet); +} + +void AssignmentTrackingLowering::processUntaggedInstruction( + Instruction &I, AssignmentTrackingLowering::BlockInfo *LiveSet) { + // Interpret stack stores that are not tagged as an assignment in memory for + // the variables associated with that address. These stores may not be tagged + // because a) the store cannot be represented using dbg.assigns (non-const + // length or offset) or b) the tag was accidentally dropped during + // optimisations. For these stores we fall back to assuming that the stack + // home is a valid location for the variables. The benefit is that this + // prevents us missing an assignment and therefore incorrectly maintaining + // earlier location definitions, and in many cases it should be a reasonable + // assumption. However, this will occasionally lead to slight + // inaccuracies. The value of a hoisted untagged store will be visible + // "early", for example. + assert(!I.hasMetadata(LLVMContext::MD_DIAssignID)); + auto It = UntaggedStoreVars.find(&I); + if (It == UntaggedStoreVars.end()) + return; // No variables associated with the store destination. + + LLVM_DEBUG(dbgs() << "processUntaggedInstruction on UNTAGGED INST " << I + << "\n"); + // Iterate over the variables that this store affects, add a NoneOrPhi dbg + // and mem def, set lockind to Mem, and emit a location def for each. + for (auto [Var, Info] : It->second) { + // This instruction is treated as both a debug and memory assignment, + // meaning the memory location should be used. We don't have an assignment + // ID though so use Assignment::makeNoneOrPhi() to create an imaginary one. + addMemDef(LiveSet, Var, Assignment::makeNoneOrPhi()); + addDbgDef(LiveSet, Var, Assignment::makeNoneOrPhi()); + setLocKind(LiveSet, Var, LocKind::Mem); + LLVM_DEBUG(dbgs() << " setting Stack LocKind to: " << locStr(LocKind::Mem) + << "\n"); + // Build the dbg location def to insert. + // + // DIExpression: Add fragment and offset. + DebugVariable V = FnVarLocs->getVariable(Var); + DIExpression *DIE = DIExpression::get(I.getContext(), None); + if (auto Frag = V.getFragment()) { + auto R = DIExpression::createFragmentExpression(DIE, Frag->OffsetInBits, + Frag->SizeInBits); + assert(R && "unexpected createFragmentExpression failure"); + DIE = R.value(); + } + SmallVector Ops; + if (Info.OffsetInBits) + Ops = {dwarf::DW_OP_plus_uconst, Info.OffsetInBits / 8}; + Ops.push_back(dwarf::DW_OP_deref); + DIE = DIExpression::prependOpcodes(DIE, Ops, /*StackValue=*/false, + /*EntryValue=*/false); + // Find a suitable insert point. + Instruction *InsertBefore = I.getNextNode(); + assert(InsertBefore && "Shouldn't be inserting after a terminator"); + + // Get DILocation for this unrecorded assignment. + DILocation *InlinedAt = const_cast(V.getInlinedAt()); + const DILocation *DILoc = DILocation::get( + Fn.getContext(), 0, 0, V.getVariable()->getScope(), InlinedAt); + + VarLocInfo VarLoc; + VarLoc.VariableID = static_cast(Var); + VarLoc.Expr = DIE; + VarLoc.V = const_cast(Info.Base); + VarLoc.DL = DILoc; + // 3. Insert it into the map for later. + InsertBeforeMap[InsertBefore].push_back(VarLoc); + } +} + +void AssignmentTrackingLowering::processTaggedInstruction( + Instruction &I, AssignmentTrackingLowering::BlockInfo *LiveSet) { + auto Linked = at::getAssignmentMarkers(&I); + // No dbg.assign intrinsics linked. + // FIXME: All vars that have a stack slot this store modifies that don't have + // a dbg.assign linked to it should probably treat this like an untagged + // store. + if (Linked.empty()) + return; + + LLVM_DEBUG(dbgs() << "processTaggedInstruction on " << I << "\n"); + for (DbgAssignIntrinsic *DAI : Linked) { + VariableID Var = getVariableID(DebugVariable(DAI)); + // Something has gone wrong if VarsWithStackSlot doesn't contain a variable + // that is linked to a store. + assert(VarsWithStackSlot->count(getAggregate(DAI)) && + "expected DAI's variable to have stack slot"); + + Assignment AV = Assignment::makeFromMemDef(getIDFromInst(I)); + addMemDef(LiveSet, Var, AV); + + LLVM_DEBUG(dbgs() << " linked to " << *DAI << "\n"); + LLVM_DEBUG(dbgs() << " LiveLoc " << locStr(getLocKind(LiveSet, Var)) + << " -> "); + + // The last assignment to the stack is now AV. Check if the last debug + // assignment has a matching Assignment. + if (hasVarWithAssignment(Var, AV, LiveSet->DebugValue)) { + // The StackHomeValue and DebugValue for this variable match so we can + // emit a stack home location here. + LLVM_DEBUG(dbgs() << "Mem, Stack matches Debug program\n";); + LLVM_DEBUG(dbgs() << " Stack val: "; AV.dump(dbgs()); dbgs() << "\n"); + LLVM_DEBUG(dbgs() << " Debug val: "; + LiveSet->DebugValue[Var].dump(dbgs()); dbgs() << "\n"); + setLocKind(LiveSet, Var, LocKind::Mem); + emitDbgValue(LocKind::Mem, DAI, &I); + continue; + } + + // The StackHomeValue and DebugValue for this variable do not match. I.e. + // The value currently stored in the stack is not what we'd expect to + // see, so we cannot use emit a stack home location here. Now we will + // look at the live LocKind for the variable and determine an appropriate + // dbg.value to emit. + LocKind PrevLoc = getLocKind(LiveSet, Var); + switch (PrevLoc) { + case LocKind::Val: { + // The value in memory in memory has changed but we're not currently + // using the memory location. Do nothing. + LLVM_DEBUG(dbgs() << "Val, (unchanged)\n";); + setLocKind(LiveSet, Var, LocKind::Val); + } break; + case LocKind::Mem: { + // There's been an assignment to memory that we were using as a + // location for this variable, and the Assignment doesn't match what + // we'd expect to see in memory. + if (LiveSet->DebugValue[Var].Status == Assignment::NoneOrPhi) { + // We need to terminate any previously open location now. + LLVM_DEBUG(dbgs() << "None, No Debug value available\n";); + setLocKind(LiveSet, Var, LocKind::None); + emitDbgValue(LocKind::None, DAI, &I); + } else { + // The previous DebugValue Value can be used here. + LLVM_DEBUG(dbgs() << "Val, Debug value is Known\n";); + setLocKind(LiveSet, Var, LocKind::Val); + Assignment PrevAV = LiveSet->DebugValue.lookup(Var); + if (PrevAV.Source) { + emitDbgValue(LocKind::Val, PrevAV.Source, &I); + } else { + // PrevAV.Source is nullptr so we must emit undef here. + emitDbgValue(LocKind::None, DAI, &I); + } + } + } break; + case LocKind::None: { + // There's been an assignment to memory and we currently are + // not tracking a location for the variable. Do not emit anything. + LLVM_DEBUG(dbgs() << "None, (unchanged)\n";); + setLocKind(LiveSet, Var, LocKind::None); + } break; + } + } +} + +void AssignmentTrackingLowering::processDbgAssign(DbgAssignIntrinsic &DAI, + BlockInfo *LiveSet) { + // Only bother tracking variables that are at some point stack homed. Other + // variables can be dealt with trivially later. + if (!VarsWithStackSlot->count(getAggregate(&DAI))) + return; + + VariableID Var = getVariableID(DebugVariable(&DAI)); + Assignment AV = Assignment::make(getIDFromMarker(DAI), &DAI); + addDbgDef(LiveSet, Var, AV); + + LLVM_DEBUG(dbgs() << "processDbgAssign on " << DAI << "\n";); + LLVM_DEBUG(dbgs() << " LiveLoc " << locStr(getLocKind(LiveSet, Var)) + << " -> "); + + // Check if the DebugValue and StackHomeValue both hold the same + // Assignment. + if (hasVarWithAssignment(Var, AV, LiveSet->StackHomeValue)) { + // They match. We can use the stack home because the debug intrinsics state + // that an assignment happened here, and we know that specific assignment + // was the last one to take place in memory for this variable. + LocKind Kind; + if (isa(DAI.getAddress())) { + // Address may be undef to indicate that although the store does take + // place, this part of the original store has been elided. + LLVM_DEBUG( + dbgs() << "Val, Stack matches Debug program but address is undef\n";); + Kind = LocKind::Val; + } else { + LLVM_DEBUG(dbgs() << "Mem, Stack matches Debug program\n";); + Kind = LocKind::Mem; + }; + setLocKind(LiveSet, Var, Kind); + emitDbgValue(Kind, &DAI, &DAI); + } else { + // The last assignment to the memory location isn't the one that we want to + // show to the user so emit a dbg.value(Value). Value may be undef. + LLVM_DEBUG(dbgs() << "Val, Stack contents is unknown\n";); + setLocKind(LiveSet, Var, LocKind::Val); + emitDbgValue(LocKind::Val, &DAI, &DAI); + } +} + +void AssignmentTrackingLowering::processDbgValue(DbgValueInst &DVI, + BlockInfo *LiveSet) { + // Only other tracking variables that are at some point stack homed. + // Other variables can be dealt with trivally later. + if (!VarsWithStackSlot->count(getAggregate(&DVI))) + return; + + VariableID Var = getVariableID(DebugVariable(&DVI)); + // We have no ID to create an Assignment with so we mark this assignment as + // NoneOrPhi. Note that the dbg.value still exists, we just cannot determine + // the assignment responsible for setting this value. + // This is fine; dbg.values are essentially interchangable with unlinked + // dbg.assigns, and some passes such as mem2reg and instcombine add them to + // PHIs for promoted variables. + Assignment AV = Assignment::makeNoneOrPhi(); + addDbgDef(LiveSet, Var, AV); + + LLVM_DEBUG(dbgs() << "processDbgValue on " << DVI << "\n";); + LLVM_DEBUG(dbgs() << " LiveLoc " << locStr(getLocKind(LiveSet, Var)) + << " -> Val, dbg.value override"); + + setLocKind(LiveSet, Var, LocKind::Val); + emitDbgValue(LocKind::Val, &DVI, &DVI); +} + +void AssignmentTrackingLowering::processDbgInstruction( + Instruction &I, AssignmentTrackingLowering::BlockInfo *LiveSet) { + assert(!isa(&I) && "unexpected dbg.addr"); + if (auto *DAI = dyn_cast(&I)) + processDbgAssign(*DAI, LiveSet); + else if (auto *DVI = dyn_cast(&I)) + processDbgValue(*DVI, LiveSet); +} + +void AssignmentTrackingLowering::resetInsertionPoint(Instruction &After) { + assert(!After.isTerminator() && "Can't insert after a terminator"); + auto R = InsertBeforeMap.find(After.getNextNode()); + if (R == InsertBeforeMap.end()) + return; + R->second.clear(); +} + +void AssignmentTrackingLowering::process(BasicBlock &BB, BlockInfo *LiveSet) { + for (auto II = BB.begin(), EI = BB.end(); II != EI;) { + assert(VarsTouchedThisFrame.empty()); + // Process the instructions in "frames". A "frame" includes a single + // non-debug instruction followed any debug instructions before the + // next non-debug instruction. + if (!isa(&*II)) { + if (II->isTerminator()) + break; + resetInsertionPoint(*II); + processNonDbgInstruction(*II, LiveSet); + assert(LiveSet->isValid()); + ++II; + } + while (II != EI) { + if (!isa(&*II)) + break; + resetInsertionPoint(*II); + processDbgInstruction(*II, LiveSet); + assert(LiveSet->isValid()); + ++II; + } + + // We've processed everything in the "frame". Now determine which variables + // cannot be represented by a dbg.declare. + for (auto Var : VarsTouchedThisFrame) { + LocKind Loc = getLocKind(LiveSet, Var); + // If a variable's LocKind is anything other than LocKind::Mem then we + // must note that it cannot be represented with a dbg.declare. + // Note that this check is enough without having to check the result of + // joins() because for join to produce anything other than Mem after + // we've already seen a Mem we'd be joining None or Val with Mem. In that + // case, we've already hit this codepath when we set the LocKind to Val + // or None in that block. + if (Loc != LocKind::Mem) { + DebugVariable DbgVar = FnVarLocs->getVariable(Var); + DebugAggregate Aggr{DbgVar.getVariable(), DbgVar.getInlinedAt()}; + NotAlwaysStackHomed.insert(Aggr); + } + } + VarsTouchedThisFrame.clear(); + } +} + +AssignmentTrackingLowering::LocKind +AssignmentTrackingLowering::joinKind(LocKind A, LocKind B) { + // Partial order: + // None > Mem, Val + return A == B ? A : LocKind::None; +} + +AssignmentTrackingLowering::LocMap +AssignmentTrackingLowering::joinLocMap(const LocMap &A, const LocMap &B) { + // Join A and B. + // + // U = join(a, b) for a in A, b in B where Var(a) == Var(b) + // D = join(x, ⊤) for x where Var(x) is in A xor B + // Join = U ∪ D + // + // This is achieved by performing a join on elements from A and B with + // variables common to both A and B (join elements indexed by var intersect), + // then adding LocKind::None elements for vars in A xor B. The latter part is + // equivalent to performing join on elements with variables in A xor B with + // LocKind::None (⊤) since join(x, ⊤) = ⊤. + LocMap Join; + SmallVector SymmetricDifference; + // Insert the join of the elements with common vars into Join. Add the + // remaining elements to into SymmetricDifference. + for (const auto &[Var, Loc] : A) { + // If this Var doesn't exist in B then add it to the symmetric difference + // set. + auto R = B.find(Var); + if (R == B.end()) { + SymmetricDifference.push_back(Var); + continue; + } + // There is an entry for Var in both, join it. + Join[Var] = joinKind(Loc, R->second); + } + unsigned IntersectSize = Join.size(); + (void)IntersectSize; + + // Add the elements in B with variables that are not in A into + // SymmetricDifference. + for (const auto &Pair : B) { + VariableID Var = Pair.first; + if (A.count(Var) == 0) + SymmetricDifference.push_back(Var); + } + + // Add SymmetricDifference elements to Join and return the result. + for (const auto &Var : SymmetricDifference) + Join.insert({Var, LocKind::None}); + + assert(Join.size() == (IntersectSize + SymmetricDifference.size())); + assert(Join.size() >= A.size() && Join.size() >= B.size()); + return Join; +} + +AssignmentTrackingLowering::Assignment +AssignmentTrackingLowering::joinAssignment(const Assignment &A, + const Assignment &B) { + // Partial order: + // NoneOrPhi(null, null) > Known(v, ?s) + + // If either are NoneOrPhi the join is NoneOrPhi. + // If either value is different then the result is + // NoneOrPhi (joining two values is a Phi). + if (!A.isSameSourceAssignment(B)) + return Assignment::makeNoneOrPhi(); + if (A.Status == Assignment::NoneOrPhi) + return Assignment::makeNoneOrPhi(); + + // Source is used to lookup the value + expression in the debug program if + // the stack slot gets assigned a value earlier than expected. Because + // we're only tracking the one dbg.assign, we can't capture debug PHIs. + // It's unlikely that we're losing out on much coverage by avoiding that + // extra work. + // The Source may differ in this situation: + // Pred.1: + // dbg.assign i32 0, ..., !1, ... + // Pred.2: + // dbg.assign i32 1, ..., !1, ... + // Here the same assignment (!1) was performed in both preds in the source, + // but we can't use either one unless they are identical (e.g. .we don't + // want to arbitrarily pick between constant values). + auto JoinSource = [&]() -> DbgAssignIntrinsic * { + if (A.Source == B.Source) + return A.Source; + if (A.Source == nullptr || B.Source == nullptr) + return nullptr; + if (A.Source->isIdenticalTo(B.Source)) + return A.Source; + return nullptr; + }; + DbgAssignIntrinsic *Source = JoinSource(); + assert(A.Status == B.Status && A.Status == Assignment::Known); + assert(A.ID == B.ID); + return Assignment::make(A.ID, Source); +} + +AssignmentTrackingLowering::AssignmentMap +AssignmentTrackingLowering::joinAssignmentMap(const AssignmentMap &A, + const AssignmentMap &B) { + // Join A and B. + // + // U = join(a, b) for a in A, b in B where Var(a) == Var(b) + // D = join(x, ⊤) for x where Var(x) is in A xor B + // Join = U ∪ D + // + // This is achieved by performing a join on elements from A and B with + // variables common to both A and B (join elements indexed by var intersect), + // then adding LocKind::None elements for vars in A xor B. The latter part is + // equivalent to performing join on elements with variables in A xor B with + // Status::NoneOrPhi (⊤) since join(x, ⊤) = ⊤. + AssignmentMap Join; + SmallVector SymmetricDifference; + // Insert the join of the elements with common vars into Join. Add the + // remaining elements to into SymmetricDifference. + for (const auto &[Var, AV] : A) { + // If this Var doesn't exist in B then add it to the symmetric difference + // set. + auto R = B.find(Var); + if (R == B.end()) { + SymmetricDifference.push_back(Var); + continue; + } + // There is an entry for Var in both, join it. + Join[Var] = joinAssignment(AV, R->second); + } + unsigned IntersectSize = Join.size(); + (void)IntersectSize; + + // Add the elements in B with variables that are not in A into + // SymmetricDifference. + for (const auto &Pair : B) { + VariableID Var = Pair.first; + if (A.count(Var) == 0) + SymmetricDifference.push_back(Var); + } + + // Add SymmetricDifference elements to Join and return the result. + for (auto Var : SymmetricDifference) + Join.insert({Var, Assignment::makeNoneOrPhi()}); + + assert(Join.size() == (IntersectSize + SymmetricDifference.size())); + assert(Join.size() >= A.size() && Join.size() >= B.size()); + return Join; +} + +AssignmentTrackingLowering::BlockInfo +AssignmentTrackingLowering::joinBlockInfo(const BlockInfo &A, + const BlockInfo &B) { + BlockInfo Join; + Join.LiveLoc = joinLocMap(A.LiveLoc, B.LiveLoc); + Join.StackHomeValue = joinAssignmentMap(A.StackHomeValue, B.StackHomeValue); + Join.DebugValue = joinAssignmentMap(A.DebugValue, B.DebugValue); + assert(Join.isValid()); + return Join; +} + +bool AssignmentTrackingLowering::join( + const BasicBlock &BB, const SmallPtrSet &Visited) { + BlockInfo BBLiveIn; + bool FirstJoin = true; + // LiveIn locs for BB is the join of the already-processed preds' LiveOut + // locs. + for (auto I = pred_begin(&BB), E = pred_end(&BB); I != E; I++) { + // Ignore backedges if we have not visited the predecessor yet. As the + // predecessor hasn't yet had locations propagated into it, most locations + // will not yet be valid, so treat them as all being uninitialized and + // potentially valid. If a location guessed to be correct here is + // invalidated later, we will remove it when we revisit this block. This + // is essentially the same as initialising all LocKinds and Assignments to + // an implicit ⊥ value which is the identity value for the join operation. + const BasicBlock *Pred = *I; + if (!Visited.count(Pred)) + continue; + + auto PredLiveOut = LiveOut.find(Pred); + // Pred must have been processed already. See comment at start of this loop. + assert(PredLiveOut != LiveOut.end()); + + // Perform the join of BBLiveIn (current live-in info) and PrevLiveOut. + if (FirstJoin) + BBLiveIn = PredLiveOut->second; + else + BBLiveIn = joinBlockInfo(std::move(BBLiveIn), PredLiveOut->second); + FirstJoin = false; + } + + auto CurrentLiveInEntry = LiveIn.find(&BB); + // Check if there isn't an entry, or there is but the LiveIn set has changed + // (expensive check). + if (CurrentLiveInEntry == LiveIn.end() || + BBLiveIn != CurrentLiveInEntry->second) { + LiveIn[&BB] = std::move(BBLiveIn); + // A change has occured. + return true; + } + // No change. + return false; +} + +/// Return true if A fully contains B. +static bool fullyContains(DIExpression::FragmentInfo A, + DIExpression::FragmentInfo B) { + auto ALeft = A.OffsetInBits; + auto BLeft = B.OffsetInBits; + if (BLeft < ALeft) + return false; + + auto ARight = ALeft + A.SizeInBits; + auto BRight = BLeft + B.SizeInBits; + if (BRight > ARight) + return false; + return true; +} + +static std::optional +getUntaggedStoreAssignmentInfo(const Instruction &I, const DataLayout &Layout) { + // Don't bother checking if this is an AllocaInst. We know this + // instruction has no tag which means there are no variables associated + // with it. + if (const auto *SI = dyn_cast(&I)) + return at::getAssignmentInfo(Layout, SI); + if (const auto *MI = dyn_cast(&I)) + return at::getAssignmentInfo(Layout, MI); + // Alloca or non-store-like inst. + return std::nullopt; +} + +/// Build a map of {Variable x: Variables y} where all variable fragments +/// contained within the variable fragment x are in set y. This means that +/// y does not contain all overlaps because partial overlaps are excluded. +/// +/// While we're iterating over the function, add single location defs for +/// dbg.declares to \p FnVarLocs +/// +/// Finally, populate UntaggedStoreVars with a mapping of untagged stores to +/// the stored-to variable fragments. +/// +/// These tasks are bundled together to reduce the number of times we need +/// to iterate over the function as they can be achieved together in one pass. +static AssignmentTrackingLowering::OverlapMap buildOverlapMapAndRecordDeclares( + Function &Fn, FunctionVarLocsBuilder *FnVarLocs, + AssignmentTrackingLowering::UntaggedStoreAssignmentMap &UntaggedStoreVars) { + DenseSet Seen; + // Map of Variable: [Fragments]. + DenseMap> FragmentMap; + // Iterate over all instructions: + // - dbg.declare -> add single location variable record + // - dbg.* -> Add fragments to FragmentMap + // - untagged store -> Add fragments to FragmentMap and update + // UntaggedStoreVars. + // We need to add fragments for untagged stores too so that we can correctly + // clobber overlapped fragment locations later. + for (auto &BB : Fn) { + for (auto &I : BB) { + if (auto *DDI = dyn_cast(&I)) { + FnVarLocs->addSingleLocVar(DebugVariable(DDI), DDI->getExpression(), + DDI->getDebugLoc(), DDI->getAddress()); + } else if (auto *DII = dyn_cast(&I)) { + DebugVariable DV = DebugVariable(DII); + DebugAggregate DA = {DV.getVariable(), DV.getInlinedAt()}; + if (Seen.insert(DV).second) + FragmentMap[DA].push_back(DV); + } else if (auto Info = getUntaggedStoreAssignmentInfo( + I, Fn.getParent()->getDataLayout())) { + // Find markers linked to this alloca. + for (DbgAssignIntrinsic *DAI : at::getAssignmentMarkers(Info->Base)) { + // Discard the fragment if it covers the entire variable. + std::optional FragInfo = + [&Info, DAI]() -> std::optional { + DIExpression::FragmentInfo F; + F.OffsetInBits = Info->OffsetInBits; + F.SizeInBits = Info->SizeInBits; + if (auto ExistingFrag = DAI->getExpression()->getFragmentInfo()) + F.OffsetInBits += ExistingFrag->OffsetInBits; + if (auto Sz = DAI->getVariable()->getSizeInBits()) { + if (F.OffsetInBits == 0 && F.SizeInBits == *Sz) + return std::nullopt; + } + return F; + }(); + + DebugVariable DV = DebugVariable(DAI->getVariable(), FragInfo, + DAI->getDebugLoc().getInlinedAt()); + DebugAggregate DA = {DV.getVariable(), DV.getInlinedAt()}; + + // Cache this info for later. + UntaggedStoreVars[&I].push_back( + {FnVarLocs->insertVariable(DV), *Info}); + + if (Seen.insert(DV).second) + FragmentMap[DA].push_back(DV); + } + } + } + } + + // Sort the fragment map for each DebugAggregate in non-descending + // order of fragment size. Assert no entries are duplicates. + for (auto &Pair : FragmentMap) { + SmallVector &Frags = Pair.second; + std::sort( + Frags.begin(), Frags.end(), [](DebugVariable Next, DebugVariable Elmt) { + assert(!(Elmt.getFragmentOrDefault() == Next.getFragmentOrDefault())); + return Elmt.getFragmentOrDefault().SizeInBits > + Next.getFragmentOrDefault().SizeInBits; + }); + } + + // Build the map. + AssignmentTrackingLowering::OverlapMap Map; + for (auto Pair : FragmentMap) { + auto &Frags = Pair.second; + for (auto It = Frags.begin(), IEnd = Frags.end(); It != IEnd; ++It) { + DIExpression::FragmentInfo Frag = It->getFragmentOrDefault(); + // Find the frags that this is contained within. + // + // Because Frags is sorted by size and none have the same offset and + // size, we know that this frag can only be contained by subsequent + // elements. + SmallVector::iterator OtherIt = It; + ++OtherIt; + VariableID ThisVar = FnVarLocs->insertVariable(*It); + for (; OtherIt != IEnd; ++OtherIt) { + DIExpression::FragmentInfo OtherFrag = OtherIt->getFragmentOrDefault(); + VariableID OtherVar = FnVarLocs->insertVariable(*OtherIt); + if (fullyContains(OtherFrag, Frag)) + Map[OtherVar].push_back(ThisVar); + } + } + } + + return Map; +} + +bool AssignmentTrackingLowering::run(FunctionVarLocsBuilder *FnVarLocsBuilder) { + if (Fn.size() > MaxNumBlocks) { + LLVM_DEBUG(dbgs() << "[AT] Dropping var locs in: " << Fn.getName() + << ": too many blocks (" << Fn.size() << ")\n"); + at::deleteAll(&Fn); + return false; + } + + FnVarLocs = FnVarLocsBuilder; + + // The general structure here is inspired by VarLocBasedImpl.cpp + // (LiveDebugValues). + + // Build the variable fragment overlap map. + // Note that this pass doesn't handle partial overlaps correctly (FWIW + // neither does LiveDebugVariables) because that is difficult to do and + // appears to be rare occurance. + VarContains = + buildOverlapMapAndRecordDeclares(Fn, FnVarLocs, UntaggedStoreVars); + + // Prepare for traversal. + ReversePostOrderTraversal RPOT(&Fn); + std::priority_queue, + std::greater> + Worklist; + std::priority_queue, + std::greater> + Pending; + DenseMap OrderToBB; + DenseMap BBToOrder; + { // Init OrderToBB and BBToOrder. + unsigned int RPONumber = 0; + for (auto RI = RPOT.begin(), RE = RPOT.end(); RI != RE; ++RI) { + OrderToBB[RPONumber] = *RI; + BBToOrder[*RI] = RPONumber; + Worklist.push(RPONumber); + ++RPONumber; + } + LiveIn.init(RPONumber); + LiveOut.init(RPONumber); + } + + // Perform the traversal. + // + // This is a standard "union of predecessor outs" dataflow problem. To solve + // it, we perform join() and process() using the two worklist method until + // the LiveIn data for each block becomes unchanging. The "proof" that this + // terminates can be put together by looking at the comments around LocKind, + // Assignment, and the various join methods, which show that all the elements + // involved are made up of join-semilattices; LiveIn(n) can only + // monotonically increase in value throughout the dataflow. + // + SmallPtrSet Visited; + while (!Worklist.empty()) { + // We track what is on the pending worklist to avoid inserting the same + // thing twice. + SmallPtrSet OnPending; + LLVM_DEBUG(dbgs() << "Processing Worklist\n"); + while (!Worklist.empty()) { + BasicBlock *BB = OrderToBB[Worklist.top()]; + LLVM_DEBUG(dbgs() << "\nPop BB " << BB->getName() << "\n"); + Worklist.pop(); + bool InChanged = join(*BB, Visited); + // Always consider LiveIn changed on the first visit. + InChanged |= Visited.insert(BB).second; + if (InChanged) { + LLVM_DEBUG(dbgs() << BB->getName() << " has new InLocs, process it\n"); + // Mutate a copy of LiveIn while processing BB. After calling process + // LiveSet is the LiveOut set for BB. + BlockInfo LiveSet = LiveIn[BB]; + + // Process the instructions in the block. + process(*BB, &LiveSet); + + // Relatively expensive check: has anything changed in LiveOut for BB? + if (LiveOut[BB] != LiveSet) { + LLVM_DEBUG(dbgs() << BB->getName() + << " has new OutLocs, add succs to worklist: [ "); + LiveOut[BB] = std::move(LiveSet); + for (auto I = succ_begin(BB), E = succ_end(BB); I != E; I++) { + if (OnPending.insert(*I).second) { + LLVM_DEBUG(dbgs() << I->getName() << " "); + Pending.push(BBToOrder[*I]); + } + } + LLVM_DEBUG(dbgs() << "]\n"); + } + } + } + Worklist.swap(Pending); + // At this point, pending must be empty, since it was just the empty + // worklist + assert(Pending.empty() && "Pending should be empty"); + } + + // That's the hard part over. Now we just have some admin to do. + + // Record whether we inserted any intrinsics. + bool InsertedAnyIntrinsics = false; + + // Identify and add defs for single location variables. + // + // Go through all of the defs that we plan to add. If the aggregate variable + // it's a part of is not in the NotAlwaysStackHomed set we can emit a single + // location def and omit the rest. Add an entry to AlwaysStackHomed so that + // we can identify those uneeded defs later. + DenseSet AlwaysStackHomed; + for (const auto &Pair : InsertBeforeMap) { + const auto &Vec = Pair.second; + for (VarLocInfo VarLoc : Vec) { + DebugVariable Var = FnVarLocs->getVariable(VarLoc.VariableID); + DebugAggregate Aggr{Var.getVariable(), Var.getInlinedAt()}; + + // Skip this Var if it's not always stack homed. + if (NotAlwaysStackHomed.contains(Aggr)) + continue; + + // Skip complex cases such as when different fragments of a variable have + // been split into different allocas. Skipping in this case means falling + // back to using a list of defs (which could reduce coverage, but is no + // less correct). + bool Simple = + VarLoc.Expr->getNumElements() == 1 && VarLoc.Expr->startsWithDeref(); + if (!Simple) { + NotAlwaysStackHomed.insert(Aggr); + continue; + } + + // All source assignments to this variable remain and all stores to any + // part of the variable store to the same address (with varying + // offsets). We can just emit a single location for the whole variable. + // + // Unless we've already done so, create the single location def now. + if (AlwaysStackHomed.insert(Aggr).second) { + assert(isa(VarLoc.V)); + // TODO: When more complex cases are handled VarLoc.Expr should be + // built appropriately rather than always using an empty DIExpression. + // The assert below is a reminder. + assert(Simple); + VarLoc.Expr = DIExpression::get(Fn.getContext(), None); + DebugVariable Var = FnVarLocs->getVariable(VarLoc.VariableID); + FnVarLocs->addSingleLocVar(Var, VarLoc.Expr, VarLoc.DL, VarLoc.V); + InsertedAnyIntrinsics = true; + } + } + } + + // Insert the other DEFs. + for (const auto &[InsertBefore, Vec] : InsertBeforeMap) { + SmallVector NewDefs; + for (const VarLocInfo &VarLoc : Vec) { + DebugVariable Var = FnVarLocs->getVariable(VarLoc.VariableID); + DebugAggregate Aggr{Var.getVariable(), Var.getInlinedAt()}; + // If this variable is always stack homed then we have already inserted a + // dbg.declare and deleted this dbg.value. + if (AlwaysStackHomed.contains(Aggr)) + continue; + NewDefs.push_back(VarLoc); + InsertedAnyIntrinsics = true; + } + + FnVarLocs->setWedge(InsertBefore, std::move(NewDefs)); + } + + InsertedAnyIntrinsics |= emitPromotedVarLocs(FnVarLocs); + + return InsertedAnyIntrinsics; +} + +bool AssignmentTrackingLowering::emitPromotedVarLocs( + FunctionVarLocsBuilder *FnVarLocs) { + bool InsertedAnyIntrinsics = false; + // Go through every block, translating debug intrinsics for fully promoted + // variables into FnVarLocs location defs. No analysis required for these. + for (auto &BB : Fn) { + for (auto &I : BB) { + // Skip instructions other than dbg.values and dbg.assigns. + auto *DVI = dyn_cast(&I); + if (!DVI) + continue; + // Skip variables that haven't been promoted - we've dealt with those + // already. + if (VarsWithStackSlot->contains(getAggregate(DVI))) + continue; + // Wrapper to get a single value (or undef) from DVI. + auto GetValue = [DVI]() -> Value * { + // Conditions for undef: Any operand undef, zero operands or single + // operand is nullptr. We also can't handle variadic DIExpressions yet. + // Some of those conditions don't have a type we can pick for + // undef. Use i32. + if (DVI->isUndef() || DVI->getValue() == nullptr || DVI->hasArgList()) + return UndefValue::get(Type::getInt32Ty(DVI->getContext())); + return DVI->getValue(); + }; + Instruction *InsertBefore = I.getNextNode(); + assert(InsertBefore && "Unexpected: debug intrinsics after a terminator"); + FnVarLocs->addVarLoc(InsertBefore, DebugVariable(DVI), + DVI->getExpression(), DVI->getDebugLoc(), + GetValue()); + InsertedAnyIntrinsics = true; + } + } + return InsertedAnyIntrinsics; +} + +/// Remove redundant definitions within sequences of consecutive location defs. +/// This is done using a backward scan to keep the last def describing a +/// specific variable/fragment. +/// +/// This implements removeRedundantDbgInstrsUsingBackwardScan from +/// lib/Transforms/Utils/BasicBlockUtils.cpp for locations described with +/// FunctionVarLocsBuilder instead of with intrinsics. +static bool +removeRedundantDbgLocsUsingBackwardScan(const BasicBlock *BB, + FunctionVarLocsBuilder &FnVarLocs) { + bool Changed = false; + SmallDenseSet VariableSet; + + // Scan over the entire block, not just over the instructions mapped by + // FnVarLocs, because wedges in FnVarLocs may only be seperated by debug + // instructions. + for (const Instruction &I : reverse(*BB)) { + if (!isa(I)) { + // Sequence of consecutive defs ended. Clear map for the next one. + VariableSet.clear(); + } + + // Get the location defs that start just before this instruction. + const auto *Locs = FnVarLocs.getWedge(&I); + if (!Locs) + continue; + + NumWedgesScanned++; + bool ChangedThisWedge = false; + // The new pruned set of defs, reversed because we're scanning backwards. + SmallVector NewDefsReversed; + + // Iterate over the existing defs in reverse. + for (auto RIt = Locs->rbegin(), REnd = Locs->rend(); RIt != REnd; ++RIt) { + NumDefsScanned++; + const DebugVariable &Key = FnVarLocs.getVariable(RIt->VariableID); + bool FirstDefOfFragment = VariableSet.insert(Key).second; + + // If the same variable fragment is described more than once it is enough + // to keep the last one (i.e. the first found in this reverse iteration). + if (FirstDefOfFragment) { + // New def found: keep it. + NewDefsReversed.push_back(*RIt); + } else { + // Redundant def found: throw it away. Since the wedge of defs is being + // rebuilt, doing nothing is the same as deleting an entry. + ChangedThisWedge = true; + NumDefsRemoved++; + } + continue; + } + + // Un-reverse the defs and replace the wedge with the pruned version. + if (ChangedThisWedge) { + std::reverse(NewDefsReversed.begin(), NewDefsReversed.end()); + FnVarLocs.setWedge(&I, std::move(NewDefsReversed)); + NumWedgesChanged++; + Changed = true; + } + } + + return Changed; +} + +/// Remove redundant location defs using a forward scan. This can remove a +/// location definition that is redundant due to indicating that a variable has +/// the same value as is already being indicated by an earlier def. +/// +/// This implements removeRedundantDbgInstrsUsingForwardScan from +/// lib/Transforms/Utils/BasicBlockUtils.cpp for locations described with +/// FunctionVarLocsBuilder instead of with intrinsics +static bool +removeRedundantDbgLocsUsingForwardScan(const BasicBlock *BB, + FunctionVarLocsBuilder &FnVarLocs) { + bool Changed = false; + DenseMap> VariableMap; + + // Scan over the entire block, not just over the instructions mapped by + // FnVarLocs, because wedges in FnVarLocs may only be seperated by debug + // instructions. + for (const Instruction &I : *BB) { + // Get the defs that come just before this instruction. + const auto *Locs = FnVarLocs.getWedge(&I); + if (!Locs) + continue; + + NumWedgesScanned++; + bool ChangedThisWedge = false; + // The new pruned set of defs. + SmallVector NewDefs; + + // Iterate over the existing defs. + for (const VarLocInfo &Loc : *Locs) { + NumDefsScanned++; + DebugVariable Key(FnVarLocs.getVariable(Loc.VariableID).getVariable(), + std::nullopt, Loc.DL.getInlinedAt()); + auto VMI = VariableMap.find(Key); + + // Update the map if we found a new value/expression describing the + // variable, or if the variable wasn't mapped already. + if (VMI == VariableMap.end() || VMI->second.first != Loc.V || + VMI->second.second != Loc.Expr) { + VariableMap[Key] = {Loc.V, Loc.Expr}; + NewDefs.push_back(Loc); + continue; + } + + // Did not insert this Loc, which is the same as removing it. + ChangedThisWedge = true; + NumDefsRemoved++; + } + + // Replace the existing wedge with the pruned version. + if (ChangedThisWedge) { + FnVarLocs.setWedge(&I, std::move(NewDefs)); + NumWedgesChanged++; + Changed = true; + } + } + + return Changed; +} + +static bool +removeUndefDbgLocsFromEntryBlock(const BasicBlock *BB, + FunctionVarLocsBuilder &FnVarLocs) { + assert(BB->isEntryBlock()); + // Do extra work to ensure that we remove semantically unimportant undefs. + // + // This is to work around the fact that SelectionDAG will hoist dbg.values + // using argument values to the top of the entry block. That can move arg + // dbg.values before undef and constant dbg.values which they previously + // followed. The easiest thing to do is to just try to feed SelectionDAG + // input it's happy with. + // + // Map of {Variable x: Fragments y} where the fragments y of variable x have + // have at least one non-undef location defined already. Don't use directly, + // instead call DefineBits and HasDefinedBits. + SmallDenseMap> + VarsWithDef; + // Specify that V (a fragment of A) has a non-undef location. + auto DefineBits = [&VarsWithDef](DebugAggregate A, DebugVariable V) { + VarsWithDef[A].insert(V.getFragmentOrDefault()); + }; + // Return true if a non-undef location has been defined for V (a fragment of + // A). Doesn't imply that the location is currently non-undef, just that a + // non-undef location has been seen previously. + auto HasDefinedBits = [&VarsWithDef](DebugAggregate A, DebugVariable V) { + auto FragsIt = VarsWithDef.find(A); + if (FragsIt == VarsWithDef.end()) + return false; + return llvm::any_of(FragsIt->second, [V](auto Frag) { + return DIExpression::fragmentsOverlap(Frag, V.getFragmentOrDefault()); + }); + }; + + bool Changed = false; + DenseMap> VariableMap; + + // Scan over the entire block, not just over the instructions mapped by + // FnVarLocs, because wedges in FnVarLocs may only be seperated by debug + // instructions. + for (const Instruction &I : *BB) { + // Get the defs that come just before this instruction. + const auto *Locs = FnVarLocs.getWedge(&I); + if (!Locs) + continue; + + NumWedgesScanned++; + bool ChangedThisWedge = false; + // The new pruned set of defs. + SmallVector NewDefs; + + // Iterate over the existing defs. + for (const VarLocInfo &Loc : *Locs) { + NumDefsScanned++; + DebugAggregate Aggr{FnVarLocs.getVariable(Loc.VariableID).getVariable(), + Loc.DL.getInlinedAt()}; + DebugVariable Var = FnVarLocs.getVariable(Loc.VariableID); + + // Remove undef entries that are encountered before any non-undef + // intrinsics from the entry block. + if (isa(Loc.V) && !HasDefinedBits(Aggr, Var)) { + // Did not insert this Loc, which is the same as removing it. + NumDefsRemoved++; + ChangedThisWedge = true; + continue; + } + + DefineBits(Aggr, Var); + NewDefs.push_back(Loc); + } + + // Replace the existing wedge with the pruned version. + if (ChangedThisWedge) { + FnVarLocs.setWedge(&I, std::move(NewDefs)); + NumWedgesChanged++; + Changed = true; + } + } + + return Changed; +} + +static bool removeRedundantDbgLocs(const BasicBlock *BB, + FunctionVarLocsBuilder &FnVarLocs) { + bool MadeChanges = false; + MadeChanges |= removeRedundantDbgLocsUsingBackwardScan(BB, FnVarLocs); + if (BB->isEntryBlock()) + MadeChanges |= removeUndefDbgLocsFromEntryBlock(BB, FnVarLocs); + MadeChanges |= removeRedundantDbgLocsUsingForwardScan(BB, FnVarLocs); + + if (MadeChanges) + LLVM_DEBUG(dbgs() << "Removed redundant dbg locs from: " << BB->getName() + << "\n"); + return MadeChanges; +} + +static DenseSet findVarsWithStackSlot(Function &Fn) { + DenseSet Result; + for (auto &BB : Fn) { + for (auto &I : BB) { + // Any variable linked to an instruction is considered + // interesting. Ideally we only need to check Allocas, however, a + // DIAssignID might get dropped from an alloca but not stores. In that + // case, we need to consider the variable interesting for NFC behaviour + // with this change. TODO: Consider only looking at allocas. + for (DbgAssignIntrinsic *DAI : at::getAssignmentMarkers(&I)) { + Result.insert({DAI->getVariable(), DAI->getDebugLoc().getInlinedAt()}); + } + } + } + return Result; +} + +static void analyzeFunction(Function &Fn, const DataLayout &Layout, + FunctionVarLocsBuilder *FnVarLocs) { + // The analysis will generate location definitions for all variables, but we + // only need to perform a dataflow on the set of variables which have a stack + // slot. Find those now. + DenseSet VarsWithStackSlot = findVarsWithStackSlot(Fn); + + bool Changed = false; + + // Use a scope block to clean up AssignmentTrackingLowering before running + // MemLocFragmentFill to reduce peak memory consumption. + { + AssignmentTrackingLowering Pass(Fn, Layout, &VarsWithStackSlot); + Changed = Pass.run(FnVarLocs); + } + + if (Changed) { + MemLocFragmentFill Pass(Fn, &VarsWithStackSlot); + Pass.run(FnVarLocs); + + // Remove redundant entries. As well as reducing memory consumption and + // avoiding waiting cycles later by burning some now, this has another + // important job. That is to work around some SelectionDAG quirks. See + // removeRedundantDbgLocsUsingForwardScan comments for more info on that. + for (auto &BB : Fn) + removeRedundantDbgLocs(&BB, *FnVarLocs); + } +} + +bool AssignmentTrackingAnalysis::runOnFunction(Function &F) { + LLVM_DEBUG(dbgs() << "AssignmentTrackingAnalysis run on " << F.getName() + << "\n"); + auto DL = std::make_unique(F.getParent()); + + // Clear previous results. + Results->clear(); + + FunctionVarLocsBuilder Builder; + analyzeFunction(F, *DL.get(), &Builder); + + // Save these results. + Results->init(Builder); + + if (PrintResults && isFunctionInPrintList(F.getName())) + Results->print(errs(), F); + + // Return false because this pass does not modify the function. + return false; +} + +AssignmentTrackingAnalysis::AssignmentTrackingAnalysis() + : FunctionPass(ID), Results(std::make_unique()) {} + +char AssignmentTrackingAnalysis::ID = 0; + +INITIALIZE_PASS(AssignmentTrackingAnalysis, DEBUG_TYPE, + "Assignment Tracking Analysis", false, true) diff --git a/llvm/lib/CodeGen/CMakeLists.txt b/llvm/lib/CodeGen/CMakeLists.txt index 4ca8501..07817d4 100644 --- a/llvm/lib/CodeGen/CMakeLists.txt +++ b/llvm/lib/CodeGen/CMakeLists.txt @@ -26,6 +26,7 @@ add_llvm_component_library(LLVMCodeGen AggressiveAntiDepBreaker.cpp AllocationOrder.cpp Analysis.cpp + AssignmentTrackingAnalysis.cpp AtomicExpandPass.cpp BasicTargetTransformInfo.cpp BranchFolding.cpp diff --git a/llvm/lib/CodeGen/CodeGen.cpp b/llvm/lib/CodeGen/CodeGen.cpp index 11654cb..ae9e65f 100644 --- a/llvm/lib/CodeGen/CodeGen.cpp +++ b/llvm/lib/CodeGen/CodeGen.cpp @@ -19,6 +19,7 @@ using namespace llvm; /// initializeCodeGen - Initialize all passes linked into the CodeGen library. void llvm::initializeCodeGen(PassRegistry &Registry) { + initializeAssignmentTrackingAnalysisPass(Registry); initializeAtomicExpandPass(Registry); initializeBasicBlockSectionsPass(Registry); initializeBranchFolderPassPass(Registry); diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAG.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAG.cpp index cf3b80f..fbe9841 100644 --- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAG.cpp +++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAG.cpp @@ -1284,11 +1284,11 @@ SelectionDAG::SelectionDAG(const TargetMachine &tm, CodeGenOpt::Level OL) } void SelectionDAG::init(MachineFunction &NewMF, - OptimizationRemarkEmitter &NewORE, - Pass *PassPtr, const TargetLibraryInfo *LibraryInfo, - LegacyDivergenceAnalysis * Divergence, - ProfileSummaryInfo *PSIin, - BlockFrequencyInfo *BFIin) { + OptimizationRemarkEmitter &NewORE, Pass *PassPtr, + const TargetLibraryInfo *LibraryInfo, + LegacyDivergenceAnalysis *Divergence, + ProfileSummaryInfo *PSIin, BlockFrequencyInfo *BFIin, + FunctionVarLocs const *VarLocs) { MF = &NewMF; SDAGISelPass = PassPtr; ORE = &NewORE; @@ -1299,6 +1299,7 @@ void SelectionDAG::init(MachineFunction &NewMF, DA = Divergence; PSI = PSIin; BFI = BFIin; + FnVarLocs = VarLocs; } SelectionDAG::~SelectionDAG() { diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp index bb4dd41..89f9b44 100644 --- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp +++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp @@ -31,6 +31,7 @@ #include "llvm/Analysis/TargetLibraryInfo.h" #include "llvm/Analysis/ValueTracking.h" #include "llvm/CodeGen/Analysis.h" +#include "llvm/CodeGen/AssignmentTrackingAnalysis.h" #include "llvm/CodeGen/CodeGenCommonISel.h" #include "llvm/CodeGen/FunctionLoweringInfo.h" #include "llvm/CodeGen/GCMetadata.h" @@ -63,6 +64,7 @@ #include "llvm/IR/ConstantRange.h" #include "llvm/IR/Constants.h" #include "llvm/IR/DataLayout.h" +#include "llvm/IR/DebugInfo.h" #include "llvm/IR/DebugInfoMetadata.h" #include "llvm/IR/DerivedTypes.h" #include "llvm/IR/DiagnosticInfo.h" @@ -1135,6 +1137,21 @@ void SelectionDAGBuilder::visit(const Instruction &I) { HandlePHINodesInSuccessorBlocks(I.getParent()); } + // Add SDDbgValue nodes for any var locs here. Do so before updating + // SDNodeOrder, as this mapping is {Inst -> Locs BEFORE Inst}. + if (FunctionVarLocs const *FnVarLocs = DAG.getFunctionVarLocs()) { + // Add SDDbgValue nodes for any var locs here. Do so before updating + // SDNodeOrder, as this mapping is {Inst -> Locs BEFORE Inst}. + for (auto It = FnVarLocs->locs_begin(&I), End = FnVarLocs->locs_end(&I); + It != End; ++It) { + auto *Var = FnVarLocs->getDILocalVariable(It->VariableID); + dropDanglingDebugInfo(Var, It->Expr); + if (!handleDebugValue(It->V, Var, It->Expr, It->DL, SDNodeOrder, + /*IsVariadic=*/false)) + addDanglingDebugInfo(It, SDNodeOrder); + } + } + // Increase the SDNodeOrder if dealing with a non-debug instruction. if (!isa(I)) ++SDNodeOrder; @@ -1190,6 +1207,11 @@ void SelectionDAGBuilder::visit(unsigned Opcode, const User &I) { } } +void SelectionDAGBuilder::addDanglingDebugInfo(const VarLocInfo *VarLoc, + unsigned Order) { + DanglingDebugInfoMap[VarLoc->V].emplace_back(VarLoc, Order); +} + void SelectionDAGBuilder::addDanglingDebugInfo(const DbgValueInst *DI, unsigned Order) { // We treat variadic dbg_values differently at this stage. @@ -1220,10 +1242,11 @@ void SelectionDAGBuilder::addDanglingDebugInfo(const DbgValueInst *DI, void SelectionDAGBuilder::dropDanglingDebugInfo(const DILocalVariable *Variable, const DIExpression *Expr) { auto isMatchingDbgValue = [&](DanglingDebugInfo &DDI) { - DIVariable *DanglingVariable = DDI.getVariable(); + DIVariable *DanglingVariable = DDI.getVariable(DAG.getFunctionVarLocs()); DIExpression *DanglingExpr = DDI.getExpression(); if (DanglingVariable == Variable && Expr->fragmentsOverlap(DanglingExpr)) { - LLVM_DEBUG(dbgs() << "Dropping dangling debug info for " << DDI << "\n"); + LLVM_DEBUG(dbgs() << "Dropping dangling debug info for " << printDDI(DDI) + << "\n"); return true; } return false; @@ -1255,7 +1278,7 @@ void SelectionDAGBuilder::resolveDanglingDebugInfo(const Value *V, DebugLoc DL = DDI.getDebugLoc(); unsigned ValSDNodeOrder = Val.getNode()->getIROrder(); unsigned DbgSDNodeOrder = DDI.getSDNodeOrder(); - DILocalVariable *Variable = DDI.getVariable(); + DILocalVariable *Variable = DDI.getVariable(DAG.getFunctionVarLocs()); DIExpression *Expr = DDI.getExpression(); assert(Variable->isValidLocationForIntrinsic(DL) && "Expected inlined-at fields to agree"); @@ -1269,7 +1292,8 @@ void SelectionDAGBuilder::resolveDanglingDebugInfo(const Value *V, // calling EmitFuncArgumentDbgValue here. if (!EmitFuncArgumentDbgValue(V, Variable, Expr, DL, FuncArgumentDbgValueKind::Value, Val)) { - LLVM_DEBUG(dbgs() << "Resolve dangling debug info for " << DDI << "\n"); + LLVM_DEBUG(dbgs() << "Resolve dangling debug info for " << printDDI(DDI) + << "\n"); LLVM_DEBUG(dbgs() << " By mapping to:\n "; Val.dump()); // Increase the SDNodeOrder for the DbgValue here to make sure it is // inserted after the definition of Val when emitting the instructions @@ -1282,10 +1306,10 @@ void SelectionDAGBuilder::resolveDanglingDebugInfo(const Value *V, std::max(DbgSDNodeOrder, ValSDNodeOrder)); DAG.AddDbgValue(SDV, false); } else - LLVM_DEBUG(dbgs() << "Resolved dangling debug info for " << DDI - << "in EmitFuncArgumentDbgValue\n"); + LLVM_DEBUG(dbgs() << "Resolved dangling debug info for " + << printDDI(DDI) << " in EmitFuncArgumentDbgValue\n"); } else { - LLVM_DEBUG(dbgs() << "Dropping debug info for " << DDI << "\n"); + LLVM_DEBUG(dbgs() << "Dropping debug info for " << printDDI(DDI) << "\n"); auto Undef = UndefValue::get(V->getType()); auto SDV = DAG.getConstantDbgValue(Variable, Expr, Undef, DL, DbgSDNodeOrder); @@ -1302,7 +1326,7 @@ void SelectionDAGBuilder::salvageUnresolvedDbgValue(DanglingDebugInfo &DDI) { // a DIArgList. Value *V = DDI.getVariableLocationOp(0); Value *OrigV = V; - DILocalVariable *Var = DDI.getVariable(); + DILocalVariable *Var = DDI.getVariable(DAG.getFunctionVarLocs()); DIExpression *Expr = DDI.getExpression(); DebugLoc DL = DDI.getDebugLoc(); unsigned SDOrder = DDI.getSDNodeOrder(); @@ -1356,7 +1380,8 @@ void SelectionDAGBuilder::salvageUnresolvedDbgValue(DanglingDebugInfo &DDI) { auto *Undef = UndefValue::get(OrigV->getType()); auto *SDV = DAG.getConstantDbgValue(Var, Expr, Undef, DL, SDNodeOrder); DAG.AddDbgValue(SDV, false); - LLVM_DEBUG(dbgs() << "Dropping debug value info for:\n " << DDI << "\n"); + LLVM_DEBUG(dbgs() << "Dropping debug value info for:\n " << printDDI(DDI) + << "\n"); } bool SelectionDAGBuilder::handleDebugValue(ArrayRef Values, @@ -6066,6 +6091,9 @@ void SelectionDAGBuilder::visitIntrinsicCall(const CallInst &I, } case Intrinsic::dbg_addr: case Intrinsic::dbg_declare: { + // Debug intrinsics are handled seperately in assignment tracking mode. + if (getEnableAssignmentTracking()) + return; // Assume dbg.addr and dbg.declare can not currently use DIArgList, i.e. // they are non-variadic. const auto &DI = cast(I); @@ -6165,7 +6193,16 @@ void SelectionDAGBuilder::visitIntrinsicCall(const CallInst &I, DAG.AddDbgLabel(SDV); return; } + case Intrinsic::dbg_assign: { + // Debug intrinsics are handled seperately in assignment tracking mode. + assert(getEnableAssignmentTracking() && + "expected assignment tracking to be enabled"); + return; + } case Intrinsic::dbg_value: { + // Debug intrinsics are handled seperately in assignment tracking mode. + if (getEnableAssignmentTracking()) + return; const DbgValueInst &DI = cast(I); assert(DI.getVariable() && "Missing variable"); diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.h b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.h index 4dcf14a..bf21110 100644 --- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.h +++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.h @@ -18,6 +18,7 @@ #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/MapVector.h" #include "llvm/ADT/SmallVector.h" +#include "llvm/CodeGen/AssignmentTrackingAnalysis.h" #include "llvm/CodeGen/CodeGenCommonISel.h" #include "llvm/CodeGen/ISDOpcodes.h" #include "llvm/CodeGen/SelectionDAGNodes.h" @@ -105,37 +106,68 @@ class SelectionDAGBuilder { /// Helper type for DanglingDebugInfoMap. class DanglingDebugInfo { - const DbgValueInst *DI = nullptr; + using DbgValTy = const DbgValueInst *; + using VarLocTy = const VarLocInfo *; + PointerUnion Info; unsigned SDNodeOrder = 0; public: DanglingDebugInfo() = default; DanglingDebugInfo(const DbgValueInst *DI, unsigned SDNO) - : DI(DI), SDNodeOrder(SDNO) { - assert(!DI->hasArgList() && - "Dangling variadic debug values not supported yet"); + : Info(DI), SDNodeOrder(SDNO) {} + DanglingDebugInfo(const VarLocInfo *VarLoc, unsigned SDNO) + : Info(VarLoc), SDNodeOrder(SDNO) {} + + DILocalVariable *getVariable(const FunctionVarLocs *Locs) const { + if (Info.is()) + return Locs->getDILocalVariable(Info.get()->VariableID); + return Info.get()->getVariable(); + } + DIExpression *getExpression() const { + if (Info.is()) + return Info.get()->Expr; + return Info.get()->getExpression(); } - - DILocalVariable *getVariable() const { return DI->getVariable(); } - DIExpression *getExpression() const { return DI->getExpression(); } Value *getVariableLocationOp(unsigned Idx) const { assert(Idx == 0 && "Dangling variadic debug values not supported yet"); - return DI->getVariableLocationOp(Idx); + if (Info.is()) + return Info.get()->V; + return Info.get()->getVariableLocationOp(Idx); + } + DebugLoc getDebugLoc() const { + if (Info.is()) + return Info.get()->DL; + return Info.get()->getDebugLoc(); } - DebugLoc getDebugLoc() const { return DI->getDebugLoc(); } unsigned getSDNodeOrder() const { return SDNodeOrder; } - friend raw_ostream &operator<<(raw_ostream &OS, - const DanglingDebugInfo &Info) { - OS << "DDI(var=" << *Info.getVariable() - << ", val= " << *Info.getVariableLocationOp(0) - << ", expr=" << *Info.getExpression() - << ", order=" << Info.getSDNodeOrder() - << ", loc=" << Info.getDebugLoc() << ")"; - return OS; - } + /// Helper for printing DanglingDebugInfo. This hoop-jumping is to + /// accommodate the fact that an argument is required for getVariable. + /// Call SelectionDAGBuilder::printDDI instead of using directly. + struct Print { + Print(const DanglingDebugInfo &DDI, const FunctionVarLocs *VarLocs) + : DDI(DDI), VarLocs(VarLocs) {} + const DanglingDebugInfo &DDI; + const FunctionVarLocs *VarLocs; + friend raw_ostream &operator<<(raw_ostream &OS, + const DanglingDebugInfo::Print &P) { + OS << "DDI(var=" << *P.DDI.getVariable(P.VarLocs) + << ", val= " << *P.DDI.getVariableLocationOp(0) + << ", expr=" << *P.DDI.getExpression() + << ", order=" << P.DDI.getSDNodeOrder() + << ", loc=" << P.DDI.getDebugLoc() << ")"; + return OS; + } + }; }; + /// Returns an object that defines `raw_ostream &operator<<` for printing. + /// Usage example: + //// errs() << printDDI(MyDanglingInfo) << " is dangling\n"; + DanglingDebugInfo::Print printDDI(const DanglingDebugInfo &DDI) { + return DanglingDebugInfo::Print(DDI, DAG.getFunctionVarLocs()); + } + /// Helper type for DanglingDebugInfoMap. typedef std::vector DanglingDebugInfoVector; @@ -317,6 +349,7 @@ public: /// Register a dbg_value which relies on a Value which we have not yet seen. void addDanglingDebugInfo(const DbgValueInst *DI, unsigned Order); + void addDanglingDebugInfo(const VarLocInfo *VarLoc, unsigned Order); /// If we have dangling debug info that describes \p Variable, or an /// overlapping part of variable considering the \p Expr, then this method diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp index 30fab77..8f47090 100644 --- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp +++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp @@ -32,6 +32,7 @@ #include "llvm/Analysis/ProfileSummaryInfo.h" #include "llvm/Analysis/TargetLibraryInfo.h" #include "llvm/Analysis/TargetTransformInfo.h" +#include "llvm/CodeGen/AssignmentTrackingAnalysis.h" #include "llvm/CodeGen/CodeGenCommonISel.h" #include "llvm/CodeGen/FastISel.h" #include "llvm/CodeGen/FunctionLoweringInfo.h" @@ -62,6 +63,7 @@ #include "llvm/IR/BasicBlock.h" #include "llvm/IR/Constants.h" #include "llvm/IR/DataLayout.h" +#include "llvm/IR/DebugInfo.h" #include "llvm/IR/DebugInfoMetadata.h" #include "llvm/IR/DebugLoc.h" #include "llvm/IR/DiagnosticInfo.h" @@ -342,6 +344,10 @@ void SelectionDAGISel::getAnalysisUsage(AnalysisUsage &AU) const { if (UseMBPI && OptLevel != CodeGenOpt::None) AU.addRequired(); AU.addRequired(); + if (getEnableAssignmentTracking()) { + AU.addRequired(); + AU.addPreserved(); + } if (OptLevel != CodeGenOpt::None) LazyBlockFrequencyInfoPass::getLazyBFIAnalysisUsage(AU); MachineFunctionPass::getAnalysisUsage(AU); @@ -412,10 +418,15 @@ bool SelectionDAGISel::runOnMachineFunction(MachineFunction &mf) { if (PSI && PSI->hasProfileSummary() && OptLevel != CodeGenOpt::None) BFI = &getAnalysis().getBFI(); + FunctionVarLocs const *FnVarLocs = nullptr; + if (getEnableAssignmentTracking()) + FnVarLocs = getAnalysis().getResults(); + LLVM_DEBUG(dbgs() << "\n\n\n=== " << Fn.getName() << "\n"); CurDAG->init(*MF, *ORE, this, LibInfo, - getAnalysisIfAvailable(), PSI, BFI); + getAnalysisIfAvailable(), PSI, BFI, + FnVarLocs); FuncInfo->set(Fn, *MF, CurDAG); SwiftError->setFunction(*MF); @@ -1292,56 +1303,75 @@ static bool isFoldedOrDeadInstruction(const Instruction *I, !FuncInfo.isExportedInst(I); // Exported instrs must be computed. } +static void processDbgDeclare(FunctionLoweringInfo &FuncInfo, + const Value *Address, DIExpression *Expr, + DILocalVariable *Var, DebugLoc DbgLoc) { + MachineFunction *MF = FuncInfo.MF; + const DataLayout &DL = MF->getDataLayout(); + + assert(Var && "Missing variable"); + assert(DbgLoc && "Missing location"); + + // Look through casts and constant offset GEPs. These mostly come from + // inalloca. + APInt Offset(DL.getTypeSizeInBits(Address->getType()), 0); + Address = Address->stripAndAccumulateInBoundsConstantOffsets(DL, Offset); + + // Check if the variable is a static alloca or a byval or inalloca + // argument passed in memory. If it is not, then we will ignore this + // intrinsic and handle this during isel like dbg.value. + int FI = std::numeric_limits::max(); + if (const auto *AI = dyn_cast(Address)) { + auto SI = FuncInfo.StaticAllocaMap.find(AI); + if (SI != FuncInfo.StaticAllocaMap.end()) + FI = SI->second; + } else if (const auto *Arg = dyn_cast(Address)) + FI = FuncInfo.getArgumentFrameIndex(Arg); + + if (FI == std::numeric_limits::max()) + return; + + if (Offset.getBoolValue()) + Expr = DIExpression::prepend(Expr, DIExpression::ApplyOffset, + Offset.getZExtValue()); + + LLVM_DEBUG(dbgs() << "processDbgDeclare: setVariableDbgInfo Var=" << *Var + << ", Expr=" << *Expr << ", FI=" << FI + << ", DbgLoc=" << DbgLoc << "\n"); + MF->setVariableDbgInfo(Var, Expr, FI, DbgLoc); +} + /// Collect llvm.dbg.declare information. This is done after argument lowering /// in case the declarations refer to arguments. static void processDbgDeclares(FunctionLoweringInfo &FuncInfo) { - MachineFunction *MF = FuncInfo.MF; - const DataLayout &DL = MF->getDataLayout(); for (const BasicBlock &BB : *FuncInfo.Fn) { for (const Instruction &I : BB) { - const DbgDeclareInst *DI = dyn_cast(&I); - if (!DI) - continue; - - assert(DI->getVariable() && "Missing variable"); - assert(DI->getDebugLoc() && "Missing location"); - const Value *Address = DI->getAddress(); - if (!Address) { - LLVM_DEBUG(dbgs() << "processDbgDeclares skipping " << *DI - << " (bad address)\n"); - continue; + if (const DbgDeclareInst *DI = dyn_cast(&I)) { + Value *Address = DI->getAddress(); + if (!Address) { + LLVM_DEBUG(dbgs() << "processDbgDeclares skipping " << *DI + << " (bad address)\n"); + return; + } + processDbgDeclare(FuncInfo, Address, DI->getExpression(), + DI->getVariable(), DI->getDebugLoc()); } - - // Look through casts and constant offset GEPs. These mostly come from - // inalloca. - APInt Offset(DL.getTypeSizeInBits(Address->getType()), 0); - Address = Address->stripAndAccumulateInBoundsConstantOffsets(DL, Offset); - - // Check if the variable is a static alloca or a byval or inalloca - // argument passed in memory. If it is not, then we will ignore this - // intrinsic and handle this during isel like dbg.value. - int FI = std::numeric_limits::max(); - if (const auto *AI = dyn_cast(Address)) { - auto SI = FuncInfo.StaticAllocaMap.find(AI); - if (SI != FuncInfo.StaticAllocaMap.end()) - FI = SI->second; - } else if (const auto *Arg = dyn_cast(Address)) - FI = FuncInfo.getArgumentFrameIndex(Arg); - - if (FI == std::numeric_limits::max()) - continue; - - DIExpression *Expr = DI->getExpression(); - if (Offset.getBoolValue()) - Expr = DIExpression::prepend(Expr, DIExpression::ApplyOffset, - Offset.getZExtValue()); - LLVM_DEBUG(dbgs() << "processDbgDeclares: setVariableDbgInfo FI=" << FI - << ", " << *DI << "\n"); - MF->setVariableDbgInfo(DI->getVariable(), Expr, FI, DI->getDebugLoc()); } } } +/// Collect single location variable information generated with assignment +/// tracking. This is done after argument lowering in case the declarations +/// refer to arguments. +static void processSingleLocVars(FunctionLoweringInfo &FuncInfo, + FunctionVarLocs const *FnVarLocs) { + for (auto It = FnVarLocs->single_locs_begin(), + End = FnVarLocs->single_locs_end(); + It != End; ++It) + processDbgDeclare(FuncInfo, It->V, It->Expr, + FnVarLocs->getDILocalVariable(It->VariableID), It->DL); +} + void SelectionDAGISel::SelectAllBasicBlocks(const Function &Fn) { FastISelFailed = false; // Initialize the Fast-ISel state, if needed. @@ -1404,7 +1434,13 @@ void SelectionDAGISel::SelectAllBasicBlocks(const Function &Fn) { if (FastIS && Inserted) FastIS->setLastLocalValue(&*std::prev(FuncInfo->InsertPt)); - processDbgDeclares(*FuncInfo); + if (getEnableAssignmentTracking()) { + assert(CurDAG->getFunctionVarLocs() && + "expected AssignmentTrackingAnalysis pass results"); + processSingleLocVars(*FuncInfo, CurDAG->getFunctionVarLocs()); + } else { + processDbgDeclares(*FuncInfo); + } // Iterate over all basic blocks in the function. StackProtector &SP = getAnalysis(); diff --git a/llvm/test/DebugInfo/assignment-tracking/X86/DSE.ll b/llvm/test/DebugInfo/assignment-tracking/X86/DSE.ll new file mode 100644 index 0000000..3be5601 --- /dev/null +++ b/llvm/test/DebugInfo/assignment-tracking/X86/DSE.ll @@ -0,0 +1,72 @@ +; RUN: llc %s -stop-before=finalize-isel -o - \ +; RUN: -experimental-assignment-tracking \ +; RUN: | FileCheck %s + +; Check basic lowering behaviour of dbg.assign intrinsics. The first +; assignment to `local`, which has been DSE'd, should be represented with a +; constant value DBG_VALUE. The second assignment should have a DBG_VALUE +; describing the stack home of the variable. + +; $ cat test.c +; void esc(int*); +; void fun() { +; int local = 5; +; // ^ killed by v +; local = 6; +; esc(&local); +; } +; $ clang -O2 -g -emit -llvm -S test.c -o - + +; CHECK: ![[LOCAL:[0-9]+]] = !DILocalVariable(name: "local", +; CHECK: DBG_VALUE 5, $noreg, ![[LOCAL]], !DIExpression(), debug-location ![[DBG:[0-9]+]] +; CHECK-NEXT: MOV32mi [[DEST:.*]], 1, $noreg, 0, $noreg, 6 +; CHECK-NEXT: DBG_VALUE [[DEST]], $noreg, ![[LOCAL]], !DIExpression(DW_OP_deref), debug-location ![[DBG]] + +target triple = "x86_64-unknown-linux-gnu" + +define dso_local void @fun() local_unnamed_addr !dbg !7 { +entry: + %local = alloca i32, align 4 + call void @llvm.dbg.assign(metadata i32 5, metadata !11, metadata !DIExpression(), metadata !30, metadata ptr %local, metadata !DIExpression()), !dbg !16 + store i32 6, ptr %local, align 4, !dbg !23, !DIAssignID !31 + call void @llvm.dbg.assign(metadata i32 6, metadata !11, metadata !DIExpression(), metadata !31, metadata ptr %local, metadata !DIExpression()), !dbg !16 + call void @esc(ptr nonnull %local), !dbg !24 + ret void, !dbg !25 +} + +declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) + +declare !dbg !26 dso_local void @esc(ptr) local_unnamed_addr + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!3, !4, !5} +!llvm.ident = !{!6} + +!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang version 12.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "test.c", directory: "/") +!2 = !{} +!3 = !{i32 7, !"Dwarf Version", i32 4} +!4 = !{i32 2, !"Debug Info Version", i32 3} +!5 = !{i32 1, !"wchar_size", i32 4} +!6 = !{!"clang version 12.0.0"} +!7 = distinct !DISubprogram(name: "fun", scope: !1, file: !1, line: 2, type: !8, scopeLine: 2, flags: DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !10) +!8 = !DISubroutineType(types: !9) +!9 = !{null} +!10 = !{!11} +!11 = !DILocalVariable(name: "local", scope: !7, file: !1, line: 3, type: !12) +!12 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!14 = !DIDerivedType(tag: DW_TAG_volatile_type, baseType: !12) +!15 = !DILocation(line: 3, column: 3, scope: !7) +!16 = !DILocation(line: 0, scope: !7) +!17 = !DILocation(line: 4, column: 3, scope: !7) +!18 = !DILocation(line: 4, column: 16, scope: !7) +!23 = !DILocation(line: 5, column: 9, scope: !7) +!24 = !DILocation(line: 6, column: 3, scope: !7) +!25 = !DILocation(line: 7, column: 1, scope: !7) +!26 = !DISubprogram(name: "esc", scope: !1, file: !1, line: 1, type: !27, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !2) +!27 = !DISubroutineType(types: !28) +!28 = !{null, !29} +!29 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !12, size: 64) +!30 = distinct !DIAssignID() +!31 = distinct !DIAssignID() + diff --git a/llvm/test/DebugInfo/assignment-tracking/X86/dbg-phi-produces-undef.ll b/llvm/test/DebugInfo/assignment-tracking/X86/dbg-phi-produces-undef.ll new file mode 100644 index 0000000..0254de04 --- /dev/null +++ b/llvm/test/DebugInfo/assignment-tracking/X86/dbg-phi-produces-undef.ll @@ -0,0 +1,106 @@ +; RUN: llc %s -stop-after=finalize-isel -o - -experimental-assignment-tracking \ +; RUN: | FileCheck %s + +;; Hand written test because the scenario is unlikely. Check that the "value" +;; of a debug def PHIs is "undef" (because we don't actually track PHIs). +;; +;; entry: +;; memdef 5, !1 +;; dbgdef !1 +;; br if.then, exit +;; +;; if.then: +;; memdef 0, !1 +;; dbgdef !1 +;; br exit +;; +;; exit: +;; ; <-- Dbg=!1, Stack=!1, Loc=Mem +;; memddef 1, !2 ; <-- Dbg=!1, Stack=!2, Loc=Val(undef) @HERE +;; call +;; dbgdef !2 +;; +;; Check that the dbg.value inserted at @HERE is undef because there's no +;; appropriate alternative value to choose. + +; CHECK: bb.0.entry: +; CHECK: DBG_VALUE %stack.0.c, $noreg, ![[var:[0-9]+]], !DIExpression(DW_OP_deref), debug-location +; CHECK-NEXT: MOV8mi %stack.0.c, 1, $noreg, 0, $noreg, 5, debug-location +; CHECL-NEXT: DBG_VALUE %stack.0.c, $noreg, ![[var]], !DIExpression(DW_OP_deref), debug-location + +; CHECK: bb.1.if.then: +; CHECK: MOV8mi %stack.0.c, 1, $noreg, 0, $noreg, 0 +; CHECK: DBG_VALUE %stack.0.c, $noreg, ![[var]], !DIExpression(DW_OP_deref), debug-location + +; CHECK: bb.2.exit: +; CHECK-NEXT: MOV8mi %stack.0.c, 1, $noreg, 0, $noreg, 1 +;; @HERE +; CHECK-NEXT: DBG_VALUE $noreg, $noreg, ![[var]], !DIExpression() +; CHECK: CALL64pcrel32 @d +; CHECK-NEXT: ADJCALLSTACKUP64 +; CHECK-NEXT: DBG_VALUE %stack.0.c, $noreg, ![[var]], !DIExpression(DW_OP_deref), debug-location + +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +; Function Attrs: nounwind uwtable +define dso_local void @b(i1 %cond) local_unnamed_addr #0 !dbg !7 { +entry: + %c = alloca i8, align 1, !DIAssignID !13 + call void @llvm.dbg.assign(metadata i1 undef, metadata !11, metadata !DIExpression(), metadata !13, metadata ptr %c, metadata !DIExpression()), !dbg !14 + store i8 5, ptr %c, align 1, !dbg !16, !DIAssignID !31 + call void @llvm.dbg.assign(metadata i8 5, metadata !11, metadata !DIExpression(), metadata !31, metadata ptr %c, metadata !DIExpression()), !dbg !14 + br i1 %cond, label %if.then, label %exit + +if.then: + tail call void (...) @d() #4, !dbg !21 + store i8 0, ptr %c, align 1, !dbg !16, !DIAssignID !31 + call void @llvm.dbg.assign(metadata i8 0, metadata !11, metadata !DIExpression(), metadata !31, metadata ptr %c, metadata !DIExpression()), !dbg !14 + br label %exit + +exit: + store i8 1, ptr %c, align 1, !dbg !16, !DIAssignID !20 + tail call void (...) @d() #4, !dbg !21 + call void @llvm.dbg.assign(metadata i8 1, metadata !11, metadata !DIExpression(), metadata !20, metadata ptr %c, metadata !DIExpression()), !dbg !14 + call void @a(ptr nonnull %c) #4, !dbg !22 + ret void, !dbg !23 +} + + +declare !dbg !24 dso_local void @d(...) local_unnamed_addr #2 +declare !dbg !27 dso_local void @a(ptr) local_unnamed_addr #2 +declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) #3 + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!3, !4, !5} +!llvm.ident = !{!6} + +!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang version 12.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "reduce.c", directory: "/") +!2 = !{} +!3 = !{i32 7, !"Dwarf Version", i32 4} +!4 = !{i32 2, !"Debug Info Version", i32 3} +!5 = !{i32 1, !"wchar_size", i32 4} +!6 = !{!"clang version 12.0.0"} +!7 = distinct !DISubprogram(name: "b", scope: !1, file: !1, line: 3, type: !8, scopeLine: 3, flags: DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !10) +!8 = !DISubroutineType(types: !9) +!9 = !{null} +!10 = !{!11} +!11 = !DILocalVariable(name: "c", scope: !7, file: !1, line: 4, type: !12) +!12 = !DIBasicType(name: "char", size: 8, encoding: DW_ATE_signed_char) +!13 = distinct !DIAssignID() +!14 = !DILocation(line: 0, scope: !7) +!15 = !DILocation(line: 4, column: 3, scope: !7) +!16 = !DILocation(line: 4, column: 8, scope: !7) +!20 = distinct !DIAssignID() +!21 = !DILocation(line: 5, column: 3, scope: !7) +!22 = !DILocation(line: 6, column: 3, scope: !7) +!23 = !DILocation(line: 7, column: 1, scope: !7) +!24 = !DISubprogram(name: "d", scope: !1, file: !1, line: 2, type: !25, spFlags: DISPFlagOptimized, retainedNodes: !2) +!25 = !DISubroutineType(types: !26) +!26 = !{null, null} +!27 = !DISubprogram(name: "a", scope: !1, file: !1, line: 1, type: !28, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !2) +!28 = !DISubroutineType(types: !29) +!29 = !{null, !30} +!30 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !12, size: 64) +!31 = distinct !DIAssignID() diff --git a/llvm/test/DebugInfo/assignment-tracking/X86/diamond-1.ll b/llvm/test/DebugInfo/assignment-tracking/X86/diamond-1.ll new file mode 100644 index 0000000..2d49dd8 --- /dev/null +++ b/llvm/test/DebugInfo/assignment-tracking/X86/diamond-1.ll @@ -0,0 +1,123 @@ +; RUN: llc %s -stop-after=finalize-isel -o - -experimental-assignment-tracking \ +; RUN: | FileCheck %s + +;; cat test.cpp +;; void d(); +;; void e(); +;; void es(int*); +;; int f(int a) { +;; if (a) { +;; e(); +;; a = 100; +;; } else { +;; d(); +;; a = 500; +;; } +;; es(&a); +;; return a; +;; } +;; $ clang++ test.cpp -S -emit-llvm -Xclang -fexperimental-assignment-tracking + +;; Check that the memory location is selected after the store in if.end: +;; entry: +;; a = param-value +;; if.then: +;; a = 100 +;; if.else: +;; a = 500 +;; if.end: +;; store (phi if.then: 100, if.else: 500) +;; a = in memory + +; CHECK-DAG: ![[VAR:[0-9]+]] = !DILocalVariable(name: "a", + +; CHECK: bb.0.entry: +; CHECK: DBG_VALUE $edi, $noreg, ![[VAR]], !DIExpression() + +; CHECK: bb.1.if.then: +; CHECK: DBG_VALUE 100, $noreg, ![[VAR]], !DIExpression() + +; CHECK: bb.2.if.else: +; CHECK: DBG_VALUE 500, $noreg, ![[VAR]], !DIExpression() + +; CHECK: bb.3.if.end: +; CHECK-NEXT: %0:gr32 = PHI %2, %bb.1, %3, %bb.2 +; CHECK-NEXT: MOV32mr %stack.0.a.addr, 1, $noreg, 0, $noreg, %0 +; CHECK-NEXT: DBG_VALUE %stack.0.a.addr, $noreg, ![[VAR]], !DIExpression(DW_OP_deref) + +target triple = "x86_64-unknown-linux-gnu" + +; Function Attrs: mustprogress uwtable +define dso_local noundef i32 @_Z1fi(i32 noundef %a) local_unnamed_addr #0 !dbg !7 { +entry: + %a.addr = alloca i32, align 4, !DIAssignID !13 + call void @llvm.dbg.assign(metadata i1 undef, metadata !12, metadata !DIExpression(), metadata !13, metadata ptr %a.addr, metadata !DIExpression()), !dbg !14 + call void @llvm.dbg.assign(metadata i32 %a, metadata !12, metadata !DIExpression(), metadata !15, metadata ptr %a.addr, metadata !DIExpression()), !dbg !14 + %tobool.not = icmp eq i32 %a, 0, !dbg !16 + br i1 %tobool.not, label %if.else, label %if.then, !dbg !18 + +if.then: ; preds = %entry + tail call void @_Z1ev(), !dbg !19 + call void @llvm.dbg.assign(metadata i32 100, metadata !12, metadata !DIExpression(), metadata !21, metadata ptr %a.addr, metadata !DIExpression()), !dbg !14 + br label %if.end, !dbg !22 + +if.else: ; preds = %entry + tail call void @_Z1dv(), !dbg !23 + call void @llvm.dbg.assign(metadata i32 500, metadata !12, metadata !DIExpression(), metadata !21, metadata ptr %a.addr, metadata !DIExpression()), !dbg !14 + br label %if.end + +if.end: ; preds = %if.else, %if.then + %storemerge = phi i32 [ 500, %if.else ], [ 100, %if.then ], !dbg !25 + store i32 %storemerge, ptr %a.addr, align 4, !dbg !25, !DIAssignID !21 + call void @_Z2esPi(ptr noundef nonnull %a.addr), !dbg !30 + %0 = load i32, ptr %a.addr, align 4, !dbg !31 + ret i32 %0, !dbg !32 +} + +declare !dbg !33 dso_local void @_Z1ev() local_unnamed_addr #1 +declare !dbg !37 dso_local void @_Z1dv() local_unnamed_addr #1 +declare !dbg !38 dso_local void @_Z2esPi(ptr noundef) local_unnamed_addr #1 +declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) #2 + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!2, !3, !4, !5} +!llvm.ident = !{!6} + +!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang version 14.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "test.cpp", directory: "/") +!2 = !{i32 7, !"Dwarf Version", i32 5} +!3 = !{i32 2, !"Debug Info Version", i32 3} +!4 = !{i32 1, !"wchar_size", i32 4} +!5 = !{i32 7, !"uwtable", i32 1} +!6 = !{!"clang version 14.0.0"} +!7 = distinct !DISubprogram(name: "f", linkageName: "_Z1fi", scope: !1, file: !1, line: 4, type: !8, scopeLine: 4, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !11) +!8 = !DISubroutineType(types: !9) +!9 = !{!10, !10} +!10 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!11 = !{!12} +!12 = !DILocalVariable(name: "a", arg: 1, scope: !7, file: !1, line: 4, type: !10) +!13 = distinct !DIAssignID() +!14 = !DILocation(line: 0, scope: !7) +!15 = distinct !DIAssignID() +!16 = !DILocation(line: 5, column: 7, scope: !17) +!17 = distinct !DILexicalBlock(scope: !7, file: !1, line: 5, column: 7) +!18 = !DILocation(line: 5, column: 7, scope: !7) +!19 = !DILocation(line: 6, column: 5, scope: !20) +!20 = distinct !DILexicalBlock(scope: !17, file: !1, line: 5, column: 10) +!21 = distinct !DIAssignID() +!22 = !DILocation(line: 8, column: 3, scope: !20) +!23 = !DILocation(line: 9, column: 5, scope: !24) +!24 = distinct !DILexicalBlock(scope: !17, file: !1, line: 8, column: 10) +!25 = !DILocation(line: 0, scope: !17) +!30 = !DILocation(line: 12, column: 3, scope: !7) +!31 = !DILocation(line: 13, column: 10, scope: !7) +!32 = !DILocation(line: 13, column: 3, scope: !7) +!33 = !DISubprogram(name: "e", linkageName: "_Z1ev", scope: !1, file: !1, line: 2, type: !34, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !36) +!34 = !DISubroutineType(types: !35) +!35 = !{null} +!36 = !{} +!37 = !DISubprogram(name: "d", linkageName: "_Z1dv", scope: !1, file: !1, line: 1, type: !34, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !36) +!38 = !DISubprogram(name: "es", linkageName: "_Z2esPi", scope: !1, file: !1, line: 3, type: !39, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !36) +!39 = !DISubroutineType(types: !40) +!40 = !{null, !41} +!41 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !10, size: 64) diff --git a/llvm/test/DebugInfo/assignment-tracking/X86/diamond-2.ll b/llvm/test/DebugInfo/assignment-tracking/X86/diamond-2.ll new file mode 100644 index 0000000..3c963d2 --- /dev/null +++ b/llvm/test/DebugInfo/assignment-tracking/X86/diamond-2.ll @@ -0,0 +1,111 @@ +; RUN: llc %s -stop-after=finalize-isel -o - -experimental-assignment-tracking \ +; RUN: | FileCheck %s + +;; Same as diamond-1.ll except that the DIAssignID attached to the store has +;; been deleted. In this case, we expect the same output as for diamond-1.ll +;; because we choose to interpret stores to stack slots that don't link to +;; debug intrinsics for a variable in the stack slot as a though they're linked +;; to one placed immediately after. +;; +;; entry: +;; a = param-value +;; if.then: +;; a = 100 +;; if.else: +;; a = 500 +;; if.end: +;; store (phi if.then: 100, if.else: 500) +;; a = in memory + +; CHECK-DAG: ![[VAR:[0-9]+]] = !DILocalVariable(name: "a", + +; CHECK: bb.0.entry: +; CHECK: DBG_VALUE $edi, $noreg, ![[VAR]], !DIExpression() + +; CHECK: bb.1.if.then: +; CHECK: DBG_VALUE 100, $noreg, ![[VAR]], !DIExpression() + +; CHECK: bb.2.if.else: +; CHECK: DBG_VALUE 500, $noreg, ![[VAR]], !DIExpression() + +; CHECK: bb.3.if.end: +; CHECK-NEXT: %0:gr32 = PHI %2, %bb.1, %3, %bb.2 +; CHECK-NEXT: MOV32mr %stack.0.a.addr, 1, $noreg, 0, $noreg, %0 +; CHECK-NEXT: DBG_VALUE %stack.0.a.addr, $noreg, ![[VAR]], !DIExpression(DW_OP_deref) + +target triple = "x86_64-unknown-linux-gnu" + +; Function Attrs: mustprogress uwtable +define dso_local noundef i32 @_Z1fi(i32 noundef %a) local_unnamed_addr #0 !dbg !7 { +entry: + %a.addr = alloca i32, align 4, !DIAssignID !13 + call void @llvm.dbg.assign(metadata i1 undef, metadata !12, metadata !DIExpression(), metadata !13, metadata ptr %a.addr, metadata !DIExpression()), !dbg !14 + call void @llvm.dbg.assign(metadata i32 %a, metadata !12, metadata !DIExpression(), metadata !15, metadata ptr %a.addr, metadata !DIExpression()), !dbg !14 + %tobool.not = icmp eq i32 %a, 0, !dbg !16 + br i1 %tobool.not, label %if.else, label %if.then, !dbg !18 + +if.then: ; preds = %entry + tail call void @_Z1ev(), !dbg !19 + call void @llvm.dbg.assign(metadata i32 100, metadata !12, metadata !DIExpression(), metadata !21, metadata ptr %a.addr, metadata !DIExpression()), !dbg !14 + br label %if.end, !dbg !22 + +if.else: ; preds = %entry + tail call void @_Z1dv(), !dbg !23 + call void @llvm.dbg.assign(metadata i32 500, metadata !12, metadata !DIExpression(), metadata !21, metadata ptr %a.addr, metadata !DIExpression()), !dbg !14 + br label %if.end + +if.end: ; preds = %if.else, %if.then + %storemerge = phi i32 [ 500, %if.else ], [ 100, %if.then ], !dbg !25 + store i32 %storemerge, ptr %a.addr, align 4, !dbg !25 + call void @_Z2esPi(ptr noundef nonnull %a.addr), !dbg !30 + %0 = load i32, ptr %a.addr, align 4, !dbg !31 + ret i32 %0, !dbg !32 +} + +declare !dbg !33 dso_local void @_Z1ev() local_unnamed_addr #1 +declare !dbg !37 dso_local void @_Z1dv() local_unnamed_addr #1 +declare !dbg !38 dso_local void @_Z2esPi(ptr noundef) local_unnamed_addr #1 +declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) #2 + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!2, !3, !4, !5} +!llvm.ident = !{!6} + +!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang version 14.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "test.cpp", directory: "/") +!2 = !{i32 7, !"Dwarf Version", i32 5} +!3 = !{i32 2, !"Debug Info Version", i32 3} +!4 = !{i32 1, !"wchar_size", i32 4} +!5 = !{i32 7, !"uwtable", i32 1} +!6 = !{!"clang version 14.0.0"} +!7 = distinct !DISubprogram(name: "f", linkageName: "_Z1fi", scope: !1, file: !1, line: 4, type: !8, scopeLine: 4, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !11) +!8 = !DISubroutineType(types: !9) +!9 = !{!10, !10} +!10 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!11 = !{!12} +!12 = !DILocalVariable(name: "a", arg: 1, scope: !7, file: !1, line: 4, type: !10) +!13 = distinct !DIAssignID() +!14 = !DILocation(line: 0, scope: !7) +!15 = distinct !DIAssignID() +!16 = !DILocation(line: 5, column: 7, scope: !17) +!17 = distinct !DILexicalBlock(scope: !7, file: !1, line: 5, column: 7) +!18 = !DILocation(line: 5, column: 7, scope: !7) +!19 = !DILocation(line: 6, column: 5, scope: !20) +!20 = distinct !DILexicalBlock(scope: !17, file: !1, line: 5, column: 10) +!21 = distinct !DIAssignID() +!22 = !DILocation(line: 8, column: 3, scope: !20) +!23 = !DILocation(line: 9, column: 5, scope: !24) +!24 = distinct !DILexicalBlock(scope: !17, file: !1, line: 8, column: 10) +!25 = !DILocation(line: 0, scope: !17) +!30 = !DILocation(line: 12, column: 3, scope: !7) +!31 = !DILocation(line: 13, column: 10, scope: !7) +!32 = !DILocation(line: 13, column: 3, scope: !7) +!33 = !DISubprogram(name: "e", linkageName: "_Z1ev", scope: !1, file: !1, line: 2, type: !34, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !36) +!34 = !DISubroutineType(types: !35) +!35 = !{null} +!36 = !{} +!37 = !DISubprogram(name: "d", linkageName: "_Z1dv", scope: !1, file: !1, line: 1, type: !34, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !36) +!38 = !DISubprogram(name: "es", linkageName: "_Z2esPi", scope: !1, file: !1, line: 3, type: !39, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !36) +!39 = !DISubroutineType(types: !40) +!40 = !{null, !41} +!41 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !10, size: 64) diff --git a/llvm/test/DebugInfo/assignment-tracking/X86/diamond-3.ll b/llvm/test/DebugInfo/assignment-tracking/X86/diamond-3.ll new file mode 100644 index 0000000..1e38cb2 --- /dev/null +++ b/llvm/test/DebugInfo/assignment-tracking/X86/diamond-3.ll @@ -0,0 +1,117 @@ +; RUN: llc %s -stop-after=finalize-isel -o - -experimental-assignment-tracking \ +; RUN: | FileCheck %s --implicit-check-not=DBG_ + +;; Hand written to test scenario we can definitely run into in the wild. This +;; file name includes "diamond" because the idea is that we lose (while +;; optimizing) one of the diamond branches which was empty except for a debug +;; intrinsic. In this case, the debug intrinsic linked to the common-and-sunk +;; store now in if.end. So we've got this: +;; +;; entry: ; -> br if.then, if.end +;; mem(a) = !19 +;; dbg(a) = !21 ; dbg and mem disagree, don't use mem loc. +;; if.then: ; -> br if.end +;; dbg(a) = !20 +;; if.end: +;; mem(a) = !20 ; two preds disagree that !20 is the last assignment, don't +;; ; use mem loc. +;; ; This feels highly unfortunate, and highlights the need to reinstate the +;; ; memory location at call sites leaking the address (in an ideal world, +;; ; the memory location would always be in use at that point and so this +;; ; wouldn't be necessary). +;; esc(a) ; force the memory location + +;; In real world examples this is caused by InstCombine sinking common code +;; followed by SimplifyCFG deleting empty-except-for-dbg blocks. + +; CHECK-DAG: ![[A:[0-9]+]] = !DILocalVariable(name: "a", +; CHECK-LABEL: bb.0.entry: +; CHECK: DBG_VALUE $edi, $noreg, ![[A]], !DIExpression() +; CHECK-LABEL: bb.1.if.then: +; CHECK: DBG_VALUE 0, $noreg, ![[A]], !DIExpression() + +;; === TODO / WISHLIST === +; LEBAL-KCEHC: bb.2.if.end: +; KCEHC: CALL64pcrel32 target-flags(x86-plt) @es +; KCEHC: DBG_VALUE %stack.0.a.addr, $noreg, ![[A]], !DIExpression(DW_OP_deref) + +target triple = "x86_64-unknown-linux-gnu" + +@g = dso_local local_unnamed_addr global ptr null, align 8, !dbg !0 + +define dso_local noundef i32 @_Z1fiii(i32 noundef %a, i32 noundef %b, i32 noundef %c) local_unnamed_addr #0 !dbg !12 { +entry: + %a.addr = alloca i32, align 4, !DIAssignID !19 + call void @llvm.dbg.assign(metadata i1 undef, metadata !16, metadata !DIExpression(), metadata !19, metadata ptr %a.addr, metadata !DIExpression()), !dbg !20 + call void @llvm.dbg.assign(metadata i32 %a, metadata !16, metadata !DIExpression(), metadata !21, metadata ptr %a.addr, metadata !DIExpression()), !dbg !20 + %tobool.not = icmp eq i32 %c, 0 + br i1 %tobool.not, label %if.then, label %if.end + +if.then: + call void @e() + call void @llvm.dbg.assign(metadata i32 0, metadata !16, metadata !DIExpression(), metadata !22, metadata ptr %a.addr, metadata !DIExpression()), !dbg !20 + br label %if.end + +if.end: ; preds = %do.body + store i32 0, ptr %a.addr, align 4, !DIAssignID !22 + call void @es(ptr %a.addr) + ret i32 0 +} + +declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) #2 +declare void @e() +declare void @es(ptr) + +!llvm.dbg.cu = !{!2} +!llvm.module.flags = !{!7, !8, !9, !10} +!llvm.ident = !{!11} + +!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression()) +!1 = distinct !DIGlobalVariable(name: "g", scope: !2, file: !3, line: 4, type: !5, isLocal: false, isDefinition: true) +!2 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !3, producer: "clang version 14.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, globals: !4, splitDebugInlining: false, nameTableKind: None) +!3 = !DIFile(filename: "test.cpp", directory: "/") +!4 = !{!0} +!5 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !6, size: 64) +!6 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!7 = !{i32 7, !"Dwarf Version", i32 5} +!8 = !{i32 2, !"Debug Info Version", i32 3} +!9 = !{i32 1, !"wchar_size", i32 4} +!10 = !{i32 7, !"uwtable", i32 1} +!11 = !{!"clang version 14.0.0"} +!12 = distinct !DISubprogram(name: "f", linkageName: "_Z1fiii", scope: !3, file: !3, line: 5, type: !13, scopeLine: 5, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !15) +!13 = !DISubroutineType(types: !14) +!14 = !{!6, !6, !6, !6} +!15 = !{!16, !17, !18} +!16 = !DILocalVariable(name: "a", arg: 1, scope: !12, file: !3, line: 5, type: !6) +!17 = !DILocalVariable(name: "b", arg: 2, scope: !12, file: !3, line: 5, type: !6) +!18 = !DILocalVariable(name: "c", arg: 3, scope: !12, file: !3, line: 5, type: !6) +!19 = distinct !DIAssignID() +!20 = !DILocation(line: 0, scope: !12) +!21 = distinct !DIAssignID() +!22 = distinct !DIAssignID() +!23 = distinct !DIAssignID() +!28 = distinct !DIAssignID() +!29 = !DILocation(line: 6, column: 3, scope: !12) +!30 = !DILocation(line: 8, column: 7, scope: !31) +!31 = distinct !DILexicalBlock(scope: !12, file: !3, line: 6, column: 6) +!32 = distinct !DIAssignID() +!33 = !DILocation(line: 10, column: 5, scope: !31) +!34 = !DILocation(line: 11, column: 12, scope: !12) +!35 = !DILocation(line: 11, column: 3, scope: !31) +!36 = distinct !{!36, !29, !37, !38} +!37 = !DILocation(line: 11, column: 15, scope: !12) +!38 = !{!"llvm.loop.mustprogress"} +!39 = !DILocation(line: 12, column: 3, scope: !12) +!40 = !DILocation(line: 13, column: 10, scope: !12) +!41 = !DILocation(line: 13, column: 12, scope: !12) +!42 = !DILocation(line: 13, column: 3, scope: !12) +!43 = !DISubprogram(name: "e", linkageName: "_Z1ev", scope: !3, file: !3, line: 2, type: !44, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !46) +!44 = !DISubroutineType(types: !45) +!45 = !{null} +!46 = !{} +!47 = !DISubprogram(name: "d", linkageName: "_Z1dv", scope: !3, file: !3, line: 1, type: !48, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !46) +!48 = !DISubroutineType(types: !49) +!49 = !{!6} +!50 = !DISubprogram(name: "es", linkageName: "_Z2esPi", scope: !3, file: !3, line: 3, type: !51, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !46) +!51 = !DISubroutineType(types: !52) +!52 = !{null, !5} diff --git a/llvm/test/DebugInfo/assignment-tracking/X86/lit.local.cfg b/llvm/test/DebugInfo/assignment-tracking/X86/lit.local.cfg new file mode 100644 index 0000000..c8625f4 --- /dev/null +++ b/llvm/test/DebugInfo/assignment-tracking/X86/lit.local.cfg @@ -0,0 +1,2 @@ +if not 'X86' in config.root.targets: + config.unsupported = True diff --git a/llvm/test/DebugInfo/assignment-tracking/X86/loop-hoist.ll b/llvm/test/DebugInfo/assignment-tracking/X86/loop-hoist.ll new file mode 100644 index 0000000..bead82a --- /dev/null +++ b/llvm/test/DebugInfo/assignment-tracking/X86/loop-hoist.ll @@ -0,0 +1,118 @@ +; RUN: llc %s -stop-after=finalize-isel -o - -experimental-assignment-tracking \ +; RUN: | FileCheck %s --implicit-check-not=DBG_ + +;; $ cat test.cpp +;; int d(); +;; void e(); +;; void es(int*); +;; int *g; +;; int f(int a, int b, int c) { +;; do { +;; /* stuff */ +;; c *= c; +;; a = b; +;; e(); +;; } while (d()); +;; es(&a); +;; return a + c; +;; } + +;; The variable of interest is `a`, which has a store that is hoisted out of the +;; loop into the entry BB. Check the memory location is not used after the +;; hoisted store until the assignment position within the loop. + +; CHECK-DAG: ![[A:[0-9]+]] = !DILocalVariable(name: "a", + +; CHECK: bb.0.entry: +; CHECK: DBG_VALUE $edi, $noreg, ![[A]], !DIExpression() + +; CHECK: bb.1.do.body: +; CHECK: DBG_VALUE %stack.0.a.addr, $noreg, ![[A]], !DIExpression(DW_OP_deref) + +target triple = "x86_64-unknown-linux-gnu" + +@g = dso_local local_unnamed_addr global ptr null, align 8, !dbg !0 + +define dso_local noundef i32 @_Z1fiii(i32 noundef %a, i32 noundef %b, i32 noundef %c) local_unnamed_addr #0 !dbg !12 { +entry: + %a.addr = alloca i32, align 4, !DIAssignID !19 + call void @llvm.dbg.assign(metadata i1 undef, metadata !16, metadata !DIExpression(), metadata !19, metadata ptr %a.addr, metadata !DIExpression()), !dbg !20 + call void @llvm.dbg.assign(metadata i32 %a, metadata !16, metadata !DIExpression(), metadata !21, metadata ptr %a.addr, metadata !DIExpression()), !dbg !20 + store i32 %b, ptr %a.addr, align 4, !DIAssignID !28 + br label %do.body, !dbg !29 + +do.body: ; preds = %do.body, %entry + %c.addr.0 = phi i32 [ %c, %entry ], [ %mul, %do.body ] + %mul = mul nsw i32 %c.addr.0, %c.addr.0, !dbg !30 + call void @llvm.dbg.assign(metadata i32 %b, metadata !16, metadata !DIExpression(), metadata !28, metadata ptr %a.addr, metadata !DIExpression()), !dbg !20 + tail call void @_Z1ev(), !dbg !33 + %call = tail call noundef i32 @_Z1dv(), !dbg !34 + %tobool.not = icmp eq i32 %call, 0, !dbg !34 + br i1 %tobool.not, label %do.end, label %do.body, !dbg !35, !llvm.loop !36 + +do.end: ; preds = %do.body + call void @_Z2esPi(ptr noundef nonnull %a.addr), !dbg !39 + %0 = load i32, ptr %a.addr, align 4, !dbg !40 + %add = add nsw i32 %0, %mul, !dbg !41 + ret i32 %add, !dbg !42 +} + +declare !dbg !43 dso_local void @_Z1ev() local_unnamed_addr #1 +declare !dbg !47 dso_local noundef i32 @_Z1dv() local_unnamed_addr #1 +declare !dbg !50 dso_local void @_Z2esPi(ptr noundef) local_unnamed_addr #1 +declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) #2 +declare void @llvm.dbg.value(metadata, metadata, metadata) #3 + +!llvm.dbg.cu = !{!2} +!llvm.module.flags = !{!7, !8, !9, !10} +!llvm.ident = !{!11} + +!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression()) +!1 = distinct !DIGlobalVariable(name: "g", scope: !2, file: !3, line: 4, type: !5, isLocal: false, isDefinition: true) +!2 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !3, producer: "clang version 14.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, globals: !4, splitDebugInlining: false, nameTableKind: None) +!3 = !DIFile(filename: "test.cpp", directory: "/") +!4 = !{!0} +!5 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !6, size: 64) +!6 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!7 = !{i32 7, !"Dwarf Version", i32 5} +!8 = !{i32 2, !"Debug Info Version", i32 3} +!9 = !{i32 1, !"wchar_size", i32 4} +!10 = !{i32 7, !"uwtable", i32 1} +!11 = !{!"clang version 14.0.0"} +!12 = distinct !DISubprogram(name: "f", linkageName: "_Z1fiii", scope: !3, file: !3, line: 5, type: !13, scopeLine: 5, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !15) +!13 = !DISubroutineType(types: !14) +!14 = !{!6, !6, !6, !6} +!15 = !{!16, !17, !18} +!16 = !DILocalVariable(name: "a", arg: 1, scope: !12, file: !3, line: 5, type: !6) +!17 = !DILocalVariable(name: "b", arg: 2, scope: !12, file: !3, line: 5, type: !6) +!18 = !DILocalVariable(name: "c", arg: 3, scope: !12, file: !3, line: 5, type: !6) +!19 = distinct !DIAssignID() +!20 = !DILocation(line: 0, scope: !12) +!21 = distinct !DIAssignID() +!22 = distinct !DIAssignID() +!23 = distinct !DIAssignID() +!28 = distinct !DIAssignID() +!29 = !DILocation(line: 6, column: 3, scope: !12) +!30 = !DILocation(line: 8, column: 7, scope: !31) +!31 = distinct !DILexicalBlock(scope: !12, file: !3, line: 6, column: 6) +!32 = distinct !DIAssignID() +!33 = !DILocation(line: 10, column: 5, scope: !31) +!34 = !DILocation(line: 11, column: 12, scope: !12) +!35 = !DILocation(line: 11, column: 3, scope: !31) +!36 = distinct !{!36, !29, !37, !38} +!37 = !DILocation(line: 11, column: 15, scope: !12) +!38 = !{!"llvm.loop.mustprogress"} +!39 = !DILocation(line: 12, column: 3, scope: !12) +!40 = !DILocation(line: 13, column: 10, scope: !12) +!41 = !DILocation(line: 13, column: 12, scope: !12) +!42 = !DILocation(line: 13, column: 3, scope: !12) +!43 = !DISubprogram(name: "e", linkageName: "_Z1ev", scope: !3, file: !3, line: 2, type: !44, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !46) +!44 = !DISubroutineType(types: !45) +!45 = !{null} +!46 = !{} +!47 = !DISubprogram(name: "d", linkageName: "_Z1dv", scope: !3, file: !3, line: 1, type: !48, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !46) +!48 = !DISubroutineType(types: !49) +!49 = !{!6} +!50 = !DISubprogram(name: "es", linkageName: "_Z2esPi", scope: !3, file: !3, line: 3, type: !51, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !46) +!51 = !DISubroutineType(types: !52) +!52 = !{null, !5} diff --git a/llvm/test/DebugInfo/assignment-tracking/X86/loop-sink.ll b/llvm/test/DebugInfo/assignment-tracking/X86/loop-sink.ll new file mode 100644 index 0000000..5257d2c --- /dev/null +++ b/llvm/test/DebugInfo/assignment-tracking/X86/loop-sink.ll @@ -0,0 +1,114 @@ +; RUN: llc %s -stop-after=finalize-isel -o - -experimental-assignment-tracking \ +; RUN: | FileCheck %s --implicit-check-not=DBG + +;; Tiny loop with a store sunk out of it: +;; void e(); +;; void es(int*); +;; int *g; +;; int getInt(); +;; int f(int a, int b) { +;; int z = getInt(); +;; while (g) { +;; e(); +;; a = z; +;; } +;; es(&a); +;; return a; +;; } +;; +;; Store to `a` has been sunk out the loop - there's a dbg.assign left in the +;; loop that is linked the store that is now outside it. Check that the memory +;; location is not used inside the loop and is reinstated after the sunk store. + +; CHECK-DAG: ![[A:[0-9]+]] = !DILocalVariable(name: "a", + +; CHECK-LABEL: bb.0.entry: +; CHECK: DBG_VALUE $edi, $noreg, ![[A]], !DIExpression() +; CHECK: CALL64pcrel32 @getInt{{.*}}debug-instr-number 1 + +; CHECK-LABEL: bb.2.while.body: +; CHECK: DBG_INSTR_REF 1, 6, ![[A]], !DIExpression() + +; CHECK-LABEL: bb.3.while.end: +; CHECK: MOV32mr %stack.0.a.addr, 1, $noreg, 0, $noreg, %1 +; CHECK-NEXT: DBG_VALUE %stack.0.a.addr, $noreg, ![[A]], !DIExpression(DW_OP_deref) + +target triple = "x86_64-unknown-linux-gnu" + +@g = dso_local local_unnamed_addr global ptr null, align 8, !dbg !0 + +; Function Attrs: mustprogress uwtable +define dso_local noundef i32 @_Z1fii(i32 noundef %a, i32 noundef %b) local_unnamed_addr #0 !dbg !12 { +entry: + %a.addr = alloca i32, align 4, !DIAssignID !18 + call void @llvm.dbg.assign(metadata i1 undef, metadata !16, metadata !DIExpression(), metadata !18, metadata ptr %a.addr, metadata !DIExpression()), !dbg !19 + call void @llvm.dbg.assign(metadata i32 %a, metadata !16, metadata !DIExpression(), metadata !20, metadata ptr %a.addr, metadata !DIExpression()), !dbg !19 + %z = call i32 @getInt() + %0 = load ptr, ptr @g, align 8, !dbg !22 + %tobool.not1 = icmp eq ptr %0, null, !dbg !22 + br i1 %tobool.not1, label %while.end, label %while.body, !dbg !27 + +while.body: ; preds = %entry, %while.body + tail call void @_Z1ev(), !dbg !28 + call void @llvm.dbg.assign(metadata i32 %z, metadata !16, metadata !DIExpression(), metadata !20, metadata ptr %a.addr, metadata !DIExpression()), !dbg !19 + %1 = load ptr, ptr @g, align 8, !dbg !22 + %tobool.not = icmp eq ptr %1, null, !dbg !22 + br i1 %tobool.not, label %while.end, label %while.body, !dbg !27, !llvm.loop !30 + +while.end: ; preds = %while.body, %entry + %storemerge.lcssa = phi i32 [ %a, %entry ], [ %b, %while.body ] + store i32 %storemerge.lcssa, ptr %a.addr, align 4, !DIAssignID !20 + call void @_Z2esPi(ptr noundef nonnull %a.addr), !dbg !35 + %2 = load i32, ptr %a.addr, align 4, !dbg !36 + %r = add i32 %2, %z + ret i32 %r, !dbg !37 +} + +declare !dbg !38 dso_local void @_Z1ev() local_unnamed_addr #1 +declare !dbg !42 dso_local void @_Z2esPi(ptr noundef) local_unnamed_addr #1 +declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) #2 +declare dso_local i32 @getInt() + +!llvm.dbg.cu = !{!2} +!llvm.module.flags = !{!7, !8, !9, !10} +!llvm.ident = !{!11} + +!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression()) +!1 = distinct !DIGlobalVariable(name: "g", scope: !2, file: !3, line: 4, type: !5, isLocal: false, isDefinition: true) +!2 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !3, producer: "clang version 14.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, globals: !4, splitDebugInlining: false, nameTableKind: None) +!3 = !DIFile(filename: "test.cpp", directory: "/") +!4 = !{!0} +!5 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !6, size: 64) +!6 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!7 = !{i32 7, !"Dwarf Version", i32 5} +!8 = !{i32 2, !"Debug Info Version", i32 3} +!9 = !{i32 1, !"wchar_size", i32 4} +!10 = !{i32 7, !"uwtable", i32 1} +!11 = !{!"clang version 14.0.0"} +!12 = distinct !DISubprogram(name: "f", linkageName: "_Z1fii", scope: !3, file: !3, line: 5, type: !13, scopeLine: 5, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !15) +!13 = !DISubroutineType(types: !14) +!14 = !{!6, !6, !6} +!15 = !{!16, !17} +!16 = !DILocalVariable(name: "a", arg: 1, scope: !12, file: !3, line: 5, type: !6) +!17 = !DILocalVariable(name: "b", arg: 2, scope: !12, file: !3, line: 5, type: !6) +!18 = distinct !DIAssignID() +!19 = !DILocation(line: 0, scope: !12) +!20 = distinct !DIAssignID() +!21 = distinct !DIAssignID() +!22 = !DILocation(line: 6, column: 10, scope: !12) +!27 = !DILocation(line: 6, column: 3, scope: !12) +!28 = !DILocation(line: 7, column: 5, scope: !29) +!29 = distinct !DILexicalBlock(scope: !12, file: !3, line: 6, column: 13) +!30 = distinct !{!30, !27, !31, !32} +!31 = !DILocation(line: 9, column: 3, scope: !12) +!32 = !{!"llvm.loop.mustprogress"} +!35 = !DILocation(line: 10, column: 3, scope: !12) +!36 = !DILocation(line: 11, column: 10, scope: !12) +!37 = !DILocation(line: 11, column: 3, scope: !12) +!38 = !DISubprogram(name: "e", linkageName: "_Z1ev", scope: !3, file: !3, line: 2, type: !39, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !41) +!39 = !DISubroutineType(types: !40) +!40 = !{null} +!41 = !{} +!42 = !DISubprogram(name: "es", linkageName: "_Z2esPi", scope: !3, file: !3, line: 3, type: !43, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !41) +!43 = !DISubroutineType(types: !44) +!44 = !{null, !5} diff --git a/llvm/test/DebugInfo/assignment-tracking/X86/loop-unroll.ll b/llvm/test/DebugInfo/assignment-tracking/X86/loop-unroll.ll new file mode 100644 index 0000000..c62faf6 --- /dev/null +++ b/llvm/test/DebugInfo/assignment-tracking/X86/loop-unroll.ll @@ -0,0 +1,88 @@ +; RUN: llc %s -stop-after=finalize-isel -o - \ +; RUN: -experimental-assignment-tracking \ +; RUN: | FileCheck %s +;; +;; Backend counterpart to ../Generic/dbg-assign-loop-unroll. This IR was +;; generated by running `opt -loop-unroll -S` on the IR in that test. +;; +;; Check that the backend can handle a mesh of dbg.assign and linked +;; instructions. +;; +;; Generated from the following source: +;; void esc(int*); +;; void d(int p) { +;; for (int i = 0; i < 2; ++i) { +;; p = i; +;; esc(&p); +;; } +;; } + +; CHECK: ![[p:[0-9]+]] = !DILocalVariable(name: "p", + +; CHECK: stack: +; CHECK-NEXT: - { id: 0, name: p.addr, type: default, offset: 0, size: 4, alignment: 4, +; CHECK-NEXT: stack-id: default, callee-saved-register: '', callee-saved-restored: true, +; CHECK-NEXT: debug-info-variable: '![[p]]', debug-info-expression: '!DIExpression()', +; CHECK-NEXT: debug-info-location: '!{{.+}}' } + +target triple = "x86_64-unknown-linux-gnu" + +define dso_local void @_Z1di(i32 %p) local_unnamed_addr !dbg !7 { +entry: + %p.addr = alloca i32, align 4 + store i32 %p, ptr %p.addr, align 4, !DIAssignID !19 + call void @llvm.dbg.assign(metadata i32 %p, metadata !12, metadata !DIExpression(), metadata !19, metadata ptr %p.addr, metadata !DIExpression()), !dbg !20 + call void @llvm.dbg.assign(metadata i32 0, metadata !13, metadata !DIExpression(), metadata !21, metadata ptr undef, metadata !DIExpression()), !dbg !22 + br label %for.body, !dbg !23 + +for.body: ; preds = %entry + store i32 0, ptr %p.addr, align 4, !dbg !24, !DIAssignID !27 + call void @llvm.dbg.assign(metadata i32 0, metadata !12, metadata !DIExpression(), metadata !27, metadata ptr %p.addr, metadata !DIExpression()), !dbg !24 + call void @_Z3escPi(ptr nonnull %p.addr), !dbg !28 + call void @llvm.dbg.assign(metadata i32 1, metadata !13, metadata !DIExpression(), metadata !29, metadata ptr undef, metadata !DIExpression()), !dbg !30 + store i32 1, ptr %p.addr, align 4, !dbg !24, !DIAssignID !27 + call void @llvm.dbg.assign(metadata i32 1, metadata !12, metadata !DIExpression(), metadata !27, metadata ptr %p.addr, metadata !DIExpression()), !dbg !24 + call void @_Z3escPi(ptr nonnull %p.addr), !dbg !28 + call void @llvm.dbg.assign(metadata i32 2, metadata !13, metadata !DIExpression(), metadata !29, metadata ptr undef, metadata !DIExpression()), !dbg !30 + ret void, !dbg !31 +} + +declare !dbg !32 dso_local void @_Z3escPi(ptr) local_unnamed_addr +declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!3, !4, !5} +!llvm.ident = !{!6} + +!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang version 12.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "test.cpp", directory: "/") +!2 = !{} +!3 = !{i32 7, !"Dwarf Version", i32 4} +!4 = !{i32 2, !"Debug Info Version", i32 3} +!5 = !{i32 1, !"wchar_size", i32 4} +!6 = !{!"clang version 12.0.0"} +!7 = distinct !DISubprogram(name: "d", linkageName: "_Z1di", scope: !1, file: !1, line: 2, type: !8, scopeLine: 2, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !11) +!8 = !DISubroutineType(types: !9) +!9 = !{null, !10} +!10 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!11 = !{!12, !13} +!12 = !DILocalVariable(name: "p", arg: 1, scope: !7, file: !1, line: 2, type: !10) +!13 = !DILocalVariable(name: "i", scope: !14, file: !1, line: 3, type: !10) +!14 = distinct !DILexicalBlock(scope: !7, file: !1, line: 3, column: 3) +!19 = distinct !DIAssignID() +!20 = !DILocation(line: 0, scope: !7) +!21 = distinct !DIAssignID() +!22 = !DILocation(line: 3, column: 12, scope: !14) +!23 = !DILocation(line: 3, column: 3, scope: !14) +!24 = !DILocation(line: 4, column: 7, scope: !25) +!25 = distinct !DILexicalBlock(scope: !26, file: !1, line: 3, column: 31) +!26 = distinct !DILexicalBlock(scope: !14, file: !1, line: 3, column: 3) +!27 = distinct !DIAssignID() +!28 = !DILocation(line: 5, column: 5, scope: !25) +!29 = distinct !DIAssignID() +!30 = !DILocation(line: 3, column: 26, scope: !26) +!31 = !DILocation(line: 7, column: 1, scope: !7) +!32 = !DISubprogram(name: "esc", linkageName: "_Z3escPi", scope: !1, file: !1, line: 1, type: !33, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !2) +!33 = !DISubroutineType(types: !34) +!34 = !{null, !35} +!35 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !10, size: 64) diff --git a/llvm/test/DebugInfo/assignment-tracking/X86/lower-offset-expression.ll b/llvm/test/DebugInfo/assignment-tracking/X86/lower-offset-expression.ll new file mode 100644 index 0000000..a367efe --- /dev/null +++ b/llvm/test/DebugInfo/assignment-tracking/X86/lower-offset-expression.ll @@ -0,0 +1,68 @@ +; RUN: llc %s -stop-after=finalize-isel -o - \ +; RUN: -experimental-assignment-tracking \ +; RUN: | FileCheck %s + +;; Handwritten test. + +;; Here we have dbg.assign intrinsics with fragments (in the value-expression) +;; and address-expressions that involve arithmetic. The generated DBG_VALUE +;; intructions needs a DIExpression that: +;; a) Uses the fragment from the value-expression, +;; b) Uses the offset expression of the address-expression, +;; c) Has a DW_OP_deref appended. + +; CHECK: DBG_VALUE %stack.0.a, $noreg, {{.+}}, !DIExpression(DW_OP_plus_uconst, 8, DW_OP_deref, DW_OP_LLVM_fragment, 64, 32), debug-location + +define dso_local void @fun() !dbg !7 { +entry: + %a = alloca <4 x i32>, !DIAssignID !24 + call void @llvm.dbg.assign(metadata i32 undef, metadata !16, metadata !DIExpression(), metadata !24, metadata ptr %a, metadata !DIExpression()), !dbg !34 + ;; unlink and undef a dbg.assign to avoid using sidetable for var loc. + call void @llvm.dbg.assign(metadata i32 undef, metadata !16, metadata !DIExpression(), metadata !26, metadata ptr undef, metadata !DIExpression()), !dbg !34 + %idx2 = getelementptr inbounds i32, i32* %a, i32 2 + store i32 100, i32* %idx2, !DIAssignID !25 + call void @llvm.dbg.assign(metadata i32 100, metadata !16, metadata !DIExpression(DW_OP_LLVM_fragment, 64, 32), metadata !25, metadata i32* %a, metadata !DIExpression(DW_OP_plus_uconst, 8)), !dbg !34 + ret void +} + +declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!3, !4, !5} +!llvm.ident = !{!6} + +!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang version 12.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "test.cpp", directory: "/") +!2 = !{} +!3 = !{i32 7, !"Dwarf Version", i32 4} +!4 = !{i32 2, !"Debug Info Version", i32 3} +!5 = !{i32 1, !"wchar_size", i32 4} +!6 = !{!"clang version 12.0.0"} +!7 = distinct !DISubprogram(name: "fun", linkageName: "fun", scope: !1, file: !1, line: 3, type: !8, scopeLine: 3, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !11) +!8 = !DISubroutineType(types: !9) +!9 = !{null, !10, !10, !10, !10} +!10 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!11 = !{!16} +!16 = !DILocalVariable(name: "quad", scope: !7, file: !1, line: 4, type: !17) +!17 = !DICompositeType(tag: DW_TAG_array_type, baseType: !10, size: 128, elements: !18) +!18 = !{!19} +!19 = !DISubrange(count: 4) +!23 = !DILocation(line: 0, scope: !7) +!24 = distinct !DIAssignID() +!25 = distinct !DIAssignID() +!26 = distinct !DIAssignID() +!34 = !DILocation(line: 0, column: 0, scope: !7) +!44 = !{!45, !45, i64 0} +!45 = !{!"float", !46, i64 0} +!46 = !{!"omnipotent char", !47, i64 0} +!47 = !{!"Simple C++ TBAA"} +!48 = !DILocation(line: 11, column: 3, scope: !7) +!49 = !DILocation(line: 12, column: 1, scope: !7) +!50 = !DISubprogram(name: "get", linkageName: "_Z3getv", scope: !1, file: !1, line: 1, type: !51, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !2) +!51 = !DISubroutineType(types: !52) +!52 = !{!10} +!53 = !DISubprogram(name: "ext", linkageName: "_Z3extPf", scope: !1, file: !1, line: 2, type: !54, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !2) +!54 = !DISubroutineType(types: !55) +!55 = !{!10, !56} +!56 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !10, size: 64) + diff --git a/llvm/test/DebugInfo/assignment-tracking/X86/lower-to-value.ll b/llvm/test/DebugInfo/assignment-tracking/X86/lower-to-value.ll new file mode 100644 index 0000000..84b4c8a --- /dev/null +++ b/llvm/test/DebugInfo/assignment-tracking/X86/lower-to-value.ll @@ -0,0 +1,114 @@ +; RUN: llc %s -stop-before finalize-isel -o - \ +; RUN: -experimental-assignment-tracking \ +; RUN: -experimental-debug-variable-locations=false \ +; RUN: | FileCheck %s --check-prefixes=CHECK,DBGVALUE --implicit-check-not=DBG_VALUE +; RUN: llc %s -stop-before finalize-isel -o - \ +; RUN: -experimental-assignment-tracking \ +; RUN: -experimental-debug-variable-locations=true \ +; RUN: | FileCheck %s --check-prefixes=CHECK,INSTRREF --implicit-check-not=DBG_VALUE \ +; RUN: --implicit-check-not=DBG_INSTR_REF + +;; Check that dbg.assigns for an aggregate variable which lives on the stack +;; for some of its lifetime are lowered into an appropriate set of DBG_VALUEs. +;; +;; $ cat test.cpp +;; void esc(long* p); +;; struct Ex { +;; long A; +;; long B; +;; }; +;; long fun() { +;; Ex X; +;; X.B = 0; +;; esc(&X.B); +;; X.B += 2; +;; return X.B; +;; } +;; $ clang++ test.cpp -O2 -g -emit-llvm -S -c -Xclang -fexperimental-assignment-tracking + +; CHECK: ![[VAR:[0-9]+]] = !DILocalVariable(name: "X", +;; Check we have no debug info for local in the side table. +; CHECK: stack: +; CHECK-NEXT: - { id: 0, name: X, type: default, offset: 0, size: 16, alignment: 8, +; CHECK-NEXT: stack-id: default, callee-saved-register: '', callee-saved-restored: true, +; CHECK-NEXT: debug-info-variable: '', debug-info-expression: '', debug-info-location: '' } + +;; Initially the whole variable is on the stack. +; CHECK: bb.0.entry: +; CHECK-NEXT: DBG_VALUE %stack.0.X, $noreg, ![[VAR]], !DIExpression(DW_OP_deref), debug-location + +;; Then there is a store to the upper 64 bits. +; CHECK: MOV64mi32 %stack.0.X, 1, $noreg, 8, $noreg, 0, debug-location +;; This DBG_VALUE is added by the mem-loc-frag-fill pass because bits [0, 64) +;; are still live in memory. +; CHECK-NEXT: DBG_VALUE %stack.0.X, $noreg, ![[VAR]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 0, 64) +; CHECK-NEXT: DBG_VALUE %stack.0.X, $noreg, ![[VAR]], !DIExpression(DW_OP_plus_uconst, 8, DW_OP_deref, DW_OP_LLVM_fragment, 64, 64), debug-location + +;; The final assignment (X.B += 2) doesn't get stored back to the alloca. This +;; means that that the stack location isn't valid for the entire lifetime of X. +; DBGVALUE: %2:gr64 = nsw ADD64ri8 %1, 2, implicit-def dead $eflags, debug-location +; DBGVALUE-NEXT: DBG_VALUE %2, $noreg, ![[VAR]], !DIExpression(DW_OP_LLVM_fragment, 64, 64), debug-location +; INSTRREF: %2:gr64 = nsw ADD64ri8 %1, 2, implicit-def dead $eflags, debug-instr-number 1 +; INSTRREF-NEXT: DBG_INSTR_REF 1, 0, ![[VAR]], !DIExpression(DW_OP_LLVM_fragment, 64, 64), debug-location + +source_filename = "test.cpp" +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +%struct.Ex = type { i64, i64 } + +define dso_local i64 @_Z3funv() local_unnamed_addr !dbg !7 { +entry: + %X = alloca %struct.Ex, align 8, !DIAssignID !17 + call void @llvm.dbg.assign(metadata i1 undef, metadata !12, metadata !DIExpression(), metadata !17, metadata ptr %X, metadata !DIExpression()), !dbg !18 + %B = getelementptr inbounds %struct.Ex, ptr %X, i64 0, i32 1, !dbg !20 + store i64 0, ptr %B, align 8, !dbg !21, !DIAssignID !27 + call void @llvm.dbg.assign(metadata i64 0, metadata !12, metadata !DIExpression(DW_OP_LLVM_fragment, 64, 64), metadata !27, metadata ptr %B, metadata !DIExpression()), !dbg !21 + call void @_Z3escPl(ptr nonnull %B), !dbg !28 + %0 = load i64, ptr %B, align 8, !dbg !29 + %add = add nsw i64 %0, 2, !dbg !29 + call void @llvm.dbg.assign(metadata i64 %add, metadata !12, metadata !DIExpression(DW_OP_LLVM_fragment, 64, 64), metadata !30, metadata ptr %B, metadata !DIExpression()), !dbg !29 + ret i64 %add, !dbg !32 +} + +declare void @llvm.lifetime.start.p0i8(i64 immarg, ptr nocapture) +declare !dbg !33 dso_local void @_Z3escPl(ptr) local_unnamed_addr +declare void @llvm.lifetime.end.p0i8(i64 immarg, ptr nocapture) +declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!3, !4, !5} +!llvm.ident = !{!6} + +!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang version 12.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "test.cpp", directory: "/") +!2 = !{} +!3 = !{i32 7, !"Dwarf Version", i32 4} +!4 = !{i32 2, !"Debug Info Version", i32 3} +!5 = !{i32 1, !"wchar_size", i32 4} +!6 = !{!"clang version 12.0.0"} +!7 = distinct !DISubprogram(name: "fun", linkageName: "_Z3funv", scope: !1, file: !1, line: 6, type: !8, scopeLine: 6, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !11) +!8 = !DISubroutineType(types: !9) +!9 = !{!10} +!10 = !DIBasicType(name: "long int", size: 64, encoding: DW_ATE_signed) +!11 = !{!12} +!12 = !DILocalVariable(name: "X", scope: !7, file: !1, line: 7, type: !13) +!13 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "Ex", file: !1, line: 2, size: 128, flags: DIFlagTypePassByValue, elements: !14, identifier: "_ZTS2Ex") +!14 = !{!15, !16} +!15 = !DIDerivedType(tag: DW_TAG_member, name: "A", scope: !13, file: !1, line: 3, baseType: !10, size: 64) +!16 = !DIDerivedType(tag: DW_TAG_member, name: "B", scope: !13, file: !1, line: 4, baseType: !10, size: 64, offset: 64) +!17 = distinct !DIAssignID() +!18 = !DILocation(line: 0, scope: !7) +!19 = !DILocation(line: 7, column: 3, scope: !7) +!20 = !DILocation(line: 8, column: 5, scope: !7) +!21 = !DILocation(line: 8, column: 7, scope: !7) +!27 = distinct !DIAssignID() +!28 = !DILocation(line: 9, column: 3, scope: !7) +!29 = !DILocation(line: 10, column: 7, scope: !7) +!30 = distinct !DIAssignID() +!31 = !DILocation(line: 12, column: 1, scope: !7) +!32 = !DILocation(line: 11, column: 3, scope: !7) +!33 = !DISubprogram(name: "esc", linkageName: "_Z3escPl", scope: !1, file: !1, line: 1, type: !34, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !2) +!34 = !DISubroutineType(types: !35) +!35 = !{null, !36} +!36 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !10, size: 64) diff --git a/llvm/test/DebugInfo/assignment-tracking/X86/mem-loc-frag-fill-cfg.ll b/llvm/test/DebugInfo/assignment-tracking/X86/mem-loc-frag-fill-cfg.ll new file mode 100644 index 0000000..e744d93 --- /dev/null +++ b/llvm/test/DebugInfo/assignment-tracking/X86/mem-loc-frag-fill-cfg.ll @@ -0,0 +1,147 @@ +; RUN: llc %s -stop-before finalize-isel -o - \ +; RUN: -experimental-assignment-tracking \ +; RUN: -experimental-debug-variable-locations=false \ +; RUN: | FileCheck %s --implicit-check-not=DBG_ +; RUN: llc %s -stop-before finalize-isel -o - \ +; RUN: -experimental-assignment-tracking \ +; RUN: -experimental-debug-variable-locations=true \ +; RUN: | FileCheck %s --implicit-check-not=DBG_ + +;; Check that the mem-loc-frag-fill pseudo-pass works on a simple CFG. When +;; LLVM sees a dbg.value with an overlapping fragment it essentially considers +;; the previous location as valid for all bits in that fragment. The pass +;; inserts dbg.value fragments to preserve memory locations for bits in memory +;; when overlapping fragments are encountered. + +;; nums lives in mem, except prior to the second call to step() where there has +;; been some DSE. At this point, the memory loc for nums.c is invalid. But the +;; rest of num's bits, [0, 64), are in memory, so check there's a dbg.value for +;; them. + +;; $ cat test.cpp +;; struct Nums { int a, b, c; }; +;; +;; void esc1(struct Nums*); +;; void esc2(struct Nums*); +;; bool step(); +;; +;; int main() { +;; struct Nums nums = { 1, 2, 1 }; +;; if (step()) +;; esc1(&nums); +;; else +;; esc2(&nums); +;; +;; nums.c = 2; //< Include some DSE to force a non-mem location. +;; step(); +;; +;; nums.c = nums.a; +;; +;; esc1(&nums); +;; return 0; +;; } +;; +;; $ clang++ test.cpp -O2 -g -Xclang -fexperimental-assignment-tracking -emit-llvm -S -o - + +;; Most check lines are inline in main. +; CHECK: ![[nums:[0-9]+]] = !DILocalVariable(name: "nums", + +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +%struct.Nums = type { i32, i32, i32 } + +@__const.main.nums = private unnamed_addr constant %struct.Nums { i32 1, i32 2, i32 1 }, align 4 + +declare void @_Z4esc1P4Nums(ptr nocapture noundef readonly %p) +declare void @_Z4esc2P4Nums(ptr nocapture noundef readonly %p) + +; Function Attrs: mustprogress norecurse uwtable +define dso_local noundef i32 @main() local_unnamed_addr #3 !dbg !40 { +; CHECK: name: main +entry: + %nums = alloca %struct.Nums, align 4, !DIAssignID !45 + call void @llvm.dbg.assign(metadata i1 undef, metadata !44, metadata !DIExpression(), metadata !45, metadata ptr %nums, metadata !DIExpression()), !dbg !46 +; CHECK: DBG_VALUE %stack.0.nums, $noreg, ![[nums]], !DIExpression(DW_OP_deref) + call void @llvm.memcpy.p0i8.p0i8.i64(ptr noundef nonnull align 4 dereferenceable(12) %nums, ptr noundef nonnull align 4 dereferenceable(12) %nums, i64 12, i1 false), !dbg !48, !DIAssignID !49 + call void @llvm.dbg.assign(metadata i1 undef, metadata !44, metadata !DIExpression(), metadata !49, metadata ptr %nums, metadata !DIExpression()), !dbg !46 + %call = tail call noundef zeroext i1 @_Z4stepv(), !dbg !50 + br i1 %call, label %if.then, label %if.else, !dbg !52 + +if.then: ; preds = %entry + call void @_Z4esc1P4Nums(ptr noundef nonnull %nums), !dbg !53 + br label %if.end, !dbg !53 + +if.else: ; preds = %entry + call void @_Z4esc2P4Nums(ptr noundef nonnull %nums), !dbg !54 + br label %if.end + +if.end: ; preds = %if.else, %if.then +; CHECK: bb.3.if.end: +; CHECK-NEXT: DBG_VALUE 2, $noreg, ![[nums]], !DIExpression(DW_OP_LLVM_fragment, 64, 32), debug-location +; CHECK-NEXT: DBG_VALUE %stack.0.nums, $noreg, ![[nums]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 0, 64) + %c = getelementptr inbounds %struct.Nums, ptr %nums, i64 0, i32 2, !dbg !55 + call void @llvm.dbg.assign(metadata i32 2, metadata !44, metadata !DIExpression(DW_OP_LLVM_fragment, 64, 32), metadata !56, metadata ptr %c, metadata !DIExpression()), !dbg !46 + %call1 = tail call noundef zeroext i1 @_Z4stepv(), !dbg !57 + store i32 1, ptr %c, align 4, !dbg !58, !DIAssignID !61 +; CHECK: MOV32mi %stack.0.nums, 1, $noreg, 8, $noreg, 1 +; CHECK-NEXT: DBG_VALUE %stack.0.nums, $noreg, ![[nums]], !DIExpression(DW_OP_plus_uconst, 8, DW_OP_deref, DW_OP_LLVM_fragment, 64, 32) + call void @llvm.dbg.assign(metadata i32 1, metadata !44, metadata !DIExpression(DW_OP_LLVM_fragment, 64, 32), metadata !61, metadata ptr %c, metadata !DIExpression()), !dbg !46 + call void @_Z4esc1P4Nums(ptr noundef nonnull %nums), !dbg !62 + ret i32 0, !dbg !64 +} + +declare void @llvm.lifetime.start.p0i8(i64 immarg, ptr nocapture) #4 +declare !dbg !65 dso_local noundef zeroext i1 @_Z4stepv() local_unnamed_addr #5 +declare void @llvm.lifetime.end.p0i8(i64 immarg, ptr nocapture) #4 +declare void @llvm.memcpy.p0i8.p0i8.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) #1 +declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) #2 + +!llvm.dbg.cu = !{!2} +!llvm.module.flags = !{!11, !12, !13, !14} +!llvm.ident = !{!15} + +!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression()) +!1 = distinct !DIGlobalVariable(name: "glob", scope: !2, file: !3, line: 2, type: !5, isLocal: false, isDefinition: true) +!2 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !3, producer: "clang version 14.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, globals: !4, splitDebugInlining: false, nameTableKind: None) +!3 = !DIFile(filename: "test.cpp", directory: "/") +!4 = !{!0} +!5 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "Nums", file: !3, line: 1, size: 96, flags: DIFlagTypePassByValue, elements: !6, identifier: "_ZTS4Nums") +!6 = !{!7, !9, !10} +!7 = !DIDerivedType(tag: DW_TAG_member, name: "a", scope: !5, file: !3, line: 1, baseType: !8, size: 32) +!8 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!9 = !DIDerivedType(tag: DW_TAG_member, name: "b", scope: !5, file: !3, line: 1, baseType: !8, size: 32, offset: 32) +!10 = !DIDerivedType(tag: DW_TAG_member, name: "c", scope: !5, file: !3, line: 1, baseType: !8, size: 32, offset: 64) +!11 = !{i32 7, !"Dwarf Version", i32 5} +!12 = !{i32 2, !"Debug Info Version", i32 3} +!13 = !{i32 1, !"wchar_size", i32 4} +!14 = !{i32 7, !"uwtable", i32 1} +!15 = !{!"clang version 14.0.0"} +!40 = distinct !DISubprogram(name: "main", scope: !3, file: !3, line: 7, type: !41, scopeLine: 7, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !43) +!41 = !DISubroutineType(types: !42) +!42 = !{!8} +!43 = !{!44} +!44 = !DILocalVariable(name: "nums", scope: !40, file: !3, line: 8, type: !5) +!45 = distinct !DIAssignID() +!46 = !DILocation(line: 0, scope: !40) +!47 = !DILocation(line: 8, column: 3, scope: !40) +!48 = !DILocation(line: 8, column: 15, scope: !40) +!49 = distinct !DIAssignID() +!50 = !DILocation(line: 9, column: 7, scope: !51) +!51 = distinct !DILexicalBlock(scope: !40, file: !3, line: 9, column: 7) +!52 = !DILocation(line: 9, column: 7, scope: !40) +!53 = !DILocation(line: 10, column: 5, scope: !51) +!54 = !DILocation(line: 12, column: 5, scope: !51) +!55 = !DILocation(line: 14, column: 8, scope: !40) +!56 = distinct !DIAssignID() +!57 = !DILocation(line: 15, column: 3, scope: !40) +!58 = !DILocation(line: 17, column: 10, scope: !40) +!61 = distinct !DIAssignID() +!62 = !DILocation(line: 19, column: 3, scope: !40) +!63 = !DILocation(line: 21, column: 1, scope: !40) +!64 = !DILocation(line: 20, column: 3, scope: !40) +!65 = !DISubprogram(name: "step", linkageName: "_Z4stepv", scope: !3, file: !3, line: 5, type: !66, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !69) +!66 = !DISubroutineType(types: !67) +!67 = !{!68} +!68 = !DIBasicType(name: "bool", size: 8, encoding: DW_ATE_boolean) +!69 = !{} diff --git a/llvm/test/DebugInfo/assignment-tracking/X86/mem-loc-frag-fill.ll b/llvm/test/DebugInfo/assignment-tracking/X86/mem-loc-frag-fill.ll new file mode 100644 index 0000000..6d52174 --- /dev/null +++ b/llvm/test/DebugInfo/assignment-tracking/X86/mem-loc-frag-fill.ll @@ -0,0 +1,109 @@ +; RUN: llc %s -stop-before finalize-isel -o - \ +; RUN: -experimental-assignment-tracking \ +; RUN: -experimental-debug-variable-locations=false \ +; RUN: | FileCheck %s --implicit-check-not=DBG_ +; RUN: llc %s -stop-before finalize-isel -o - \ +; RUN: -experimental-assignment-tracking \ +; RUN: -experimental-debug-variable-locations=true \ +; RUN: | FileCheck %s --implicit-check-not=DBG_ + +;; Check that the mem-loc-frag-fill analysis works on a simple case; ensure +;; that location definitions are added to preserve memory locations of +;; fragments of variables at subsequent location definitions for other +;; fragments of the variable are not currently in memory. + +;; Test generated from: +;; $ cat test.cpp +;; struct Nums { int a, b, c; }; +;; void esc(struct Nums*); +;; void step(); +;; int main() { +;; struct Nums nums = { 1, 2, 1 }; //< Store to .c is elided. +;; step(); +;; nums.c = 2; //< Killing store. +;; step(); +;; esc(&nums); +;; return 0; +;; } +;; $ clang++ test.cpp -O2 -g -Xclang -fexperimental-assignmment-tracking -emit-llvm -S -o - + +;; Most check lines are inline in main. +; CHECK: ![[nums:[0-9]+]] = !DILocalVariable(name: "nums", + +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +%struct.Nums = type { i32, i32, i32 } + +; Function Attrs: mustprogress norecurse uwtable +define dso_local noundef i32 @main() local_unnamed_addr #0 !dbg !7 { +entry: + %nums = alloca %struct.Nums, align 8, !DIAssignID !18 +; CHECK: DBG_VALUE %stack.0.nums, $noreg, ![[nums]], !DIExpression(DW_OP_deref) + call void @llvm.dbg.assign(metadata i1 undef, metadata !12, metadata !DIExpression(), metadata !18, metadata ptr %nums, metadata !DIExpression()), !dbg !19 + store i64 8589934593, ptr %nums, align 8, !dbg !21, !DIAssignID !22 +; CHECK: MOV64mr %stack.0.nums, 1, $noreg, 0, $noreg, killed %0 + call void @llvm.dbg.assign(metadata i1 undef, metadata !12, metadata !DIExpression(), metadata !22, metadata ptr %nums, metadata !DIExpression()), !dbg !19 + call void @llvm.dbg.assign(metadata i1 undef, metadata !12, metadata !DIExpression(DW_OP_LLVM_fragment, 64, 32), metadata !22, metadata ptr undef, metadata !DIExpression()), !dbg !19 +; CHECK-NEXT: DBG_VALUE $noreg, $noreg, ![[nums]], !DIExpression(DW_OP_LLVM_fragment, 64, 32) +; CHECK-NEXT: DBG_VALUE %stack.0.nums, $noreg, ![[nums]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 0, 64) + tail call void @_Z4stepv(), !dbg !23 + %c = getelementptr inbounds %struct.Nums, ptr %nums, i64 0, i32 2, !dbg !24 + store i32 2, ptr %c, align 8, !dbg !25, !DIAssignID !31 +; CHECK: MOV32mi %stack.0.nums, 1, $noreg, 8, $noreg, 2 + call void @llvm.dbg.assign(metadata i32 2, metadata !12, metadata !DIExpression(DW_OP_LLVM_fragment, 64, 32), metadata !31, metadata ptr %c, metadata !DIExpression()), !dbg !19 +; CHECK-NEXT: DBG_VALUE %stack.0.nums, $noreg, ![[nums]], !DIExpression(DW_OP_plus_uconst, 8, DW_OP_deref, DW_OP_LLVM_fragment, 64, 32) + tail call void @_Z4stepv(), !dbg !32 + call void @_Z3escP4Nums(ptr noundef nonnull %nums), !dbg !33 + ret i32 0, !dbg !35 +} + +declare void @llvm.lifetime.start.p0i8(i64 immarg, ptr nocapture) #1 +declare !dbg !36 dso_local void @_Z4stepv() local_unnamed_addr #2 +declare !dbg !40 dso_local void @_Z3escP4Nums(ptr noundef) local_unnamed_addr #2 +declare void @llvm.lifetime.end.p0i8(i64 immarg, ptr nocapture) #1 +declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) #3 + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!2, !3, !4, !5} +!llvm.ident = !{!6} + +!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang version 14.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "test.cpp", directory: "/") +!2 = !{i32 7, !"Dwarf Version", i32 5} +!3 = !{i32 2, !"Debug Info Version", i32 3} +!4 = !{i32 1, !"wchar_size", i32 4} +!5 = !{i32 7, !"uwtable", i32 1} +!6 = !{!"clang version 14.0.0"} +!7 = distinct !DISubprogram(name: "main", scope: !1, file: !1, line: 4, type: !8, scopeLine: 4, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !11) +!8 = !DISubroutineType(types: !9) +!9 = !{!10} +!10 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!11 = !{!12} +!12 = !DILocalVariable(name: "nums", scope: !7, file: !1, line: 5, type: !13) +!13 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "Nums", file: !1, line: 1, size: 96, flags: DIFlagTypePassByValue, elements: !14, identifier: "_ZTS4Nums") +!14 = !{!15, !16, !17} +!15 = !DIDerivedType(tag: DW_TAG_member, name: "a", scope: !13, file: !1, line: 1, baseType: !10, size: 32) +!16 = !DIDerivedType(tag: DW_TAG_member, name: "b", scope: !13, file: !1, line: 1, baseType: !10, size: 32, offset: 32) +!17 = !DIDerivedType(tag: DW_TAG_member, name: "c", scope: !13, file: !1, line: 1, baseType: !10, size: 32, offset: 64) +!18 = distinct !DIAssignID() +!19 = !DILocation(line: 0, scope: !7) +!20 = !DILocation(line: 5, column: 3, scope: !7) +!21 = !DILocation(line: 5, column: 15, scope: !7) +!22 = distinct !DIAssignID() +!23 = !DILocation(line: 6, column: 3, scope: !7) +!24 = !DILocation(line: 7, column: 8, scope: !7) +!25 = !DILocation(line: 7, column: 10, scope: !7) +!31 = distinct !DIAssignID() +!32 = !DILocation(line: 8, column: 3, scope: !7) +!33 = !DILocation(line: 9, column: 3, scope: !7) +!34 = !DILocation(line: 11, column: 1, scope: !7) +!35 = !DILocation(line: 10, column: 3, scope: !7) +!36 = !DISubprogram(name: "step", linkageName: "_Z4stepv", scope: !1, file: !1, line: 3, type: !37, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !39) +!37 = !DISubroutineType(types: !38) +!38 = !{null} +!39 = !{} +!40 = !DISubprogram(name: "esc", linkageName: "_Z3escP4Nums", scope: !1, file: !1, line: 2, type: !41, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !39) +!41 = !DISubroutineType(types: !42) +!42 = !{null, !43} +!43 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !13, size: 64) diff --git a/llvm/test/DebugInfo/assignment-tracking/X86/nested-loop-frags.ll b/llvm/test/DebugInfo/assignment-tracking/X86/nested-loop-frags.ll new file mode 100644 index 0000000..09d39cb --- /dev/null +++ b/llvm/test/DebugInfo/assignment-tracking/X86/nested-loop-frags.ll @@ -0,0 +1,331 @@ +; RUN: llc %s -stop-after=finalize-isel -o - -experimental-assignment-tracking \ +; RUN: | FileCheck %s --implicit-check-not=DBG + +;; Test a variety of block inputs and lattice configurations for the assignment +;; tracking analysis (debug-ata). This is similar to nested-loop.ll and +;; nested-loop-sroa.ll except that the allocas are 64 bits and the stores are a +;; mix of 32 and 64 bits. Unlike nested-loop-sroa.ll this one is not a clone +;; of nested-loop.ll. + +;; The CFG looks like this: +;; entry +;; | +;; v +;; do.body <-----+ +;; | | +;; V | +;; do.body1 <--+ | +;; / \ | | +;; / \ | | +;; / \ | | +;; v v | | +;; if.then if.else | | +;; \ / | | +;; \ / | | +;; \ / | | +;; do.cond ----+ | +;; | | +;; v | +;; do.cond4 -----+ +;; | +;; v +;; do.end6 + +;; This version doesn't contain tables of assignments as it is difficult to neatly represent +;; the additional dimension of fragments (stores to part of the variable/alloca). + +;; Variable 'a' (!21) +;; Check that both the full assignment (!70) assignment to the lower bits (and !63) are +;; propagated to do.end6. +;; +;; Variable 'b' (!22) +;; Check mem=dbg assignment to the lower 32 bits in if.then causes a mem=phi (tested by +;; looking for value-based DBG_VALUE in do.end6). Meanwhile, check the assignment (!71) +;; is propagated to do.end6 for the upper bits (checked by looking for a memory location). +;; +;; Variable 'c' (!67) +;; Check initial dbg and mem assignment values are propagated through all blocks, with +;; dbg defs with the inital assignment ID put in do.cond and do.end6 (variable is always +;; in memory). The initial mem and dbg defs are to the whole variable, and the subsequent +;; dbg defs come in pairs, split for the high and low bits of the variable. +;; +;; Variable 'd' (!72) +;; Same as above, except the dbg def in do.cond has been split into two assignments +;; that both use the same ID as the inital one - one fragment is assigned in each of +;; the if-branches. +;; +;; Variable 'e' (!75) +;; The join in do.body covers assignments to different fragments. Out of entry +;; we have [0-31: mem=!77 dbg=!78 loc=val] [32-63: mem=!77 dbg=!76 loc=val]. +;; There's a tagged store to the lower bits in if.then and an untagged store to +;; the upper bits in if.else. The important check here is that in do.body the +;; memory location isn't used at the dbg def !77 as the live-in loc=val and the +;; incoming mem assignments are not all !77. + +; CHECK-DAG: ![[a:[0-9]+]] = !DILocalVariable(name: "a", +; CHECK-DAG: ![[b:[0-9]+]] = !DILocalVariable(name: "b", +; CHECK-DAG: ![[c:[0-9]+]] = !DILocalVariable(name: "c", +; CHECK-DAG: ![[d:[0-9]+]] = !DILocalVariable(name: "d", +; CHECK-DAG: ![[e:[0-9]+]] = !DILocalVariable(name: "e", + +;; Variables 'c' (!67) and 'd' (!72) are always stack-homed. +; CHECK: - { id: 2, name: c.addr, type: default, offset: 0, size: 8, alignment: 8, +; CHECK-NEXT: stack-id: default, callee-saved-register: '', callee-saved-restored: true, +; CHECK-NEXT: debug-info-variable: '![[c]]', debug-info-expression: '!DIExpression()', +; CHECK: - { id: 3, name: d.addr, type: default, offset: 0, size: 8, alignment: 8, +; CHECK-NEXT: stack-id: default, callee-saved-register: '', callee-saved-restored: true, +; CHECK-NEXT: debug-info-variable: '![[d]]', debug-info-expression: '!DIExpression()', + +source_filename = "test.cpp" +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +@g_a = dso_local local_unnamed_addr global i32 0, align 4, !dbg !0 +@g_b = dso_local local_unnamed_addr global i32 0, align 4, !dbg !5 +@g_c = dso_local local_unnamed_addr global i32 0, align 4, !dbg !8 + +define dso_local noundef i32 @_Z3funii(i32 noundef %a, i32 noundef %b) local_unnamed_addr #0 !dbg !17 { +entry: + %a.addr = alloca i64, align 4, !DIAssignID !58 ; VAR:a + call void @llvm.dbg.assign(metadata i1 undef, metadata !21, metadata !DIExpression(), metadata !58, metadata ptr %a.addr, metadata !DIExpression()), !dbg !27 ; VAR:a + %b.addr = alloca i64, align 4, !DIAssignID !64 ; VAR:b + call void @llvm.dbg.assign(metadata i1 undef, metadata !22, metadata !DIExpression(), metadata !64, metadata ptr %b.addr, metadata !DIExpression()), !dbg !27 ; VAR:b + %c.addr = alloca i64, align 4, !DIAssignID !68 ; VAR:c + call void @llvm.dbg.assign(metadata i1 undef, metadata !67, metadata !DIExpression(), metadata !68, metadata ptr %c.addr, metadata !DIExpression()), !dbg !27 ; VAR:c + %d.addr = alloca i64, align 4, !DIAssignID !73 ; VAR:d + call void @llvm.dbg.assign(metadata i1 undef, metadata !72, metadata !DIExpression(), metadata !73, metadata ptr %d.addr, metadata !DIExpression()), !dbg !27 ; VAR:d + %e.addr = alloca i64, align 4, !DIAssignID !76 ; VAR:e + call void @llvm.dbg.assign(metadata i1 undef, metadata !75, metadata !DIExpression(), metadata !76, metadata ptr %e.addr, metadata !DIExpression()), !dbg !27 ; VAR:e + ;%f.addr = alloca i64, align 4, !DIAssignID !80 ; VAR:f + ;call void @llvm.dbg.assign(metadata i1 undef, metadata !79, metadata !DIExpression(), metadata !80, metadata ptr %f.addr, metadata !DIExpression()), !dbg !27 ; VAR:f + store i64 1, ptr %a.addr, !DIAssignID !70 ; VAR:a + call void @llvm.dbg.assign(metadata i64 1, metadata !21, metadata !DIExpression(), metadata !70, metadata ptr %a.addr, metadata !DIExpression()), !dbg !27 ; VAR:a + store i64 2, ptr %b.addr, !DIAssignID !71 ; VAR:b + call void @llvm.dbg.assign(metadata i32 2, metadata !22, metadata !DIExpression(), metadata !71, metadata ptr %b.addr, metadata !DIExpression()), !dbg !27 ; VAR:b + store i32 9, ptr %e.addr, !DIAssignID !78 ; VAR:e + call void @llvm.dbg.assign(metadata i32 9, metadata !75, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 32), metadata !78, metadata ptr %e.addr, metadata !DIExpression()), !dbg !27 ; VAR:e + store i32 3, ptr %a.addr, !DIAssignID !63 ; VAR:a + store i32 4, ptr %b.addr, !DIAssignID !65 ; VAR:b + store i64 5, ptr %c.addr, !DIAssignID !69 ; VAR:c + call void @llvm.dbg.assign(metadata i64 5, metadata !67, metadata !DIExpression(), metadata !69, metadata ptr %c.addr, metadata !DIExpression()), !dbg !27 ; VAR:c + store i64 6, ptr %d.addr, !DIAssignID !74 ; VAR:d + call void @llvm.dbg.assign(metadata i64 6, metadata !72, metadata !DIExpression(), metadata !74, metadata ptr %d.addr, metadata !DIExpression()), !dbg !27 ; VAR:d + store i64 16, ptr %e.addr, !DIAssignID !77 ; VAR:e + ;store i32 11, ptr %f.addr, !DIAssignID !81 ; VAR:f + ;call void @llvm.dbg.assign(metadata i32 11, metadata !79, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 32), metadata !81, metadata ptr %f.addr, metadata !DIExpression()), !dbg !27 ; VAR:f + br label %do.body, !dbg !24 +; CHECK-LABEL: bb.0.entry: +; CHECK-NEXT: successors +; CHECK-NEXT: {{^ *$}} +; CHECK-NEXT: DBG_VALUE %stack.0.a.addr, $noreg, ![[a]], !DIExpression(DW_OP_deref) +; CHECK-NEXT: DBG_VALUE %stack.1.b.addr, $noreg, ![[b]], !DIExpression(DW_OP_deref) +; CHECK-NEXT: DBG_VALUE %stack.4.e.addr, $noreg, ![[e]], !DIExpression(DW_OP_deref) +; CHECK-NEXT: MOV64mi32 %stack.0.a.addr, 1, $noreg, 0, $noreg, 1 +; CHECK-NEXT: DBG_VALUE $noreg, $noreg, ![[a]], !DIExpression(DW_OP_LLVM_fragment, 32, 32) +; CHECK-NEXT: DBG_VALUE %stack.0.a.addr, $noreg, ![[a]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 0, 32) +; CHECK-NEXT: DBG_VALUE %stack.0.a.addr, $noreg, ![[a]], !DIExpression(DW_OP_deref) +; CHECK-NEXT: MOV64mi32 %stack.1.b.addr, 1, $noreg, 0, $noreg, 2 +; CHECK-NEXT: DBG_VALUE $noreg, $noreg, ![[b]], !DIExpression(DW_OP_LLVM_fragment, 32, 32) +; CHECK-NEXT: DBG_VALUE %stack.1.b.addr, $noreg, ![[b]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 0, 32) +; CHECK-NEXT: DBG_VALUE %stack.1.b.addr, $noreg, ![[b]], !DIExpression(DW_OP_deref) +; CHECK-NEXT: DBG_VALUE %stack.4.e.addr, $noreg, ![[e]], !DIExpression(DW_OP_plus_uconst, 4, DW_OP_deref, DW_OP_LLVM_fragment, 32, 32) +; CHECK-NEXT: DBG_VALUE %stack.4.e.addr, $noreg, ![[e]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 0, 32) +; CHECK-NEXT: MOV32mi %stack.0.a.addr, 1, $noreg, 0, $noreg, 3 +; CHECK-NEXT: DBG_VALUE $noreg, $noreg, ![[a]], !DIExpression(DW_OP_LLVM_fragment, 0, 32) +; CHECK-NEXT: DBG_VALUE %stack.0.a.addr, $noreg, ![[a]], !DIExpression(DW_OP_plus_uconst, 4, DW_OP_deref, DW_OP_LLVM_fragment, 32, 32) +; CHECK-NEXT: MOV32mi %stack.1.b.addr, 1, $noreg, 0, $noreg, 4 +; CHECK-NEXT: DBG_VALUE $noreg, $noreg, ![[b]], !DIExpression(DW_OP_LLVM_fragment, 0, 32) +; CHECK-NEXT: DBG_VALUE %stack.1.b.addr, $noreg, ![[b]], !DIExpression(DW_OP_plus_uconst, 4, DW_OP_deref, DW_OP_LLVM_fragment, 32, 32) +; CHECK-NEXT: MOV64mi32 %stack.2.c.addr, 1, $noreg, 0, $noreg, 5 +; CHECK-NEXT: MOV64mi32 %stack.3.d.addr, 1, $noreg, 0, $noreg, 6 +; CHECK-NEXT: MOV64mi32 %stack.4.e.addr, 1, $noreg, 0, $noreg, 16 +; CHECK-NEXT: DBG_VALUE $noreg, $noreg, ![[e]], !DIExpression() +; CHECK-NEXT: {{^ *$}} + +do.body: ; preds = %do.cond4, %entry + call void @llvm.dbg.assign(metadata i64 16, metadata !75, metadata !DIExpression(), metadata !77, metadata ptr %e.addr, metadata !DIExpression()), !dbg !27 ; VAR:e + ;call void @llvm.dbg.assign(metadata i32 11, metadata !79, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 32), metadata !81, metadata ptr %f.addr, metadata !DIExpression()), !dbg !27 ; VAR:f + %.pre10 = load i32, ptr @g_a, align 4, !dbg !27 + br label %do.body1, !dbg !34 +; CHECK-LABEL: bb.1.do.body: +; CHECK-NEXT: successors +; CHECK-NEXT: {{^ *$}} +; CHECK-NEXT: DBG_VALUE 16, $noreg, ![[e]], !DIExpression() +; CHECK-NEXT: %0:gr32 = MOV32rm $rip, 1, $noreg, @g_a, $noreg +; CHECK-NEXT: {{^ *$}} + +do.body1: ; preds = %do.cond, %do.body + %0 = phi i32 [ %.pre10, %do.body ], [ %1, %do.cond ], !dbg !27 + ;call void @llvm.dbg.assign(metadata i32 11, metadata !79, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 32), metadata !81, metadata ptr %f.addr, metadata !DIExpression()), !dbg !27 ; VAR:f + %tobool.not = icmp eq i32 %0, 0, !dbg !27 + br i1 %tobool.not, label %if.else, label %if.then, !dbg !35 +; CHECK-LABEL: bb.2.do.body1: +; CHECK-NEXT: successors +; CHECK-NEXT: {{^ *$}} +; CxHECK-NEXT: DBG_VALUE 11, $noreg, ![[f]], !DIExpression(DW_OP_LLVM_fragment, 0, 32) +; CHECK: JMP_1 +; CHECK-NEXT: {{^ *$}} + +if.then: ; preds = %do.body1 + %.pre = load i32, ptr @g_a, align 4, !dbg !27 + store i32 %.pre, ptr %b.addr, !DIAssignID !66 ; VAR:b + call void @llvm.dbg.assign(metadata i32 %.pre, metadata !22, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 32), metadata !66, metadata ptr %b.addr, metadata !DIExpression()), !dbg !27 ; VAR:b + store i32 6, ptr %d.addr, !DIAssignID !74 ; VAR:d + call void @llvm.dbg.assign(metadata i32 6, metadata !72, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !74, metadata ptr %d.addr, metadata !DIExpression()), !dbg !27 ; VAR:d + call void @llvm.dbg.assign(metadata i32 8, metadata !75, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !82, metadata ptr %e.addr, metadata !DIExpression()), !dbg !27 ; VAR:e + store i32 8, ptr %e.addr, !DIAssignID !82 ; VAR:e + br label %do.cond, !dbg !39 +; CHECK-LABEL: bb.3.if.then: +; CHECK-NEXT: successors +; CHECK-NEXT: {{^ *$}} +; CHECK-NEXT: %5:gr32 = MOV32rm $rip, 1, $noreg, @g_a, $noreg +; CHECK-NEXT: MOV32mr %stack.1.b.addr, 1, $noreg, 0, $noreg, killed %5 +; CHECK-NEXT: DBG_VALUE %stack.1.b.addr, $noreg, ![[b]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 0, 32) +; CHECK-NEXT: MOV32mi %stack.3.d.addr, 1, $noreg, 0, $noreg, 6 +; CHECK-NEXT: DBG_VALUE 8, $noreg, ![[e]], !DIExpression(DW_OP_LLVM_fragment, 32, 32) +; CHECK-NEXT: MOV32mi %stack.4.e.addr, 1, $noreg, 0, $noreg, 8 +; CHECK-NEXT: DBG_VALUE %stack.4.e.addr, $noreg, ![[e]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 32, 32) +; CHECK-NEXT: JMP_1 %bb.5 +; CHECxK-NEXT: {{^ *$}} + +if.else: ; preds = %do.body1 + store i32 6, ptr %d.addr, !DIAssignID !74 ; VAR:d + call void @llvm.dbg.assign(metadata i32 6, metadata !72, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 32), metadata !74, metadata ptr %d.addr, metadata !DIExpression()), !dbg !27 ; VAR:d + %e.high32 = getelementptr i32, ptr %e.addr, i32 1 + store i32 15, ptr %e.high32 ; VAR:e + ;store i32 10, ptr %f.addr ; VAR:f + br label %do.cond +; CHECK-LABEL: bb.4.if.else: +; CHECK-NEXT: successors +; CHECK-NEXT: {{^ *$}} +; CHECK-NEXT: MOV32mi %stack.3.d.addr, 1, $noreg, 0, $noreg, 6 +; CHECK-NEXT: MOV32mi %stack.4.e.addr, 1, $noreg, 4, $noreg, 15 +; CHECK-NEXT: DBG_VALUE %stack.4.e.addr, $noreg, !34, !DIExpression(DW_OP_plus_uconst, 4, DW_OP_deref, DW_OP_LLVM_fragment, 32, 32) +; CHECK: {{^ *$}} + +do.cond: ; preds = %if.then, %if.else + call void @llvm.dbg.assign(metadata i1 undef, metadata !67, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 32), metadata !69, metadata ptr %c.addr, metadata !DIExpression()), !dbg !27 ; VAR:c + call void @llvm.dbg.assign(metadata i1 undef, metadata !67, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !69, metadata ptr %c.addr, metadata !DIExpression()), !dbg !27 ; VAR:c + ;call void @llvm.dbg.assign(metadata i32 11, metadata !79, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 32), metadata !81, metadata ptr %f.addr, metadata !DIExpression()), !dbg !27 ; VAR:f + %1 = load i32, ptr @g_b, align 4, !dbg !43 + %tobool3.not = icmp eq i32 %1, 0, !dbg !43 + br i1 %tobool3.not, label %do.cond4, label %do.body1, !dbg !44, !llvm.loop !45 +; CHECK-LABEL: bb.5.do.cond: +; CHECK-NEXT: successors +; CHECK-NEXT: {{^ *$}} +; xCHECK-NEXT: XXX ?DBG_VALUE %stack.2.c.addr, $noreg, ![[c]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 32, 32) +; xCHECK-NEXT: DBG_VALUE 11, $noreg, ![[f]], !DIExpression(DW_OP_LLVM_fragment, 32, 32) +; CHECK-NOT: DBG_VALUE +; CHECK: {{^ *$}} + +do.cond4: ; preds = %do.cond + %2 = load i32, ptr @g_c, align 4, !dbg !48 + %tobool5.not = icmp eq i32 %2, 0, !dbg !48 + br i1 %tobool5.not, label %do.end6, label %do.body, !dbg !49, !llvm.loop !50 +; CHECK-LABEL: bb.6.do.cond4: +; CHECK-NEXT: successors +; CHECK-NEXT: {{^ *$}} +; CHECK-NOT: DBG +; CHECK: {{^ *$}} + +do.end6: ; preds = %do.cond4 + call void @llvm.dbg.assign(metadata i32 3, metadata !21, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 32), metadata !63, metadata ptr %a.addr, metadata !DIExpression()), !dbg !27; VAR:a + call void @llvm.dbg.assign(metadata i32 0, metadata !21, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !70, metadata ptr %a.addr, metadata !DIExpression()), !dbg !27; VAR:a + ;call void @llvm.dbg.assign(metadata i32 3, metadata !21, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !81, metadata ptr %a.addr, metadata !DIExpression()), !dbg !27; VAR:a + call void @llvm.dbg.assign(metadata i32 4, metadata !22, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 32), metadata !65, metadata ptr %b.addr, metadata !DIExpression()), !dbg !27 ; VAR:b + call void @llvm.dbg.assign(metadata i32 0, metadata !22, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !71, metadata ptr %b.addr, metadata !DIExpression()), !dbg !27 ; VAR:b + call void @llvm.dbg.assign(metadata i1 undef, metadata !67, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 32), metadata !69, metadata ptr %c.addr, metadata !DIExpression()), !dbg !27 ; VAR:c + call void @llvm.dbg.assign(metadata i1 undef, metadata !67, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !69, metadata ptr %c.addr, metadata !DIExpression()), !dbg !27 ; VAR:c + call void @llvm.dbg.assign(metadata i32 6, metadata !72, metadata !DIExpression(), metadata !74, metadata ptr %d.addr, metadata !DIExpression()), !dbg !27 ; VAR:d + ;call void @llvm.dbg.assign(metadata i32 11, metadata !79, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 32), metadata !81, metadata ptr %f.addr, metadata !DIExpression()), !dbg !27 ; VAR:f + ret i32 0, !dbg !53 +; CHECK-LABEL: bb.7.do.end6: +; CHECK-NEXT: DBG_VALUE %stack.0.a.addr, $noreg, ![[a]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 0, 32) +; CHECK-NEXT: DBG_VALUE %stack.0.a.addr, $noreg, ![[a]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 32, 32) +; CHECK-NEXT: DBG_VALUE 4, $noreg, ![[b]], !DIExpression(DW_OP_LLVM_fragment, 0, 32) +; CHECK-NEXT: DBG_VALUE %stack.1.b.addr, $noreg, ![[b]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 32, 32) +; xCHECK-NEXT: XXX ? DBG_VALUE %stack.3.d.addr, $noreg, ![[d]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 32, 32) +; xCHECK-NEXT: DBG_VALUE 11, $noreg, ![[f]], !DIExpression(DW_OP_LLVM_fragment, 32, 32) +} + +declare !dbg !54 void @_Z4calli(i32 noundef) local_unnamed_addr #1 +declare void @llvm.dbg.declare(metadata, metadata, metadata) +declare void @llvm.dbg.value(metadata, metadata, metadata) +declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) + +!llvm.dbg.cu = !{!2} +!llvm.module.flags = !{!10, !11, !12, !13, !14, !15} +!llvm.ident = !{!16} + +!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression()) +!1 = distinct !DIGlobalVariable(name: "g_a", scope: !2, file: !3, line: 1, type: !7, isLocal: false, isDefinition: true) +!2 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !3, producer: "clang version 16.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, globals: !4, splitDebugInlining: false, nameTableKind: None) +!3 = !DIFile(filename: "test.cpp", directory: "/") +!4 = !{!0, !5, !8} +!5 = !DIGlobalVariableExpression(var: !6, expr: !DIExpression()) +!6 = distinct !DIGlobalVariable(name: "g_b", scope: !2, file: !3, line: 1, type: !7, isLocal: false, isDefinition: true) +!7 = !DIBasicType(name: "long long", size: 64, encoding: DW_ATE_signed) +!8 = !DIGlobalVariableExpression(var: !9, expr: !DIExpression()) +!9 = distinct !DIGlobalVariable(name: "g_c", scope: !2, file: !3, line: 1, type: !7, isLocal: false, isDefinition: true) +!10 = !{i32 7, !"Dwarf Version", i32 5} +!11 = !{i32 2, !"Debug Info Version", i32 3} +!12 = !{i32 1, !"wchar_size", i32 4} +!13 = !{i32 8, !"PIC Level", i32 2} +!14 = !{i32 7, !"PIE Level", i32 2} +!15 = !{i32 7, !"uwtable", i32 2} +!16 = !{!"clang version 16.0.0"} +!17 = distinct !DISubprogram(name: "fun", linkageName: "_Z3funii", scope: !3, file: !3, line: 3, type: !18, scopeLine: 3, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !20) +!18 = !DISubroutineType(types: !19) +!19 = !{!7, !7, !7} +!20 = !{!21, !22} +!21 = !DILocalVariable(name: "a", arg: 1, scope: !17, file: !3, line: 3, type: !7) +!22 = !DILocalVariable(name: "b", arg: 2, scope: !17, file: !3, line: 3, type: !7) +!23 = !DILocation(line: 0, scope: !17) +!24 = !DILocation(line: 4, column: 3, scope: !17) +!25 = !DILocation(line: 5, column: 5, scope: !26) +!26 = distinct !DILexicalBlock(scope: !17, file: !3, line: 4, column: 6) +!27 = !DILocation(line: 7, column: 11, scope: !28) +!28 = distinct !DILexicalBlock(scope: !29, file: !3, line: 7, column: 11) +!29 = distinct !DILexicalBlock(scope: !26, file: !3, line: 6, column: 8) +!34 = !DILocation(line: 6, column: 5, scope: !26) +!35 = !DILocation(line: 7, column: 11, scope: !29) +!36 = !DILocation(line: 8, column: 11, scope: !37) +!37 = distinct !DILexicalBlock(scope: !28, file: !3, line: 7, column: 16) +!38 = !DILocation(line: 9, column: 9, scope: !37) +!39 = !DILocation(line: 10, column: 7, scope: !37) +!40 = !DILocation(line: 11, column: 11, scope: !41) +!41 = distinct !DILexicalBlock(scope: !28, file: !3, line: 10, column: 14) +!42 = !DILocation(line: 0, scope: !28) +!43 = !DILocation(line: 13, column: 14, scope: !26) +!44 = !DILocation(line: 13, column: 5, scope: !29) +!45 = distinct !{!45, !34, !46, !47} +!46 = !DILocation(line: 13, column: 17, scope: !26) +!47 = !{!"llvm.loop.mustprogress"} +!48 = !DILocation(line: 14, column: 12, scope: !17) +!49 = !DILocation(line: 14, column: 3, scope: !26) +!50 = distinct !{!50, !24, !51, !47} +!51 = !DILocation(line: 14, column: 15, scope: !17) +!52 = !DILocation(line: 15, column: 12, scope: !17) +!53 = !DILocation(line: 15, column: 3, scope: !17) +!54 = !DISubprogram(name: "call", linkageName: "_Z4calli", scope: !3, file: !3, line: 2, type: !55, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !57) +!55 = !DISubroutineType(types: !56) +!56 = !{null, !7} +!57 = !{} +!58 = distinct !DIAssignID() +!63 = distinct !DIAssignID() +!64 = distinct !DIAssignID() +!65 = distinct !DIAssignID() +!66 = distinct !DIAssignID() +!67 = !DILocalVariable(name: "c", scope: !17, file: !3, line: 3, type: !7) +!68 = distinct !DIAssignID() +!69 = distinct !DIAssignID() +!70 = distinct !DIAssignID() +!71 = distinct !DIAssignID() +!72 = !DILocalVariable(name: "d", scope: !17, file: !3, line: 3, type: !7) +!73 = distinct !DIAssignID() +!74 = distinct !DIAssignID() +!75 = !DILocalVariable(name: "e", scope: !17, file: !3, line: 3, type: !7) +!76 = distinct !DIAssignID() +!77 = distinct !DIAssignID() +!78 = distinct !DIAssignID() +!82 = distinct !DIAssignID() diff --git a/llvm/test/DebugInfo/assignment-tracking/X86/nested-loop-sroa.ll b/llvm/test/DebugInfo/assignment-tracking/X86/nested-loop-sroa.ll new file mode 100644 index 0000000..055a6a1 --- /dev/null +++ b/llvm/test/DebugInfo/assignment-tracking/X86/nested-loop-sroa.ll @@ -0,0 +1,403 @@ +; RUN: llc %s -stop-after=finalize-isel -o - -experimental-assignment-tracking \ +; RUN: | FileCheck %s --implicit-check-not=DBG + +;; Test a variety of block inputs and lattice configurations for the assignment +;; tracking analysis (debug-ata). This is the same as nested-loop.ll except each +;; alloca holds a fragment of a variable instead of a whole variable. +;; The CFG looks like this: +;; entry +;; | +;; v +;; do.body <-----+ +;; | | +;; V | +;; do.body1 <--+ | +;; / \ | | +;; / \ | | +;; / \ | | +;; v v | | +;; if.then if.else | | +;; \ / | | +;; \ / | | +;; \ / | | +;; do.cond ----+ | +;; | | +;; v | +;; do.cond4 -----+ +;; | +;; v +;; do.end6 + +;; Key +;; ╔═════════════════════╦═══════════════════════════════════════════════════════════════════╗ +;; ║ thing ║ meaning ║ +;; ╠═════════════════════╬═══════════════════════════════════════════════════════════════════╣ +;; ║ mem= ║ assignment of !n or phi to memory ║ +;; ║ dbg= ║ assignment of !n or phi to source variable ║ +;; ║ phi ║ phi of assignments (operands not traked)* ║ +;; ║ loc= ║ location to use is value (implicit location), stack home, or none ║ +;; ╚═════════════════════╩═══════════════════════════════════════════════════════════════════╝ +;; (*) A phi in the def column represents an assignment made by an untagged store. +;; +;; Variable 'a' (!21) +;; Check initial dbg and mem assignment values are propagated through all blocks. +;; ╔═════════════╦══════════════════════════╦═════════════════╦══════════════════════════╗ +;; ║ block ║ in ║ def ║ out ║ +;; ╠═════════════╬══════════════════════════╬═════════════════╬══════════════════════════╣ +;; ║ entry ║ ║ mem=!63 dbg=!70 ║ mem=!63 dbg=!70 loc=val ║ +;; ║ do.end6 ║ mem=!63 dbg=!70 loc=val ║ mem=!63 ║ mem=!63 dbg=!63 loc=mem ║ +;; ╚═════════════╩══════════════════════════╩═════════════════╩══════════════════════════╝ +;; +;; Variable 'b' (!22) +;; Check mem=dbg assignment on branch in nested loop causes a mem=phi (tested by looking +;; for value-based DBG_VALUE in do.end6). +;; ╔═════════════╦══════════════════════════╦═════════════════╦══════════════════════════╗ +;; ║ block ║ in ║ def ║ out ║ +;; ╠═════════════╬══════════════════════════╬═════════════════╬══════════════════════════╣ +;; ║ entry ║ ║ mem=!65 dbg=!71 ║ mem=!65 dbg=!71 loc=val ║ +;; ║ if.then ║ mem=phi dbg=phi loc=none ║ mem=!66 dbg=!66 ║ mem=!66 dbg=!66 loc=mem ║ +;; ║ do.end6 ║ mem=phi dbg=phi loc=none ║ mem=!65 ║ mem=phi dbg=!65 loc=val ║ +;; ╚═════════════╩══════════════════════════╩═════════════════╩══════════════════════════╝ +;; +;; Variable 'c' (!67) +;; Check initial dbg and mem assignment values are propagated through all blocks, with +;; dbg defs with the inital assignment ID put in do.cond and do.end6 (variable is always +;; in memory). +;; ╔═════════════╦══════════════════════════╦═════════════════╦══════════════════════════╗ +;; ║ block ║ in ║ def ║ out ║ +;; ╠═════════════╬══════════════════════════╬═════════════════╬══════════════════════════╣ +;; ║ entry ║ ║ mem=!69 dbg=!69 ║ mem=!69 dbg=!69 loc=mem ║ +;; ║ do.cond ║ mem=!69 dbg=!69 loc=mem ║ dbg=!69 ║ mem=!69 dbg=!69 loc=mem ║ +;; ║ do.end6 ║ mem=!69 dbg=!69 loc=mem ║ dbg=!69 ║ mem=!69 dbg=!69 loc=mem ║ +;; ╚═════════════╩══════════════════════════╩═════════════════╩══════════════════════════╝ +;; +;; Variable 'd' (!72) +;; Same as above, except the dbg def in do.cond has been swapped for a dbg=mem def (with +;; the initial assignment ID) and has been moved to if.else. +;; ╔═════════════╦══════════════════════════╦═════════════════╦══════════════════════════╗ +;; ║ block ║ in ║ def ║ out ║ +;; ╠═════════════╬══════════════════════════╬═════════════════╬══════════════════════════╣ +;; ║ entry ║ ║ mem=!74 dbg=!74 ║ mem=!74 dbg=!74 loc=mem ║ +;; ║ if.else ║ mem=!74 dbg=!74 loc=mem ║ mem=!74 dbg=!74 ║ mem=!74 dbg=!74 loc=mem ║ +;; ║ do.end6 ║ mem=!74 dbg=!74 loc=mem ║ dbg=!74 ║ mem=!74 dbg=!74 loc=mem ║ +;; ╚═════════════╩══════════════════════════╩═════════════════╩══════════════════════════╝ +;; +;; Variable 'e' (!75) +;; mem defs in entry, if.then and if.else with same ID (!77). Check these join correct +;; (tested using the dbg defs of the same ID - the memory location is valid at each of +;; these with that ID). +;; ╔═════════════╦══════════════════════════╦═════════════════╦══════════════════════════╗ +;; ║ block ║ in ║ def ║ out ║ +;; ╠═════════════╬══════════════════════════╬═════════════════╬══════════════════════════╣ +;; ║ entry ║ ║ mem=!77 dbg=!78 ║ mem=!77 dbg=!78 loc=val ║ +;; ║ do.body ║ mem=!77 dbg=phi loc=none ║ dbg=!77 ║ mem=!77 dbg=!77 loc=mem ║ +;; ║ do.body1 ║ mem=!77 dbg=!77 loc=mem ║ dbg=!77 ║ mem=!77 dbg=!77 loc=mem ║ +;; ║ if.then ║ mem=!77 dbg=!77 loc=mem ║ mem=!77 ║ mem=!77 dbg=!77 loc=mem ║ +;; ║ if.else ║ mem=!77 dbg=!77 loc=mem ║ mem=!77 ║ mem=!77 dbg=!77 loc=mem ║ +;; ╚═════════════╩══════════════════════════╩═════════════════╩══════════════════════════╝ +;; +;; Variable 'f' (!79) +;; mem def in entry and an untagged store in if.else (results in mem=phi, dbg=phi defs). +;; Use dbg defs in do.body, do.body1, do.cond and do.end6 to check the phi-ness +;; has been propagated (the memory loc at each is not a valid location). Check the memory +;; loc is used in if.else after the untagged store. +;; ╔═════════════╦══════════════════════════╦═════════════════╦══════════════════════════╗ +;; ║ block ║ in ║ def ║ out ║ +;; ╠═════════════╬══════════════════════════╬═════════════════╬══════════════════════════╣ +;; ║ entry ║ ║ mem=!81 dbg=!81 ║ mem=!81 dbg=!81 loc=mem ║ +;; ║ do.body ║ mem=phi dbg=phi loc=none ║ dbg=!81 ║ mem=phi dbg=!81 loc=val ║ +;; ║ do.body1 ║ mem=phi dbg=phi loc=none ║ dbg=!81 ║ mem=phi dbg=!81 loc=val ║ +;; ║ if.else ║ mem=phi dbg=phi loc=none ║ mem=phi dbg=phi ║ mem=phi dbg=phi loc=mem ║ +;; ║ do.cond ║ mem=phi dbg=phi loc=none ║ dbg=!81 ║ mem=phi dbg=!81 loc=val ║ +;; ║ do.end6 ║ mem=phi dbg=!81 loc=val ║ dbg=!81 ║ mem=!69 dbg=!81 loc=val ║ +;; ╚═════════════╩══════════════════════════╩═════════════════╩══════════════════════════╝ +;; +;; Variable 'g' (!82) +;; Check that joining loc=none with anything else results in loc=none. The out-loc of +;; entry is set up to be loc=none by following an untagged store with a tagged store, +;; with the linked dbg.assign in another block. The dbg.assign is in do.body - it follows +;; another store linked to it. Importantly, there are other instructions wedged between +;; them, which is how we test that the in-loc is loc=none. The result of encountering +;; a tagged store while the loc=none is to emit nothing. Thus, we check that no location +;; def is emitted in do.body until the dbg.assign is encountered (after the load that was +;; wedged between the store and intrinsic). +;; ╔═════════════╦══════════════════════════╦═════════════════╦══════════════════════════╗ +;; ║ block ║ in ║ def ║ out ║ +;; ╠═════════════╬══════════════════════════╬═════════════════╬══════════════════════════╣ +;; ║ entry ║ ║ mem=phi dbg=phi ║ mem=phi dbg=phi loc=none ║ +;; ║ do.body ║ mem=phi dbg=phi loc=none ║ mem=!84 dbg=!84 ║ mem=!84 dbg=!84 loc=mem ║ +;; ╚═════════════╩══════════════════════════╩═════════════════╩══════════════════════════╝ + +; CHECK-DAG: ![[a:[0-9]+]] = !DILocalVariable(name: "a", +; CHECK-DAG: ![[b:[0-9]+]] = !DILocalVariable(name: "b", +; CHECK-DAG: ![[c:[0-9]+]] = !DILocalVariable(name: "c", +; CHECK-DAG: ![[d:[0-9]+]] = !DILocalVariable(name: "d", +; CHECK-DAG: ![[e:[0-9]+]] = !DILocalVariable(name: "e", +; CHECK-DAG: ![[f:[0-9]+]] = !DILocalVariable(name: "f", +; CHECK-DAG: ![[g:[0-9]+]] = !DILocalVariable(name: "g", + +;; Variables 'c' (!67) and 'd' (!72) are always stack-homed, but the analysis +;; currently doesn't try to work this out for sroa-d vars. Instead, we get a +;; set of DBG_VALUEs. +;; TODO / Wishlist: +; KCEHC: - { id: 2, name: c.addr, type: default, offset: 0, size: 4, alignment: 4, +; TXEN-KCEHC: stack-id: default, callee-saved-register: '', callee-saved-restored: true, +; TXEN-KCEHC: debug-info-variable: '![[c]]', debug-info-expression: '!DIExpression()', +; KCEHC: - { id: 3, name: d.addr, type: default, offset: 0, size: 4, alignment: 4, +; TXEN-KCEHC: stack-id: default, callee-saved-register: '', callee-saved-restored: true, +; TXEN-KCEHC: debug-info-variable: '![[d]]', debug-info-expression: '!DIExpression()', + +source_filename = "test.cpp" +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +@g_a = dso_local local_unnamed_addr global i32 0, align 4, !dbg !0 +@g_b = dso_local local_unnamed_addr global i32 0, align 4, !dbg !5 +@g_c = dso_local local_unnamed_addr global i32 0, align 4, !dbg !8 + +define dso_local noundef i32 @_Z3funii(i32 noundef %a, i32 noundef %b) local_unnamed_addr #0 !dbg !17 { +entry: + %a.addr = alloca i32, align 4, !DIAssignID !58 ; VAR:a + call void @llvm.dbg.assign(metadata i1 undef, metadata !21, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !58, metadata ptr %a.addr, metadata !DIExpression()), !dbg !27 ; VAR:a + %b.addr = alloca i32, align 4, !DIAssignID !64 ; VAR:b + call void @llvm.dbg.assign(metadata i1 undef, metadata !22, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !64, metadata ptr %b.addr, metadata !DIExpression()), !dbg !27 ; VAR:b + %c.addr = alloca i32, align 4, !DIAssignID !68 ; VAR:c + call void @llvm.dbg.assign(metadata i1 undef, metadata !67, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !68, metadata ptr %c.addr, metadata !DIExpression()), !dbg !27 ; VAR:c + %d.addr = alloca i32, align 4, !DIAssignID !73 ; VAR:d + call void @llvm.dbg.assign(metadata i1 undef, metadata !72, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !73, metadata ptr %d.addr, metadata !DIExpression()), !dbg !27 ; VAR:d + %e.addr = alloca i32, align 4, !DIAssignID !76 ; VAR:e + call void @llvm.dbg.assign(metadata i1 undef, metadata !75, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !76, metadata ptr %e.addr, metadata !DIExpression()), !dbg !27 ; VAR:e + %f.addr = alloca i32, align 4, !DIAssignID !80 ; VAR:f + call void @llvm.dbg.assign(metadata i1 undef, metadata !79, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !80, metadata ptr %f.addr, metadata !DIExpression()), !dbg !27 ; VAR:f + %g.addr = alloca i32, align 4, !DIAssignID !83 ; VAR:g + call void @llvm.dbg.assign(metadata i1 undef, metadata !82, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !83, metadata ptr %g.addr, metadata !DIExpression()), !dbg !27 ; VAR:g + store i32 1, ptr %a.addr, !DIAssignID !70 ; VAR:a + call void @llvm.dbg.assign(metadata i32 1, metadata !21, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !70, metadata ptr %a.addr, metadata !DIExpression()), !dbg !27 ; VAR:a + store i32 2, ptr %b.addr, !DIAssignID !71 ; VAR:b + call void @llvm.dbg.assign(metadata i32 2, metadata !22, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !71, metadata ptr %b.addr, metadata !DIExpression()), !dbg !27 ; VAR:b + store i32 12, ptr %g.addr ; VAR:g + store i32 9, ptr %e.addr, !DIAssignID !78 ; VAR:e + call void @llvm.dbg.assign(metadata i32 9, metadata !75, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !78, metadata ptr %e.addr, metadata !DIExpression()), !dbg !27 ; VAR:e + store i32 3, ptr %a.addr, !DIAssignID !63 ; VAR:a + store i32 4, ptr %b.addr, !DIAssignID !65 ; VAR:b + store i32 5, ptr %c.addr, !DIAssignID !69 ; VAR:c + call void @llvm.dbg.assign(metadata i32 5, metadata !67, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !69, metadata ptr %c.addr, metadata !DIExpression()), !dbg !27 ; VAR:c + store i32 6, ptr %d.addr, !DIAssignID !74 ; VAR:d + call void @llvm.dbg.assign(metadata i32 6, metadata !72, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !74, metadata ptr %d.addr, metadata !DIExpression()), !dbg !27 ; VAR:d + store i32 8, ptr %e.addr, !DIAssignID !77 ; VAR:e + store i32 13, ptr %g.addr, !DIAssignID !84 ; VAR:g + store i32 11, ptr %f.addr, !DIAssignID !81 ; VAR:f + call void @llvm.dbg.assign(metadata i32 11, metadata !79, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !81, metadata ptr %f.addr, metadata !DIExpression()), !dbg !27 ; VAR:f + br label %do.body, !dbg !24 +; CHECK-LABEL: bb.0.entry: +; CHECK-NEXT: successors +; CHECK-NEXT: {{^ *$}} +; CHECK-NEXT: DBG_VALUE %stack.0.a.addr, $noreg, ![[a]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 32, 32) +; CHECK-NEXT: DBG_VALUE %stack.1.b.addr, $noreg, ![[b]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 32, 32) +; CHECK-NEXT: DBG_VALUE %stack.2.c.addr, $noreg, ![[c]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 32, 32) +; CHECK-NEXT: DBG_VALUE %stack.3.d.addr, $noreg, ![[d]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 32, 32) +; CHECK-NEXT: DBG_VALUE %stack.4.e.addr, $noreg, ![[e]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 32, 32) +; CHECK-NEXT: DBG_VALUE %stack.5.f.addr, $noreg, ![[f]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 32, 32) +; CHECK-NEXT: DBG_VALUE %stack.6.g.addr, $noreg, ![[g]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 32, 32) +; CHECK-NEXT: MOV32mi %stack.0.a.addr, 1, $noreg, 0, $noreg, 3 +; CHECK-NEXT: DBG_VALUE 1, $noreg, ![[a]], !DIExpression(DW_OP_LLVM_fragment, 32, 32) +; CHECK-NEXT: MOV32mi %stack.1.b.addr, 1, $noreg, 0, $noreg, 4 +; CHECK-NEXT: DBG_VALUE 2, $noreg, ![[b]], !DIExpression(DW_OP_LLVM_fragment, 32, 32) +; CHECK-NEXT: MOV32mi %stack.2.c.addr, 1, $noreg, 0, $noreg, 5 +; CHECK-NEXT: MOV32mi %stack.3.d.addr, 1, $noreg, 0, $noreg, 6 +; CHECK-NEXT: MOV32mi %stack.4.e.addr, 1, $noreg, 0, $noreg, 8 +; CHECK-NEXT: DBG_VALUE 9, $noreg, ![[e]], !DIExpression(DW_OP_LLVM_fragment, 32, 32) +; CHECK-NEXT: MOV32mi %stack.6.g.addr, 1, $noreg, 0, $noreg, 13 +; CHECK-NEXT: DBG_VALUE $noreg, $noreg, ![[g]], !DIExpression(DW_OP_LLVM_fragment, 32, 32) +; CHECK-NEXT: MOV32mi %stack.5.f.addr, 1, $noreg, 0, $noreg, 11 +; CHECK-NEXT: {{^ *$}} + +do.body: ; preds = %do.cond4, %entry + call void @llvm.dbg.assign(metadata i32 8, metadata !75, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !77, metadata ptr %e.addr, metadata !DIExpression()), !dbg !27 ; VAR:e + call void @llvm.dbg.assign(metadata i32 11, metadata !79, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !81, metadata ptr %f.addr, metadata !DIExpression()), !dbg !27 ; VAR:f + store i32 13, ptr %g.addr, !DIAssignID !84 ; VAR:g + %.pre10 = load i32, ptr @g_a, align 4, !dbg !27 + call void @llvm.dbg.assign(metadata i32 11, metadata !82, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !84, metadata ptr %g.addr, metadata !DIExpression()), !dbg !27 ; VAR:g + br label %do.body1, !dbg !34 +; CHECK-LABEL: bb.1.do.body: +; CHECK-NEXT: successors +; CHECK-NEXT: {{^ *$}} +; CHECK-NEXT: DBG_VALUE %stack.4.e.addr, $noreg, ![[e]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 32, 32) +; CHECK-NEXT: DBG_VALUE 11, $noreg, ![[f]], !DIExpression(DW_OP_LLVM_fragment, 32, 32) +; CHECK-NEXT: MOV32mi %stack.6.g.addr, 1, $noreg, 0, $noreg, 13 +; CHECK-NEXT: %0:gr32 = MOV32rm $rip, 1, $noreg, @g_a, $noreg +; CHECK-NEXT: DBG_VALUE %stack.6.g.addr, $noreg, ![[g]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 32, 32) +; CHECK-NEXT: {{^ *$}} + +do.body1: ; preds = %do.cond, %do.body + %0 = phi i32 [ %.pre10, %do.body ], [ %1, %do.cond ], !dbg !27 + call void @llvm.dbg.assign(metadata i32 8, metadata !75, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !77, metadata ptr %e.addr, metadata !DIExpression()), !dbg !27 ; VAR:e + call void @llvm.dbg.assign(metadata i32 11, metadata !79, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !81, metadata ptr %f.addr, metadata !DIExpression()), !dbg !27 ; VAR:f + %tobool.not = icmp eq i32 %0, 0, !dbg !27 + br i1 %tobool.not, label %if.else, label %if.then, !dbg !35 +; CHECK-LABEL: bb.2.do.body1: +; CHECK-NEXT: successors +; CHECK-NEXT: {{^ *$}} +; CHECK: DBG_VALUE %stack.4.e.addr, $noreg, ![[e]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 32, 32) +; CHECK-NEXT: DBG_VALUE 11, $noreg, ![[f]], !DIExpression(DW_OP_LLVM_fragment, 32, 32) +; CHECK: JMP_1 +; CHECK-NEXT: {{^ *$}} + +if.then: ; preds = %do.body1 + %.pre = load i32, ptr @g_a, align 4, !dbg !27 + store i32 %.pre, ptr %b.addr, !DIAssignID !66 ; VAR:b + call void @llvm.dbg.assign(metadata i32 %.pre, metadata !22, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !66, metadata ptr %b.addr, metadata !DIExpression()), !dbg !27 ; VAR:b + store i32 8, ptr %e.addr, !DIAssignID !77 ; VAR:e + br label %do.cond, !dbg !39 +; CHECK-LABEL: bb.3.if.then: +; CHECK-NEXT: successors +; CHECK-NEXT: {{^ *$}} +; CHECK-NEXT: %5:gr32 = MOV32rm +; CHECK-NEXT: MOV32mr %stack.1.b.addr, 1, $noreg, 0, $noreg, killed %5 +; CHECK-NEXT: DBG_VALUE %stack.1.b.addr, $noreg, ![[b]], !DIExpression(DW_OP_deref +; CHECK-NEXT: MOV32mi %stack.4.e.addr, 1, $noreg, 0, $noreg, 8 +; CHECK-NEXT: DBG_VALUE %stack.4.e.addr, $noreg, ![[e]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 32, 32) +; CHECK-NEXT: JMP_1 %bb.5 +; CHECK-NEXT: {{^ *$}} + +if.else: ; preds = %do.body1 + store i32 6, ptr %d.addr, !DIAssignID !74 ; VAR:d + call void @llvm.dbg.assign(metadata i32 6, metadata !72, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !74, metadata ptr %d.addr, metadata !DIExpression()), !dbg !27 ; VAR:d + store i32 8, ptr %e.addr, !DIAssignID !77 ; VAR:e + store i32 10, ptr %f.addr ; VAR:f + br label %do.cond +; CHECK-LABEL: bb.4.if.else: +; CHECK-NEXT: successors +; CHECK-NEXT: {{^ *$}} +; CHECK-NEXT: MOV32mi %stack.3.d.addr, 1, $noreg, 0, $noreg, 6 +; CHECK-NEXT: DBG_VALUE %stack.3.d.addr, $noreg, ![[d]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 32, 32) +; CHECK-NEXT: MOV32mi %stack.4.e.addr, 1, $noreg, 0, $noreg, 8 +; CHECK-NEXT: DBG_VALUE %stack.4.e.addr, $noreg, ![[e]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 32, 32) +; CHECK-NEXT: MOV32mi %stack.5.f.addr, 1, $noreg, 0, $noreg, 10 +; CHECK-NEXT: DBG_VALUE %stack.5.f.addr, $noreg, !36, !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 32, 32) +; CHECK-NEXT: {{^ *$}} + +do.cond: ; preds = %if.then, %if.else + call void @llvm.dbg.assign(metadata i1 undef, metadata !67, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !69, metadata ptr %c.addr, metadata !DIExpression()), !dbg !27 ; VAR:c + call void @llvm.dbg.assign(metadata i32 11, metadata !79, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !81, metadata ptr %f.addr, metadata !DIExpression()), !dbg !27 ; VAR:f + %1 = load i32, ptr @g_b, align 4, !dbg !43 + %tobool3.not = icmp eq i32 %1, 0, !dbg !43 + br i1 %tobool3.not, label %do.cond4, label %do.body1, !dbg !44, !llvm.loop !45 +; CHECK-LABEL: bb.5.do.cond: +; CHECK-NEXT: successors +; CHECK-NEXT: {{^ *$}} +; CHECK-NEXT: DBG_VALUE %stack.2.c.addr, $noreg, ![[c]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 32, 32) +; CHECK-NEXT: DBG_VALUE 11, $noreg, ![[f]], !DIExpression(DW_OP_LLVM_fragment, 32, 32) +; CHECK: {{^ *$}} + +do.cond4: ; preds = %do.cond + %2 = load i32, ptr @g_c, align 4, !dbg !48 + %tobool5.not = icmp eq i32 %2, 0, !dbg !48 + br i1 %tobool5.not, label %do.end6, label %do.body, !dbg !49, !llvm.loop !50 +; CHECK-LABEL: bb.6.do.cond4: +; CHECK-NEXT: successors +; CHECK-NEXT: {{^ *$}} +; CHECK-NOT: DBG +; CHECK: {{^ *$}} + +do.end6: ; preds = %do.cond4 + call void @llvm.dbg.assign(metadata i32 3, metadata !21, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !63, metadata ptr %a.addr, metadata !DIExpression()), !dbg !27; VAR:a + call void @llvm.dbg.assign(metadata i32 4, metadata !22, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !65, metadata ptr %b.addr, metadata !DIExpression()), !dbg !27 ; VAR:b + call void @llvm.dbg.assign(metadata i1 undef, metadata !67, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !69, metadata ptr %c.addr, metadata !DIExpression()), !dbg !27 ; VAR:c + call void @llvm.dbg.assign(metadata i32 6, metadata !72, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !74, metadata ptr %d.addr, metadata !DIExpression()), !dbg !27 ; VAR:d + call void @llvm.dbg.assign(metadata i32 11, metadata !79, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !81, metadata ptr %f.addr, metadata !DIExpression()), !dbg !27 ; VAR:f + ret i32 0, !dbg !53 +; CHECK-LABEL: bb.7.do.end6: +; CHECK-NEXT: DBG_VALUE %stack.0.a.addr, $noreg, ![[a]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 32, 32) +; CHECK-NEXT: DBG_VALUE 4, $noreg, ![[b]], !DIExpression(DW_OP_LLVM_fragment, 32, 32) +; CHECK-NEXT: DBG_VALUE %stack.2.c.addr, $noreg, ![[c]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 32, 32) +; CHECK-NEXT: DBG_VALUE %stack.3.d.addr, $noreg, ![[d]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 32, 32) +; CHECK-NEXT: DBG_VALUE 11, $noreg, ![[f]], !DIExpression(DW_OP_LLVM_fragment, 32, 32) +} + +declare !dbg !54 void @_Z4calli(i32 noundef) local_unnamed_addr #1 +declare void @llvm.dbg.declare(metadata, metadata, metadata) +declare void @llvm.dbg.value(metadata, metadata, metadata) +declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) + +!llvm.dbg.cu = !{!2} +!llvm.module.flags = !{!10, !11, !12, !13, !14, !15} +!llvm.ident = !{!16} + +!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression()) +!1 = distinct !DIGlobalVariable(name: "g_a", scope: !2, file: !3, line: 1, type: !7, isLocal: false, isDefinition: true) +!2 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !3, producer: "clang version 16.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, globals: !4, splitDebugInlining: false, nameTableKind: None) +!3 = !DIFile(filename: "test.cpp", directory: "/") +!4 = !{!0, !5, !8} +!5 = !DIGlobalVariableExpression(var: !6, expr: !DIExpression()) +!6 = distinct !DIGlobalVariable(name: "g_b", scope: !2, file: !3, line: 1, type: !7, isLocal: false, isDefinition: true) +!7 = !DIBasicType(name: "long long", size: 64, encoding: DW_ATE_signed) +!8 = !DIGlobalVariableExpression(var: !9, expr: !DIExpression()) +!9 = distinct !DIGlobalVariable(name: "g_c", scope: !2, file: !3, line: 1, type: !7, isLocal: false, isDefinition: true) +!10 = !{i32 7, !"Dwarf Version", i32 5} +!11 = !{i32 2, !"Debug Info Version", i32 3} +!12 = !{i32 1, !"wchar_size", i32 4} +!13 = !{i32 8, !"PIC Level", i32 2} +!14 = !{i32 7, !"PIE Level", i32 2} +!15 = !{i32 7, !"uwtable", i32 2} +!16 = !{!"clang version 16.0.0"} +!17 = distinct !DISubprogram(name: "fun", linkageName: "_Z3funii", scope: !3, file: !3, line: 3, type: !18, scopeLine: 3, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !20) +!18 = !DISubroutineType(types: !19) +!19 = !{!7, !7, !7} +!20 = !{!21, !22} +!21 = !DILocalVariable(name: "a", arg: 1, scope: !17, file: !3, line: 3, type: !7) +!22 = !DILocalVariable(name: "b", arg: 2, scope: !17, file: !3, line: 3, type: !7) +!23 = !DILocation(line: 0, scope: !17) +!24 = !DILocation(line: 4, column: 3, scope: !17) +!25 = !DILocation(line: 5, column: 5, scope: !26) +!26 = distinct !DILexicalBlock(scope: !17, file: !3, line: 4, column: 6) +!27 = !DILocation(line: 7, column: 11, scope: !28) +!28 = distinct !DILexicalBlock(scope: !29, file: !3, line: 7, column: 11) +!29 = distinct !DILexicalBlock(scope: !26, file: !3, line: 6, column: 8) +!34 = !DILocation(line: 6, column: 5, scope: !26) +!35 = !DILocation(line: 7, column: 11, scope: !29) +!36 = !DILocation(line: 8, column: 11, scope: !37) +!37 = distinct !DILexicalBlock(scope: !28, file: !3, line: 7, column: 16) +!38 = !DILocation(line: 9, column: 9, scope: !37) +!39 = !DILocation(line: 10, column: 7, scope: !37) +!40 = !DILocation(line: 11, column: 11, scope: !41) +!41 = distinct !DILexicalBlock(scope: !28, file: !3, line: 10, column: 14) +!42 = !DILocation(line: 0, scope: !28) +!43 = !DILocation(line: 13, column: 14, scope: !26) +!44 = !DILocation(line: 13, column: 5, scope: !29) +!45 = distinct !{!45, !34, !46, !47} +!46 = !DILocation(line: 13, column: 17, scope: !26) +!47 = !{!"llvm.loop.mustprogress"} +!48 = !DILocation(line: 14, column: 12, scope: !17) +!49 = !DILocation(line: 14, column: 3, scope: !26) +!50 = distinct !{!50, !24, !51, !47} +!51 = !DILocation(line: 14, column: 15, scope: !17) +!52 = !DILocation(line: 15, column: 12, scope: !17) +!53 = !DILocation(line: 15, column: 3, scope: !17) +!54 = !DISubprogram(name: "call", linkageName: "_Z4calli", scope: !3, file: !3, line: 2, type: !55, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !57) +!55 = !DISubroutineType(types: !56) +!56 = !{null, !7} +!57 = !{} +!58 = distinct !DIAssignID() +!63 = distinct !DIAssignID() +!64 = distinct !DIAssignID() +!65 = distinct !DIAssignID() +!66 = distinct !DIAssignID() +!67 = !DILocalVariable(name: "c", scope: !17, file: !3, line: 3, type: !7) +!68 = distinct !DIAssignID() +!69 = distinct !DIAssignID() +!70 = distinct !DIAssignID() +!71 = distinct !DIAssignID() +!72 = !DILocalVariable(name: "d", scope: !17, file: !3, line: 3, type: !7) +!73 = distinct !DIAssignID() +!74 = distinct !DIAssignID() +!75 = !DILocalVariable(name: "e", scope: !17, file: !3, line: 3, type: !7) +!76 = distinct !DIAssignID() +!77 = distinct !DIAssignID() +!78 = distinct !DIAssignID() +!79 = !DILocalVariable(name: "f", scope: !17, file: !3, line: 3, type: !7) +!80 = distinct !DIAssignID() +!81 = distinct !DIAssignID() +!82 = !DILocalVariable(name: "g", scope: !17, file: !3, line: 3, type: !7) +!83 = distinct !DIAssignID() +!84 = distinct !DIAssignID() diff --git a/llvm/test/DebugInfo/assignment-tracking/X86/nested-loop.ll b/llvm/test/DebugInfo/assignment-tracking/X86/nested-loop.ll new file mode 100644 index 0000000..e710487 --- /dev/null +++ b/llvm/test/DebugInfo/assignment-tracking/X86/nested-loop.ll @@ -0,0 +1,398 @@ +; RUN: llc %s -stop-after=finalize-isel -o - -experimental-assignment-tracking \ +; RUN: | FileCheck %s --implicit-check-not=DBG + +;; Test a variety of block inputs and lattice configurations for the assignment +;; tracking analysis (debug-ata). + +;; The CFG looks like this: +;; entry +;; | +;; v +;; do.body <-----+ +;; | | +;; V | +;; do.body1 <--+ | +;; / \ | | +;; / \ | | +;; / \ | | +;; v v | | +;; if.then if.else | | +;; \ / | | +;; \ / | | +;; \ / | | +;; do.cond ----+ | +;; | | +;; v | +;; do.cond4 -----+ +;; | +;; v +;; do.end6 + +;; Key +;; ╔═════════════════════╦═══════════════════════════════════════════════════════════════════╗ +;; ║ thing ║ meaning ║ +;; ╠═════════════════════╬═══════════════════════════════════════════════════════════════════╣ +;; ║ mem= ║ assignment of !n or phi to memory ║ +;; ║ dbg= ║ assignment of !n or phi to source variable ║ +;; ║ phi ║ phi of assignments (operands not traked)* ║ +;; ║ loc= ║ location to use is value (implicit location), stack home, or none ║ +;; ╚═════════════════════╩═══════════════════════════════════════════════════════════════════╝ +;; (*) A phi in the def column represents an assignment made by an untagged store. +;; +;; Variable 'a' (!21) +;; Check initial dbg and mem assignment values are propagated through all blocks. +;; ╔═════════════╦══════════════════════════╦═════════════════╦══════════════════════════╗ +;; ║ block ║ in ║ def ║ out ║ +;; ╠═════════════╬══════════════════════════╬═════════════════╬══════════════════════════╣ +;; ║ entry ║ ║ mem=!63 dbg=!70 ║ mem=!63 dbg=!70 loc=val ║ +;; ║ do.end6 ║ mem=!63 dbg=!70 loc=val ║ mem=!63 ║ mem=!63 dbg=!63 loc=mem ║ +;; ╚═════════════╩══════════════════════════╩═════════════════╩══════════════════════════╝ +;; +;; Variable 'b' (!22) +;; Check mem=dbg assignment on branch in nested loop causes a mem=phi (tested by looking +;; for value-based DBG_VALUE in do.end6). +;; ╔═════════════╦══════════════════════════╦═════════════════╦══════════════════════════╗ +;; ║ block ║ in ║ def ║ out ║ +;; ╠═════════════╬══════════════════════════╬═════════════════╬══════════════════════════╣ +;; ║ entry ║ ║ mem=!65 dbg=!71 ║ mem=!65 dbg=!71 loc=val ║ +;; ║ if.then ║ mem=phi dbg=phi loc=none ║ mem=!66 dbg=!66 ║ mem=!66 dbg=!66 loc=mem ║ +;; ║ do.end6 ║ mem=phi dbg=phi loc=none ║ mem=!65 ║ mem=phi dbg=!65 loc=val ║ +;; ╚═════════════╩══════════════════════════╩═════════════════╩══════════════════════════╝ +;; +;; Variable 'c' (!67) +;; Check initial dbg and mem assignment values are propagated through all blocks, with +;; dbg defs with the inital assignment ID put in do.cond and do.end6 (variable is always +;; in memory). +;; ╔═════════════╦══════════════════════════╦═════════════════╦══════════════════════════╗ +;; ║ block ║ in ║ def ║ out ║ +;; ╠═════════════╬══════════════════════════╬═════════════════╬══════════════════════════╣ +;; ║ entry ║ ║ mem=!69 dbg=!69 ║ mem=!69 dbg=!69 loc=mem ║ +;; ║ do.cond ║ mem=!69 dbg=!69 loc=mem ║ dbg=!69 ║ mem=!69 dbg=!69 loc=mem ║ +;; ║ do.end6 ║ mem=!69 dbg=!69 loc=mem ║ dbg=!69 ║ mem=!69 dbg=!69 loc=mem ║ +;; ╚═════════════╩══════════════════════════╩═════════════════╩══════════════════════════╝ +;; +;; Variable 'd' (!72) +;; Same as above, except the dbg def in do.cond has been swapped for a dbg=mem def (with +;; the initial assignment ID) and has been moved to if.else. +;; ╔═════════════╦══════════════════════════╦═════════════════╦══════════════════════════╗ +;; ║ block ║ in ║ def ║ out ║ +;; ╠═════════════╬══════════════════════════╬═════════════════╬══════════════════════════╣ +;; ║ entry ║ ║ mem=!74 dbg=!74 ║ mem=!74 dbg=!74 loc=mem ║ +;; ║ if.else ║ mem=!74 dbg=!74 loc=mem ║ mem=!74 dbg=!74 ║ mem=!74 dbg=!74 loc=mem ║ +;; ║ do.end6 ║ mem=!74 dbg=!74 loc=mem ║ dbg=!74 ║ mem=!74 dbg=!74 loc=mem ║ +;; ╚═════════════╩══════════════════════════╩═════════════════╩══════════════════════════╝ +;; +;; Variable 'e' (!75) +;; mem defs in entry, if.then and if.else with same ID (!77). Check these join correct +;; (tested using the dbg defs of the same ID - the memory location is valid at each of +;; these with that ID). +;; ╔═════════════╦══════════════════════════╦═════════════════╦══════════════════════════╗ +;; ║ block ║ in ║ def ║ out ║ +;; ╠═════════════╬══════════════════════════╬═════════════════╬══════════════════════════╣ +;; ║ entry ║ ║ mem=!77 dbg=!78 ║ mem=!77 dbg=!78 loc=val ║ +;; ║ do.body ║ mem=!77 dbg=phi loc=none ║ dbg=!77 ║ mem=!77 dbg=!77 loc=mem ║ +;; ║ do.body1 ║ mem=!77 dbg=!77 loc=mem ║ dbg=!77 ║ mem=!77 dbg=!77 loc=mem ║ +;; ║ if.then ║ mem=!77 dbg=!77 loc=mem ║ mem=!77 ║ mem=!77 dbg=!77 loc=mem ║ +;; ║ if.else ║ mem=!77 dbg=!77 loc=mem ║ mem=!77 ║ mem=!77 dbg=!77 loc=mem ║ +;; ╚═════════════╩══════════════════════════╩═════════════════╩══════════════════════════╝ +;; +;; Variable 'f' (!79) +;; mem def in entry and an untagged store in if.else (results in mem=phi, dbg=phi defs). +;; Use dbg defs in do.body, do.body1, do.cond and do.end6 to check the phi-ness +;; has been propagated (the memory loc at each is not a valid location). Check the memory +;; loc is used in if.else after the untagged store. +;; ╔═════════════╦══════════════════════════╦═════════════════╦══════════════════════════╗ +;; ║ block ║ in ║ def ║ out ║ +;; ╠═════════════╬══════════════════════════╬═════════════════╬══════════════════════════╣ +;; ║ entry ║ ║ mem=!81 dbg=!81 ║ mem=!81 dbg=!81 loc=mem ║ +;; ║ do.body ║ mem=phi dbg=phi loc=none ║ dbg=!81 ║ mem=phi dbg=!81 loc=val ║ +;; ║ do.body1 ║ mem=phi dbg=phi loc=none ║ dbg=!81 ║ mem=phi dbg=!81 loc=val ║ +;; ║ if.else ║ mem=phi dbg=phi loc=none ║ mem=phi dbg=phi ║ mem=phi dbg=phi loc=mem ║ +;; ║ do.cond ║ mem=phi dbg=phi loc=none ║ dbg=!81 ║ mem=phi dbg=!81 loc=val ║ +;; ║ do.end6 ║ mem=phi dbg=!81 loc=val ║ dbg=!81 ║ mem=!69 dbg=!81 loc=val ║ +;; ╚═════════════╩══════════════════════════╩═════════════════╩══════════════════════════╝ +;; +;; Variable 'g' (!82) +;; Check that joining loc=none with anything else results in loc=none. The out-loc of +;; entry is set up to be loc=none by following an untagged store with a tagged store, +;; with the linked dbg.assign in another block. The dbg.assign is in do.body - it follows +;; another store linked to it. Importantly, there are other instructions wedged between +;; them, which is how we test that the in-loc is loc=none. The result of encountering +;; a tagged store while the loc=none is to emit nothing. Thus, we check that no location +;; def is emitted in do.body until the dbg.assign is encountered (after the load that was +;; wedged between the store and intrinsic). +;; ╔═════════════╦══════════════════════════╦═════════════════╦══════════════════════════╗ +;; ║ block ║ in ║ def ║ out ║ +;; ╠═════════════╬══════════════════════════╬═════════════════╬══════════════════════════╣ +;; ║ entry ║ ║ mem=phi dbg=phi ║ mem=phi dbg=phi loc=none ║ +;; ║ do.body ║ mem=phi dbg=phi loc=none ║ mem=!84 dbg=!84 ║ mem=!84 dbg=!84 loc=mem ║ +;; ╚═════════════╩══════════════════════════╩═════════════════╩══════════════════════════╝ + +; CHECK-DAG: ![[a:[0-9]+]] = !DILocalVariable(name: "a", +; CHECK-DAG: ![[b:[0-9]+]] = !DILocalVariable(name: "b", +; CHECK-DAG: ![[c:[0-9]+]] = !DILocalVariable(name: "c", +; CHECK-DAG: ![[d:[0-9]+]] = !DILocalVariable(name: "d", +; CHECK-DAG: ![[e:[0-9]+]] = !DILocalVariable(name: "e", +; CHECK-DAG: ![[f:[0-9]+]] = !DILocalVariable(name: "f", +; CHECK-DAG: ![[g:[0-9]+]] = !DILocalVariable(name: "g", + +;; Variables 'c' (!67) and 'd' (!72) are always stack-homed. +; CHECK: - { id: 2, name: c.addr, type: default, offset: 0, size: 4, alignment: 4, +; CHECK-NEXT: stack-id: default, callee-saved-register: '', callee-saved-restored: true, +; CHECK-NEXT: debug-info-variable: '![[c]]', debug-info-expression: '!DIExpression()', +; CHECK: - { id: 3, name: d.addr, type: default, offset: 0, size: 4, alignment: 4, +; CHECK-NEXT: stack-id: default, callee-saved-register: '', callee-saved-restored: true, +; CHECK-NEXT: debug-info-variable: '![[d]]', debug-info-expression: '!DIExpression()', + +source_filename = "test.cpp" +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +@g_a = dso_local local_unnamed_addr global i32 0, align 4, !dbg !0 +@g_b = dso_local local_unnamed_addr global i32 0, align 4, !dbg !5 +@g_c = dso_local local_unnamed_addr global i32 0, align 4, !dbg !8 + +define dso_local noundef i32 @_Z3funii(i32 noundef %a, i32 noundef %b) local_unnamed_addr #0 !dbg !17 { +entry: + %a.addr = alloca i32, align 4, !DIAssignID !58 ; VAR:a + call void @llvm.dbg.assign(metadata i1 undef, metadata !21, metadata !DIExpression(), metadata !58, metadata ptr %a.addr, metadata !DIExpression()), !dbg !27 ; VAR:a + %b.addr = alloca i32, align 4, !DIAssignID !64 ; VAR:b + call void @llvm.dbg.assign(metadata i1 undef, metadata !22, metadata !DIExpression(), metadata !64, metadata ptr %b.addr, metadata !DIExpression()), !dbg !27 ; VAR:b + %c.addr = alloca i32, align 4, !DIAssignID !68 ; VAR:c + call void @llvm.dbg.assign(metadata i1 undef, metadata !67, metadata !DIExpression(), metadata !68, metadata ptr %c.addr, metadata !DIExpression()), !dbg !27 ; VAR:c + %d.addr = alloca i32, align 4, !DIAssignID !73 ; VAR:d + call void @llvm.dbg.assign(metadata i1 undef, metadata !72, metadata !DIExpression(), metadata !73, metadata ptr %d.addr, metadata !DIExpression()), !dbg !27 ; VAR:d + %e.addr = alloca i32, align 4, !DIAssignID !76 ; VAR:e + call void @llvm.dbg.assign(metadata i1 undef, metadata !75, metadata !DIExpression(), metadata !76, metadata ptr %e.addr, metadata !DIExpression()), !dbg !27 ; VAR:e + %f.addr = alloca i32, align 4, !DIAssignID !80 ; VAR:f + call void @llvm.dbg.assign(metadata i1 undef, metadata !79, metadata !DIExpression(), metadata !80, metadata ptr %f.addr, metadata !DIExpression()), !dbg !27 ; VAR:f + %g.addr = alloca i32, align 4, !DIAssignID !83 ; VAR:g + call void @llvm.dbg.assign(metadata i1 undef, metadata !82, metadata !DIExpression(), metadata !83, metadata ptr %g.addr, metadata !DIExpression()), !dbg !27 ; VAR:g + store i32 1, ptr %a.addr, !DIAssignID !70 ; VAR:a + call void @llvm.dbg.assign(metadata i32 1, metadata !21, metadata !DIExpression(), metadata !70, metadata ptr %a.addr, metadata !DIExpression()), !dbg !27 ; VAR:a + store i32 2, ptr %b.addr, !DIAssignID !71 ; VAR:b + call void @llvm.dbg.assign(metadata i32 2, metadata !22, metadata !DIExpression(), metadata !71, metadata ptr %b.addr, metadata !DIExpression()), !dbg !27 ; VAR:b + store i32 12, ptr %g.addr ; VAR:g + store i32 9, ptr %e.addr, !DIAssignID !78 ; VAR:e + call void @llvm.dbg.assign(metadata i32 9, metadata !75, metadata !DIExpression(), metadata !78, metadata ptr %e.addr, metadata !DIExpression()), !dbg !27 ; VAR:c + store i32 3, ptr %a.addr, !DIAssignID !63 ; VAR:a + store i32 4, ptr %b.addr, !DIAssignID !65 ; VAR:b + store i32 5, ptr %c.addr, !DIAssignID !69 ; VAR:c + call void @llvm.dbg.assign(metadata i32 5, metadata !67, metadata !DIExpression(), metadata !69, metadata ptr %c.addr, metadata !DIExpression()), !dbg !27 ; VAR:c + store i32 6, ptr %d.addr, !DIAssignID !74 ; VAR:d + call void @llvm.dbg.assign(metadata i32 6, metadata !72, metadata !DIExpression(), metadata !74, metadata ptr %d.addr, metadata !DIExpression()), !dbg !27 ; VAR:d + store i32 8, ptr %e.addr, !DIAssignID !77 ; VAR:e + store i32 13, ptr %g.addr, !DIAssignID !84 ; VAR:g + store i32 11, ptr %f.addr, !DIAssignID !81 ; VAR:f + call void @llvm.dbg.assign(metadata i32 11, metadata !79, metadata !DIExpression(), metadata !81, metadata ptr %f.addr, metadata !DIExpression()), !dbg !27 ; VAR:f + br label %do.body, !dbg !24 +; CHECK-LABEL: bb.0.entry: +; CHECK-NEXT: successors +; CHECK-NEXT: {{^ *$}} +; CHECK-NEXT: DBG_VALUE %stack.0.a.addr, $noreg, ![[a]], !DIExpression(DW_OP_deref) +; CHECK-NEXT: DBG_VALUE %stack.1.b.addr, $noreg, ![[b]], !DIExpression(DW_OP_deref) +; CHECK-NEXT: DBG_VALUE %stack.4.e.addr, $noreg, ![[e]], !DIExpression(DW_OP_deref) +; CHECK-NEXT: DBG_VALUE %stack.5.f.addr, $noreg, ![[f]], !DIExpression(DW_OP_deref) +; CHECK-NEXT: DBG_VALUE %stack.6.g.addr, $noreg, ![[g]], !DIExpression(DW_OP_deref) +; CHECK-NEXT: MOV32mi %stack.0.a.addr, 1, $noreg, 0, $noreg, 3 +; CHECK-NEXT: DBG_VALUE 1, $noreg, ![[a]], !DIExpression() +; CHECK-NEXT: MOV32mi %stack.1.b.addr, 1, $noreg, 0, $noreg, 4 +; CHECK-NEXT: DBG_VALUE 2, $noreg, ![[b]], !DIExpression() +; CHECK-NEXT: MOV32mi %stack.2.c.addr, 1, $noreg, 0, $noreg, 5 +; CHECK-NEXT: MOV32mi %stack.3.d.addr, 1, $noreg, 0, $noreg, 6 +; CHECK-NEXT: MOV32mi %stack.4.e.addr, 1, $noreg, 0, $noreg, 8 +; CHECK-NEXT: DBG_VALUE 9, $noreg, ![[e]], !DIExpression() +; CHECK-NEXT: MOV32mi %stack.6.g.addr, 1, $noreg, 0, $noreg, 13 +; CHECK-NEXT: DBG_VALUE $noreg, $noreg, ![[g]], !DIExpression() +; CHECK-NEXT: MOV32mi %stack.5.f.addr, 1, $noreg, 0, $noreg, 11 +; CHECK-NEXT: {{^ *$}} + +do.body: ; preds = %do.cond4, %entry + call void @llvm.dbg.assign(metadata i32 8, metadata !75, metadata !DIExpression(), metadata !77, metadata ptr %e.addr, metadata !DIExpression()), !dbg !27 ; VAR:e + call void @llvm.dbg.assign(metadata i32 11, metadata !79, metadata !DIExpression(), metadata !81, metadata ptr %f.addr, metadata !DIExpression()), !dbg !27 ; VAR:f + store i32 13, ptr %g.addr, !DIAssignID !84 ; VAR:g + %.pre10 = load i32, ptr @g_a, align 4, !dbg !27 + call void @llvm.dbg.assign(metadata i32 11, metadata !82, metadata !DIExpression(), metadata !84, metadata ptr %g.addr, metadata !DIExpression()), !dbg !27 ; VAR:g + br label %do.body1, !dbg !34 +; CHECK-LABEL: bb.1.do.body: +; CHECK-NEXT: successors +; CHECK-NEXT: {{^ *$}} +; CHECK-NEXT: DBG_VALUE %stack.4.e.addr, $noreg, ![[e]], !DIExpression(DW_OP_deref) +; CHECK-NEXT: DBG_VALUE 11, $noreg, ![[f]], !DIExpression() +; CHECK-NEXT: MOV32mi %stack.6.g.addr, 1, $noreg, 0, $noreg, 13 +; CHECK-NEXT: %0:gr32 = MOV32rm $rip, 1, $noreg, @g_a, $noreg +; CHECK-NEXT: DBG_VALUE %stack.6.g.addr, $noreg, ![[g]], !DIExpression(DW_OP_deref) +; CHECK-NEXT: {{^ *$}} + +do.body1: ; preds = %do.cond, %do.body + %0 = phi i32 [ %.pre10, %do.body ], [ %1, %do.cond ], !dbg !27 + call void @llvm.dbg.assign(metadata i32 8, metadata !75, metadata !DIExpression(), metadata !77, metadata ptr %e.addr, metadata !DIExpression()), !dbg !27 ; VAR:e + call void @llvm.dbg.assign(metadata i32 11, metadata !79, metadata !DIExpression(), metadata !81, metadata ptr %f.addr, metadata !DIExpression()), !dbg !27 ; VAR:f + %tobool.not = icmp eq i32 %0, 0, !dbg !27 + br i1 %tobool.not, label %if.else, label %if.then, !dbg !35 +; CHECK-LABEL: bb.2.do.body1: +; CHECK-NEXT: successors +; CHECK-NEXT: {{^ *$}} +; CHECK: DBG_VALUE %stack.4.e.addr, $noreg, ![[e]], !DIExpression(DW_OP_deref) +; CHECK-NEXT: DBG_VALUE 11, $noreg, ![[f]], !DIExpression() +; CHECK: JMP_1 +; CHECK-NEXT: {{^ *$}} + +if.then: ; preds = %do.body1 + %.pre = load i32, ptr @g_a, align 4, !dbg !27 + store i32 %.pre, ptr %b.addr, !DIAssignID !66 ; VAR:b + call void @llvm.dbg.assign(metadata i32 %.pre, metadata !22, metadata !DIExpression(), metadata !66, metadata ptr %b.addr, metadata !DIExpression()), !dbg !27 ; VAR:b + store i32 8, ptr %e.addr, !DIAssignID !77 ; VAR:e + br label %do.cond, !dbg !39 +; CHECK-LABEL: bb.3.if.then: +; CHECK-NEXT: successors +; CHECK-NEXT: {{^ *$}} +; CHECK-NEXT: %5:gr32 = MOV32rm +; CHECK-NEXT: MOV32mr %stack.1.b.addr, 1, $noreg, 0, $noreg, killed %5 +; CHECK-NEXT: DBG_VALUE %stack.1.b.addr, $noreg, ![[b]], !DIExpression(DW_OP_deref +; CHECK-NEXT: MOV32mi %stack.4.e.addr, 1, $noreg, 0, $noreg, 8 +; CHECK-NEXT: DBG_VALUE %stack.4.e.addr, $noreg, ![[e]], !DIExpression(DW_OP_deref) +; CHECK-NEXT: JMP_1 %bb.5 +; CHECK-NEXT: {{^ *$}} + +if.else: ; preds = %do.body1 + store i32 6, ptr %d.addr, !DIAssignID !74 ; VAR:d + call void @llvm.dbg.assign(metadata i32 6, metadata !72, metadata !DIExpression(), metadata !74, metadata ptr %d.addr, metadata !DIExpression()), !dbg !27 ; VAR:d + store i32 8, ptr %e.addr, !DIAssignID !77 ; VAR:e + store i32 10, ptr %f.addr ; VAR:f + br label %do.cond +; CHECK-LABEL: bb.4.if.else: +; CHECK-NEXT: successors +; CHECK-NEXT: {{^ *$}} +; CHECK-NEXT: MOV32mi %stack.3.d.addr, 1, $noreg, 0, $noreg, 6 +; CHECK-NEXT: MOV32mi %stack.4.e.addr, 1, $noreg, 0, $noreg, 8 +; CHECK-NEXT: DBG_VALUE %stack.4.e.addr, $noreg, ![[e]], !DIExpression(DW_OP_deref) +; CHECK-NEXT: MOV32mi %stack.5.f.addr, 1, $noreg, 0, $noreg, 10 +; CHECK-NEXT: DBG_VALUE %stack.5.f.addr, $noreg, !36, !DIExpression(DW_OP_deref) +; CHECK-NEXT: {{^ *$}} + +do.cond: ; preds = %if.then, %if.else + call void @llvm.dbg.assign(metadata i1 undef, metadata !67, metadata !DIExpression(), metadata !69, metadata ptr %c.addr, metadata !DIExpression()), !dbg !27 ; VAR:c + call void @llvm.dbg.assign(metadata i32 11, metadata !79, metadata !DIExpression(), metadata !81, metadata ptr %f.addr, metadata !DIExpression()), !dbg !27 ; VAR:f + %1 = load i32, ptr @g_b, align 4, !dbg !43 + %tobool3.not = icmp eq i32 %1, 0, !dbg !43 + br i1 %tobool3.not, label %do.cond4, label %do.body1, !dbg !44, !llvm.loop !45 +; CHECK-LABEL: bb.5.do.cond: +; CHECK-NEXT: successors +; CHECK-NEXT: {{^ *$}} +; CHECK-NEXT: DBG_VALUE 11, $noreg, ![[f]], !DIExpression() +; CHECK: {{^ *$}} + +do.cond4: ; preds = %do.cond + %2 = load i32, ptr @g_c, align 4, !dbg !48 + %tobool5.not = icmp eq i32 %2, 0, !dbg !48 + br i1 %tobool5.not, label %do.end6, label %do.body, !dbg !49, !llvm.loop !50 +; CHECK-LABEL: bb.6.do.cond4: +; CHECK-NEXT: successors +; CHECK-NEXT: {{^ *$}} +; CHECK-NOT: DBG +; CHECK: {{^ *$}} + +do.end6: ; preds = %do.cond4 + call void @llvm.dbg.assign(metadata i32 3, metadata !21, metadata !DIExpression(), metadata !63, metadata ptr %a.addr, metadata !DIExpression()), !dbg !27; VAR:a + call void @llvm.dbg.assign(metadata i32 4, metadata !22, metadata !DIExpression(), metadata !65, metadata ptr %b.addr, metadata !DIExpression()), !dbg !27 ; VAR:b + call void @llvm.dbg.assign(metadata i1 undef, metadata !67, metadata !DIExpression(), metadata !69, metadata ptr %c.addr, metadata !DIExpression()), !dbg !27 ; VAR:c + call void @llvm.dbg.assign(metadata i32 6, metadata !72, metadata !DIExpression(), metadata !74, metadata ptr %d.addr, metadata !DIExpression()), !dbg !27 ; VAR:d + call void @llvm.dbg.assign(metadata i32 11, metadata !79, metadata !DIExpression(), metadata !81, metadata ptr %f.addr, metadata !DIExpression()), !dbg !27 ; VAR:f + ret i32 0, !dbg !53 +; CHECK-LABEL: bb.7.do.end6: +; CHECK-NEXT: DBG_VALUE %stack.0.a.addr, $noreg, ![[a]], !DIExpression(DW_OP_deref) +; CHECK-NEXT: DBG_VALUE 4, $noreg, ![[b]], !DIExpression() +; CHECK-NEXT: DBG_VALUE 11, $noreg, ![[f]], !DIExpression() +} + +declare !dbg !54 void @_Z4calli(i32 noundef) local_unnamed_addr #1 +declare void @llvm.dbg.declare(metadata, metadata, metadata) +declare void @llvm.dbg.value(metadata, metadata, metadata) +declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) + +!llvm.dbg.cu = !{!2} +!llvm.module.flags = !{!10, !11, !12, !13, !14, !15} +!llvm.ident = !{!16} + +!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression()) +!1 = distinct !DIGlobalVariable(name: "g_a", scope: !2, file: !3, line: 1, type: !7, isLocal: false, isDefinition: true) +!2 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !3, producer: "clang version 16.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, globals: !4, splitDebugInlining: false, nameTableKind: None) +!3 = !DIFile(filename: "test.cpp", directory: "/") +!4 = !{!0, !5, !8} +!5 = !DIGlobalVariableExpression(var: !6, expr: !DIExpression()) +!6 = distinct !DIGlobalVariable(name: "g_b", scope: !2, file: !3, line: 1, type: !7, isLocal: false, isDefinition: true) +!7 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!8 = !DIGlobalVariableExpression(var: !9, expr: !DIExpression()) +!9 = distinct !DIGlobalVariable(name: "g_c", scope: !2, file: !3, line: 1, type: !7, isLocal: false, isDefinition: true) +!10 = !{i32 7, !"Dwarf Version", i32 5} +!11 = !{i32 2, !"Debug Info Version", i32 3} +!12 = !{i32 1, !"wchar_size", i32 4} +!13 = !{i32 8, !"PIC Level", i32 2} +!14 = !{i32 7, !"PIE Level", i32 2} +!15 = !{i32 7, !"uwtable", i32 2} +!16 = !{!"clang version 16.0.0"} +!17 = distinct !DISubprogram(name: "fun", linkageName: "_Z3funii", scope: !3, file: !3, line: 3, type: !18, scopeLine: 3, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !20) +!18 = !DISubroutineType(types: !19) +!19 = !{!7, !7, !7} +!20 = !{!21, !22} +!21 = !DILocalVariable(name: "a", arg: 1, scope: !17, file: !3, line: 3, type: !7) +!22 = !DILocalVariable(name: "b", arg: 2, scope: !17, file: !3, line: 3, type: !7) +!23 = !DILocation(line: 0, scope: !17) +!24 = !DILocation(line: 4, column: 3, scope: !17) +!25 = !DILocation(line: 5, column: 5, scope: !26) +!26 = distinct !DILexicalBlock(scope: !17, file: !3, line: 4, column: 6) +!27 = !DILocation(line: 7, column: 11, scope: !28) +!28 = distinct !DILexicalBlock(scope: !29, file: !3, line: 7, column: 11) +!29 = distinct !DILexicalBlock(scope: !26, file: !3, line: 6, column: 8) +!34 = !DILocation(line: 6, column: 5, scope: !26) +!35 = !DILocation(line: 7, column: 11, scope: !29) +!36 = !DILocation(line: 8, column: 11, scope: !37) +!37 = distinct !DILexicalBlock(scope: !28, file: !3, line: 7, column: 16) +!38 = !DILocation(line: 9, column: 9, scope: !37) +!39 = !DILocation(line: 10, column: 7, scope: !37) +!40 = !DILocation(line: 11, column: 11, scope: !41) +!41 = distinct !DILexicalBlock(scope: !28, file: !3, line: 10, column: 14) +!42 = !DILocation(line: 0, scope: !28) +!43 = !DILocation(line: 13, column: 14, scope: !26) +!44 = !DILocation(line: 13, column: 5, scope: !29) +!45 = distinct !{!45, !34, !46, !47} +!46 = !DILocation(line: 13, column: 17, scope: !26) +!47 = !{!"llvm.loop.mustprogress"} +!48 = !DILocation(line: 14, column: 12, scope: !17) +!49 = !DILocation(line: 14, column: 3, scope: !26) +!50 = distinct !{!50, !24, !51, !47} +!51 = !DILocation(line: 14, column: 15, scope: !17) +!52 = !DILocation(line: 15, column: 12, scope: !17) +!53 = !DILocation(line: 15, column: 3, scope: !17) +!54 = !DISubprogram(name: "call", linkageName: "_Z4calli", scope: !3, file: !3, line: 2, type: !55, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !57) +!55 = !DISubroutineType(types: !56) +!56 = !{null, !7} +!57 = !{} +!58 = distinct !DIAssignID() +!59 = !DILocalVariable(name: "Arr", scope: !17, file: !3, line: 4, type: !60) +!60 = !DICompositeType(tag: DW_TAG_array_type, baseType: !7, size: 96, elements: !61) +!61 = !{!62} +!62 = !DISubrange(count: 3) +!63 = distinct !DIAssignID() +!64 = distinct !DIAssignID() +!65 = distinct !DIAssignID() +!66 = distinct !DIAssignID() +!67 = !DILocalVariable(name: "c", scope: !17, file: !3, line: 3, type: !7) +!68 = distinct !DIAssignID() +!69 = distinct !DIAssignID() +!70 = distinct !DIAssignID() +!71 = distinct !DIAssignID() +!72 = !DILocalVariable(name: "d", scope: !17, file: !3, line: 3, type: !7) +!73 = distinct !DIAssignID() +!74 = distinct !DIAssignID() +!75 = !DILocalVariable(name: "e", scope: !17, file: !3, line: 3, type: !7) +!76 = distinct !DIAssignID() +!77 = distinct !DIAssignID() +!78 = distinct !DIAssignID() +!79 = !DILocalVariable(name: "f", scope: !17, file: !3, line: 3, type: !7) +!80 = distinct !DIAssignID() +!81 = distinct !DIAssignID() +!82 = !DILocalVariable(name: "g", scope: !17, file: !3, line: 3, type: !7) +!83 = distinct !DIAssignID() +!84 = distinct !DIAssignID() diff --git a/llvm/test/DebugInfo/assignment-tracking/X86/no-redundant-def-after-alloca.ll b/llvm/test/DebugInfo/assignment-tracking/X86/no-redundant-def-after-alloca.ll new file mode 100644 index 0000000..beb6472 --- /dev/null +++ b/llvm/test/DebugInfo/assignment-tracking/X86/no-redundant-def-after-alloca.ll @@ -0,0 +1,48 @@ +; RUN: llc %s -o - -stop-after=finalize-isel \ +; RUN: -experimental-assignment-tracking \ +; RUN: | FileCheck %s --implicit-check-not=DBG_ + +;; Hand written. Check that no unnecessary undef is inserted after an alloca +;; that has a linked dbg.assign that doesn't immediately follow it. + +; CHECK: CALL64pcrel32 @a +; CHECK-NEXT: ADJCALLSTACKUP64 +; CHECK-NEXT: DBG_VALUE %stack.0.c, $noreg, !{{.+}}, !DIExpression(DW_OP_deref), debug-location + +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +define dso_local void @b() #0 !dbg !7 { +entry: + %c = alloca i8, align 1, !DIAssignID !10 + call void (...) @a(), !dbg !16 + call void @llvm.dbg.assign(metadata i1 undef, metadata !11, metadata !DIExpression(), metadata !10, metadata ptr %c, metadata !DIExpression()), !dbg !13 + ret void, !dbg !17 +} + +declare void @llvm.dbg.declare(metadata, metadata, metadata) #1 +declare dso_local void @a(...) +declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!3, !4, !5} +!llvm.ident = !{!6} + +!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang version 12.0.0", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "test.c", directory: "/") +!2 = !{} +!3 = !{i32 7, !"Dwarf Version", i32 4} +!4 = !{i32 2, !"Debug Info Version", i32 3} +!5 = !{i32 1, !"wchar_size", i32 4} +!6 = !{!"clang version 12.0.0)"} +!7 = distinct !DISubprogram(name: "b", scope: !1, file: !1, line: 2, type: !8, scopeLine: 2, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !2) +!8 = !DISubroutineType(types: !9) +!9 = !{null} +!10 = distinct !DIAssignID() +!11 = !DILocalVariable(name: "c", scope: !7, file: !1, line: 3, type: !12) +!12 = !DIBasicType(name: "char", size: 8, encoding: DW_ATE_signed_char) +!13 = !DILocation(line: 0, scope: !7) +!14 = !DILocation(line: 3, column: 8, scope: !7) +!15 = distinct !DIAssignID() +!16 = !DILocation(line: 4, column: 3, scope: !7) +!17 = !DILocation(line: 5, column: 1, scope: !7) diff --git a/llvm/test/DebugInfo/assignment-tracking/X86/order-of-defs.ll b/llvm/test/DebugInfo/assignment-tracking/X86/order-of-defs.ll new file mode 100644 index 0000000..34ea7cb --- /dev/null +++ b/llvm/test/DebugInfo/assignment-tracking/X86/order-of-defs.ll @@ -0,0 +1,59 @@ +; RUN: llc %s -stop-after=finalize-isel -o - -experimental-assignment-tracking \ +; RUN: | FileCheck %s --implicit-check-not=DBG_ + +;; Ensure that the order of several debug intrinsics between non-debug +;; instructions is maintained. + +; CHECK-DAG: ![[A:[0-9]+]] = !DILocalVariable(name: "a", +; CHECK-DAG: ![[B:[0-9]+]] = !DILocalVariable(name: "b", +; CHECK-DAG: ![[C:[0-9]+]] = !DILocalVariable(name: "c", + +; CHECK: DBG_VALUE $esi, $noreg, ![[B]], !DIExpression() +; CHECK-NEXT: DBG_VALUE $edx, $noreg, ![[C]], !DIExpression() +; CHECK-NEXT: DBG_VALUE $esi, $noreg, ![[A]], !DIExpression(DW_OP_LLVM_fragment, 0, 32) +; CHECK-NEXT: DBG_VALUE $edx, $noreg, ![[A]], !DIExpression(DW_OP_LLVM_fragment, 32, 32) + +target triple = "x86_64-unknown-linux-gnu" + +define dso_local i32 @fun(i64 %a.coerce, i32 noundef %b, i32 noundef %c) local_unnamed_addr #0 !dbg !7 { +entry: + call void @llvm.dbg.assign(metadata i32 %b, metadata !17, metadata !DIExpression(), metadata !19, metadata ptr undef, metadata !DIExpression()), !dbg !20 + call void @llvm.dbg.assign(metadata i32 %c, metadata !18, metadata !DIExpression(), metadata !21, metadata ptr undef, metadata !DIExpression()), !dbg !20 + call void @llvm.dbg.assign(metadata i32 %b, metadata !16, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 32), metadata !22, metadata ptr undef, metadata !DIExpression()), !dbg !20 + call void @llvm.dbg.assign(metadata i32 %c, metadata !16, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !23, metadata ptr undef, metadata !DIExpression()), !dbg !20 + %mul = mul nsw i32 %c, %b, !dbg !24 + ret i32 %mul, !dbg !25 +} + +declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!2, !3, !4, !5} +!llvm.ident = !{!6} + +!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang version 14.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "test.c", directory: "/") +!2 = !{i32 7, !"Dwarf Version", i32 5} +!3 = !{i32 2, !"Debug Info Version", i32 3} +!4 = !{i32 1, !"wchar_size", i32 4} +!5 = !{i32 7, !"uwtable", i32 1} +!6 = !{!"clang version 14.0.0"} +!7 = distinct !DISubprogram(name: "fun", scope: !1, file: !1, line: 3, type: !8, scopeLine: 3, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !15) +!8 = !DISubroutineType(types: !9) +!9 = !{!10, !11, !10, !10} +!10 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!11 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "S", file: !1, line: 2, size: 64, elements: !12) +!12 = !{!13, !14} +!13 = !DIDerivedType(tag: DW_TAG_member, name: "x", scope: !11, file: !1, line: 2, baseType: !10, size: 32) +!14 = !DIDerivedType(tag: DW_TAG_member, name: "y", scope: !11, file: !1, line: 2, baseType: !10, size: 32, offset: 32) +!15 = !{!16, !17, !18} +!16 = !DILocalVariable(name: "a", arg: 1, scope: !7, file: !1, line: 3, type: !11) +!17 = !DILocalVariable(name: "b", arg: 2, scope: !7, file: !1, line: 3, type: !10) +!18 = !DILocalVariable(name: "c", arg: 3, scope: !7, file: !1, line: 3, type: !10) +!19 = distinct !DIAssignID() +!20 = !DILocation(line: 0, scope: !7) +!21 = distinct !DIAssignID() +!22 = distinct !DIAssignID() +!23 = distinct !DIAssignID() +!24 = !DILocation(line: 6, column: 14, scope: !7) +!25 = !DILocation(line: 6, column: 3, scope: !7) diff --git a/llvm/test/DebugInfo/assignment-tracking/X86/remove-redundant-defs-to-prevent-reordering.ll b/llvm/test/DebugInfo/assignment-tracking/X86/remove-redundant-defs-to-prevent-reordering.ll new file mode 100644 index 0000000..3d113dc --- /dev/null +++ b/llvm/test/DebugInfo/assignment-tracking/X86/remove-redundant-defs-to-prevent-reordering.ll @@ -0,0 +1,108 @@ +; RUN: llc %s -stop-before finalize-isel -o - \ +; RUN: -experimental-assignment-tracking \ +; RUN: -experimental-debug-variable-locations=false \ +; RUN: | FileCheck %s --check-prefixes=CHECK,DBGVALUE --implicit-check-not="DBG_VALUE \$noreg" +; RUN: llc %s -stop-before finalize-isel -o - \ +; RUN: -experimental-assignment-tracking \ +; RUN: -experimental-debug-variable-locations=true \ +; RUN: | FileCheck %s --check-prefixes=CHECK,INSTRREF --implicit-check-not="DBG_VALUE \$noreg" + +;; Found in the wild, but this test involves modifications from: +;; int b; +;; void ext(); +;; int fun(int a) { +;; if (b == 0) +;; ext(); +;; +;; a += b; +;; return a; +;; } +;; A `dbg.assign(undef, ...)` has been added by hand in if.end. + +;; For some variable we generate: +;; %inc = add nuw nsw i32 %i.0128, 1 +;; call void @llvm.dbg.value(metadata i32 undef, ... +;; call void @llvm.dbg.value(metadata i32 %inc, ... +;; +;; SelectionDAG swaps the dbg.value positions: +;; %31:gr32 = nuw nsw ADD32ri8 %30:gr32(tied-def 0), 1 +;; DBG_VALUE %31:gr32, ... +;; DBG_VALUE $noreg, ... +;; +;; Make sure to avoid this by removing redundant dbg.values after lowering +;; dbg.assigns. + +;; Check that there's a debug instruction (--implicit-check-not checks no +;; `DBG_VALUE $noreg, ...` has been added). +; CHECK: bb.2.if.end: +; DBGVALUE: DBG_VALUE +; INSTRREF: INSTR_REF + +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +@b = dso_local local_unnamed_addr global i32 0, align 4, !dbg !0 + +; Function Attrs: uwtable mustprogress +define dso_local i32 @_Z3funi(i32 %a) local_unnamed_addr #0 !dbg !11 { +entry: + call void @llvm.dbg.assign(metadata i1 undef, metadata !15, metadata !DIExpression(), metadata !16, metadata ptr undef, metadata !DIExpression()), !dbg !17 + call void @llvm.dbg.assign(metadata i32 %a, metadata !15, metadata !DIExpression(), metadata !18, metadata ptr undef, metadata !DIExpression()), !dbg !17 + %0 = load i32, ptr @b, align 4, !dbg !19 + %cmp = icmp eq i32 %0, 0, !dbg !25 + br i1 %cmp, label %if.then, label %if.end, !dbg !26 + +if.then: ; preds = %entry + tail call void @_Z3extv(), !dbg !27 + %.pre = load i32, ptr @b, align 4, !dbg !28 + br label %if.end, !dbg !27 + +if.end: ; preds = %if.then, %entry + %1 = phi i32 [ %.pre, %if.then ], [ %0, %entry ], !dbg !28 + %add = add nsw i32 %1, %a, !dbg !29 + ;; Added by hand: + call void @llvm.dbg.assign(metadata i32 undef, metadata !15, metadata !DIExpression(), metadata !30, metadata ptr undef, metadata !DIExpression()), !dbg !17 +call void @llvm.dbg.assign(metadata i32 %add, metadata !15, metadata !DIExpression(), metadata !30, metadata ptr undef, metadata !DIExpression()), !dbg !17 + ret i32 %add, !dbg !31 +} + +declare !dbg !32 dso_local void @_Z3extv() local_unnamed_addr #1 + +; Function Attrs: nofree nosync nounwind readnone speculatable willreturn +declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) #2 + +!llvm.dbg.cu = !{!2} +!llvm.module.flags = !{!7, !8, !9} +!llvm.ident = !{!10} + +!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression()) +!1 = distinct !DIGlobalVariable(name: "b", scope: !2, file: !3, line: 1, type: !6, isLocal: false, isDefinition: true) +!2 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !3, producer: "clang version 12.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !4, globals: !5, splitDebugInlining: false, nameTableKind: None) +!3 = !DIFile(filename: "test.cpp", directory: "/") +!4 = !{} +!5 = !{!0} +!6 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!7 = !{i32 7, !"Dwarf Version", i32 4} +!8 = !{i32 2, !"Debug Info Version", i32 3} +!9 = !{i32 1, !"wchar_size", i32 4} +!10 = !{!"clang version 12.0.0"} +!11 = distinct !DISubprogram(name: "fun", linkageName: "_Z3funi", scope: !3, file: !3, line: 3, type: !12, scopeLine: 3, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !14) +!12 = !DISubroutineType(types: !13) +!13 = !{!6, !6} +!14 = !{!15} +!15 = !DILocalVariable(name: "a", arg: 1, scope: !11, file: !3, line: 3, type: !6) +!16 = distinct !DIAssignID() +!17 = !DILocation(line: 0, scope: !11) +!18 = distinct !DIAssignID() +!19 = !DILocation(line: 4, column: 7, scope: !20) +!20 = distinct !DILexicalBlock(scope: !11, file: !3, line: 4, column: 7) +!25 = !DILocation(line: 4, column: 9, scope: !20) +!26 = !DILocation(line: 4, column: 7, scope: !11) +!27 = !DILocation(line: 5, column: 5, scope: !20) +!28 = !DILocation(line: 7, column: 8, scope: !11) +!29 = !DILocation(line: 7, column: 5, scope: !11) +!30 = distinct !DIAssignID() +!31 = !DILocation(line: 8, column: 3, scope: !11) +!32 = !DISubprogram(name: "ext", linkageName: "_Z3extv", scope: !3, file: !3, line: 2, type: !33, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !4) +!33 = !DISubroutineType(types: !34) +!34 = !{null} diff --git a/llvm/test/DebugInfo/assignment-tracking/X86/remove-undef-fragment.ll b/llvm/test/DebugInfo/assignment-tracking/X86/remove-undef-fragment.ll new file mode 100644 index 0000000..e2060c0 --- /dev/null +++ b/llvm/test/DebugInfo/assignment-tracking/X86/remove-undef-fragment.ll @@ -0,0 +1,106 @@ +; RUN: llc %s -o - -stop-after=finalize-isel \ +; RUN: -experimental-assignment-tracking \ +; RUN: | FileCheck %s --implicit-check-not=DBG + +;; In the IR below, for variable n, we get dbg intrinsics that describe this: +;; +;; entry-block: +;; Frag (off=0, sz=32): non-undef +;; Frag (off=64, sz=64): undef +;; Frag (off=64, sz=32): non-undef +;; +;; The undef is redundant, as it doesn't close any open location ranges. Check +;; that it has been removed. Removing redundant undefs from the entry block +;; helps avoid losing coverage due to SelectionDAG doing weird (/bad) things. +;; Even if SelectionDAG is fixed, fewer redundant DBG instructions is still a +;; valuable goal. + +;; The test +;; -------- +;; We expect to see two DBG instructions, one for each non-undef fragment. We +;; don't bother checking the operands because it doesn't matter if either of +;; these have become undef as a result of SelectionDAG dropping the values +;; (which happens to be the case here). It's just important that SelectionDAG +;; was fed these fragments. + +; CHECK: DBG{{.*}}DIExpression(DW_OP_LLVM_fragment, 0, 32) +; CHECK: DBG{{.*}}DIExpression(DW_OP_LLVM_fragment, 64, 32) + +;; Source +;; ------ +;; IR llvm-reduced from optimized IR generated from, itself reduced from +;; CTMark's bullet source file btScaledBvhTriangleMeshShape.cpp: +;; class a { +;; public: +;; float b[4]; +;; __attribute__((nodebug)) a() {} +;; __attribute__((nodebug)) a(float c, float p2) { +;; b[0] = c; +;; b[2] = p2; +;; } +;; __attribute__((nodebug)) void operator+=(a) { +;; b[0] += 0; +;; b[2] += 2; +;; } +;; __attribute__((nodebug)) float d(a c) { return c.b[0] + c.b[2]; } +;; }; +;; +;; __attribute__((nodebug)) void operator-(a, a); +;; __attribute__((nodebug)) a operator*(float, a p2) { +;; a e(p2.b[0], p2.b[2]); +;; return e; +;; } +;; +;; __attribute__((nodebug)) a x(); +;; __attribute__((nodebug)) a y(int); +;; +;; void k() { +;; __attribute__((nodebug)) a l = x(); +;; __attribute__((nodebug)) a m = l; +;; __attribute__((nodebug)) a ag; +;; a n = 0.f * m; +;; +;; n += a(); +;; __attribute__((nodebug)) a ah(y(0).d(n), 0); +;; ag - ah; +;; } + +define void @_Z1kv({ <2 x float>, <2 x float> } %call, <2 x float> %0, float %n.sroa.6.8.vec.extract) !dbg !7 { +entry: + %call1 = tail call { <2 x float>, <2 x float> } poison(), !dbg !13 + %1 = extractvalue { <2 x float>, <2 x float> } %call, 1 + %add.i = fadd float poison, 0.000000e+00 + call void @llvm.dbg.assign(metadata float %add.i, metadata !11, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 32), metadata !14, metadata ptr undef, metadata !DIExpression()), !dbg !15 + %n.sroa.6.8.vec.extract2 = extractelement <2 x float> %0, i64 0 + %add4.i = fadd float %n.sroa.6.8.vec.extract, 0.000000e+00 + call void @llvm.dbg.value(metadata <2 x float> undef, metadata !11, metadata !DIExpression(DW_OP_LLVM_fragment, 64, 64)), !dbg !15 + call void @llvm.dbg.assign(metadata float %add4.i, metadata !11, metadata !DIExpression(DW_OP_LLVM_fragment, 64, 32), metadata !16, metadata ptr undef, metadata !DIExpression()), !dbg !15 + %add.i23 = fadd float 0.000000e+00, 0.000000e+00 + %ah.sroa.0.0.vec.insert = insertelement <2 x float> zeroinitializer, float %add4.i, i64 0 + tail call void poison(<2 x float> zeroinitializer, <2 x float> zeroinitializer, <2 x float> %ah.sroa.0.0.vec.insert, <2 x float> zeroinitializer) + ret void +} + +declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) +declare void @llvm.dbg.value(metadata, metadata, metadata) + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!2, !3, !4, !5, !6} + +!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus, file: !1, producer: "clang version 14.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "reduce.cpp", directory: "/") +!2 = !{i32 7, !"Dwarf Version", i32 5} +!3 = !{i32 2, !"Debug Info Version", i32 3} +!4 = !{i32 1, !"wchar_size", i32 4} +!5 = !{i32 7, !"uwtable", i32 1} +!6 = !{i32 7, !"frame-pointer", i32 2} +!7 = distinct !DISubprogram(name: "k", linkageName: "_Z1kv", scope: !1, file: !1, line: 25, type: !8, scopeLine: 25, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !10) +!8 = !DISubroutineType(types: !9) +!9 = !{null} +!10 = !{!11} +!11 = !DILocalVariable(name: "n", scope: !7, file: !1, line: 29, type: !12) +!12 = !DICompositeType(tag: DW_TAG_class_type, name: "a", file: !1, line: 1, size: 128, flags: DIFlagFwdDecl | DIFlagNonTrivial, identifier: "_ZTS1a") +!13 = !DILocation(line: 26, scope: !7) +!14 = distinct !DIAssignID() +!15 = !DILocation(line: 0, scope: !7) +!16 = distinct !DIAssignID() diff --git a/llvm/test/DebugInfo/assignment-tracking/X86/sdag-dangling-dbgassign.ll b/llvm/test/DebugInfo/assignment-tracking/X86/sdag-dangling-dbgassign.ll new file mode 100644 index 0000000..f581453 --- /dev/null +++ b/llvm/test/DebugInfo/assignment-tracking/X86/sdag-dangling-dbgassign.ll @@ -0,0 +1,218 @@ +; RUN: llc %s -stop-before finalize-isel -o - \ +; RUN: -experimental-assignment-tracking \ +; RUN: -experimental-debug-variable-locations=false \ +; RUN: | FileCheck %s --check-prefixes=CHECK,DBGVALUE +; RUN: llc %s -stop-before finalize-isel -o - \ +; RUN: -experimental-assignment-tracking \ +; RUN: -experimental-debug-variable-locations=true \ +; RUN: | FileCheck %s --check-prefixes=CHECK,INSTRREF + +;-------------------------------------------------------------------- +; Adapted from sdag-dangling-dbgvalue.ll to test dbg.assign intrinsics. This +; ensures that dbg.assigns with no linked store are treated as dbg.values. For +; ease of writing, all the dbg.assign intrinsics refer to the same DIAssignID +; !54. There is no linked store in any case. +; +; This test case is basically generated from the following C code. +; Compiled with "--target=x86_64-apple-darwin -S -g -O3" to get debug +; info for optimized code. +; +; struct SS { +; int a; +; int b; +; } S = { .a = 23, .b = -17 }; +; +; int test1() { +; struct SS* foo1 = &S; +; return (int)foo1; +; } +; +; int test2() { +; struct SS* foo2 = &S; +; struct SS* bar2 = &S; +; return (int)foo2 + (int)bar2; +; } +; +; int test3() { +; struct SS* bar3 = &S; +; struct SS* foo3 = &S; +; return (int)foo3 + (int)bar3; +; } +; +; int test4() { +; struct SS* foo4 = &S; +; struct SS* bar4 = &S; +; foo = 0; +; return (int)foo4 + (int)bar4; +; } +; +; int test5() { +; struct SS* bar5 = &S; +; struct SS* foo5 = &S; +; foo5 = 0; +; return (int)foo5 + (int)bar5; +; } +;-------------------------------------------------------------------- + +; CHECK: ![[FOO1:.*]] = !DILocalVariable(name: "foo1" +; CHECK: ![[BAR1:.*]] = !DILocalVariable(name: "bar1" +; CHECK: ![[FOO2:.*]] = !DILocalVariable(name: "foo2" +; CHECK: ![[BAR2:.*]] = !DILocalVariable(name: "bar2" +; CHECK: ![[FOO3:.*]] = !DILocalVariable(name: "bar3" +; CHECK: ![[BAR3:.*]] = !DILocalVariable(name: "foo3" +; CHECK: ![[FOO4:.*]] = !DILocalVariable(name: "foo4" +; CHECK: ![[BAR4:.*]] = !DILocalVariable(name: "bar4" +; CHECK: ![[BAR5:.*]] = !DILocalVariable(name: "bar5" +; CHECK: ![[FOO5:.*]] = !DILocalVariable(name: "foo5" + +source_filename = "sdag-dangling-dbgvalue.c" +target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-apple-macosx10.4.0" + +%struct.SS = type { i32, i32 } + +@S = global %struct.SS { i32 23, i32 -17 }, align 4, !dbg !0 + +; Verify that the def comes before the for foo1. +define i32 @test1() local_unnamed_addr #0 !dbg !17 { +; CHECK-LABEL: bb.0.entry1 +; CHECK-NEXT: DBG_VALUE 0, $noreg, ![[BAR1]], !DIExpression() +; CHECK-NEXT: [[REG1:%[0-9]+]]:gr64 = LEA64r +; INSTRREF-SAME: debug-instr-number 1 +; INSTRREF-NEXT: DBG_INSTR_REF 1, 0, ![[FOO1]], !DIExpression() +; DBGVALUE-NEXT: DBG_VALUE [[REG1]], $noreg, ![[FOO1]], !DIExpression() +entry1: + call void @llvm.dbg.assign(metadata ptr @S, metadata !20, metadata !DIExpression(), metadata !54, metadata ptr undef, metadata !DIExpression()), !dbg !23 + call void @llvm.dbg.assign(metadata ptr null, metadata !22, metadata !DIExpression(), metadata !54, metadata ptr undef, metadata !DIExpression()), !dbg !24 + ret i32 ptrtoint (ptr @S to i32), !dbg !25 +} + +; Verify that the def comes before the for foo2 and bar2. +define i32 @test2() local_unnamed_addr #0 !dbg !26 { +; CHECK-LABEL: bb.0.entry2 +; CHECK-NEXT: [[REG2:%[0-9]+]]:gr64 = LEA64r +; INSTRREF-SAME: debug-instr-number 1 +; INSTRREF-NEXT: DBG_INSTR_REF 1, 0, ![[FOO2]], !DIExpression() +; INSTRREF-NEXT: DBG_INSTR_REF 1, 0, ![[BAR2]], !DIExpression() +; DBGVALUE-NEXT: DBG_VALUE [[REG2]], $noreg, ![[FOO2]], !DIExpression +; DBGVALUE-NEXT: DBG_VALUE [[REG2]], $noreg, ![[BAR2]], !DIExpression +entry2: + call void @llvm.dbg.assign(metadata ptr @S, metadata !28, metadata !DIExpression(), metadata !54, metadata ptr undef, metadata !DIExpression()), !dbg !30 + call void @llvm.dbg.assign(metadata ptr @S, metadata !29, metadata !DIExpression(), metadata !54, metadata ptr undef, metadata !DIExpression()), !dbg !31 + ret i32 add (i32 ptrtoint (ptr @S to i32), i32 ptrtoint (ptr @S to i32)), !dbg !32 +} + +; Verify that the def comes before the for foo3 and bar3. +define i32 @test3() local_unnamed_addr #0 !dbg !33 { +; CHECK-LABEL: bb.0.entry3 +; CHECK-NEXT: [[REG3:%[0-9]+]]:gr64 = LEA64r +; INSTRREF-SAME: debug-instr-number 1 +; INSTRREF-NEXT: DBG_INSTR_REF 1, 0, ![[BAR3]], !DIExpression() +; INSTRREF-NEXT: DBG_INSTR_REF 1, 0, ![[FOO3]], !DIExpression() +; DBGVALUE-NEXT: DBG_VALUE [[REG3]], $noreg, ![[BAR3]], !DIExpression() +; DBGVALUE-NEXT: DBG_VALUE [[REG3]], $noreg, ![[FOO3]], !DIExpression() +entry3: + call void @llvm.dbg.assign(metadata ptr @S, metadata !36, metadata !DIExpression(), metadata !54, metadata ptr undef, metadata !DIExpression()), !dbg !38 + call void @llvm.dbg.assign(metadata ptr @S, metadata !35, metadata !DIExpression(), metadata !54, metadata ptr undef, metadata !DIExpression()), !dbg !37 + ret i32 add (i32 ptrtoint (ptr @S to i32), i32 ptrtoint (ptr @S to i32)), !dbg !39 +} + +; Verify that the def comes before the for bar4. +define i32 @test4() local_unnamed_addr #0 !dbg !40 { +; CHECK-LABEL: bb.0.entry4 +;; NOTE: The check for `DBG_VALUE $noreg, $noreg, ![[FOO4]], !DIExpression()` +;; has been removed because AT lowering removes redundant debug intrinsics. +; CHECK-NEXT: DBG_VALUE 0, $noreg, ![[FOO4]], !DIExpression() +; CHECK-NEXT: [[REG4:%[0-9]+]]:gr64 = LEA64r +; INSTRREF-SAME: debug-instr-number 1 +; INSTRREF-NEXT: DBG_INSTR_REF 1, 0, ![[BAR4]], !DIExpression() +; DBGVALUE-NEXT: DBG_VALUE [[REG4]], $noreg, ![[BAR4]], !DIExpression() +entry4: + call void @llvm.dbg.assign(metadata ptr @S, metadata !42, metadata !DIExpression(), metadata !54, metadata ptr undef, metadata !DIExpression()), !dbg !44 + call void @llvm.dbg.assign(metadata ptr @S, metadata !43, metadata !DIExpression(), metadata !54, metadata ptr undef, metadata !DIExpression()), !dbg !45 + call void @llvm.dbg.assign(metadata ptr null, metadata !42, metadata !DIExpression(), metadata !54, metadata ptr undef, metadata !DIExpression()), !dbg !44 + ret i32 ptrtoint (ptr @S to i32), !dbg !46 +} + +; Verify that we do not get a DBG_VALUE that maps foo5 to @S here. +define i32 @test5() local_unnamed_addr #0 !dbg !47 { +; CHECK-LABEL: bb.0.entry5: +; cHECK-NEXT: DBG_VALUE $noreg, $noreg, ![[FOO5]], !DIExpression() +; CHECK-NEXT: DBG_VALUE 0, $noreg, ![[FOO5]], !DIExpression() +; CHECK-NEXT: [[REG5:%[0-9]+]]:gr64 = LEA64r +; INSTRREF-SAME: debug-instr-number 1 +; INSTRREF-NEXT: DBG_INSTR_REF 1, 0, ![[BAR5]], !DIExpression() +; DBGVALUE-NEXT: DBG_VALUE [[REG5]], $noreg, ![[BAR5]], !DIExpression() +; CHECK-NOT: DBG_{{.*}} ![[FOO5]], !DIExpression() +; CHECK: RET +entry5: + call void @llvm.dbg.assign(metadata ptr @S, metadata !49, metadata !DIExpression(), metadata !54, metadata ptr undef, metadata !DIExpression()), !dbg !51 + call void @llvm.dbg.assign(metadata ptr @S, metadata !50, metadata !DIExpression(), metadata !54, metadata ptr undef, metadata !DIExpression()), !dbg !52 + call void @llvm.dbg.assign(metadata ptr null, metadata !50, metadata !DIExpression(), metadata !54, metadata ptr undef, metadata !DIExpression()), !dbg !52 + ret i32 ptrtoint (ptr @S to i32), !dbg !53 +} + +declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) #1 + +attributes #0 = { nounwind readnone uwtable } +attributes #1 = { nounwind readnone speculatable } + +!llvm.dbg.cu = !{!2} +!llvm.module.flags = !{!12, !13, !14, !15} +!llvm.ident = !{!16} + +!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression()) +!1 = distinct !DIGlobalVariable(name: "S", scope: !2, file: !3, line: 4, type: !8, isLocal: false, isDefinition: true) +!2 = distinct !DICompileUnit(language: DW_LANG_C99, file: !3, producer: "clang version 7.0.0 (trunk 327229) (llvm/trunk 327239)", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !4, retainedTypes: !5, globals: !7) +!3 = !DIFile(filename: "sdag-dangling-dbgvalue.c", directory: "/repo/uabbpet/llvm-master") +!4 = !{} +!5 = !{!6} +!6 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!7 = !{!0} +!8 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "SS", file: !3, line: 1, size: 64, elements: !9) +!9 = !{!10, !11} +!10 = !DIDerivedType(tag: DW_TAG_member, name: "a", scope: !8, file: !3, line: 2, baseType: !6, size: 32) +!11 = !DIDerivedType(tag: DW_TAG_member, name: "b", scope: !8, file: !3, line: 3, baseType: !6, size: 32, offset: 32) +!12 = !{i32 2, !"Dwarf Version", i32 2} +!13 = !{i32 2, !"Debug Info Version", i32 3} +!14 = !{i32 1, !"wchar_size", i32 4} +!15 = !{i32 7, !"PIC Level", i32 2} +!16 = !{!"clang version 7.0.0 (trunk 327229) (llvm/trunk 327239)"} +!17 = distinct !DISubprogram(name: "test1", scope: !3, file: !3, line: 6, type: !18, isLocal: false, isDefinition: true, scopeLine: 6, isOptimized: true, unit: !2, retainedNodes: !19) +!18 = !DISubroutineType(types: !5) +!19 = !{!20, !22} +!20 = !DILocalVariable(name: "foo1", scope: !17, file: !3, line: 7, type: !21) +!21 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !8, size: 64) +!22 = !DILocalVariable(name: "bar1", scope: !17, file: !3, line: 8, type: !21) +!23 = !DILocation(line: 7, column: 14, scope: !17) +!24 = !DILocation(line: 8, column: 14, scope: !17) +!25 = !DILocation(line: 9, column: 3, scope: !17) +!26 = distinct !DISubprogram(name: "test2", scope: !3, file: !3, line: 12, type: !18, isLocal: false, isDefinition: true, scopeLine: 12, isOptimized: true, unit: !2, retainedNodes: !27) +!27 = !{!28, !29} +!28 = !DILocalVariable(name: "foo2", scope: !26, file: !3, line: 13, type: !21) +!29 = !DILocalVariable(name: "bar2", scope: !26, file: !3, line: 14, type: !21) +!30 = !DILocation(line: 13, column: 14, scope: !26) +!31 = !DILocation(line: 14, column: 14, scope: !26) +!32 = !DILocation(line: 15, column: 3, scope: !26) +!33 = distinct !DISubprogram(name: "test3", scope: !3, file: !3, line: 18, type: !18, isLocal: false, isDefinition: true, scopeLine: 18, isOptimized: true, unit: !2, retainedNodes: !34) +!34 = !{!35, !36} +!35 = !DILocalVariable(name: "bar3", scope: !33, file: !3, line: 19, type: !21) +!36 = !DILocalVariable(name: "foo3", scope: !33, file: !3, line: 20, type: !21) +!37 = !DILocation(line: 19, column: 14, scope: !33) +!38 = !DILocation(line: 20, column: 14, scope: !33) +!39 = !DILocation(line: 21, column: 3, scope: !33) +!40 = distinct !DISubprogram(name: "test4", scope: !3, file: !3, line: 24, type: !18, isLocal: false, isDefinition: true, scopeLine: 24, isOptimized: true, unit: !2, retainedNodes: !41) +!41 = !{!42, !43} +!42 = !DILocalVariable(name: "foo4", scope: !40, file: !3, line: 25, type: !21) +!43 = !DILocalVariable(name: "bar4", scope: !40, file: !3, line: 26, type: !21) +!44 = !DILocation(line: 25, column: 14, scope: !40) +!45 = !DILocation(line: 26, column: 14, scope: !40) +!46 = !DILocation(line: 28, column: 3, scope: !40) +!47 = distinct !DISubprogram(name: "test5", scope: !3, file: !3, line: 31, type: !18, isLocal: false, isDefinition: true, scopeLine: 31, isOptimized: true, unit: !2, retainedNodes: !48) +!48 = !{!49, !50} +!49 = !DILocalVariable(name: "bar5", scope: !47, file: !3, line: 32, type: !21) +!50 = !DILocalVariable(name: "foo5", scope: !47, file: !3, line: 33, type: !21) +!51 = !DILocation(line: 32, column: 14, scope: !47) +!52 = !DILocation(line: 33, column: 14, scope: !47) +!53 = !DILocation(line: 35, column: 3, scope: !47) +!54 = distinct !DIAssignID() diff --git a/llvm/test/DebugInfo/assignment-tracking/X86/sdag-ir-salvage-assign.ll b/llvm/test/DebugInfo/assignment-tracking/X86/sdag-ir-salvage-assign.ll new file mode 100644 index 0000000..f7b06a8 --- /dev/null +++ b/llvm/test/DebugInfo/assignment-tracking/X86/sdag-ir-salvage-assign.ll @@ -0,0 +1,72 @@ +; RUN: llc -mtriple=x86_64-unknown-unknown -start-after=codegenprepare \ +; RUN: -experimental-assignment-tracking \ +; RUN: -stop-before finalize-isel %s -o - \ +; RUN: -experimental-debug-variable-locations=false \ +; RUN: | FileCheck %s --check-prefixes=CHECK,DBGVALUE +; RUN: llc -mtriple=x86_64-unknown-unknown -start-after=codegenprepare \ +; RUN: -experimental-assignment-tracking \ +; RUN: -stop-before finalize-isel %s -o - \ +; RUN: -experimental-debug-variable-locations=true \ +; RUN: | FileCheck %s --check-prefixes=CHECK,INSTRREF + +; Adapted from sdag-ir-salvage.ll to test dbg.assign intrinsics. This ensures +; that dbg.assigns with no linked store are treated as dbg.values. + +; Test that the dbg.value for %baz, which doesn't exist in the 'next' bb, +; can be salvaged back to the underlying argument vreg. + +; CHECK: ![[AAAVAR:.*]] = !DILocalVariable(name: "aaa", +; CHECK-LABEL: bb.0.entry: +; INSTRREF: DBG_PHI $rdi, 1 +; CHECK-LABEL: bb.1.next: +; INSTRREF: DBG_INSTR_REF 1, 0, ![[AAAVAR]] +; DBGVALUE: DBG_VALUE %{{[0-9]+}}, $noreg, ![[AAAVAR]] + +target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-linux-gnu" + +define i8 @f(ptr %foo) local_unnamed_addr !dbg !6 { +entry: + %bar = getelementptr i32, ptr %foo, i32 4 + %baz = bitcast ptr %bar to ptr + %quux = load i8, ptr %baz + br label %next + +next: ; preds = %entry + tail call void @llvm.dbg.assign(metadata ptr %baz, metadata !15, metadata !DIExpression(), metadata !31, metadata ptr undef, metadata !DIExpression()), !dbg !30 + %xyzzy = add i8 %quux, 123 + br label %fin + +fin: ; preds = %next + %trains = getelementptr i32, ptr %foo, i32 3 + %planes = bitcast ptr %trains to ptr + %cars = load i8, ptr %planes + %ret = add i8 %xyzzy, %cars + ret i8 %ret +} + +; Function Attrs: nounwind readnone speculatable +declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) #0 + +attributes #0 = { nounwind readnone speculatable } + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!25, !26, !27, !28} +!llvm.ident = !{!29} + +!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2) +!1 = !DIFile(filename: "test.c", directory: ".") +!2 = !{} +!6 = distinct !DISubprogram(name: "f", scope: !1, file: !1, line: 18, type: !7, scopeLine: 19, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !14) +!7 = !DISubroutineType(types: !8) +!8 = !{!13} +!13 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_unsigned) +!14 = !{!15} +!15 = !DILocalVariable(name: "aaa", scope: !6, file: !1, line: 18, type: !13) +!25 = !{i32 2, !"Dwarf Version", i32 4} +!26 = !{i32 2, !"Debug Info Version", i32 3} +!27 = !{i32 1, !"wchar_size", i32 4} +!28 = !{i32 7, !"PIC Level", i32 2} +!29 = !{!"clang"} +!30 = !DILocation(line: 18, column: 14, scope: !6) +!31 = distinct !DIAssignID() diff --git a/llvm/test/DebugInfo/assignment-tracking/X86/sdag-transfer-dbgassign.ll b/llvm/test/DebugInfo/assignment-tracking/X86/sdag-transfer-dbgassign.ll new file mode 100644 index 0000000..5d5a306 --- /dev/null +++ b/llvm/test/DebugInfo/assignment-tracking/X86/sdag-transfer-dbgassign.ll @@ -0,0 +1,77 @@ +; RUN: llc %s -start-after=codegenprepare -stop-before finalize-isel -o - \ +; RUN: -experimental-assignment-tracking \ +; RUN: -experimental-debug-variable-locations=false \ +; RUN: | FileCheck %s --check-prefixes=CHECK,DBGVALUE +; RUN: llc %s -start-after=codegenprepare -stop-before finalize-isel -o - \ +; RUN: -experimental-assignment-tracking \ +; RUN: -experimental-debug-variable-locations=true \ +; RUN: | FileCheck %s --check-prefixes=CHECK,INSTRREF + +; Adapted from sdag-transfer-dbgvalue.ll to test dbg.assign intrinsics. This +; ensures that dbg.assigns with no linked store are treated as dbg.values. + +; This tests that transferDbgValues() changes order of SDDbgValue transferred +; to another node and debug info for 'ADD32ri' appears *after* the instruction. +; +; This test case was generated from the following program +; using: clang -g -O3 -S -emit-llvm test.c +; +; int foo(int a, int *b) { +; int c = a + 512; +; if (c != 0) +; *b = a; +; return c; +; } + +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +; CHECK-LABEL: bb.0.entry: +; DBGVALUE: %[[REG:[0-9]+]]:gr32 = ADD32ri %1, 512 +; DBGVALUE-NEXT: DBG_VALUE %[[REG]] +; INSTRREF: ADD32ri %1, 512, {{.*}}debug-instr-number 1 +; INSTRREF-NEXT: DBG_INSTR_REF 1, 0 + +; Function Attrs: nofree norecurse nounwind uwtable writeonly +define dso_local i32 @foo(i32 %a, ptr nocapture %b) local_unnamed_addr !dbg !7 { +entry: + %add = add nsw i32 %a, 512, !dbg !18 + call void @llvm.dbg.assign(metadata i32 %add, metadata !16, metadata !DIExpression(), metadata !19, metadata ptr undef, metadata !DIExpression()), !dbg !17 + %cmp = icmp eq i32 %add, 0, !dbg !18 + br i1 %cmp, label %if.end, label %if.then, !dbg !18 + +if.then: ; preds = %entry + store i32 %a, ptr %b, align 4, !dbg !18 + br label %if.end, !dbg !18 + +if.end: ; preds = %entry, %if.then + ret i32 %add, !dbg !18 +} + +; Function Attrs: nounwind readnone speculatable willreturn +declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!3, !4, !5} +!llvm.ident = !{!6} + +!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang version 10.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, nameTableKind: None) +!1 = !DIFile(filename: "test.c", directory: "/") +!2 = !{} +!3 = !{i32 2, !"Dwarf Version", i32 4} +!4 = !{i32 2, !"Debug Info Version", i32 3} +!5 = !{i32 1, !"wchar_size", i32 4} +!6 = !{!"clang version 10.0.0"} +!7 = distinct !DISubprogram(name: "foo", scope: !8, file: !8, line: 1, type: !9, scopeLine: 1, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !13) +!8 = !DIFile(filename: "test.c", directory: "/") +!9 = !DISubroutineType(types: !10) +!10 = !{!11, !11, !12} +!11 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!12 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !11, size: 64) +!13 = !{!14, !15, !16} +!14 = !DILocalVariable(name: "a", arg: 1, scope: !7, file: !8, line: 1, type: !11) +!15 = !DILocalVariable(name: "b", arg: 2, scope: !7, file: !8, line: 1, type: !12) +!16 = !DILocalVariable(name: "c", scope: !7, file: !8, line: 2, type: !11) +!17 = !DILocation(line: 0, scope: !7) +!18 = !DILocation(line: 2, column: 13, scope: !7) +!19 = distinct !DIAssignID() diff --git a/llvm/test/DebugInfo/assignment-tracking/X86/single-memory-location-2.ll b/llvm/test/DebugInfo/assignment-tracking/X86/single-memory-location-2.ll new file mode 100644 index 0000000..e1682d8 --- /dev/null +++ b/llvm/test/DebugInfo/assignment-tracking/X86/single-memory-location-2.ll @@ -0,0 +1,111 @@ +; RUN: llc -stop-after=finalize-isel %s -o - \ +; RUN: -experimental-assignment-tracking \ +; RUN: | FileCheck %s + +;; Check that a dbg.assign for a fully stack-homed variable causes the variable +;; location to appear in the Machine Function side table. Similar to +;; single-memory-location.ll except this has slightly more complicated input +;; (there's a loop and an assignment). +;; +;; $ cat test.cpp +;; int get(); +;; void esc(int*); +;; void doSomething(int); +;; void fun() { +;; int local; +;; esc(&local); +;; while (local) { +;; local = get(); +;; doSomething(local); +;; esc(&local); +;; } +;; } +;; $ clang++ -O2 -g -emit-llvm -S -c -Xclang -fexperimental-assignment-tracking + +; CHECK: ![[VAR:[0-9]+]] = !DILocalVariable(name: "local", +; CHECK: stack: +; CHECK-NEXT: - { id: 0, name: local, type: default, offset: 0, size: 4, alignment: 4, +; CHECK-NEXT: stack-id: default, callee-saved-register: '', callee-saved-restored: true, +; CHECK-NEXT: debug-info-variable: '![[VAR]]', debug-info-expression: '!DIExpression()', +; CHECK-NEXT: debug-info-location: '!{{.+}}' } + +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +; Function Attrs: uwtable mustprogress +define dso_local void @_Z3funv() local_unnamed_addr #0 !dbg !7 { +entry: + %local = alloca i32, align 4, !DIAssignID !13 + call void @llvm.dbg.assign(metadata i1 undef, metadata !11, metadata !DIExpression(), metadata !13, metadata ptr %local, metadata !DIExpression()), !dbg !14 + %0 = bitcast ptr %local to ptr, !dbg !15 + call void @llvm.lifetime.start.p0i8(i64 4, ptr nonnull %0) #4, !dbg !15 + call void @_Z3escPi(ptr nonnull %local), !dbg !16 + %1 = load i32, ptr %local, align 4, !dbg !17 + %tobool.not1 = icmp eq i32 %1, 0, !dbg !17 + br i1 %tobool.not1, label %while.end, label %while.body, !dbg !22 + +while.body: ; preds = %entry, %while.body + %call = call i32 @_Z3getv(), !dbg !23 + store i32 %call, ptr %local, align 4, !dbg !25, !DIAssignID !26 + call void @llvm.dbg.assign(metadata i32 %call, metadata !11, metadata !DIExpression(), metadata !26, metadata ptr %local, metadata !DIExpression()), !dbg !14 + call void @_Z11doSomethingi(i32 %call), !dbg !27 + call void @_Z3escPi(ptr nonnull %local), !dbg !28 + %2 = load i32, ptr %local, align 4, !dbg !17 + %tobool.not = icmp eq i32 %2, 0, !dbg !17 + br i1 %tobool.not, label %while.end, label %while.body, !dbg !22, !llvm.loop !29 + +while.end: ; preds = %while.body, %entry + call void @llvm.lifetime.end.p0i8(i64 4, ptr nonnull %0) #4, !dbg !32 + ret void, !dbg !32 +} + +declare void @llvm.lifetime.start.p0i8(i64 immarg, ptr nocapture) +declare !dbg !33 dso_local void @_Z3escPi(ptr) local_unnamed_addr +declare !dbg !37 dso_local i32 @_Z3getv() local_unnamed_addr +declare !dbg !40 dso_local void @_Z11doSomethingi(i32) local_unnamed_addr +declare void @llvm.lifetime.end.p0i8(i64 immarg, ptr nocapture) +declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!3, !4, !5} +!llvm.ident = !{!6} + +!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang version 12.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "test.cpp", directory: "/") +!2 = !{} +!3 = !{i32 7, !"Dwarf Version", i32 4} +!4 = !{i32 2, !"Debug Info Version", i32 3} +!5 = !{i32 1, !"wchar_size", i32 4} +!6 = !{!"clang version 12.0.0"} +!7 = distinct !DISubprogram(name: "fun", linkageName: "_Z3funv", scope: !1, file: !1, line: 4, type: !8, scopeLine: 4, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !10) +!8 = !DISubroutineType(types: !9) +!9 = !{null} +!10 = !{!11} +!11 = !DILocalVariable(name: "local", scope: !7, file: !1, line: 5, type: !12) +!12 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!13 = distinct !DIAssignID() +!14 = !DILocation(line: 0, scope: !7) +!15 = !DILocation(line: 5, column: 3, scope: !7) +!16 = !DILocation(line: 6, column: 3, scope: !7) +!17 = !DILocation(line: 7, column: 10, scope: !7) +!22 = !DILocation(line: 7, column: 3, scope: !7) +!23 = !DILocation(line: 8, column: 13, scope: !24) +!24 = distinct !DILexicalBlock(scope: !7, file: !1, line: 7, column: 17) +!25 = !DILocation(line: 8, column: 11, scope: !24) +!26 = distinct !DIAssignID() +!27 = !DILocation(line: 9, column: 5, scope: !24) +!28 = !DILocation(line: 10, column: 5, scope: !24) +!29 = distinct !{!29, !22, !30, !31} +!30 = !DILocation(line: 11, column: 3, scope: !7) +!31 = !{!"llvm.loop.mustprogress"} +!32 = !DILocation(line: 12, column: 1, scope: !7) +!33 = !DISubprogram(name: "esc", linkageName: "_Z3escPi", scope: !1, file: !1, line: 2, type: !34, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !2) +!34 = !DISubroutineType(types: !35) +!35 = !{null, !36} +!36 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !12, size: 64) +!37 = !DISubprogram(name: "get", linkageName: "_Z3getv", scope: !1, file: !1, line: 1, type: !38, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !2) +!38 = !DISubroutineType(types: !39) +!39 = !{!12} +!40 = !DISubprogram(name: "doSomething", linkageName: "_Z11doSomethingi", scope: !1, file: !1, line: 3, type: !41, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !2) +!41 = !DISubroutineType(types: !42) +!42 = !{null, !12} diff --git a/llvm/test/DebugInfo/assignment-tracking/X86/single-memory-location.ll b/llvm/test/DebugInfo/assignment-tracking/X86/single-memory-location.ll new file mode 100644 index 0000000..14dc30c --- /dev/null +++ b/llvm/test/DebugInfo/assignment-tracking/X86/single-memory-location.ll @@ -0,0 +1,80 @@ +; RUN: llc -stop-after=finalize-isel %s -o - \ +; RUN: -experimental-assignment-tracking \ +; RUN: | FileCheck %s + +;; Check that a dbg.assign for a fully stack-homed variable causes the variable +;; location to appear in the Machine Function side table. +;; +;; $ cat test.cpp +;; void maybe_writes(int*); +;; void ext(int, int, int, int, int, int, int, int, int, int); +;; int example() { +;; int local; +;; maybe_writes(&local); +;; ext(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); +;; return local; +;; } +;; $ clang++ -O2 -g -emit-llvm -S -c -Xclang -fexperimental-assignment-tracking + +; CHECK: ![[VAR:[0-9]+]] = !DILocalVariable(name: "local", +; CHECK: stack: +; CHECK-NEXT: - { id: 0, name: local, type: default, offset: 0, size: 4, alignment: 4, +; CHECK-NEXT: stack-id: default, callee-saved-register: '', callee-saved-restored: true, +; CHECK-NEXT: debug-info-variable: '![[VAR]]', debug-info-expression: '!DIExpression()', +; CHECK-NEXT: debug-info-location: '!{{.+}}' } + +source_filename = "test.cpp" +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +define dso_local i32 @_Z7examplev() local_unnamed_addr !dbg !7 { +entry: + %local = alloca i32, align 4, !DIAssignID !13 + call void @llvm.dbg.assign(metadata i1 undef, metadata !12, metadata !DIExpression(), metadata !13, metadata ptr %local, metadata !DIExpression()), !dbg !14 + %0 = bitcast ptr %local to ptr, !dbg !15 + call void @llvm.lifetime.start.p0i8(i64 4, ptr nonnull %0), !dbg !15 + call void @_Z12maybe_writesPi(ptr nonnull %local), !dbg !16 + call void @_Z3extiiiiiiiiii(i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7, i32 8, i32 9), !dbg !17 + %1 = load i32, ptr %local, align 4, !dbg !18 + call void @llvm.lifetime.end.p0i8(i64 4, ptr nonnull %0), !dbg !23 + ret i32 %1, !dbg !24 +} + +declare !dbg !25 dso_local void @_Z12maybe_writesPi(ptr) local_unnamed_addr +declare !dbg !29 dso_local void @_Z3extiiiiiiiiii(i32, i32, i32, i32, i32, i32, i32, i32, i32, i32) local_unnamed_addr +declare void @llvm.lifetime.start.p0i8(i64 immarg, ptr nocapture) +declare void @llvm.lifetime.end.p0i8(i64 immarg, ptr nocapture) +declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!3, !4, !5} +!llvm.ident = !{!6} + +!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang version 12.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "test.cpp", directory: "/") +!2 = !{} +!3 = !{i32 7, !"Dwarf Version", i32 4} +!4 = !{i32 2, !"Debug Info Version", i32 3} +!5 = !{i32 1, !"wchar_size", i32 4} +!6 = !{!"clang version 12.0.0"} +!7 = distinct !DISubprogram(name: "example", linkageName: "_Z7examplev", scope: !1, file: !1, line: 3, type: !8, scopeLine: 3, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !11) +!8 = !DISubroutineType(types: !9) +!9 = !{!10} +!10 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!11 = !{!12} +!12 = !DILocalVariable(name: "local", scope: !7, file: !1, line: 4, type: !10) +!13 = distinct !DIAssignID() +!14 = !DILocation(line: 0, scope: !7) +!15 = !DILocation(line: 4, column: 4, scope: !7) +!16 = !DILocation(line: 5, column: 4, scope: !7) +!17 = !DILocation(line: 6, column: 4, scope: !7) +!18 = !DILocation(line: 7, column: 11, scope: !7) +!23 = !DILocation(line: 8, column: 1, scope: !7) +!24 = !DILocation(line: 7, column: 4, scope: !7) +!25 = !DISubprogram(name: "maybe_writes", linkageName: "_Z12maybe_writesPi", scope: !1, file: !1, line: 1, type: !26, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !2) +!26 = !DISubroutineType(types: !27) +!27 = !{null, !28} +!28 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !10, size: 64) +!29 = !DISubprogram(name: "ext", linkageName: "_Z3extiiiiiiiiii", scope: !1, file: !1, line: 2, type: !30, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !2) +!30 = !DISubroutineType(types: !31) +!31 = !{null, !10, !10, !10, !10, !10, !10, !10, !10, !10, !10} diff --git a/llvm/test/DebugInfo/assignment-tracking/X86/split-alloca.ll b/llvm/test/DebugInfo/assignment-tracking/X86/split-alloca.ll new file mode 100644 index 0000000..cce3bc7 --- /dev/null +++ b/llvm/test/DebugInfo/assignment-tracking/X86/split-alloca.ll @@ -0,0 +1,58 @@ +; RUN: llc %s -o - -stop-after=finalize-isel \ +; RUN: -experimental-assignment-tracking \ +; RUN: | FileCheck %s --implicit-check-not=DBG + +;; Hand written. Check that we fall back to emitting a list of defs for +;; variables with split allocas (i.e. we want to see DBG_VALUEs and no +;; debug-info-variable entry in the stack slot table). + +; CHECK: stack: +; CHECK: - { id: 0, name: a, type: default, offset: 0, size: 4, alignment: 4, +; CHECK: stack-id: default, callee-saved-register: '', callee-saved-restored: true, +; CHECK: debug-info-variable: '', debug-info-expression: '', debug-info-location: '' } +; CHECK: - { id: 1, name: c, type: default, offset: 0, size: 4, alignment: 4, +; CHECK: stack-id: default, callee-saved-register: '', callee-saved-restored: true, +; CHECK: debug-info-variable: '', debug-info-expression: '', debug-info-location: '' } +; CHECK: DBG_VALUE %stack.0.a, $noreg, !{{.*}}, !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 0, 32) +; CHECK: DBG_VALUE %stack.1.c, $noreg, !{{.*}}, !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 64, 32) + +target triple = "x86_64-unknown-linux-gnu" + +define dso_local void @fun() !dbg !7 { +entry: + %a = alloca i32, align 4, !DIAssignID !16 + call void @llvm.dbg.assign(metadata i1 undef, metadata !11, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 32), metadata !16, metadata ptr %a, metadata !DIExpression()), !dbg !17 + %c = alloca i32, align 4, !DIAssignID !20 + call void @llvm.dbg.assign(metadata i1 undef, metadata !11, metadata !DIExpression(DW_OP_LLVM_fragment, 64, 32), metadata !20, metadata ptr %c, metadata !DIExpression()), !dbg !17 + store i32 5, ptr %a, !DIAssignID !21 + ret void, !dbg !19 +} + +declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!2, !3, !4, !5} +!llvm.ident = !{!6} + +!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang version 14.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "test.c", directory: "/") +!2 = !{i32 7, !"Dwarf Version", i32 5} +!3 = !{i32 2, !"Debug Info Version", i32 3} +!4 = !{i32 1, !"wchar_size", i32 4} +!5 = !{i32 7, !"uwtable", i32 1} +!6 = !{!"clang version 14.0.0"} +!7 = distinct !DISubprogram(name: "fun", scope: !1, file: !1, line: 1, type: !8, scopeLine: 1, flags: DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !10) +!8 = !DISubroutineType(types: !9) +!9 = !{null} +!10 = !{!11} +!11 = !DILocalVariable(name: "local", scope: !7, file: !1, line: 2, type: !12) +!12 = !DICompositeType(tag: DW_TAG_array_type, baseType: !13, size: 96, elements: !14) +!13 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!14 = !{!15} +!15 = !DISubrange(count: 3) +!16 = distinct !DIAssignID() +!17 = !DILocation(line: 0, scope: !7) +!18 = !DILocation(line: 2, column: 3, scope: !7) +!19 = !DILocation(line: 3, column: 1, scope: !7) +!20 = distinct !DIAssignID() +!21 = distinct !DIAssignID() diff --git a/llvm/test/DebugInfo/assignment-tracking/X86/untagged-store-frag.ll b/llvm/test/DebugInfo/assignment-tracking/X86/untagged-store-frag.ll new file mode 100644 index 0000000..4b59c81 --- /dev/null +++ b/llvm/test/DebugInfo/assignment-tracking/X86/untagged-store-frag.ll @@ -0,0 +1,82 @@ +; RUN: llc %s -stop-after=finalize-isel -o - -experimental-assignment-tracking \ +; RUN: | FileCheck %s --implicit-check-not=DBG_ + +;; Hand-written to test untagged store handling on a simple case. Here's what +;; we're looking at in the IR: + +;; 1. mem(a): bits [0, 64) = !14 +;; 2. dbg(a): bits [0, 64) = !14 ; Use memory loc +;; 3. dbg(a): bits [0, 32) = ; Use implicit loc, dbg.value has no ID +;; 4. dbg(a): bits [32, 64) = !16 ; These bits don't use mem loc. +;; ; Linked to a def that comes later ---+ +;; ... ; | +;; 5. mem(a): bits [0, 32) = ; Untagged store ; | +;; .. ; | +;; 6. mem(a): bits [32, 64) = !16 ; <-----------------------------------+ + +;; Taking the '.' above as the 'position', check we get defs that look +;; like this: +;; Position | bits [0, 32) | bits [32, 64) +;; ---------+--------------+--------------- +;; 2. | Mem | Mem +;; 3. | Value | Mem +;; 4. | Value | Value +;; 5. | Mem | Value +;; 6. | Mem | Mem + +; CHECK-DAG: ![[A:[0-9]+]] = !DILocalVariable(name: "a", + +; CHECK: DBG_VALUE %stack.0.a.addr, $noreg, ![[A]], !DIExpression(DW_OP_deref) +; CHECK-NEXT: DBG_VALUE 5, $noreg, ![[A]], !DIExpression(DW_OP_LLVM_fragment, 0, 32) +; CHECK-NEXT: DBG_VALUE $noreg, $noreg, ![[A]], !DIExpression(DW_OP_LLVM_fragment, 32, 32) +; CHECK-NEXT: MOV32mi %stack.0.a.addr, 1, $noreg, 0, $noreg, 123 +; CHECK-NEXT: DBG_VALUE %stack.0.a.addr, $noreg, ![[A]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 0, 32) +; CHECK-NEXT: MOV32mr %stack.0.a.addr, 1, $noreg, 4, $noreg, %1 :: (store (s32) into %ir.add.ptr, align 8) +; CHECK-NEXT: DBG_VALUE %stack.0.a.addr, $noreg, ![[A]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 32, 32) + +;; NOTE: The second and third DBG_VALUE combined make the first redundant. If +;; removeRedundantDbgInstrs gets smarter, add an instruction between the first +;; dbg.assign and the subsequent dbg.value. + +target triple = "x86_64-unknown-linux-gnu" + +define dso_local noundef i64 @_Z1fl(i64 noundef %a, i32 %b) #0 !dbg !8 { +entry: + %a.addr = alloca i64, align 8, !DIAssignID !13 + call void @llvm.dbg.assign(metadata i1 undef, metadata !14, metadata !DIExpression(), metadata !13, metadata ptr %a.addr, metadata !DIExpression()), !dbg !15 + call void @llvm.dbg.value(metadata i64 5, metadata !14, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 32)), !dbg !15 + call void @llvm.dbg.assign(metadata i1 undef, metadata !14, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !16, metadata ptr %a.addr, metadata !DIExpression()), !dbg !15 + %frag.addr = bitcast ptr %a.addr to ptr + store i32 123, ptr %frag.addr, align 8 + %0 = bitcast ptr %a.addr to ptr + %add.ptr = getelementptr inbounds i32, ptr %0, i64 1 + store i32 %b, ptr %add.ptr, align 8, !DIAssignID !16 + %1 = load i64, ptr %a.addr, align 8 + ret i64 %1 +} + +declare void @llvm.dbg.value(metadata, metadata, metadata) #1 +declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) #1 + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!2, !3, !4, !5, !6} +!llvm.ident = !{!7} + +!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang version 14.0.0", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "test.cpp", directory: "/") +!2 = !{i32 7, !"Dwarf Version", i32 5} +!3 = !{i32 2, !"Debug Info Version", i32 3} +!4 = !{i32 1, !"wchar_size", i32 4} +!5 = !{i32 7, !"uwtable", i32 1} +!6 = !{i32 7, !"frame-pointer", i32 2} +!7 = !{!"clang version 14.0.0"} +!8 = distinct !DISubprogram(name: "f", linkageName: "_Z1fl", scope: !1, file: !1, line: 1, type: !9, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !12) +!9 = !DISubroutineType(types: !10) +!10 = !{!11, !11} +!11 = !DIBasicType(name: "long", size: 64, encoding: DW_ATE_signed) +!12 = !{} +!13 = distinct !DIAssignID() +!14 = !DILocalVariable(name: "a", arg: 1, scope: !8, file: !1, line: 1, type: !11) +!15 = !DILocation(line: 0, scope: !8) +!16 = distinct !DIAssignID() +!17 = !DILocalVariable(name: "b", arg: 2, scope: !8, file: !1, line: 1, type: !11) diff --git a/llvm/test/DebugInfo/assignment-tracking/X86/use-known-value-at-early-mem-def-2.ll b/llvm/test/DebugInfo/assignment-tracking/X86/use-known-value-at-early-mem-def-2.ll new file mode 100644 index 0000000..0b61a11 --- /dev/null +++ b/llvm/test/DebugInfo/assignment-tracking/X86/use-known-value-at-early-mem-def-2.ll @@ -0,0 +1,93 @@ +; RUN: llc %s -stop-after=finalize-isel -o - \ +; RUN: -experimental-assignment-tracking \ +; RUN: | FileCheck %s --implicit-check-not=DBG_VALUE + +;; Check that sandwiching instructions between a linked store and dbg.assign +;; results in a dbg.value(prev_value) being inserted at the store, and a +;; dbg.value(deref) at the dbg.assign. +;; Same as use-known-value-at-early-mem-def.ll except the "early mem def" is +;; for a fragment of the variable rather than the whole. + +; CHECK: bb.0.entry: +; CHECK-NEXT: DBG_VALUE %stack.0.c, $noreg, ![[var:[0-9]+]], !DIExpression(DW_OP_deref), debug-location +; CHECK: MOV64mi32 %stack.0.c, 1, $noreg, 0, $noreg, 5 +;; No DBG_VALUE required because the stack location is still valid. + +; CHECK: MOV32mi %stack.0.c, 1, $noreg, 0, $noreg, 1 +; CHECK-NEXT: DBG_VALUE $noreg, $noreg, ![[var]], !DIExpression(DW_OP_LLVM_fragment, 0, 32), debug-location +;; This DBG_VALUE is added by the frag-agg pass because bits [32, 64) are still +;; live in memory. +; CHECK-NEXT: DBG_VALUE %stack.0.c, $noreg, ![[var]], !DIExpression(DW_OP_plus_uconst, 4, DW_OP_deref, DW_OP_LLVM_fragment, 32, 32) + +; CHECK: CALL64pcrel32 @d +; CHECK-NEXT: ADJCALLSTACKUP64 +; CHECK-NEXT: DBG_VALUE %stack.0.c, $noreg, ![[var]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 0, 32), debug-location + +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +; Function Attrs: nounwind uwtable +define dso_local void @b() local_unnamed_addr #0 !dbg !7 { +entry: + %c = alloca i64, align 1, !DIAssignID !13 + call void @llvm.dbg.assign(metadata i1 undef, metadata !11, metadata !DIExpression(), metadata !13, metadata i64* %c, metadata !DIExpression()), !dbg !14 + call void @llvm.lifetime.start.p0i64(i64 1, i64* nonnull %c) #4, !dbg !15 + store i64 5, i64* %c, align 1, !dbg !16, !DIAssignID !20 + call void @llvm.dbg.assign(metadata i64 5, metadata !11, metadata !DIExpression(), metadata !20, metadata i64* %c, metadata !DIExpression()), !dbg !14 + tail call void (...) @d() #4, !dbg !21 + + ; --- VV Hand written VV --- ; + %bc = bitcast i64* %c to i32* + store i32 1, i32* %bc, align 1, !dbg !16, !DIAssignID !31 + ;; Check that a dbg.value(undef, frag(0, 32)) is inserted here. The value of + ;; the fragment is "unknown". TODO: In this case the value of the fragment is + ;; still obviously 5; a future improvement could be to be smarter and work + ;; this out. But that's a lot of work for an uncommon case. + tail call void (...) @d() #4, !dbg !21 + call void @llvm.dbg.assign(metadata i32 1, metadata !11, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 32), metadata !31, metadata i32* %bc, metadata !DIExpression()), !dbg !14 + ; --- AA Hand written AA --- ; + + call void @a(i64* nonnull %c) #4, !dbg !22 + call void @llvm.lifetime.end.p0i64(i64 1, i64* nonnull %c) #4, !dbg !23 + ret void, !dbg !23 +} + +declare void @llvm.lifetime.start.p0i64(i64 immarg, i64* nocapture) #1 +declare !dbg !24 dso_local void @d(...) local_unnamed_addr #2 +declare !dbg !27 dso_local void @a(i64*) local_unnamed_addr #2 +declare void @llvm.lifetime.end.p0i64(i64 immarg, i64* nocapture) #1 +declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) #3 + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!3, !4, !5} +!llvm.ident = !{!6} + +!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang version 12.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "reduce.c", directory: "/") +!2 = !{} +!3 = !{i32 7, !"Dwarf Version", i32 4} +!4 = !{i32 2, !"Debug Info Version", i32 3} +!5 = !{i32 1, !"wchar_size", i32 4} +!6 = !{!"clang version 12.0.0"} +!7 = distinct !DISubprogram(name: "b", scope: !1, file: !1, line: 3, type: !8, scopeLine: 3, flags: DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !10) +!8 = !DISubroutineType(types: !9) +!9 = !{null} +!10 = !{!11} +!11 = !DILocalVariable(name: "c", scope: !7, file: !1, line: 4, type: !12) +!12 = !DIBasicType(name: "char", size: 64, encoding: DW_ATE_unsigned) +!13 = distinct !DIAssignID() +!14 = !DILocation(line: 0, scope: !7) +!15 = !DILocation(line: 4, column: 3, scope: !7) +!16 = !DILocation(line: 4, column: 8, scope: !7) +!20 = distinct !DIAssignID() +!21 = !DILocation(line: 5, column: 3, scope: !7) +!22 = !DILocation(line: 6, column: 3, scope: !7) +!23 = !DILocation(line: 7, column: 1, scope: !7) +!24 = !DISubprogram(name: "d", scope: !1, file: !1, line: 2, type: !25, spFlags: DISPFlagOptimized, retainedNodes: !2) +!25 = !DISubroutineType(types: !26) +!26 = !{null, null} +!27 = !DISubprogram(name: "a", scope: !1, file: !1, line: 1, type: !28, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !2) +!28 = !DISubroutineType(types: !29) +!29 = !{null, !30} +!30 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !12, size: 64) +!31 = distinct !DIAssignID() diff --git a/llvm/test/DebugInfo/assignment-tracking/X86/use-known-value-at-early-mem-def.ll b/llvm/test/DebugInfo/assignment-tracking/X86/use-known-value-at-early-mem-def.ll new file mode 100644 index 0000000..86673af --- /dev/null +++ b/llvm/test/DebugInfo/assignment-tracking/X86/use-known-value-at-early-mem-def.ll @@ -0,0 +1,83 @@ +; RUN: llc %s -stop-after=finalize-isel -o - \ +; RUN: -experimental-assignment-tracking \ +; RUN: | FileCheck %s --implicit-check-not=DBG_VALUE + +;; Check that sandwiching instructions between a linked store and dbg.assign +;; results in a dbg.value(prev_value) being inserted at the store, and a +;; dbg.value(deref) at the dbg.assign. + +; CHECK: bb.0.entry: +; CHECK-NEXT: DBG_VALUE %stack.0.c, $noreg, ![[var:[0-9]+]], !DIExpression(DW_OP_deref), debug-location +; CHECK: MOV8mi %stack.0.c, 1, $noreg, 0, $noreg, 0 +;; No DBG_VALUE required because the stack location is still valid. + +; CHECK: MOV8mi %stack.0.c, 1, $noreg, 0, $noreg, 1 +; CHECK-NEXT: DBG_VALUE 0, $noreg, ![[var]], !DIExpression(), debug-location +; CHECK: CALL64pcrel32 @d +; CHECK-NEXT: ADJCALLSTACKUP64 +; CHECK-NEXT: DBG_VALUE %stack.0.c, $noreg, ![[var]], !DIExpression(DW_OP_deref), debug-location + +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +; Function Attrs: nounwind uwtable +define dso_local void @b() local_unnamed_addr #0 !dbg !7 { +entry: + %c = alloca i8, align 1, !DIAssignID !13 + call void @llvm.dbg.assign(metadata i1 undef, metadata !11, metadata !DIExpression(), metadata !13, metadata i8* %c, metadata !DIExpression()), !dbg !14 + call void @llvm.lifetime.start.p0i8(i64 1, i8* nonnull %c) #4, !dbg !15 + store i8 0, i8* %c, align 1, !dbg !16, !DIAssignID !20 + call void @llvm.dbg.assign(metadata i8 0, metadata !11, metadata !DIExpression(), metadata !20, metadata i8* %c, metadata !DIExpression()), !dbg !14 + tail call void (...) @d() #4, !dbg !21 + + ; --- VV Hand written VV --- ; + store i8 1, i8* %c, align 1, !dbg !16, !DIAssignID !31 + ; Check that a dbg.value(0) is inserted here. + tail call void (...) @d() #4, !dbg !21 + call void @llvm.dbg.assign(metadata i8 1, metadata !11, metadata !DIExpression(), metadata !31, metadata i8* %c, metadata !DIExpression()), !dbg !14 + ; --- AA Hand written AA --- ; + + call void @a(i8* nonnull %c) #4, !dbg !22 + call void @llvm.lifetime.end.p0i8(i64 1, i8* nonnull %c) #4, !dbg !23 + ret void, !dbg !23 +} + +declare void @llvm.lifetime.start.p0i8(i64 immarg, i8* nocapture) #1 +declare !dbg !24 dso_local void @d(...) local_unnamed_addr #2 +declare !dbg !27 dso_local void @a(i8*) local_unnamed_addr #2 +declare void @llvm.lifetime.end.p0i8(i64 immarg, i8* nocapture) #1 +declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) #3 + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!3, !4, !5} +!llvm.ident = !{!6} + +!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang version 12.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "reduce.c", directory: "/") +!2 = !{} +!3 = !{i32 7, !"Dwarf Version", i32 4} +!4 = !{i32 2, !"Debug Info Version", i32 3} +!5 = !{i32 1, !"wchar_size", i32 4} +!6 = !{!"clang version 12.0.0"} +!7 = distinct !DISubprogram(name: "b", scope: !1, file: !1, line: 3, type: !8, scopeLine: 3, flags: DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !10) +!8 = !DISubroutineType(types: !9) +!9 = !{null} +!10 = !{!11} +!11 = !DILocalVariable(name: "c", scope: !7, file: !1, line: 4, type: !12) +!12 = !DIBasicType(name: "char", size: 8, encoding: DW_ATE_signed_char) +!13 = distinct !DIAssignID() +!14 = !DILocation(line: 0, scope: !7) +!15 = !DILocation(line: 4, column: 3, scope: !7) +!16 = !DILocation(line: 4, column: 8, scope: !7) +!20 = distinct !DIAssignID() +!21 = !DILocation(line: 5, column: 3, scope: !7) +!22 = !DILocation(line: 6, column: 3, scope: !7) +!23 = !DILocation(line: 7, column: 1, scope: !7) +!24 = !DISubprogram(name: "d", scope: !1, file: !1, line: 2, type: !25, spFlags: DISPFlagOptimized, retainedNodes: !2) +!25 = !DISubroutineType(types: !26) +!26 = !{null, null} +!27 = !DISubprogram(name: "a", scope: !1, file: !1, line: 1, type: !28, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !2) +!28 = !DISubroutineType(types: !29) +!29 = !{null, !30} +!30 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !12, size: 64) +!31 = distinct !DIAssignID() -- 2.7.4