Skip to content

Commit b6b8fa3

Browse files
authored
[llvm-cov][gcov] Support multi-files coverage in one basic block (#144504)
In the current gcov implementation, all lines within a basic block are attributed to the source file of the block's containing function. This is inaccurate when a block contains lines from other files (e.g., via #include "foo.inc"). Commit [406e81b](406e81b) attempted to address this by filtering lines based on debug info types, but this approach has two limitations: * **Over-filtering**: Some valid lines belonging to the function are incorrectly excluded. * **Under-counting**: Lines not belonging to the function are filtered out and omitted from coverage statistics. **GCC Reference Behavior** GCC's gcov implementation handles this case correctly.This change aligns the LLVM behavior with GCC. **Proposed Solution** 1. **GCNO Generation**: * **Current**: Each block stores a single GCOVLines record (filename + lines). * **New**: Dynamically create new GCOVLines records whenever consecutive lines in a block originate from different source files. Group subsequent lines from the same file under one record. 2. **GCNO Parsing**: * **Current**: Lines are directly attributed to the function's source file. * **New**: Introduce a GCOVLocation type to track filename/line mappings within blocks. Statistics will reflect the actual source file for each line.
1 parent bc14e5e commit b6b8fa3

File tree

5 files changed

+80
-49
lines changed

5 files changed

+80
-49
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// RUN: rm -rf %t && split-file %s %t && cd %t
2+
// RUN: %clangxx --coverage main.cpp -o t
3+
// RUN: %run ./t
4+
// RUN: llvm-cov gcov -t t-main. | FileCheck %s
5+
6+
//--- main.cpp
7+
#include <stdio.h>
8+
9+
int main(int argc, char *argv[]) { // CHECK: 2: [[#]]:int main
10+
puts(""); // CHECK-NEXT: 2: [[#]]:
11+
#line 3
12+
puts(""); // line 3
13+
return 0; // line 4
14+
}
15+
// CHECK-NOT: {{^ +[0-9]+:}}

compiler-rt/test/profile/Posix/gcov-file-change.cpp

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ inline auto *const inl_var_main = // CHECK: 1: [[#]]:inline auto
1616
void foo(int x) { // CHECK-NEXT: 1: [[#]]:
1717
if (x) { // CHECK-NEXT: 1: [[#]]:
1818
#include "a.inc"
19-
}
20-
}
19+
} // CHECK: 1: [[#]]:
20+
} // CHECK-NEXT: 1: [[#]]:
2121
// CHECK-NOT: {{^ +[0-9]+:}}
2222

