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,309 @@ +# 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 () +## 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_inlined_subroutine +## DW_AT_abstract_origin (0x00000014) +## DW_AT_low_pc (0x0000000000000002) +## DW_AT_high_pc (0x000000000000000a) +## DW_AT_call_file (0x01) +## DW_AT_call_line (3) +## DW_AT_call_column (0x03) +## DW_TAG_inlined_subroutine +## DW_AT_abstract_origin (0x000000b7) +## DW_AT_low_pc (0x0000000000000006) +## DW_AT_high_pc (0x0000000000000010) +## DW_AT_call_file (0x01) +## DW_AT_call_line (3) +## DW_AT_call_column (0x03) +## DW_TAG_inlined_subroutine +## DW_AT_abstract_origin (0x000000b7) +## DW_AT_low_pc (0x0000000000000006) +## DW_AT_high_pc (0x0000000000000010) +## DW_AT_call_file (0x01) +## DW_AT_call_line (3) +## DW_AT_call_column (0x03) +## DW_TAG_formal_parameter +## DW_AT_abstract_origin (0x000000bb) +## DW_AT_location () +## DW_TAG_subprogram <-- (0x000000b7) +## DW_AT_decl_file (0x01) +## DW_AT_decl_line (3) +## DW_AT_inline (DW_INL_inlined) +## DW_TAG_formal_parameter <-- (0x000000bb) +## DW_AT_decl_file (0x01) +## DW_AT_decl_line (1) +## DW_TAG_variable <-- (0x000000be) +## DW_AT_decl_file (0x01) +## DW_AT_decl_line (1) + +# CHECK: "version": 6, +# CHECK: "#variables processed by location statistics": 13, +# CHECK: "#variables with 0% of parent scope covered by DW_AT_location": 9, +# CHECK: "#variables with 100% of parent scope covered by DW_AT_location": 4, + +--- !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: 1 ## 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: 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: 1 ## DW_AT_call_file + - Value: 3 ## DW_AT_call_line + - Value: 3 ## DW_AT_call_column + - AbbrCode: 0 ## NULL + - AbbrCode: 7 ## DW_TAG_inlined_subroutine + Values: + - Value: 0xb7 ## DW_AT_abstract_origin + - Value: 0x06 ## DW_AT_low_pc + - Value: 0x0a ## DW_AT_high_pc + - Value: 1 ## DW_AT_call_file + - Value: 3 ## DW_AT_call_line + - Value: 3 ## DW_AT_call_column + - AbbrCode: 0 ## NULL + - AbbrCode: 7 ## DW_TAG_inlined_subroutine + Values: + - Value: 0xb7 ## DW_AT_abstract_origin + - Value: 0x06 ## DW_AT_low_pc + - Value: 0x0a ## DW_AT_high_pc + - Value: 1 ## DW_AT_call_file + - Value: 3 ## DW_AT_call_line + - Value: 3 ## DW_AT_call_column + - AbbrCode: 10 ## DW_TAG_formal_parameter + Values: + - Value: 0xbb ## DW_AT_abstract_origin + - Value: 0x0 ## DW_AT_location + - AbbrCode: 0 ## NULL + - AbbrCode: 2 ## DW_TAG_subprogram + Values: + - Value: 1 ## DW_AT_decl_file + - Value: 3 ## 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: 5 ## DW_TAG_variable + Values: + - Value: 1 ## DW_AT_decl_file + - Value: 1 ## DW_AT_decl_line + - 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,23 @@ 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 is used for zero location coverage bucket. +constexpr unsigned ZeroCoverageBucket = 0; + +/// This represents variables DIE offsets. +using InlinedVarsTy = llvm::SmallVector; +/// This maps function DIE offset to its variables. +using InlinedVarsTyMap = llvm::DenseMap; +/// This represents inlined_subroutine DIE offsets. +using InlinedFnInstacesTy = llvm::SmallVector; + /// 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 +178,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 +224,8 @@ uint32_t InlineDepth, StringMap &FnStatMap, GlobalStats &GlobalStats, - LocationStats &LocStats) { + LocationStats &LocStats, + InlinedVarsTy *InlinedVariables) { bool HasLoc = false; bool HasSrcLoc = false; bool HasType = false; @@ -228,6 +238,10 @@ bool IsConstantMember = Die.getTag() == dwarf::DW_TAG_member && Die.find(dwarf::DW_AT_const_value); + // For zero covered inlined variables the locstats will be + // calculated later. + bool DeferLocStats = false; + if (Die.getTag() == dwarf::DW_TAG_call_site || Die.getTag() == dwarf::DW_TAG_GNU_call_site) { GlobalStats.CallSiteDIEs++; @@ -256,6 +270,26 @@ if (Die.findRecursively(dwarf::DW_AT_type)) HasType = true; + // Check if it is an inlined variable. + if (Die.find(dwarf::DW_AT_abstract_origin)) { + if (InlinedVariables) { + 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. + InlinedVariables->erase(std::remove(InlinedVariables->begin(), + InlinedVariables->end(), + (*Offset).getRawUValue()), + InlinedVariables->end()); + } else { + // The locstats will be handled at the end of + // the collectStatsRecursive(). + DeferLocStats = true; + } + } + } + auto IsEntryValue = [&](ArrayRef D) -> bool { DWARFUnit *U = Die.getDwarfUnit(); DataExtractor Data(toStringRef(D), @@ -315,7 +349,7 @@ } // Calculate the debug location statistics. - if (BytesInScope) { + if (BytesInScope && !DeferLocStats) { LocStats.NumVarParam++; if (IsParam) LocStats.NumParam++; @@ -395,7 +429,10 @@ uint32_t InlineDepth, StringMap &FnStatMap, GlobalStats &GlobalStats, - LocationStats &LocStats) { + LocationStats &LocStats, + InlinedVarsTyMap &GlobalInlinedFnInfo, + InlinedFnInstacesTy &InlinedFnsToBeProcessed, + InlinedVarsTy *InlinedVarsPtr = nullptr) { const dwarf::Tag Tag = Die.getTag(); // Skip function types. if (Tag == dwarf::DW_TAG_subroutine_type) @@ -405,11 +442,27 @@ 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; - if (IsFunction || IsInlinedFunction || IsBlock) { + InlinedVarsTy InlinedVars; + // 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) { + uint64_t OffsetOfInlineFnCopy = (*OffsetFn).getRawUValue(); + if (GlobalInlinedFnInfo.count(OffsetOfInlineFnCopy)) { + InlinedVars = GlobalInlinedFnInfo[OffsetOfInlineFnCopy]; + InlinedVarsPtr = &InlinedVars; + } else { + // This means that the DW_AT_inline fn copy is out of order, + // so this inlined instance will be processed later. + InlinedFnsToBeProcessed.push_back(Die.getOffset()); + } + } + } + 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,9 +487,21 @@ // 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); // We've seen an instance of this function. auto &FnStats = FnStatMap[FnID]; @@ -465,7 +530,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, InlinedVarsPtr); } // Set InlineDepth correctly for child recursion @@ -486,9 +551,29 @@ ChildVarPrefix += 'p' + toHex(FormalParameterIndex++) + '.'; collectStatsRecursive(Child, FnPrefix, ChildVarPrefix, BytesInScope, - InlineDepth, FnStatMap, GlobalStats, LocStats); + InlineDepth, FnStatMap, GlobalStats, LocStats, + GlobalInlinedFnInfo, InlinedFnsToBeProcessed, + InlinedVarsPtr); 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. + for (auto Offset : InlinedVars) { + LocStats.NumVarParam++; + LocStats.VarParamLocStats[ZeroCoverageBucket]++; + auto Tag = Die.getDwarfUnit()->getDIEForOffset(Offset).getTag(); + if (Tag == dwarf::DW_TAG_formal_parameter) { + LocStats.NumParam++; + LocStats.ParamLocStats[ZeroCoverageBucket]++; + } else if (Tag == dwarf::DW_TAG_variable) { + LocStats.NumVar++; + LocStats.LocalVarLocStats[ZeroCoverageBucket]++; + } + } } /// Print human-readable output. @@ -541,6 +626,53 @@ int64_t(DebugSec.getValue())); } +/// Collect zero location coverage for inlined variables which refer to +/// a DW_AT_inline copy of subprogram that is out of order in the DWARF. +static void +collectZeroCovInlinedVars(DWARFUnit *DwUnit, GlobalStats &GlobalStats, + LocationStats &LocStats, + InlinedVarsTyMap &GlobalInlinedFnInfo, + InlinedFnInstacesTy &InlinedFnsToBeProcessed) { + for (auto FnOffset : InlinedFnsToBeProcessed) { + DWARFDie InlinedFnDie = DwUnit->getDIEForOffset(FnOffset); + auto InlinedCopy = InlinedFnDie.find(dwarf::DW_AT_abstract_origin); + InlinedVarsTy InlinedVars; + if (!InlinedCopy) + continue; + + InlinedVars = GlobalInlinedFnInfo[(*InlinedCopy).getRawUValue()]; + DWARFDie Child = InlinedFnDie.getFirstChild(); + while (Child) { + const dwarf::Tag ChildTag = Child.getTag(); + if ((ChildTag == dwarf::DW_TAG_formal_parameter || + ChildTag == dwarf::DW_TAG_variable) && + (Child.find(dwarf::DW_AT_location) || + Child.find(dwarf::DW_AT_const_value))) { + auto OffsetVar = Child.find(dwarf::DW_AT_abstract_origin); + if (OffsetVar) + InlinedVars.erase(std::remove(InlinedVars.begin(), InlinedVars.end(), + (*OffsetVar).getRawUValue()), + InlinedVars.end()); + } + + Child = Child.getSibling(); + } + + for (auto Offset : InlinedVars) { + LocStats.NumVarParam++; + LocStats.VarParamLocStats[ZeroCoverageBucket]++; + auto Tag = DwUnit->getDIEForOffset(Offset).getTag(); + if (Tag == dwarf::DW_TAG_formal_parameter) { + LocStats.NumParam++; + LocStats.ParamLocStats[ZeroCoverageBucket]++; + } else if (Tag == dwarf::DW_TAG_variable) { + LocStats.NumVar++; + LocStats.LocalVarLocStats[ZeroCoverageBucket]++; + } + } + } +} + /// \} /// Collect debug info quality metrics for an entire DIContext. @@ -557,11 +689,19 @@ StringRef FormatName = Obj.getFileFormatName(); GlobalStats GlobalStats; LocationStats LocStats; + InlinedVarsTyMap GlobalInlinedFnInfo; + InlinedFnInstacesTy InlinedFnsToBeProcessed; StringMap Statistics; - for (const auto &CU : static_cast(&DICtx)->compile_units()) - if (DWARFDie CUDie = CU->getNonSkeletonUnitDIE(false)) + 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, + InlinedFnsToBeProcessed); + + collectZeroCovInlinedVars(CUDie.getDwarfUnit(), GlobalStats, LocStats, + GlobalInlinedFnInfo, InlinedFnsToBeProcessed); + } + } /// Collect the sizes of debug sections. SectionSizes Sizes;