Skip to content

Commit b9d0866

Browse files
committed
[llvm-cov gcov] Compute unmeasured arc counts by Kirchhoff's circuit law
For a CFG G=(V,E), Knuth describes that by Kirchoff's circuit law, the minimum number of counters necessary is |E|-(|V|-1). The emitted edges form a spanning tree. libgcov emitted .gcda files leverages this optimization while clang --coverage's doesn't. Propagate counts by Kirchhoff's circuit law so that llvm-cov gcov can correctly print line counts of gcc --coverage emitted files and enable the future improvement of clang --coverage.
1 parent 88b368a commit b9d0866

File tree

6 files changed

+91
-60
lines changed

6 files changed

+91
-60
lines changed

compiler-rt/test/profile/Inputs/instrprof-gcov-multiple-bbs-single-line.c.gcov

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// CHECK-NEXT: -: 0:Data:instrprof-gcov-multiple-bbs-single-line.gcda
44
// CHECK-NEXT: -: 0:Runs:1
55
// CHECK-NEXT: -: 0:Programs:1
6-
// CHECK-NEXT:function main called 1 returned 100% blocks executed 80%
6+
// CHECK-NEXT:function main called 1 returned 100% blocks executed 77%
77
// CHECK-NEXT: 1: 1:int main(void)
88
// CHECK-NEXT: -: 2:{
99
// CHECK-NEXT: -: 3: int var;

llvm/include/llvm/ProfileData/GCOV.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -212,12 +212,13 @@ class GCOVFile {
212212
};
213213

