Index: llvm/test/tools/llvm-dwarfdump/X86/locstats-for-inlined-vars.yaml =================================================================== --- /dev/null +++ llvm/test/tools/llvm-dwarfdump/X86/locstats-for-inlined-vars.yaml @@ -0,0 +1,223 @@ +# RUN: yaml2obj %s | llvm-dwarfdump --statistics - | FileCheck %s + +## Check that zero coverage was reported for inlined variable with +## DW_AT_abstract_origin with no location attribute as well as +## for the variable that has not been generated within the inlined subroutine. +## +## The yaml represents DWARF as: +## +## DW_TAG_compile_unit +## DW_AT_low_pc (0x0000000000000000) +## DW_TAG_subprogram <-- (0x00000014) +## DW_AT_decl_file (0x01) +## DW_AT_decl_line (1) +## DW_AT_inline (DW_INL_inlined) +## DW_TAG_formal_parameter <-- (0x00000018) +## DW_AT_decl_file (0x01) +## DW_AT_decl_line (1) +## DW_TAG_formal_parameter <-- (0x0000001b) +## DW_AT_decl_file (0x01) +## DW_AT_decl_line (1) +## DW_TAG_variable <-- (0x0000001e) +## DW_AT_decl_file (0x01) +## DW_AT_decl_line (1) +## DW_TAG_subprogram +## DW_AT_decl_file (0x01) +## DW_AT_decl_line (1) +## DW_AT_low_pc (0x0000000000000000) +## DW_AT_high_pc (0x000000000000000b) +## DW_TAG_inlined_subroutine +## DW_AT_abstract_origin (0x00000014) +## DW_AT_low_pc (0x0000000000000000) +## DW_AT_high_pc (0x0000000000000007) +## DW_AT_call_file (0x01) +## DW_AT_call_line (1) +## DW_AT_call_column (0x01) +## DW_TAG_formal_parameter +## DW_AT_abstract_origin (0x00000018) +## DW_TAG_variable +## DW_AT_abstract_origin (0x0000001e) +## DW_AT_location () +## DW_TAG_inlined_subroutine +## DW_AT_abstract_origin (0x00000014) +## DW_AT_low_pc (0x0000000000000000) +## DW_AT_high_pc (0x0000000000000007) +## DW_AT_call_file (0x01) +## DW_AT_call_line (1) +## DW_AT_call_column (0x01) +## DW_TAG_formal_parameter +## DW_AT_abstract_origin (0x00000018) +## DW_AT_location () +## DW_TAG_variable +## DW_AT_abstract_origin (0x0000001e) +## DW_AT_location () + +# CHECK: "version": 6, +# CHECK: "#variables processed by location statistics": 6, +# CHECK: "#variables with 0% of parent scope covered by DW_AT_location": 3, +# CHECK: "#variables with 100% of parent scope covered by DW_AT_location": 3, + +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_EXEC + Machine: EM_X86_64 +DWARF: + debug_abbrev: + - Table: + - Code: 1 + Tag: DW_TAG_compile_unit + Children: DW_CHILDREN_yes + Attributes: + - Attribute: DW_AT_low_pc + Form: DW_FORM_addr + - Code: 2 + Tag: DW_TAG_subprogram + Children: DW_CHILDREN_yes + Attributes: + - Attribute: DW_AT_decl_file + Form: DW_FORM_data1 + - Attribute: DW_AT_decl_line + Form: DW_FORM_data1 + - Attribute: DW_AT_inline + Form: DW_FORM_data1 + - Code: 3 + Tag: DW_TAG_formal_parameter + Children: DW_CHILDREN_no + Attributes: + - Attribute: DW_AT_decl_file + Form: DW_FORM_data1 + - Attribute: DW_AT_decl_line + Form: DW_FORM_data1 + - Code: 4 + Tag: DW_TAG_formal_parameter + Children: DW_CHILDREN_no + Attributes: + - Attribute: DW_AT_decl_file + Form: DW_FORM_data1 + - Attribute: DW_AT_decl_line + Form: DW_FORM_data1 + - Code: 5 + Tag: DW_TAG_variable + Children: DW_CHILDREN_no + Attributes: + - Attribute: DW_AT_decl_file + Form: DW_FORM_data1 + - Attribute: DW_AT_decl_line + Form: DW_FORM_data1 + - Code: 6 + Tag: DW_TAG_subprogram + Children: DW_CHILDREN_yes + Attributes: + - Attribute: DW_AT_decl_file + Form: DW_FORM_data1 + - Attribute: DW_AT_decl_line + Form: DW_FORM_data1 + - Attribute: DW_AT_low_pc + Form: DW_FORM_addr + - Attribute: DW_AT_high_pc + Form: DW_FORM_data4 + - Code: 7 + Tag: DW_TAG_inlined_subroutine + Children: DW_CHILDREN_yes + Attributes: + - Attribute: DW_AT_abstract_origin + Form: DW_FORM_ref4 + - Attribute: DW_AT_low_pc + Form: DW_FORM_addr + - Attribute: DW_AT_high_pc + Form: DW_FORM_data4 + - Attribute: DW_AT_call_file + Form: DW_FORM_data1 + - Attribute: DW_AT_call_line + Form: DW_FORM_data1 + - Attribute: DW_AT_call_column + Form: DW_FORM_data1 + - Code: 8 + Tag: DW_TAG_formal_parameter + Children: DW_CHILDREN_no + Attributes: + - Attribute: DW_AT_abstract_origin + Form: DW_FORM_ref4 + - Code: 9 + Tag: DW_TAG_variable + Children: DW_CHILDREN_no + Attributes: + - Attribute: DW_AT_abstract_origin + Form: DW_FORM_ref4 + - Attribute: DW_AT_location + Form: DW_FORM_exprloc + - Code: 10 + Tag: DW_TAG_formal_parameter + Children: DW_CHILDREN_no + Attributes: + - Attribute: DW_AT_abstract_origin + Form: DW_FORM_ref4 + - Attribute: DW_AT_location + Form: DW_FORM_exprloc + debug_info: + - Version: 4 + AbbrOffset: 0x00 + Entries: + - AbbrCode: 1 ## DW_TAG_compile_unit + Values: + - Value: 0x00 ## DW_AT_producer + - AbbrCode: 2 ## DW_TAG_subprogram + Values: + - Value: 1 ## DW_AT_decl_file + - Value: 1 ## DW_AT_decl_line + - Value: 1 ## DW_AT_inline + - AbbrCode: 3 ## DW_TAG_formal_parameter + Values: + - Value: 1 ## DW_AT_decl_file + - Value: 1 ## DW_AT_decl_line + - AbbrCode: 4 ## DW_TAG_formal_parameter + Values: + - Value: 1 ## DW_AT_decl_file + - Value: 1 ## DW_AT_decl_line + - AbbrCode: 5 ## DW_TAG_variable + Values: + - Value: 1 ## DW_AT_decl_file + - Value: 1 ## DW_AT_decl_line + - AbbrCode: 0 ## NULL + - AbbrCode: 6 ## DW_TAG_subprogram + Values: + - Value: 1 ## DW_AT_decl_file + - Value: 1 ## DW_AT_decl_line + - Value: 0x00 ## DW_AT_low_pc + - Value: 0x0b ## DW_AT_high_pc + - AbbrCode: 7 ## DW_TAG_inlined_subroutine + Values: + - Value: 0x14 ## DW_AT_abstract_origin + - Value: 0x00 ## DW_AT_low_pc + - Value: 0x07 ## DW_AT_high_pc + - Value: 1 ## DW_AT_call_file + - Value: 1 ## DW_AT_call_line + - Value: 1 ## DW_AT_call_column + - AbbrCode: 8 ## DW_TAG_formal_parameter + Values: + - Value: 0x18 ## DW_AT_abstract_origin + - AbbrCode: 9 ## DW_TAG_formal_parameter + Values: + - Value: 0x1e ## DW_AT_abstract_origin + - Value: 0x0 ## DW_AT_location + - AbbrCode: 0 ## NULL + - AbbrCode: 7 ## DW_TAG_inlined_subroutine + Values: + - Value: 0x14 ## DW_AT_abstract_origin + - Value: 0x02 ## DW_AT_low_pc + - Value: 0x08 ## DW_AT_high_pc + - Value: 2 ## DW_AT_call_file + - Value: 2 ## DW_AT_call_line + - Value: 2 ## DW_AT_call_column + - AbbrCode: 10 ## DW_TAG_formal_parameter + Values: + - Value: 0x18 ## DW_AT_abstract_origin + - Value: 0x0 ## DW_AT_location + - AbbrCode: 9 ## DW_TAG_formal_parameter + Values: + - Value: 0x1e ## DW_AT_abstract_origin + - Value: 0x0 ## DW_AT_location + - AbbrCode: 0 ## NULL + - AbbrCode: 0 ## NULL Index: llvm/tools/llvm-dwarfdump/Statistics.cpp =================================================================== --- llvm/tools/llvm-dwarfdump/Statistics.cpp +++ llvm/tools/llvm-dwarfdump/Statistics.cpp @@ -8,9 +8,7 @@ #include "llvm-dwarfdump.h" #include "llvm/ADT/DenseMap.h" -#include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringSet.h" -#include "llvm/DebugInfo/DIContext.h" #include "llvm/DebugInfo/DWARF/DWARFContext.h" #include "llvm/DebugInfo/DWARF/DWARFDebugLoc.h" #include "llvm/Object/ObjectFile.h" @@ -21,13 +19,20 @@ using namespace llvm::dwarfdump; using namespace llvm::object; +namespace { /// This represents the number of categories of debug location coverage being /// calculated. The first category is the number of variables with 0% location /// coverage, but the last category is the number of variables with 100% /// location coverage. constexpr int NumOfCoverageCategories = 12; -namespace { +/// This represents variables DIE offsets. +using InlinedVarsTy = llvm::SmallVector; +/// This maps function DIE offset to its variables. +using InlinedVarsTyMap = llvm::DenseMap; +/// This maps inlined function site DIE offset to its variables. +using InlinedSiteVarsTyMap = llvm::DenseMap; + /// Holds statistics for one function (or other entity that has a PC range and /// contains variables, such as a compile unit). struct PerFunctionStats { @@ -170,6 +175,7 @@ else if (IsLocalVar) LocalVarLocStats[CoverageBucket]++; } + /// Construct an identifier for a given DIE from its Prefix, Name, DeclFileName /// and DeclLine. The identifier aims to be unique for any unique entities, /// but keeping the same among different instances of the same entity. @@ -215,7 +221,8 @@ uint32_t InlineDepth, StringMap &FnStatMap, GlobalStats &GlobalStats, - LocationStats &LocStats) { + LocationStats &LocStats, + InlinedSiteVarsTyMap &InlinedVariables) { bool HasLoc = false; bool HasSrcLoc = false; bool HasType = false; @@ -256,6 +263,28 @@ if (Die.findRecursively(dwarf::DW_AT_type)) HasType = true; + // Check if it is an inlined variable. + if (Die.find(dwarf::DW_AT_abstract_origin)) { + DWARFDie Parent = Die.getParent(); + auto &InlineSiteVars = InlinedVariables[Parent.getOffset()]; + if (InlineSiteVars.size()) { + if (Die.find(dwarf::DW_AT_location) || + Die.find(dwarf::DW_AT_const_value)) { + auto Offset = Die.find(dwarf::DW_AT_abstract_origin); + // Do not track this inlined var any more, since it has location + // coverage. + InlineSiteVars.erase(std::remove(InlineSiteVars.begin(), + InlineSiteVars.end(), + (*Offset).getRawUValue()), + InlineSiteVars.end()); + } else { + // The rest of the stats will be handled at the end of + // the collectStatsRecursive(). + return; + } + } + } + auto IsEntryValue = [&](ArrayRef D) -> bool { DWARFUnit *U = Die.getDwarfUnit(); DataExtractor Data(toStringRef(D), @@ -395,7 +424,9 @@ uint32_t InlineDepth, StringMap &FnStatMap, GlobalStats &GlobalStats, - LocationStats &LocStats) { + LocationStats &LocStats, + InlinedVarsTyMap &GlobalInlinedFnInfo, + InlinedSiteVarsTyMap &InlinedVariables) { const dwarf::Tag Tag = Die.getTag(); // Skip function types. if (Tag == dwarf::DW_TAG_subroutine_type) @@ -405,11 +436,11 @@ const bool IsFunction = Tag == dwarf::DW_TAG_subprogram; const bool IsBlock = Tag == dwarf::DW_TAG_lexical_block; const bool IsInlinedFunction = Tag == dwarf::DW_TAG_inlined_subroutine; + std::string FnID; if (IsFunction || IsInlinedFunction || IsBlock) { // Reset VarPrefix when entering a new function. - if (Die.getTag() == dwarf::DW_TAG_subprogram || - Die.getTag() == dwarf::DW_TAG_inlined_subroutine) + if (IsFunction || IsInlinedFunction) VarPrefix = "v"; // Ignore forward declarations. @@ -434,10 +465,22 @@ // Count the function. if (!IsBlock) { - // Skip over abstract origins. - if (Die.find(dwarf::DW_AT_inline)) + // Skip over abstract origins, but collect variables + // from it so it can be used for location statistics + // for inlined instancies. + if (Die.find(dwarf::DW_AT_inline)) { + DWARFDie Child = Die.getFirstChild(); + while (Child) { + const dwarf::Tag ChildTag = Child.getTag(); + if (ChildTag == dwarf::DW_TAG_formal_parameter || + ChildTag == dwarf::DW_TAG_variable) + GlobalInlinedFnInfo[Die.getOffset()].push_back(Child.getOffset()); + Child = Child.getSibling(); + } return; - std::string FnID = constructDieID(Die); + } + + FnID = constructDieID(Die); // We've seen an instance of this function. auto &FnStats = FnStatMap[FnID]; FnStats.IsFunction = true; @@ -465,7 +508,7 @@ } else { // Not a scope, visit the Die itself. It could be a variable. collectStatsForDie(Die, FnPrefix, VarPrefix, BytesInScope, InlineDepth, - FnStatMap, GlobalStats, LocStats); + FnStatMap, GlobalStats, LocStats, InlinedVariables); } // Set InlineDepth correctly for child recursion @@ -474,6 +517,15 @@ else if (IsInlinedFunction) ++InlineDepth; + // Get the vars of the inlined fn, so the locstats + // reports the missing vars (with coverage 0%). + if (IsInlinedFunction) { + auto OffsetFn = Die.find(dwarf::DW_AT_abstract_origin); + if (OffsetFn) + InlinedVariables[Die.getOffset()] = + GlobalInlinedFnInfo[(*OffsetFn).getRawUValue()]; + } + // Traverse children. unsigned LexicalBlockIndex = 0; unsigned FormalParameterIndex = 0; @@ -486,9 +538,63 @@ ChildVarPrefix += 'p' + toHex(FormalParameterIndex++) + '.'; collectStatsRecursive(Child, FnPrefix, ChildVarPrefix, BytesInScope, - InlineDepth, FnStatMap, GlobalStats, LocStats); + InlineDepth, FnStatMap, GlobalStats, LocStats, + GlobalInlinedFnInfo, InlinedVariables); Child = Child.getSibling(); } + + if (!IsInlinedFunction) + return; + + // After we have processed all vars of the inlined function, + // we want to know how many variables have no location. + constexpr unsigned ZeroCoverageBucket = 0; + auto &InlinedFnStats = FnStatMap[FnID]; + auto &InlineSiteVars = InlinedVariables[Die.getOffset()]; + for (auto Offset : InlineSiteVars) { + // We are done with this variable of this inlined site. + InlineSiteVars.erase( + std::remove(InlineSiteVars.begin(), InlineSiteVars.end(), Offset), + InlineSiteVars.end()); + + auto InlineVarDie = Die.getDwarfUnit()->getDIEForOffset(Offset); + LocStats.NumVarParam++; + LocStats.VarParamLocStats[ZeroCoverageBucket]++; + bool HasSrcLoc = false; + bool HasType = false; + + if (InlineVarDie.find(dwarf::DW_AT_decl_file) && + InlineVarDie.find(dwarf::DW_AT_decl_line)) + HasSrcLoc = true; + + if (InlineVarDie.find(dwarf::DW_AT_type)) + HasType = true; + + if (BytesInScope) + GlobalStats.ScopeBytes += BytesInScope; + auto Tag = InlineVarDie.getTag(); + if (Tag == dwarf::DW_TAG_formal_parameter) { + LocStats.NumParam++; + LocStats.ParamLocStats[ZeroCoverageBucket]++; + InlinedFnStats.NumParams++; + GlobalStats.ParamScopeBytes += BytesInScope; + if (HasType) + InlinedFnStats.NumParamTypes++; + if (HasSrcLoc) + InlinedFnStats.NumParamSourceLocations++; + } else if (Tag == dwarf::DW_TAG_variable) { + LocStats.NumVar++; + LocStats.LocalVarLocStats[ZeroCoverageBucket]++; + InlinedFnStats.NumLocalVars++; + GlobalStats.LocalVarScopeBytes += BytesInScope; + if (HasType) + InlinedFnStats.NumLocalVarTypes++; + if (HasSrcLoc) + InlinedFnStats.NumLocalVarSourceLocations++; + } + } + assert(!InlineSiteVars.size() && + "the inlined var map for the site should be clean"); } /// Print human-readable output. @@ -557,11 +663,13 @@ StringRef FormatName = Obj.getFileFormatName(); GlobalStats GlobalStats; LocationStats LocStats; + InlinedVarsTyMap GlobalInlinedFnInfo; + InlinedSiteVarsTyMap InlinedVariables; StringMap Statistics; for (const auto &CU : static_cast(&DICtx)->compile_units()) if (DWARFDie CUDie = CU->getNonSkeletonUnitDIE(false)) collectStatsRecursive(CUDie, "/", "g", 0, 0, Statistics, GlobalStats, - LocStats); + LocStats, GlobalInlinedFnInfo, InlinedVariables); /// Collect the sizes of debug sections. SectionSizes Sizes;