2323
int main(int argc, char *argv[]) { // CHECK: 1: [[#]]:int main
@@ -32,10 +32,8 @@ int main(int argc, char *argv[]) { // CHECK: 1: [[#]]:int main
3232
//--- a.h
3333
/// Apple targets doesn't enable -mconstructor-aliases by default and the count may be 4.
3434
struct A { A() { } }; // CHECK: {{[24]}}: [[#]]:struct A
35-
inline auto *const inl_var_a =
36-
new A;
37-
/// TODO a.inc:1 should have line execution.
38-
// CHECK-NOT: {{^ +[0-9]+:}}
35+
inline auto *const inl_var_a = // CHECK-NEXT: 1: [[#]]:
36+
new A; // CHECK-NEXT: 1: [[#]]:
3937

4038
//--- a.inc
41-
puts("");
39+
puts(""); // CHECK: 1: [[#]]:puts

llvm/include/llvm/ProfileData/GCOV.h

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,14 @@ class GCOVFunction {
271271
DenseSet<const GCOVBlock *> visited;
272272
};
273273

274+
/// Represent file of lines same with block_location_info in gcc.
275+
struct GCOVBlockLocation {
276+
GCOVBlockLocation(unsigned idx) : srcIdx(idx) {}
277+
278+
unsigned srcIdx;
279+
SmallVector<uint32_t, 4> lines;
280+
};
281+
274282
/// GCOVBlock - Collects block information.
275283
class GCOVBlock {
276284
public:
@@ -281,8 +289,13 @@ class GCOVBlock {
281289

282290
GCOVBlock(uint32_t N) : number(N) {}
283291

284-
void addLine(uint32_t N) { lines.push_back(N); }
285-
uint32_t getLastLine() const { return lines.back(); }
292+
void addLine(uint32_t N) {
293+
locations.back().lines.push_back(N);
294+
lastLine = N;
295+
}
296+
void addFile(unsigned fileIdx) { locations.emplace_back(fileIdx); }
297+
298+
uint32_t getLastLine() const { return lastLine; }
286299
uint64_t getCount() const { return count; }
287300

288301
void addSrcEdge(GCOVArc *Edge) { pred.push_back(Edge); }
@@ -311,7 +324,8 @@ class GCOVBlock {
311324
uint64_t count = 0;
312325
SmallVector<GCOVArc *, 2> pred;
313326
SmallVector<GCOVArc *, 2> succ;
314-
SmallVector<uint32_t, 4> lines;
327+
SmallVector<GCOVBlockLocation> locations;
328+
uint32_t lastLine = 0;
315329
bool traversable = false;
316330
GCOVArc *incoming = nullptr;
317331
};

llvm/lib/ProfileData/GCOV.cpp

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ bool GCOVFile::readGCNO(GCOVBuffer &buf) {
191191
buf.readString(filename);
192192
if (filename.empty())
193193
break;
194-
// TODO Unhandled
194+
Block.addFile(addNormalizedPathToMap(filename));
195195
}
196196
}
197197
}
@@ -456,11 +456,13 @@ void GCOVBlock::print(raw_ostream &OS) const {
456456
}
457457
OS << "\n";
458458
}
459-
if (!lines.empty()) {
460-
OS << "\tLines : ";
461-
for (uint32_t N : lines)
462-
OS << (N) << ",";
463-
OS << "\n";
459+
if (!locations.empty()) {
460+
for (const GCOVBlockLocation &loc : locations) {
461+
OS << "\tFile: " << loc.srcIdx << ": ";
462+
for (uint32_t N : loc.lines)
463+
OS << (N) << ",";
464+
OS << "\n";
465+
}
464466
}
465467
}
466468

@@ -701,20 +703,25 @@ void Context::collectFunction(GCOVFunction &f, Summary &summary) {
701703
SmallSet<uint32_t, 16> lines;
702704
SmallSet<uint32_t, 16> linesExec;
703705
for (const GCOVBlock &b : f.blocksRange()) {
704-
if (b.lines.empty())
706+
if (b.locations.empty())
705707
continue;
706-
uint32_t maxLineNum = *llvm::max_element(b.lines);
707-
if (maxLineNum >= si.lines.size())
708-
si.lines.resize(maxLineNum + 1);
709-
for (uint32_t lineNum : b.lines) {
710-
LineInfo &line = si.lines[lineNum];
711-
if (lines.insert(lineNum).second)
712-
++summary.lines;
713-
if (b.count && linesExec.insert(lineNum).second)
714-
++summary.linesExec;
715-
line.exists = true;
716-
line.count += b.count;
717-
line.blocks.push_back(&b);
708+
for (const GCOVBlockLocation &loc : b.locations) {
709+
SourceInfo &locSource = sources[loc.srcIdx];
710+
uint32_t maxLineNum = *llvm::max_element(loc.lines);
711+
if (maxLineNum >= locSource.lines.size())
712+
locSource.lines.resize(maxLineNum + 1);
713+
for (uint32_t lineNum : loc.lines) {
714+
LineInfo &line = locSource.lines[lineNum];
715+
line.exists = true;
716+
line.count += b.count;
717+
line.blocks.push_back(&b);
718+
if (f.srcIdx == loc.srcIdx) {
719+
if (lines.insert(lineNum).second)
720+
++summary.lines;
721+
if (b.count && linesExec.insert(lineNum).second)
722+
++summary.linesExec;
723+
}
724+
}
718725
}
719726
}
720727
}

llvm/lib/Transforms/Instrumentation/GCOVProfiling.cpp

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -210,12 +210,12 @@ static StringRef getFunctionName(const DISubprogram *SP) {
210210
return SP->getName();
211211
}
212212

213-
/// Extract a filename for a DISubprogram.
213+
/// Extract a filename for a DIScope.
214214
///
215215
/// Prefer relative paths in the coverage notes. Clang also may split
216216
/// up absolute paths into a directory and filename component. When
217217
/// the relative path doesn't exist, reconstruct the absolute path.
218-
static SmallString<128> getFilename(const DISubprogram *SP) {
218+
static SmallString<128> getFilename(const DIScope *SP) {
219219
SmallString<128> Path;
220220
StringRef RelPath = SP->getFilename();
221221
if (sys::fs::exists(RelPath))
@@ -244,7 +244,9 @@ namespace {
244244
// list of line numbers and a single filename, representing lines that belong
245245
// to the block.
246246
class GCOVLines : public GCOVRecord {
247-
public:
247+
public:
248+
const StringRef getFilename() { return Filename; }
249+
248250
void addLine(uint32_t Line) {
249251
assert(Line != 0 && "Line zero is not a valid real line number.");
250252
Lines.push_back(Line);
@@ -276,7 +278,9 @@ namespace {
276278
class GCOVBlock : public GCOVRecord {
277279
public:
278280
GCOVLines &getFile(StringRef Filename) {
279-
return LinesByFile.try_emplace(Filename, P, Filename).first->second;
281+
if (Lines.empty() || Lines.back().getFilename() != Filename)
282+
Lines.emplace_back(P, Filename);
283+
return Lines.back();
280284
}
281285

282286
void addEdge(GCOVBlock &Successor, uint32_t Flags) {
@@ -285,22 +289,16 @@ namespace {
285289

286290
void writeOut() {
287291
uint32_t Len = 3;
288-
SmallVector<StringMapEntry<GCOVLines> *, 32> SortedLinesByFile;
289-
for (auto &I : LinesByFile) {
290-
Len += I.second.length();
291-
SortedLinesByFile.push_back(&I);
292-
}
292+
293+
for (auto &L : Lines)
294+
Len += L.length();
293295

294296
write(GCOV_TAG_LINES);
295297
write(Len);
296298
write(Number);
297299

298-
llvm::sort(SortedLinesByFile, [](StringMapEntry<GCOVLines> *LHS,
299-
StringMapEntry<GCOVLines> *RHS) {
300-
return LHS->getKey() < RHS->getKey();
301-
});
302-
for (auto &I : SortedLinesByFile)
303-
I->getValue().writeOut();
300+
for (auto &L : Lines)
301+
L.writeOut();
304302
write(0);
305303
write(0);
306304
}
@@ -309,7 +307,7 @@ namespace {
309307
// Only allow copy before edges and lines have been added. After that,
310308
// there are inter-block pointers (eg: edges) that won't take kindly to
311309
// blocks being copied or moved around.
312-
assert(LinesByFile.empty());
310+
assert(Lines.empty());
313311
assert(OutEdges.empty());
314312
}
315313

@@ -322,7 +320,7 @@ namespace {
322320
GCOVBlock(GCOVProfiler *P, uint32_t Number)
323321
: GCOVRecord(P), Number(Number) {}
324322

325-
StringMap<GCOVLines> LinesByFile;
323+
SmallVector<GCOVLines> Lines;
326324
};
327325

328326
// A function has a unique identifier, a checksum (we leave as zero) and a
@@ -881,11 +879,10 @@ bool GCOVProfiler::emitProfileNotes(
881879
if (Line == Loc.getLine()) continue;
882880
Line = Loc.getLine();
883881
MDNode *Scope = Loc.getScope();
884-
// TODO: Handle blocks from another file due to #line, #include, etc.
885-
if (isa<DILexicalBlockFile>(Scope) || SP != getDISubprogram(Scope))
882+
if (SP != getDISubprogram(Scope))
886883
continue;
887884

888-
GCOVLines &Lines = Block.getFile(Filename);
885+
GCOVLines &Lines = Block.getFile(getFilename(Loc->getScope()));
889886
Lines.addLine(Loc.getLine());
890887
}
891888
Line = 0;

0 commit comments

Comments
 (0)