214214
struct GCOVArc {
215-
GCOVArc(GCOVBlock &src, GCOVBlock &dst, bool fallthrough)
216-
: src(src), dst(dst), fallthrough(fallthrough) {}
215+
GCOVArc(GCOVBlock &src, GCOVBlock &dst, uint32_t flags)
216+
: src(src), dst(dst), flags(flags) {}
217+
bool onTree() const;
217218

218219
GCOVBlock &src;
219220
GCOVBlock &dst;
220-
bool fallthrough;
221+
uint32_t flags;
221222
uint64_t Count = 0;
222223
uint64_t CyclesCount = 0;
223224
};
@@ -234,14 +235,15 @@ class GCOVFunction {
234235
StringRef getFilename() const;
235236
size_t getNumBlocks() const { return Blocks.size(); }
236237
uint64_t getEntryCount() const;
237-
uint64_t getExitCount() const;
238+
GCOVBlock &getExitBlock() const;
238239

239240
BlockIterator block_begin() const { return Blocks.begin(); }
240241
BlockIterator block_end() const { return Blocks.end(); }
241242
iterator_range<BlockIterator> blocks() const {
242243
return make_range(block_begin(), block_end());
243244
}
244245

246+
uint64_t propagateCounts(const GCOVBlock &v, GCOVArc *arc);
245247
void print(raw_ostream &OS) const;
246248
void dump() const;
247249
void collectLineCounts(FileInfo &FI);

llvm/lib/ProfileData/GCOV.cpp

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,10 @@ bool GCOVFile::readGCNO(GCOVBuffer &buf) {
108108
for (uint32_t i = 0, e = (length - 1) / 2; i != e; ++i) {
109109
uint32_t dstNo = buf.getWord(), flags = buf.getWord();
110110
GCOVBlock *dst = fn->Blocks[dstNo].get();
111-
auto arc =
112-
std::make_unique<GCOVArc>(*src, *dst, flags & GCOV_ARC_FALLTHROUGH);
111+
auto arc = std::make_unique<GCOVArc>(*src, *dst, flags);
113112
src->addDstEdge(arc.get());
114113
dst->addSrcEdge(arc.get());
115-
if (flags & GCOV_ARC_ON_TREE)
114+
if (arc->onTree())
116115
fn->treeArcs.push_back(std::move(arc));
117116
else
118117
fn->arcs.push_back(std::move(arc));
@@ -226,6 +225,17 @@ bool GCOVFile::readGCDA(GCOVBuffer &buf) {
226225
if (arc->dst.succ.empty())
227226
arc->dst.Counter += arc->Count;
228227
}
228+
229+
if (fn->Blocks.size() >= 2) {
230+
GCOVBlock &src = *fn->Blocks[0];
231+
GCOVBlock &sink =
232+
Version < GCOV::V408 ? *fn->Blocks.back() : *fn->Blocks[1];
233+
auto arc = std::make_unique<GCOVArc>(sink, src, GCOV_ARC_ON_TREE);
234+
sink.addDstEdge(arc.get());
235+
src.addSrcEdge(arc.get());
236+
fn->treeArcs.push_back(std::move(arc));
237+
fn->propagateCounts(src, nullptr);
238+
}
229239
}
230240
pos += 4 * length;
231241
if (pos < buf.cursor.tell())
@@ -260,6 +270,8 @@ void GCOVFile::collectLineCounts(FileInfo &fi) {
260270
fi.setProgramCount(ProgramCount);
261271
}
262272

273+
bool GCOVArc::onTree() const { return flags & GCOV_ARC_ON_TREE; }
274+
263275
//===----------------------------------------------------------------------===//
264276
// GCOVFunction implementation.
265277

@@ -271,10 +283,27 @@ uint64_t GCOVFunction::getEntryCount() const {
271283
return Blocks.front()->getCount();
272284
}
273285

274-
/// getExitCount - Get the number of times the function returned by retrieving
275-
/// the exit block's count.
276-
uint64_t GCOVFunction::getExitCount() const {
277-
return Blocks.back()->getCount();
286+
GCOVBlock &GCOVFunction::getExitBlock() const {
287+
return file.getVersion() < GCOV::V408 ? *Blocks.back() : *Blocks[1];
288+
}
289+
290+
// For each basic block, the sum of incoming edge counts equals the sum of
291+
// outgoing edge counts by Kirchoff's circuit law. If the unmeasured arcs form a
292+
// spanning tree, the count for each unmeasured arc (GCOV_ARC_ON_TREE) can be
293+
// uniquely identified.
294+
uint64_t GCOVFunction::propagateCounts(const GCOVBlock &v, GCOVArc *pred) {
295+
uint64_t excess = 0;
296+
for (GCOVArc *e : v.srcs())
297+
if (e != pred)
298+
excess += e->onTree() ? propagateCounts(e->src, e) : e->Count;
299+
for (GCOVArc *e : v.dsts())
300+
if (e != pred)
301+
excess -= e->onTree() ? propagateCounts(e->dst, e) : e->Count;
302+
if (int64_t(excess) < 0)
303+
excess = -excess;
304+
if (pred)
305+
pred->Count = excess;
306+
return excess;
278307
}
279308

280309
void GCOVFunction::print(raw_ostream &OS) const {
@@ -322,8 +351,11 @@ void GCOVBlock::print(raw_ostream &OS) const {
322351
}
323352
if (!succ.empty()) {
324353
OS << "\tDestination Edges : ";
325-
for (const GCOVArc *Edge : succ)
354+
for (const GCOVArc *Edge : succ) {
355+
if (Edge->flags & GCOV_ARC_ON_TREE)
356+
OS << '*';
326357
OS << Edge->dst.Number << " (" << Edge->Count << "), ";
358+
}
327359
OS << "\n";
328360
}
329361
if (!Lines.empty()) {
@@ -441,7 +473,7 @@ uint64_t GCOVBlock::getLineCount(const BlockVector &Blocks) {
441473
uint64_t Count = 0;
442474

443475
for (auto Block : Blocks) {
444-
if (Block->getNumSrcEdges() == 0) {
476+
if (Block->getNumSrcEdges() == 0 || Block->Number == 0) {
445477
// The block has no predecessors and a non-null counter
446478
// (can be the case with entry block in functions).
447479
Count += Block->getCount();
@@ -467,11 +499,13 @@ uint64_t GCOVBlock::getLineCount(const BlockVector &Blocks) {
467499
//===----------------------------------------------------------------------===//
468500
// FileInfo implementation.
469501

470-
// Safe integer division, returns 0 if numerator is 0.
471-
static uint32_t safeDiv(uint64_t Numerator, uint64_t Divisor) {
472-
if (!Numerator)
502+
// Format dividend/divisor as a percentage. Return 1 if the result is greater
503+
// than 0% and less than 1%.
504+
static uint32_t formatPercentage(uint64_t dividend, uint64_t divisor) {
505+
if (!dividend || !divisor)
473506
return 0;
474-
return Numerator / Divisor;
507+
dividend *= 100;
508+
return dividend < divisor ? 1 : dividend / divisor;
475509
}
476510

477511
// This custom division function mimics gcov's branch ouputs:
@@ -794,14 +828,15 @@ void FileInfo::printFunctionSummary(raw_ostream &OS,
794828
for (const GCOVFunction *Func : Funcs) {
795829
uint64_t EntryCount = Func->getEntryCount();
796830
uint32_t BlocksExec = 0;
831+
const GCOVBlock &ExitBlock = Func->getExitBlock();
797832
for (const GCOVBlock &Block : Func->blocks())
798-
if (Block.getNumDstEdges() && Block.getCount())
833+
if (Block.Number != 0 && &Block != &ExitBlock && Block.getCount())
799834
++BlocksExec;
800835

801836
OS << "function " << Func->getName() << " called " << EntryCount
802-
<< " returned " << safeDiv(Func->getExitCount() * 100, EntryCount)
837+
<< " returned " << formatPercentage(ExitBlock.getCount(), EntryCount)
803838
<< "% blocks executed "
804-
<< safeDiv(BlocksExec * 100, Func->getNumBlocks() - 1) << "%\n";
839+
<< formatPercentage(BlocksExec, Func->getNumBlocks() - 2) << "%\n";
805840
}
806841
}
807842

llvm/test/tools/llvm-cov/gcov-4.7.c

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,25 @@
11
/// Test that llvm-cov supports gcov [4.7,8) compatible format.
22
#include <math.h>
33
#include <stdio.h>
4-
int main() { // GCOV: #####: [[@LINE]]:int main
5-
double a[11], result; // GCOV-NEXT: -: [[@LINE]]:
6-
for (int i = 0; i < 11; i++) // GCOV-NEXT: #####: [[@LINE]]:
4+
int main() { // GCOV: 1: [[@LINE]]:int main
5+
double a[11], result; // GCOV-NEXT: -: [[@LINE]]:
6+
for (int i = 0; i < 11; i++) // GCOV-NEXT: 12: [[@LINE]]:
77
scanf("%lf", &a[i]); // GCOV-NEXT: 11: [[@LINE]]:
8-
for (int i = 10; i >= 0; i--) { // GCOV-NEXT: 4: [[@LINE]]:
8+
for (int i = 10; i >= 0; i--) { // GCOV-NEXT: 12: [[@LINE]]:
99
result = sqrt(fabs(a[i])) + 5 * pow(a[i], 3); // GCOV-NEXT: 11: [[@LINE]]:
1010
printf("\nf(%lf) = "); // GCOV-NEXT: 11: [[@LINE]]:
11-
if (result > 400) printf("Overflow!"); // GCOV-NEXT: #####: [[@LINE]]:
12-
else printf("%lf", result); // GCOV-NEXT: 4: [[@LINE]]:
13-
} // GCOV-NEXT: -: [[@LINE]]:
14-
return 0; // GCOV-NEXT: #####: [[@LINE]]:
15-
} // GCOV-NEXT: -: [[@LINE]]:
16-
/// FIXME several lines do not match gcov 7
11+
if (result > 400) printf("Overflow!"); // GCOV-NEXT: 11: [[@LINE]]:
12+
else printf("%lf", result); // GCOV-NEXT: 4: [[@LINE]]:
13+
} // GCOV-NEXT: -: [[@LINE]]:
14+
return 0; // GCOV-NEXT: 1: [[@LINE]]:
15+
} // GCOV-NEXT: -: [[@LINE]]:
1716

1817
// RUN: rm -rf %t && mkdir %t && cd %t
1918
// RUN: cp %s %p/Inputs/gcov-4.7.gc* .
2019

21-
/// FIXME Lines executed:100.00% of 12
2220
// RUN: llvm-cov gcov gcov-4.7.c | FileCheck %s
2321
// CHECK: File 'gcov-4.7.c'
24-
// CHECK-NEXT: Lines executed:55.56% of 9
22+
// CHECK-NEXT: Lines executed:100.00% of 9
2523
// CHECK-NEXT: Creating 'gcov-4.7.c.gcov'
2624

2725
// RUN: FileCheck --input-file=%t/gcov-4.7.c.gcov --check-prefix=HEADER %s

llvm/test/tools/llvm-cov/gcov-8.c

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,27 @@
11
/// Test that llvm-cov supports gcov 8 compatible format.
22
#include <math.h>
33
#include <stdio.h>
4-
int main() { // GCOV: 1: [[@LINE]]:int main
5-
double a[11], result; // GCOV-NEXT: -: [[@LINE]]:
4+
int main() { // GCOV: 1: [[@LINE]]:int main
5+
double a[11], result; // GCOV-NEXT: -: [[@LINE]]:
66
for (int i = 0; i < 11; i++) // GCOV-NEXT: 12: [[@LINE]]:
77
scanf("%lf", &a[i]); // GCOV-NEXT: 11: [[@LINE]]:
8-
for (int i = 10; i >= 0; i--) { // GCOV-NEXT: 7: [[@LINE]]:
8+
for (int i = 10; i >= 0; i--) { // GCOV-NEXT: 12: [[@LINE]]:
99
result = sqrt(fabs(a[i])) + 5 * pow(a[i], 3); // GCOV-NEXT: 11: [[@LINE]]:
1010
printf("\nf(%lf) = "); // GCOV-NEXT: 11: [[@LINE]]:
1111
if (result > 400) printf("Overflow!"); // GCOV-NEXT: 11: [[@LINE]]:
12-
else printf("%lf", result); // GCOV-NEXT: #####: [[@LINE]]:
13-
} // GCOV-NEXT: -: [[@LINE]]:
14-
return 0; // GCOV-NEXT: #####: [[@LINE]]:
15-
} // GCOV-NEXT: -: [[@LINE]]:
16-
/// FIXME several lines do not match gcov 8
12+
else printf("%lf", result); // GCOV-NEXT: 4: [[@LINE]]:
13+
} // GCOV-NEXT: -: [[@LINE]]:
14+
return 0; // GCOV-NEXT: 1: [[@LINE]]:
15+
} // GCOV-NEXT: -: [[@LINE]]:
1716

1817
// RUN: rm -rf %t && mkdir %t && cd %t
1918
// RUN: cp %s %p/Inputs/gcov-8.gc* .
2019

21-
/// FIXME Lines executed:100.00% of 12
2220
// RUN: llvm-cov gcov gcov-8.c | FileCheck %s --check-prefixes=OUT,OUTFILE
2321
// OUT: File 'gcov-8.c'
24-
// OUT-NEXT: Lines executed:77.78% of 9
22+
// OUT-NEXT: Lines executed:100.00% of 9
2523
// OUT-B-NEXT: Branches executed:85.71% of 14
26-
// OUT-B-NEXT: Taken at least once:42.86% of 14
24+
// OUT-B-NEXT: Taken at least once:71.43% of 14
2725
// OUT-B-NEXT: No calls
2826
// OUTFILE-NEXT: Creating 'gcov-8.c.gcov'
2927
// OUT-EMPTY:
@@ -51,23 +49,23 @@ int main() { // GCOV: 1: [[@LINE]]:int
5149
// I-NEXT:lcount:4,1
5250
// I-NEXT:lcount:6,12
5351
// I-B-NEXT:branch:6,taken
54-
// I-B-NEXT:branch:6,nottaken
52+
// I-B-NEXT:branch:6,taken
5553
// I-NEXT:lcount:7,11
5654
// I-B-NEXT:branch:7,taken
5755
// I-B-NEXT:branch:7,nottaken
58-
// I-NEXT:lcount:8,7
56+
// I-NEXT:lcount:8,12
57+
// I-B-NEXT:branch:8,taken
5958
// I-B-NEXT:branch:8,taken
60-
// I-B-NEXT:branch:8,nottaken
6159
// I-NEXT:lcount:9,11
6260
// I-NEXT:lcount:10,11
6361
// I-B-NEXT:branch:10,taken
6462
// I-B-NEXT:branch:10,nottaken
6563
// I-NEXT:lcount:11,11
6664
// I-B-NEXT:branch:11,taken
67-
// I-B-NEXT:branch:11,nottaken
65+
// I-B-NEXT:branch:11,taken
6866
// I-B-NEXT:branch:11,taken
6967
// I-B-NEXT:branch:11,nottaken
70-
// I-NEXT:lcount:12,0
68+
// I-NEXT:lcount:12,4
7169
// I-B-NEXT:branch:12,notexec
7270
// I-B-NEXT:branch:12,notexec
73-
// I-NEXT:lcount:14,0
71+
// I-NEXT:lcount:14,1

llvm/test/tools/llvm-cov/gcov-9.c

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,25 @@
11
/// Test that llvm-cov supports gcov 9 compatible format.
22
#include <math.h>
33
#include <stdio.h>
4-
int main() { // GCOV: 1: [[@LINE]]:int main
5-
double a[11], result; // GCOV-NEXT: -: [[@LINE]]:
4+
int main() { // GCOV: 1: [[@LINE]]:int main
5+
double a[11], result; // GCOV-NEXT: -: [[@LINE]]:
66
for (int i = 0; i < 11; i++) // GCOV-NEXT: 12: [[@LINE]]:
77
scanf("%lf", &a[i]); // GCOV-NEXT: 11: [[@LINE]]:
8-
for (int i = 10; i >= 0; i--) { // GCOV-NEXT: 7: [[@LINE]]:
8+
for (int i = 10; i >= 0; i--) { // GCOV-NEXT: 12: [[@LINE]]:
99
result = sqrt(fabs(a[i])) + 5 * pow(a[i], 3); // GCOV-NEXT: 11: [[@LINE]]:
1010
printf("\nf(%lf) = "); // GCOV-NEXT: 11: [[@LINE]]:
1111
if (result > 400) printf("Overflow!"); // GCOV-NEXT: 11: [[@LINE]]:
12-
else printf("%lf", result); // GCOV-NEXT: #####: [[@LINE]]:
13-
} // GCOV-NEXT: -: [[@LINE]]:
14-
return 0; // GCOV-NEXT: #####: [[@LINE]]:
15-
} // GCOV-NEXT: -: [[@LINE]]:
16-
/// FIXME several lines do not match gcov 9
12+
else printf("%lf", result); // GCOV-NEXT: 4: [[@LINE]]:
13+
} // GCOV-NEXT: -: [[@LINE]]:
14+
return 0; // GCOV-NEXT: 1: [[@LINE]]:
15+
} // GCOV-NEXT: -: [[@LINE]]:
1716

1817
// RUN: rm -rf %t && mkdir %t && cd %t
1918
// RUN: cp %s %p/Inputs/gcov-9.gc* .
2019

21-
/// FIXME Lines executed:100.00% of 12
2220
// RUN: llvm-cov gcov gcov-9.c | FileCheck %s
2321
// CHECK: File 'gcov-9.c'
24-
// CHECK-NEXT: Lines executed:77.78% of 9
22+
// CHECK-NEXT: Lines executed:100.00% of 9
2523
// CHECK-NEXT: Creating 'gcov-9.c.gcov'
2624

2725
// RUN: FileCheck --input-file=%t/gcov-9.c.gcov --check-prefix=HEADER %s

0 commit comments

Comments
 (0)