Skip to content

Commit 7074258

Browse files
[llvm-dwarfdump][LineCov 2/3] Add coverage baseline comparison and line table coverage in isolation (llvm#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.
1 parent dfbae3e commit 7074258

File tree

5 files changed

+268
-46
lines changed

5 files changed

+268
-46
lines changed

llvm/docs/CommandGuide/llvm-dwarfdump.rst

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,11 @@ OPTIONS
198198
Show per-variable coverage metrics. The output format is described
199199
in the section below (:ref:`variable-coverage-format`).
200200

201+
.. option:: --coverage-baseline
202+
203+
File to use as the baseline for variable coverage statistics
204+
(implies :option:`--show-variable-coverage`)
205+
201206
.. option:: --combine-inline-variable-instances
202207

203208
Use with :option:`--show-variable-coverage` to average variable
@@ -280,7 +285,19 @@ a tab-separated table containing the following columns:
280285
declaration
281286
- `LinesCovered` ==> Number of source lines covered by the variable's
282287
debug information in the input file
283-
288+
- `Baseline` (empty if :option:`--coverage-baseline` is not specified)
289+
==> Number of source lines covered by the variable's debug information
290+
in the baseline
291+
- `CoveredRatio` (empty if :option:`--coverage-baseline` is not
292+
specified) ==> Ratio of the coverage compared to the baseline
293+
(calculated as `LinesCovered/Baseline`)
294+
- `LinesPresent` (empty if :option:`--coverage-baseline` is not
295+
specified) ==> Number of source lines covered in the variable's
296+
baseline debug information that are also present in the input file's
297+
line table
298+
- `LinesPresentRatio` (empty if :option:`--coverage-baseline` is not
299+
specified) ==> Ratio of the line table coverage compared to the
300+
baseline (calculated as `LinesPresent/Baseline`)
284301

285302
EXIT STATUS
286303
-----------

llvm/test/tools/llvm-dwarfdump/X86/coverage.test

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,15 @@ COMBINE-NEXT: fn1 1 u test.c:4 1
2424
COMBINE-NEXT: fn1 1 v test.c:13 0
2525
COMBINE-NEXT: fn1 1 x test.c:3 7
2626
COMBINE-NEXT: fn1 1 y test.c:3 7
27+
28+
RUN: llvm-dwarfdump --show-variable-coverage --coverage-baseline=%t.o %t-opt.o | FileCheck %s --check-prefix=BASELINE
29+
30+
BASELINE: Variable coverage statistics:
31+
BASELINE-NEXT: Function InlChain Variable Decl LinesCovered Baseline CoveredRatio LinesPresent LinesPresentRatio
32+
BASELINE-NEXT: f k test.c:20 5 5 1 5 1
33+
BASELINE-NEXT: f l test.c:20 5 5 1 5 1
34+
BASELINE-NEXT: fn1 a test.c:11 5 14 0.357 7 0.5
35+
BASELINE-NEXT: fn1 u test.c:4 1 14 0.0714 7 0.5
36+
BASELINE-NEXT: fn1 v test.c:13 0 14 0 7 0.5
37+
BASELINE-NEXT: fn1 x test.c:3 7 14 0.5 7 0.5
38+
BASELINE-NEXT: fn1 y test.c:3 7 14 0.5 7 0.5

llvm/tools/llvm-dwarfdump/Coverage.cpp

Lines changed: 211 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -30,32 +30,70 @@ using namespace llvm::object;
3030
/// Pair of file index and line number representing a source location.
3131
typedef std::pair<uint16_t, size_t> SourceLocation;
3232

33-
/// Returns the set of source lines covered by a variable's debug information,
34-
/// computed by intersecting the variable's location ranges and the containing
35-
/// scope's address ranges.
36-
static DenseSet<SourceLocation>
37-
computeVariableCoverage(DWARFContext &DICtx, DWARFDie VariableDIE,
38-
const DWARFDebugLine::LineTable *const LineTable) {
39-
/// Adds source locations to the set that correspond to an address range.
40-
auto addLines = [](const DWARFDebugLine::LineTable *LineTable,
33+
/// Adds source locations to the line set that correspond to an address range.
34+
static void addLines(const DWARFDebugLine::LineTable *LineTable,
4135
DenseSet<SourceLocation> &Lines, DWARFAddressRange Range) {
42-
std::vector<uint32_t> Rows;
43-
if (LineTable->lookupAddressRange({Range.LowPC, Range.SectionIndex},
44-
Range.HighPC - Range.LowPC, Rows)) {
45-
for (const auto &RowI : Rows) {
46-
const auto Row = LineTable->Rows[RowI];
47-
// Lookup can return addresses below the LowPC - filter these out.
48-
if (Row.Address.Address < Range.LowPC)
49-
continue;
50-
const auto FileIndex = Row.File;
36+
std::vector<uint32_t> Rows;
37+
if (LineTable->lookupAddressRange({Range.LowPC, Range.SectionIndex},
38+
Range.HighPC - Range.LowPC, Rows)) {
39+
for (const auto &RowI : Rows) {
40+
const auto Row = LineTable->Rows[RowI];
41+
// Lookup can return addresses below the LowPC - filter these out.
42+
if (Row.Address.Address < Range.LowPC)
43+
continue;
44+
45+
if (Row.Line) // Ignore zero lines.
46+
Lines.insert({Row.File, Row.Line});
47+
}
48+
}
49+
}
5150

52-
const auto Line = Row.Line;
53-
if (Line) // Ignore zero lines.
54-
Lines.insert({FileIndex, Line});
51+
// Converts the file index of each line in the set to use our own internal
52+
// file index. This is required for a reliable comparison as the DWARF index may
53+
// differ across compilations.
54+
static DenseSet<SourceLocation>
55+
convertFileIndices(DenseSet<SourceLocation> Lines,
56+
const DWARFDebugLine::LineTable *const LineTable,
57+
DenseMap<uint16_t, uint16_t> &FileIndexMap,
58+
StringMap<uint16_t> &FileNameMap) {
59+
DenseSet<SourceLocation> ResultLines;
60+
for (const auto &L : Lines) {
61+
uint16_t Index;
62+
const auto IndexIt = FileIndexMap.find(L.first);
63+
if (IndexIt != FileIndexMap.end()) {
64+
Index = IndexIt->second;
65+
} else {
66+
std::string Name;
67+
[[maybe_unused]] bool ValidIndex = LineTable->getFileNameByIndex(
68+
L.first, "", DILineInfoSpecifier::FileLineInfoKind::RelativeFilePath,
69+
Name);
70+
assert(ValidIndex && "File index was not valid for its own line table");
71+
72+
auto NameIt = FileNameMap.find(Name);
73+
if (NameIt != FileNameMap.end()) {
74+
Index = NameIt->second;
75+
} else {
76+
Index = FileNameMap.size();
77+
FileNameMap.insert({Name, Index});
5578
}
79+
80+
FileIndexMap.insert({L.first, Index});
5681
}
57-
};
5882

83+
ResultLines.insert({Index, L.second});
84+
}
85+
86+
return ResultLines;
87+
}
88+
89+
/// Returns the set of source lines covered by a variable's debug information,
90+
/// computed by intersecting the variable's location ranges and the containing
91+
/// scope's address ranges.
92+
static DenseSet<SourceLocation>
93+
computeVariableCoverage(DWARFDie VariableDIE,
94+
const DWARFDebugLine::LineTable *const LineTable,
95+
DenseMap<uint16_t, uint16_t> &FileIndexMap,
96+
StringMap<uint16_t> &FileNameMap) {
5997
// The optionals below will be empty if no address ranges were found, and
6098
// present (but containing an empty set) if ranges were found but contained no
6199
// source locations, in order to distinguish the two cases.
@@ -92,9 +130,55 @@ computeVariableCoverage(DWARFContext &DICtx, DWARFDie VariableDIE,
92130
if (!Lines && ParentLines)
93131
Lines = std::move(ParentLines);
94132
else if (ParentLines)
95-
llvm::set_intersect(*Lines, *ParentLines);
133+
set_intersect(*Lines, *ParentLines);
96134

97-
return Lines.value_or(DenseSet<SourceLocation>());
135+
if (!Lines)
136+
return {};
137+
138+
return convertFileIndices(Lines.value_or(DenseSet<SourceLocation>()),
139+
LineTable, FileIndexMap, FileNameMap);
140+
}
141+
142+
/// Adds source locations to the line set that are within an inlined subroutine.
143+
static void getInlinedLines(DWARFDie SubroutineDIE,
144+
DenseSet<SourceLocation> &Lines,
145+
const DWARFDebugLine::LineTable *const LineTable) {
146+
for (const auto &ChildDIE : SubroutineDIE.children()) {
147+
if (ChildDIE.getTag() == DW_TAG_inlined_subroutine) {
148+
auto Ranges = ChildDIE.getAddressRanges();
149+
if (Ranges) {
150+
for (const auto &R : Ranges.get())
151+
addLines(LineTable, Lines, R);
152+
} else {
153+
consumeError(Ranges.takeError());
154+
}
155+
} else {
156+
getInlinedLines(ChildDIE, Lines, LineTable);
157+
}
158+
}
159+
}
160+
161+
/// Returns the set of source lines present in the line table for a subroutine.
162+
static DenseSet<SourceLocation>
163+
computeSubroutineCoverage(DWARFDie SubroutineDIE,
164+
const DWARFDebugLine::LineTable *const LineTable,
165+
DenseMap<uint16_t, uint16_t> &FileIndexMap,
166+
StringMap<uint16_t> &FileNameMap) {
167+
auto Ranges = SubroutineDIE.getAddressRanges();
168+
DenseSet<SourceLocation> Lines;
169+
if (Ranges) {
170+
for (const auto &R : Ranges.get())
171+
addLines(LineTable, Lines, R);
172+
} else {
173+
consumeError(Ranges.takeError());
174+
}
175+
176+
// Exclude lines from any subroutines inlined into this one.
177+
DenseSet<SourceLocation> InlinedLines;
178+
getInlinedLines(SubroutineDIE, InlinedLines, LineTable);
179+
set_subtract(Lines, InlinedLines);
180+
181+
return convertFileIndices(Lines, LineTable, FileIndexMap, FileNameMap);
98182
}
99183

100184
static const SmallVector<DWARFDie> getParentSubroutines(DWARFDie DIE) {
@@ -140,19 +224,27 @@ struct VarKey {
140224
struct VarCoverage {
141225
SmallVector<DWARFDie> Parents;
142226
size_t Cov;
227+
size_t BaselineCov;
228+
size_t LTCov;
229+
size_t Missing;
143230
size_t Instances;
231+
bool MissingBaseline;
144232
};
145233

146234
typedef std::multimap<VarKey, VarCoverage, std::less<>> VarMap;
235+
typedef std::map<VarKey, DenseSet<SourceLocation>, std::less<>> BaselineVarMap;
147236

148-
static std::optional<const VarKey> getVarKey(DWARFDie Die, DWARFDie Parent) {
149-
const auto *const DieName = Die.getName(DINameKind::LinkageName);
150-
const auto DieFile =
151-
Die.getDeclFile(DILineInfoSpecifier::FileLineInfoKind::RelativeFilePath);
152-
const auto *const ParentName = Parent.getName(DINameKind::LinkageName);
153-
if (!DieName || !ParentName)
237+
static std::optional<const VarKey> getVarKey(DWARFDie VariableDIE,
238+
DWARFDie SubroutineDIE) {
239+
const auto *const VariableName = VariableDIE.getName(DINameKind::LinkageName);
240+
const auto DeclFile = VariableDIE.getDeclFile(
241+
DILineInfoSpecifier::FileLineInfoKind::RelativeFilePath);
242+
const auto *const SubroutineName =
243+
SubroutineDIE.getName(DINameKind::LinkageName);
244+
if (!VariableName || !SubroutineName)
154245
return std::nullopt;
155-
return VarKey{ParentName, DieName, DieFile, Die.getDeclLine()};
246+
return VarKey{SubroutineName, VariableName, DeclFile,
247+
VariableDIE.getDeclLine()};
156248
}
157249

158250
static void displayParents(SmallVector<DWARFDie> Parents, raw_ostream &OS) {
@@ -182,34 +274,105 @@ static void displayVariableCoverage(const VarKey &Key, const VarCoverage &Var,
182274
displayParents(Var.Parents, OS);
183275
OS << "\t";
184276
WithColor(OS, HighlightColor::String) << Key.Name;
185-
OS << "\t" << Key.DeclFile << ":" << Key.DeclLine;
277+
OS << "\t";
278+
if (!Key.DeclFile.empty())
279+
OS << Key.DeclFile << ":" << Key.DeclLine;
186280
OS << "\t" << format("%.3g", ((float)Var.Cov / Var.Instances));
281+
if (Var.BaselineCov)
282+
OS << "\t" << format("%.3g", ((float)Var.BaselineCov / Var.Instances))
283+
<< "\t" << format("%.3g", ((float)Var.Cov / Var.BaselineCov)) << "\t"
284+
<< format("%.3g", ((float)Var.LTCov / Var.Instances)) << "\t"
285+
<< format("%.3g", ((float)Var.LTCov / Var.BaselineCov));
187286
OS << "\n";
287+
if (Var.MissingBaseline)
288+
WithColor(errs(), HighlightColor::Warning).warning()
289+
<< "DIE not found in baseline\n";
290+
if (Var.Missing)
291+
WithColor(errs(), HighlightColor::Warning).warning()
292+
<< Var.Missing << " lines not found in baseline\n";
188293
}
189294

190295
bool dwarfdump::showVariableCoverage(ObjectFile &Obj, DWARFContext &DICtx,
296+
ObjectFile *BaselineObj,
297+
DWARFContext *BaselineCtx,
191298
bool CombineInstances, raw_ostream &OS) {
299+
BaselineVarMap BaselineVars;
300+
StringMap<uint16_t> FileNameMap;
301+
302+
if (BaselineCtx) {
303+
for (const auto &U : BaselineCtx->info_section_units()) {
304+
const auto *const LT = BaselineCtx->getLineTableForUnit(U.get());
305+
DenseMap<uint16_t, uint16_t> FileIndexMap;
306+
for (const auto &Entry : U->dies()) {
307+
DWARFDie VariableDIE = {U.get(), &Entry};
308+
if (VariableDIE.getTag() != DW_TAG_variable &&
309+
VariableDIE.getTag() != DW_TAG_formal_parameter)
310+
continue;
311+
312+
const auto Parents = getParentSubroutines(VariableDIE);
313+
if (!Parents.size())
314+
continue;
315+
const auto SubroutineDIE = Parents.front();
316+
auto Key = getVarKey(VariableDIE, SubroutineDIE);
317+
if (!Key)
318+
continue;
319+
320+
auto Cov =
321+
computeVariableCoverage(VariableDIE, LT, FileIndexMap, FileNameMap);
322+
const auto SubroutineCov = computeSubroutineCoverage(
323+
SubroutineDIE, LT, FileIndexMap, FileNameMap);
324+
set_intersect(Cov, SubroutineCov);
325+
326+
auto Result = BaselineVars.insert({*Key, Cov});
327+
if (!Result.second)
328+
Result.first->second.insert_range(Cov);
329+
}
330+
}
331+
}
332+
192333
VarMap Vars;
193334

194335
for (const auto &U : DICtx.info_section_units()) {
195336
const auto *const LT = DICtx.getLineTableForUnit(U.get());
337+
DenseMap<uint16_t, uint16_t> FileIndexMap;
196338
for (const auto &Entry : U->dies()) {
197-
DWARFDie Die = {U.get(), &Entry};
198-
if (Die.getTag() != DW_TAG_variable &&
199-
Die.getTag() != DW_TAG_formal_parameter)
339+
DWARFDie VariableDIE = {U.get(), &Entry};
340+
if (VariableDIE.getTag() != DW_TAG_variable &&
341+
VariableDIE.getTag() != DW_TAG_formal_parameter)
200342
continue;
201343

202-
const auto Parents = getParentSubroutines(Die);
344+
const auto Parents = getParentSubroutines(VariableDIE);
203345
if (!Parents.size())
204346
continue;
205-
const auto Parent = Parents.front();
206-
auto Key = getVarKey(Die, Parent);
347+
const auto SubroutineDIE = Parents.front();
348+
auto Key = getVarKey(VariableDIE, SubroutineDIE);
207349
if (!Key)
208350
continue;
209351

210-
const auto Cov = computeVariableCoverage(DICtx, Die, LT);
352+
auto Cov =
353+
computeVariableCoverage(VariableDIE, LT, FileIndexMap, FileNameMap);
354+
const auto SubroutineCov = computeSubroutineCoverage(
355+
SubroutineDIE, LT, FileIndexMap, FileNameMap);
356+
set_intersect(Cov, SubroutineCov);
211357

212-
VarCoverage VarCov = {Parents, Cov.size(), 1};
358+
VarCoverage VarCov = {Parents, Cov.size(), 0, 0, 0, 1, false};
359+
360+
if (BaselineCtx) {
361+
BaselineVarMap::iterator Var = BaselineVars.find(*Key);
362+
363+
if (Var != BaselineVars.end()) {
364+
const auto BCov = Var->second;
365+
VarCov.BaselineCov = BCov.size();
366+
367+
for (const auto &L : Cov)
368+
VarCov.Missing += (1 - BCov.count(L));
369+
370+
for (const auto &L : BCov)
371+
VarCov.LTCov += SubroutineCov.count(L);
372+
} else {
373+
VarCov.MissingBaseline = true;
374+
}
375+
}
213376

214377
Vars.insert({*Key, VarCov});
215378
}
@@ -219,16 +382,23 @@ bool dwarfdump::showVariableCoverage(ObjectFile &Obj, DWARFContext &DICtx,
219382

220383
OS << "\nVariable coverage statistics:\nFunction\t"
221384
<< (CombineInstances ? "InstanceCount" : "InlChain")
222-
<< "\tVariable\tDecl\tLinesCovered\n";
385+
<< "\tVariable\tDecl\tLinesCovered";
386+
if (BaselineCtx)
387+
OS << "\tBaseline\tCoveredRatio\tLinesPresent\tLinesPresentRatio";
388+
OS << "\n";
223389

224390
if (CombineInstances) {
225391
for (auto FirstVar = Vars.begin(); FirstVar != Vars.end();
226392
FirstVar = Range.second) {
227393
Range = Vars.equal_range(FirstVar->first);
228-
VarCoverage CombinedCov = {{}, 0, 0};
394+
VarCoverage CombinedCov = {{}, 0, 0, 0, 0, 0, false};
229395
for (auto Var = Range.first; Var != Range.second; ++Var) {
230396
++CombinedCov.Instances;
231397
CombinedCov.Cov += Var->second.Cov;
398+
CombinedCov.BaselineCov += Var->second.BaselineCov;
399+
CombinedCov.LTCov += Var->second.LTCov;
400+
CombinedCov.Missing += Var->second.Missing;
401+
CombinedCov.MissingBaseline |= Var->second.MissingBaseline;
232402
}
233403
displayVariableCoverage(FirstVar->first, CombinedCov, true, OS);
234404
}

0 commit comments

Comments
 (0)