[llvm] 7074258 - [llvm-dwarfdump][LineCov 2/3] Add coverage baseline comparison and line table coverage in isolation (#183790)

via llvm-commits llvm-commits at lists.llvm.org
Fri Apr 10 06:07:08 PDT 2026


Author: unexpectedlydefined
Date: 2026-04-10T14:07:03+01:00
New Revision: 70742589079f16232908ed6e616f5f91a07df4e9

URL: https://github.com/llvm/llvm-project/commit/70742589079f16232908ed6e616f5f91a07df4e9
DIFF: https://github.com/llvm/llvm-project/commit/70742589079f16232908ed6e616f5f91a07df4e9.diff

LOG: [llvm-dwarfdump][LineCov 2/3] Add coverage baseline comparison and line table coverage in isolation (#183790)

Patch 2 of 3 to add to llvm-dwarfdump the ability to measure DWARF
coverage of local variables in terms of source lines, as discussed in
[this
RFC](https://discourse.llvm.org/t/rfc-debug-info-coverage-tool-v2/83266).

This patch adds the ability to compare a variable’s coverage against a
baseline, e.g. an unoptimised compilation of the same code. This is
provided using the optional `--coverage-baseline` argument.

When a baseline is provided, the output also includes a per-variable
measure of the line table’s coverage (`LT`, `LTRatio`), distinct from
the variable’s coverage proper. See section 2.2 of the RFC for details
on this metric.

Added: 
    

Modified: 
    llvm/docs/CommandGuide/llvm-dwarfdump.rst
    llvm/test/tools/llvm-dwarfdump/X86/coverage.test
    llvm/tools/llvm-dwarfdump/Coverage.cpp
    llvm/tools/llvm-dwarfdump/llvm-dwarfdump.cpp
    llvm/tools/llvm-dwarfdump/llvm-dwarfdump.h

Removed: 
    


################################################################################
diff  --git a/llvm/docs/CommandGuide/llvm-dwarfdump.rst b/llvm/docs/CommandGuide/llvm-dwarfdump.rst
index 86bdfd6f5224e..03d3623259610 100644
--- a/llvm/docs/CommandGuide/llvm-dwarfdump.rst
+++ b/llvm/docs/CommandGuide/llvm-dwarfdump.rst
@@ -198,6 +198,11 @@ OPTIONS
             Show per-variable coverage metrics. The output format is described
             in the section below (:ref:`variable-coverage-format`).
 
+.. option:: --coverage-baseline
+
+            File to use as the baseline for variable coverage statistics
+            (implies :option:`--show-variable-coverage`)
+
 .. option:: --combine-inline-variable-instances
 
             Use with :option:`--show-variable-coverage` to average variable
@@ -280,7 +285,19 @@ a tab-separated table containing the following columns:
         declaration
       - `LinesCovered` ==> Number of source lines covered by the variable's
         debug information in the input file
-
+      - `Baseline` (empty if :option:`--coverage-baseline` is not specified)
+        ==> Number of source lines covered by the variable's debug information
+        in the baseline
+      - `CoveredRatio` (empty if :option:`--coverage-baseline` is not
+        specified) ==> Ratio of the coverage compared to the baseline
+        (calculated as `LinesCovered/Baseline`)
+      - `LinesPresent` (empty if :option:`--coverage-baseline` is not
+        specified) ==> Number of source lines covered in the variable's
+        baseline debug information that are also present in the input file's
+        line table
+      - `LinesPresentRatio` (empty if :option:`--coverage-baseline` is not
+        specified) ==> Ratio of the line table coverage compared to the
+        baseline (calculated as `LinesPresent/Baseline`)
 
 EXIT STATUS
 -----------

diff  --git a/llvm/test/tools/llvm-dwarfdump/X86/coverage.test b/llvm/test/tools/llvm-dwarfdump/X86/coverage.test
index 15762a38ef7bd..647f277ae64e5 100644
--- a/llvm/test/tools/llvm-dwarfdump/X86/coverage.test
+++ b/llvm/test/tools/llvm-dwarfdump/X86/coverage.test
@@ -24,3 +24,15 @@ COMBINE-NEXT: fn1 1 u test.c:4 1
 COMBINE-NEXT: fn1 1 v test.c:13 0
 COMBINE-NEXT: fn1 1 x test.c:3 7
 COMBINE-NEXT: fn1 1 y test.c:3 7
+
+RUN: llvm-dwarfdump --show-variable-coverage --coverage-baseline=%t.o %t-opt.o | FileCheck %s --check-prefix=BASELINE
+
+BASELINE:      Variable coverage statistics:
+BASELINE-NEXT: Function InlChain Variable Decl LinesCovered Baseline CoveredRatio LinesPresent LinesPresentRatio
+BASELINE-NEXT: f k test.c:20 5 5 1 5 1
+BASELINE-NEXT: f l test.c:20 5 5 1 5 1
+BASELINE-NEXT: fn1 a test.c:11 5 14 0.357 7 0.5
+BASELINE-NEXT: fn1 u test.c:4 1 14 0.0714 7 0.5
+BASELINE-NEXT: fn1 v test.c:13 0 14 0 7 0.5
+BASELINE-NEXT: fn1 x test.c:3 7 14 0.5 7 0.5
+BASELINE-NEXT: fn1 y test.c:3 7 14 0.5 7 0.5

diff  --git a/llvm/tools/llvm-dwarfdump/Coverage.cpp b/llvm/tools/llvm-dwarfdump/Coverage.cpp
index 8b70b27c24334..35ea9bc0a1deb 100644
--- a/llvm/tools/llvm-dwarfdump/Coverage.cpp
+++ b/llvm/tools/llvm-dwarfdump/Coverage.cpp
@@ -30,32 +30,70 @@ using namespace llvm::object;
 /// Pair of file index and line number representing a source location.
 typedef std::pair<uint16_t, size_t> SourceLocation;
 
-/// Returns the set of source lines covered by a variable's debug information,
-/// computed by intersecting the variable's location ranges and the containing
-/// scope's address ranges.
-static DenseSet<SourceLocation>
-computeVariableCoverage(DWARFContext &DICtx, DWARFDie VariableDIE,
-                        const DWARFDebugLine::LineTable *const LineTable) {
-  /// Adds source locations to the set that correspond to an address range.
-  auto addLines = [](const DWARFDebugLine::LineTable *LineTable,
+/// Adds source locations to the line set that correspond to an address range.
+static void addLines(const DWARFDebugLine::LineTable *LineTable,
                      DenseSet<SourceLocation> &Lines, DWARFAddressRange Range) {
-    std::vector<uint32_t> Rows;
-    if (LineTable->lookupAddressRange({Range.LowPC, Range.SectionIndex},
-                                      Range.HighPC - Range.LowPC, Rows)) {
-      for (const auto &RowI : Rows) {
-        const auto Row = LineTable->Rows[RowI];
-        // Lookup can return addresses below the LowPC - filter these out.
-        if (Row.Address.Address < Range.LowPC)
-          continue;
-        const auto FileIndex = Row.File;
+  std::vector<uint32_t> Rows;
+  if (LineTable->lookupAddressRange({Range.LowPC, Range.SectionIndex},
+                                    Range.HighPC - Range.LowPC, Rows)) {
+    for (const auto &RowI : Rows) {
+      const auto Row = LineTable->Rows[RowI];
+      // Lookup can return addresses below the LowPC - filter these out.
+      if (Row.Address.Address < Range.LowPC)
+        continue;
+
+      if (Row.Line) // Ignore zero lines.
+        Lines.insert({Row.File, Row.Line});
+    }
+  }
+}
 
-        const auto Line = Row.Line;
-        if (Line) // Ignore zero lines.
-          Lines.insert({FileIndex, Line});
+// Converts the file index of each line in the set to use our own internal
+// file index. This is required for a reliable comparison as the DWARF index may
+// 
diff er across compilations.
+static DenseSet<SourceLocation>
+convertFileIndices(DenseSet<SourceLocation> Lines,
+                   const DWARFDebugLine::LineTable *const LineTable,
+                   DenseMap<uint16_t, uint16_t> &FileIndexMap,
+                   StringMap<uint16_t> &FileNameMap) {
+  DenseSet<SourceLocation> ResultLines;
+  for (const auto &L : Lines) {
+    uint16_t Index;
+    const auto IndexIt = FileIndexMap.find(L.first);
+    if (IndexIt != FileIndexMap.end()) {
+      Index = IndexIt->second;
+    } else {
+      std::string Name;
+      [[maybe_unused]] bool ValidIndex = LineTable->getFileNameByIndex(
+          L.first, "", DILineInfoSpecifier::FileLineInfoKind::RelativeFilePath,
+          Name);
+      assert(ValidIndex && "File index was not valid for its own line table");
+
+      auto NameIt = FileNameMap.find(Name);
+      if (NameIt != FileNameMap.end()) {
+        Index = NameIt->second;
+      } else {
+        Index = FileNameMap.size();
+        FileNameMap.insert({Name, Index});
       }
+
+      FileIndexMap.insert({L.first, Index});
     }
-  };
 
+    ResultLines.insert({Index, L.second});
+  }
+
+  return ResultLines;
+}
+
+/// Returns the set of source lines covered by a variable's debug information,
+/// computed by intersecting the variable's location ranges and the containing
+/// scope's address ranges.
+static DenseSet<SourceLocation>
+computeVariableCoverage(DWARFDie VariableDIE,
+                        const DWARFDebugLine::LineTable *const LineTable,
+                        DenseMap<uint16_t, uint16_t> &FileIndexMap,
+                        StringMap<uint16_t> &FileNameMap) {
   // The optionals below will be empty if no address ranges were found, and
   // present (but containing an empty set) if ranges were found but contained no
   // source locations, in order to distinguish the two cases.
@@ -92,9 +130,55 @@ computeVariableCoverage(DWARFContext &DICtx, DWARFDie VariableDIE,
   if (!Lines && ParentLines)
     Lines = std::move(ParentLines);
   else if (ParentLines)
-    llvm::set_intersect(*Lines, *ParentLines);
+    set_intersect(*Lines, *ParentLines);
 
-  return Lines.value_or(DenseSet<SourceLocation>());
+  if (!Lines)
+    return {};
+
+  return convertFileIndices(Lines.value_or(DenseSet<SourceLocation>()),
+                            LineTable, FileIndexMap, FileNameMap);
+}
+
+/// Adds source locations to the line set that are within an inlined subroutine.
+static void getInlinedLines(DWARFDie SubroutineDIE,
+                            DenseSet<SourceLocation> &Lines,
+                            const DWARFDebugLine::LineTable *const LineTable) {
+  for (const auto &ChildDIE : SubroutineDIE.children()) {
+    if (ChildDIE.getTag() == DW_TAG_inlined_subroutine) {
+      auto Ranges = ChildDIE.getAddressRanges();
+      if (Ranges) {
+        for (const auto &R : Ranges.get())
+          addLines(LineTable, Lines, R);
+      } else {
+        consumeError(Ranges.takeError());
+      }
+    } else {
+      getInlinedLines(ChildDIE, Lines, LineTable);
+    }
+  }
+}
+
+/// Returns the set of source lines present in the line table for a subroutine.
+static DenseSet<SourceLocation>
+computeSubroutineCoverage(DWARFDie SubroutineDIE,
+                          const DWARFDebugLine::LineTable *const LineTable,
+                          DenseMap<uint16_t, uint16_t> &FileIndexMap,
+                          StringMap<uint16_t> &FileNameMap) {
+  auto Ranges = SubroutineDIE.getAddressRanges();
+  DenseSet<SourceLocation> Lines;
+  if (Ranges) {
+    for (const auto &R : Ranges.get())
+      addLines(LineTable, Lines, R);
+  } else {
+    consumeError(Ranges.takeError());
+  }
+
+  // Exclude lines from any subroutines inlined into this one.
+  DenseSet<SourceLocation> InlinedLines;
+  getInlinedLines(SubroutineDIE, InlinedLines, LineTable);
+  set_subtract(Lines, InlinedLines);
+
+  return convertFileIndices(Lines, LineTable, FileIndexMap, FileNameMap);
 }
 
 static const SmallVector<DWARFDie> getParentSubroutines(DWARFDie DIE) {
@@ -140,19 +224,27 @@ struct VarKey {
 struct VarCoverage {
   SmallVector<DWARFDie> Parents;
   size_t Cov;
+  size_t BaselineCov;
+  size_t LTCov;
+  size_t Missing;
   size_t Instances;
+  bool MissingBaseline;
 };
 
 typedef std::multimap<VarKey, VarCoverage, std::less<>> VarMap;
+typedef std::map<VarKey, DenseSet<SourceLocation>, std::less<>> BaselineVarMap;
 
-static std::optional<const VarKey> getVarKey(DWARFDie Die, DWARFDie Parent) {
-  const auto *const DieName = Die.getName(DINameKind::LinkageName);
-  const auto DieFile =
-      Die.getDeclFile(DILineInfoSpecifier::FileLineInfoKind::RelativeFilePath);
-  const auto *const ParentName = Parent.getName(DINameKind::LinkageName);
-  if (!DieName || !ParentName)
+static std::optional<const VarKey> getVarKey(DWARFDie VariableDIE,
+                                             DWARFDie SubroutineDIE) {
+  const auto *const VariableName = VariableDIE.getName(DINameKind::LinkageName);
+  const auto DeclFile = VariableDIE.getDeclFile(
+      DILineInfoSpecifier::FileLineInfoKind::RelativeFilePath);
+  const auto *const SubroutineName =
+      SubroutineDIE.getName(DINameKind::LinkageName);
+  if (!VariableName || !SubroutineName)
     return std::nullopt;
-  return VarKey{ParentName, DieName, DieFile, Die.getDeclLine()};
+  return VarKey{SubroutineName, VariableName, DeclFile,
+                VariableDIE.getDeclLine()};
 }
 
 static void displayParents(SmallVector<DWARFDie> Parents, raw_ostream &OS) {
@@ -182,34 +274,105 @@ static void displayVariableCoverage(const VarKey &Key, const VarCoverage &Var,
     displayParents(Var.Parents, OS);
   OS << "\t";
   WithColor(OS, HighlightColor::String) << Key.Name;
-  OS << "\t" << Key.DeclFile << ":" << Key.DeclLine;
+  OS << "\t";
+  if (!Key.DeclFile.empty())
+    OS << Key.DeclFile << ":" << Key.DeclLine;
   OS << "\t" << format("%.3g", ((float)Var.Cov / Var.Instances));
+  if (Var.BaselineCov)
+    OS << "\t" << format("%.3g", ((float)Var.BaselineCov / Var.Instances))
+       << "\t" << format("%.3g", ((float)Var.Cov / Var.BaselineCov)) << "\t"
+       << format("%.3g", ((float)Var.LTCov / Var.Instances)) << "\t"
+       << format("%.3g", ((float)Var.LTCov / Var.BaselineCov));
   OS << "\n";
+  if (Var.MissingBaseline)
+    WithColor(errs(), HighlightColor::Warning).warning()
+        << "DIE not found in baseline\n";
+  if (Var.Missing)
+    WithColor(errs(), HighlightColor::Warning).warning()
+        << Var.Missing << " lines not found in baseline\n";
 }
 
 bool dwarfdump::showVariableCoverage(ObjectFile &Obj, DWARFContext &DICtx,
+                                     ObjectFile *BaselineObj,
+                                     DWARFContext *BaselineCtx,
                                      bool CombineInstances, raw_ostream &OS) {
+  BaselineVarMap BaselineVars;
+  StringMap<uint16_t> FileNameMap;
+
+  if (BaselineCtx) {
+    for (const auto &U : BaselineCtx->info_section_units()) {
+      const auto *const LT = BaselineCtx->getLineTableForUnit(U.get());
+      DenseMap<uint16_t, uint16_t> FileIndexMap;
+      for (const auto &Entry : U->dies()) {
+        DWARFDie VariableDIE = {U.get(), &Entry};
+        if (VariableDIE.getTag() != DW_TAG_variable &&
+            VariableDIE.getTag() != DW_TAG_formal_parameter)
+          continue;
+
+        const auto Parents = getParentSubroutines(VariableDIE);
+        if (!Parents.size())
+          continue;
+        const auto SubroutineDIE = Parents.front();
+        auto Key = getVarKey(VariableDIE, SubroutineDIE);
+        if (!Key)
+          continue;
+
+        auto Cov =
+            computeVariableCoverage(VariableDIE, LT, FileIndexMap, FileNameMap);
+        const auto SubroutineCov = computeSubroutineCoverage(
+            SubroutineDIE, LT, FileIndexMap, FileNameMap);
+        set_intersect(Cov, SubroutineCov);
+
+        auto Result = BaselineVars.insert({*Key, Cov});
+        if (!Result.second)
+          Result.first->second.insert_range(Cov);
+      }
+    }
+  }
+
   VarMap Vars;
 
   for (const auto &U : DICtx.info_section_units()) {
     const auto *const LT = DICtx.getLineTableForUnit(U.get());
+    DenseMap<uint16_t, uint16_t> FileIndexMap;
     for (const auto &Entry : U->dies()) {
-      DWARFDie Die = {U.get(), &Entry};
-      if (Die.getTag() != DW_TAG_variable &&
-          Die.getTag() != DW_TAG_formal_parameter)
+      DWARFDie VariableDIE = {U.get(), &Entry};
+      if (VariableDIE.getTag() != DW_TAG_variable &&
+          VariableDIE.getTag() != DW_TAG_formal_parameter)
         continue;
 
-      const auto Parents = getParentSubroutines(Die);
+      const auto Parents = getParentSubroutines(VariableDIE);
       if (!Parents.size())
         continue;
-      const auto Parent = Parents.front();
-      auto Key = getVarKey(Die, Parent);
+      const auto SubroutineDIE = Parents.front();
+      auto Key = getVarKey(VariableDIE, SubroutineDIE);
       if (!Key)
         continue;
 
-      const auto Cov = computeVariableCoverage(DICtx, Die, LT);
+      auto Cov =
+          computeVariableCoverage(VariableDIE, LT, FileIndexMap, FileNameMap);
+      const auto SubroutineCov = computeSubroutineCoverage(
+          SubroutineDIE, LT, FileIndexMap, FileNameMap);
+      set_intersect(Cov, SubroutineCov);
 
-      VarCoverage VarCov = {Parents, Cov.size(), 1};
+      VarCoverage VarCov = {Parents, Cov.size(), 0, 0, 0, 1, false};
+
+      if (BaselineCtx) {
+        BaselineVarMap::iterator Var = BaselineVars.find(*Key);
+
+        if (Var != BaselineVars.end()) {
+          const auto BCov = Var->second;
+          VarCov.BaselineCov = BCov.size();
+
+          for (const auto &L : Cov)
+            VarCov.Missing += (1 - BCov.count(L));
+
+          for (const auto &L : BCov)
+            VarCov.LTCov += SubroutineCov.count(L);
+        } else {
+          VarCov.MissingBaseline = true;
+        }
+      }
 
       Vars.insert({*Key, VarCov});
     }
@@ -219,16 +382,23 @@ bool dwarfdump::showVariableCoverage(ObjectFile &Obj, DWARFContext &DICtx,
 
   OS << "\nVariable coverage statistics:\nFunction\t"
      << (CombineInstances ? "InstanceCount" : "InlChain")
-     << "\tVariable\tDecl\tLinesCovered\n";
+     << "\tVariable\tDecl\tLinesCovered";
+  if (BaselineCtx)
+    OS << "\tBaseline\tCoveredRatio\tLinesPresent\tLinesPresentRatio";
+  OS << "\n";
 
   if (CombineInstances) {
     for (auto FirstVar = Vars.begin(); FirstVar != Vars.end();
          FirstVar = Range.second) {
       Range = Vars.equal_range(FirstVar->first);
-      VarCoverage CombinedCov = {{}, 0, 0};
+      VarCoverage CombinedCov = {{}, 0, 0, 0, 0, 0, false};
       for (auto Var = Range.first; Var != Range.second; ++Var) {
         ++CombinedCov.Instances;
         CombinedCov.Cov += Var->second.Cov;
+        CombinedCov.BaselineCov += Var->second.BaselineCov;
+        CombinedCov.LTCov += Var->second.LTCov;
+        CombinedCov.Missing += Var->second.Missing;
+        CombinedCov.MissingBaseline |= Var->second.MissingBaseline;
       }
       displayVariableCoverage(FirstVar->first, CombinedCov, true, OS);
     }

diff  --git a/llvm/tools/llvm-dwarfdump/llvm-dwarfdump.cpp b/llvm/tools/llvm-dwarfdump/llvm-dwarfdump.cpp
index 26778f51e674a..88e5fb33471a6 100644
--- a/llvm/tools/llvm-dwarfdump/llvm-dwarfdump.cpp
+++ b/llvm/tools/llvm-dwarfdump/llvm-dwarfdump.cpp
@@ -338,6 +338,11 @@ static opt<bool>
     ShowVariableCoverage("show-variable-coverage",
                          desc("Show per-variable coverage metrics."),
                          cat(DwarfDumpCategory));
+static opt<std::string>
+    CoverageBaseline("coverage-baseline",
+                     desc("File to use as the baseline for variable coverage "
+                          "statistics (implies --show-variable-coverage)"),
+                     value_desc("filename"), cat(DwarfDumpCategory));
 static opt<bool> CombineInstances(
     "combine-inline-variable-instances",
     desc(
@@ -915,7 +920,8 @@ int main(int argc, char **argv) {
     DumpType |= DIDT_UUID;
   if (DumpAll)
     DumpType = DIDT_All;
-  if (DumpType == DIDT_Null && !ShowVariableCoverage) {
+  if (DumpType == DIDT_Null && !ShowVariableCoverage &&
+      CoverageBaseline.empty()) {
     if (Verbose || Verify)
       DumpType = DIDT_All;
     else
@@ -967,10 +973,25 @@ int main(int argc, char **argv) {
       Success &= handleFile(Object, dumpObjectFile, OutputFile.os());
   }
 
-  if (ShowVariableCoverage) {
+  if (!CoverageBaseline.empty()) {
+    auto handleBaseline = [&](ObjectFile &BaselineObj,
+                              DWARFContext &BaselineCtx, const Twine &Filename,
+                              raw_ostream &OS) {
+      auto showCoverage = [&](ObjectFile &Obj, DWARFContext &DICtx,
+                              const Twine &Filename, raw_ostream &OS) {
+        return showVariableCoverage(Obj, DICtx, &BaselineObj, &BaselineCtx,
+                                    CombineInstances, OS);
+      };
+      for (StringRef Object : Objects)
+        Success &= handleFile(Object, showCoverage, OutputFile.os());
+      return true;
+    };
+    Success &= handleFile(CoverageBaseline, handleBaseline, OutputFile.os());
+  } else if (ShowVariableCoverage) {
     auto showCoverage = [&](ObjectFile &Obj, DWARFContext &DICtx,
                             const Twine &Filename, raw_ostream &OS) {
-      return showVariableCoverage(Obj, DICtx, CombineInstances, OS);
+      return showVariableCoverage(Obj, DICtx, nullptr, nullptr,
+                                  CombineInstances, OS);
     };
     for (StringRef Object : Objects)
       Success &= handleFile(Object, showCoverage, OutputFile.os());

diff  --git a/llvm/tools/llvm-dwarfdump/llvm-dwarfdump.h b/llvm/tools/llvm-dwarfdump/llvm-dwarfdump.h
index ff6d91f12009e..90a49e8f75ffc 100644
--- a/llvm/tools/llvm-dwarfdump/llvm-dwarfdump.h
+++ b/llvm/tools/llvm-dwarfdump/llvm-dwarfdump.h
@@ -40,7 +40,9 @@ bool collectObjectSectionSizes(object::ObjectFile &Obj, DWARFContext &DICtx,
                                const Twine &Filename, raw_ostream &OS);
 
 bool showVariableCoverage(object::ObjectFile &Obj, DWARFContext &DICtx,
-                          bool CombineInstances, raw_ostream &OS);
+                          object::ObjectFile *BaselineObj,
+                          DWARFContext *BaselineCtx, bool CombineInstances,
+                          raw_ostream &OS);
 } // namespace dwarfdump
 } // namespace llvm
 


        


More information about the llvm-commits mailing list