diff --git a/llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h b/llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h --- a/llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h +++ b/llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h @@ -22,6 +22,7 @@ #include "llvm/ADT/StringRef.h" #include "llvm/ADT/iterator.h" #include "llvm/ADT/iterator_range.h" +#include "llvm/Object/BuildID.h" #include "llvm/ProfileData/InstrProf.h" #include "llvm/Support/Alignment.h" #include "llvm/Support/Compiler.h" @@ -43,6 +44,10 @@ class IndexedInstrProfReader; +namespace object { +class BuildIDFetcher; +} // namespace object + namespace coverage { class CoverageMappingReader; @@ -580,6 +585,13 @@ ArrayRef> CoverageReaders, IndexedInstrProfReader &ProfileReader, CoverageMapping &Coverage); + // Load coverage records from file. + static Error + loadFromFile(StringRef Filename, StringRef Arch, StringRef CompilationDir, + IndexedInstrProfReader &ProfileReader, CoverageMapping &Coverage, + bool &DataFound, + SmallVectorImpl *FoundBinaryIDs = nullptr); + /// Add a function record corresponding to \p Record. Error loadFunctionRecord(const CoverageMappingRecord &Record, IndexedInstrProfReader &ProfileReader); @@ -605,7 +617,8 @@ /// Ignores non-instrumented object files unless all are not instrumented. static Expected> load(ArrayRef ObjectFilenames, StringRef ProfileFilename, - ArrayRef Arches = None, StringRef CompilationDir = ""); + ArrayRef Arches = None, StringRef CompilationDir = "", + const object::BuildIDFetcher* BIDFetcher = nullptr); /// The number of functions that couldn't have their profiles mapped. /// diff --git a/llvm/include/llvm/ProfileData/Coverage/CoverageMappingReader.h b/llvm/include/llvm/ProfileData/Coverage/CoverageMappingReader.h --- a/llvm/include/llvm/ProfileData/Coverage/CoverageMappingReader.h +++ b/llvm/include/llvm/ProfileData/Coverage/CoverageMappingReader.h @@ -205,7 +205,8 @@ static Expected>> create(MemoryBufferRef ObjectBuffer, StringRef Arch, SmallVectorImpl> &ObjectFileBuffers, - StringRef CompilationDir = ""); + StringRef CompilationDir = "", + SmallVectorImpl *BinaryIDs = nullptr); static Expected> createCoverageReaderFromBuffer(StringRef Coverage, diff --git a/llvm/lib/ProfileData/Coverage/CoverageMapping.cpp b/llvm/lib/ProfileData/Coverage/CoverageMapping.cpp --- a/llvm/lib/ProfileData/Coverage/CoverageMapping.cpp +++ b/llvm/lib/ProfileData/Coverage/CoverageMapping.cpp @@ -19,6 +19,7 @@ #include "llvm/ADT/SmallBitVector.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" +#include "llvm/Object/BuildID.h" #include "llvm/ProfileData/Coverage/CoverageMappingReader.h" #include "llvm/ProfileData/InstrProfReader.h" #include "llvm/Support/Debug.h" @@ -343,10 +344,49 @@ }); } +Error CoverageMapping::loadFromFile( + StringRef Filename, StringRef Arch, StringRef CompilationDir, + IndexedInstrProfReader &ProfileReader, CoverageMapping &Coverage, + bool &DataFound, SmallVectorImpl *FoundBinaryIDs) { + auto CovMappingBufOrErr = MemoryBuffer::getFileOrSTDIN( + Filename, /*IsText=*/false, /*RequiresNullTerminator=*/false); + if (std::error_code EC = CovMappingBufOrErr.getError()) + return createFileError(Filename, errorCodeToError(EC)); + MemoryBufferRef CovMappingBufRef = + CovMappingBufOrErr.get()->getMemBufferRef(); + SmallVector, 4> Buffers; + + SmallVector BinaryIDs; + auto CoverageReadersOrErr = BinaryCoverageReader::create( + CovMappingBufRef, Arch, Buffers, CompilationDir, + FoundBinaryIDs ? &BinaryIDs : nullptr); + if (Error E = CoverageReadersOrErr.takeError()) { + E = handleMaybeNoDataFoundError(std::move(E)); + if (E) + return createFileError(Filename, std::move(E)); + return E; + } + + SmallVector, 4> Readers; + for (auto &Reader : CoverageReadersOrErr.get()) + Readers.push_back(std::move(Reader)); + if (FoundBinaryIDs && !Readers.empty()) { + llvm::append_range(*FoundBinaryIDs, + llvm::map_range(BinaryIDs, [](object::BuildIDRef BID) { + return object::BuildID(BID); + })); + } + DataFound |= !Readers.empty(); + if (Error E = loadFromReaders(Readers, ProfileReader, Coverage)) + return createFileError(Filename, std::move(E)); + return Error::success(); +} + Expected> CoverageMapping::load(ArrayRef ObjectFilenames, StringRef ProfileFilename, ArrayRef Arches, - StringRef CompilationDir) { + StringRef CompilationDir, + const object::BuildIDFetcher* BIDFetcher) { auto ProfileReaderOrErr = IndexedInstrProfReader::create(ProfileFilename); if (Error E = ProfileReaderOrErr.takeError()) return createFileError(ProfileFilename, std::move(E)); @@ -354,32 +394,54 @@ auto Coverage = std::unique_ptr(new CoverageMapping()); bool DataFound = false; + auto GetArch = [&](size_t Idx) { + if (Arches.empty()) + return StringRef(); + if (Arches.size() == 1) + return Arches.front(); + return Arches[Idx]; + }; + + SmallVector FoundBinaryIDs; for (const auto &File : llvm::enumerate(ObjectFilenames)) { - auto CovMappingBufOrErr = MemoryBuffer::getFileOrSTDIN( - File.value(), /*IsText=*/false, /*RequiresNullTerminator=*/false); - if (std::error_code EC = CovMappingBufOrErr.getError()) - return createFileError(File.value(), errorCodeToError(EC)); - StringRef Arch = Arches.empty() ? StringRef() : Arches[File.index()]; - MemoryBufferRef CovMappingBufRef = - CovMappingBufOrErr.get()->getMemBufferRef(); - SmallVector, 4> Buffers; - auto CoverageReadersOrErr = BinaryCoverageReader::create( - CovMappingBufRef, Arch, Buffers, CompilationDir); - if (Error E = CoverageReadersOrErr.takeError()) { - E = handleMaybeNoDataFoundError(std::move(E)); - if (E) - return createFileError(File.value(), std::move(E)); - // E == success (originally a no_data_found error). - continue; + if (Error E = + loadFromFile(File.value(), GetArch(File.index()), CompilationDir, + *ProfileReader, *Coverage, DataFound, &FoundBinaryIDs)) + return E; + } + + if (BIDFetcher) { + const auto &Compare = [](object::BuildIDRef A, object::BuildIDRef B) { + return StringRef(reinterpret_cast(A.data()), A.size()) < + StringRef(reinterpret_cast(B.data()), B.size()); + }; + std::vector ProfileBinaryIDs; + if (Error E = ProfileReader->readBinaryIds(ProfileBinaryIDs)) + return createFileError(ProfileFilename, std::move(E)); + llvm::sort(ProfileBinaryIDs, Compare); + std::unique(ProfileBinaryIDs.begin(), ProfileBinaryIDs.end(), Compare); + + SmallVector BinaryIDsToFetch; + if (!ProfileBinaryIDs.empty()) { + llvm::sort(FoundBinaryIDs, Compare); + std::unique(FoundBinaryIDs.begin(), FoundBinaryIDs.end(), Compare); + std::set_difference(ProfileBinaryIDs.begin(), ProfileBinaryIDs.end(), + FoundBinaryIDs.begin(), FoundBinaryIDs.end(), + BinaryIDsToFetch.begin(), Compare); } - SmallVector, 4> Readers; - for (auto &Reader : CoverageReadersOrErr.get()) - Readers.push_back(std::move(Reader)); - DataFound |= !Readers.empty(); - if (Error E = loadFromReaders(Readers, *ProfileReader, *Coverage)) - return createFileError(File.value(), std::move(E)); + for (object::BuildIDRef BinaryID : BinaryIDsToFetch) { + Optional PathOpt = BIDFetcher->fetch(BinaryID); + if (!PathOpt) + continue; + std::string Path = std::move(*PathOpt); + StringRef Arch = Arches.size() == 1 ? Arches.front() : StringRef(); + if (Error E = loadFromFile(Path, Arch, CompilationDir, *ProfileReader, + *Coverage, DataFound)) + return E; + } } + // If no readers were created, either no objects were provided or none of them // had coverage data. Return an error in the latter case. if (!DataFound && !ObjectFilenames.empty()) diff --git a/llvm/lib/ProfileData/Coverage/CoverageMappingReader.cpp b/llvm/lib/ProfileData/Coverage/CoverageMappingReader.cpp --- a/llvm/lib/ProfileData/Coverage/CoverageMappingReader.cpp +++ b/llvm/lib/ProfileData/Coverage/CoverageMappingReader.cpp @@ -954,7 +954,8 @@ static Expected> loadBinaryFormat(std::unique_ptr Bin, StringRef Arch, - StringRef CompilationDir = "") { + StringRef CompilationDir = "", + Optional *BinaryID = nullptr) { std::unique_ptr OF; if (auto *Universal = dyn_cast(Bin.get())) { // If we have a universal binary, try to look up the object for the @@ -1052,6 +1053,9 @@ FuncRecords = std::move(WritableBuffer); } + if (BinaryID) + *BinaryID = getBuildID(OF.get()); + return BinaryCoverageReader::createCoverageReaderFromBuffer( CoverageMapping, std::move(FuncRecords), std::move(ProfileNames), BytesInAddress, Endian, CompilationDir); @@ -1074,7 +1078,8 @@ BinaryCoverageReader::create( MemoryBufferRef ObjectBuffer, StringRef Arch, SmallVectorImpl> &ObjectFileBuffers, - StringRef CompilationDir) { + StringRef CompilationDir, + SmallVectorImpl *BinaryIDs) { std::vector> Readers; if (ObjectBuffer.getBuffer().startswith(TestingFormatMagic)) { @@ -1114,7 +1119,7 @@ return BinaryCoverageReader::create( ArchiveOrErr.get()->getMemoryBufferRef(), Arch, ObjectFileBuffers, - CompilationDir); + CompilationDir, BinaryIDs); } } @@ -1127,7 +1132,8 @@ return ChildBufOrErr.takeError(); auto ChildReadersOrErr = BinaryCoverageReader::create( - ChildBufOrErr.get(), Arch, ObjectFileBuffers, CompilationDir); + ChildBufOrErr.get(), Arch, ObjectFileBuffers, CompilationDir, + BinaryIDs); if (!ChildReadersOrErr) return ChildReadersOrErr.takeError(); for (auto &Reader : ChildReadersOrErr.get()) @@ -1146,10 +1152,14 @@ return std::move(Readers); } - auto ReaderOrErr = loadBinaryFormat(std::move(Bin), Arch, CompilationDir); + Optional BinaryID; + auto ReaderOrErr = loadBinaryFormat(std::move(Bin), Arch, CompilationDir, + BinaryIDs ? &BinaryID : nullptr); if (!ReaderOrErr) return ReaderOrErr.takeError(); Readers.push_back(std::move(ReaderOrErr.get())); + if (BinaryID) + BinaryIDs->push_back(*BinaryID); return std::move(Readers); } diff --git a/llvm/tools/llvm-cov/CMakeLists.txt b/llvm/tools/llvm-cov/CMakeLists.txt --- a/llvm/tools/llvm-cov/CMakeLists.txt +++ b/llvm/tools/llvm-cov/CMakeLists.txt @@ -14,3 +14,5 @@ SourceCoverageViewText.cpp TestingSupport.cpp ) + +target_link_libraries(llvm-cov PRIVATE LLVMDebuginfod) diff --git a/llvm/tools/llvm-cov/CodeCoverage.cpp b/llvm/tools/llvm-cov/CodeCoverage.cpp --- a/llvm/tools/llvm-cov/CodeCoverage.cpp +++ b/llvm/tools/llvm-cov/CodeCoverage.cpp @@ -23,6 +23,10 @@ #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/Triple.h" +#include "llvm/Debuginfod/BuildIDFetcher.h" +#include "llvm/Debuginfod/Debuginfod.h" +#include "llvm/Debuginfod/HTTPClient.h" +#include "llvm/Object/BuildID.h" #include "llvm/ProfileData/Coverage/CoverageMapping.h" #include "llvm/ProfileData/InstrProfReader.h" #include "llvm/Support/CommandLine.h" @@ -50,7 +54,11 @@ const CoverageViewOptions &Options, raw_ostream &OS); + namespace { + +ExitOnError ExitOnErr; + /// The implementation of the coverage tool. class CodeCoverageTool { public: @@ -178,6 +186,8 @@ /// Allowlist from -name-allowlist to be used for filtering. std::unique_ptr NameAllowlist; + + std::unique_ptr BIDFetcher; }; } @@ -434,7 +444,7 @@ ObjectFilename); auto CoverageOrErr = CoverageMapping::load(ObjectFilenames, PGOFilename, CoverageArches, - ViewOpts.CompilationDirectory); + ViewOpts.CompilationDirectory, BIDFetcher.get()); if (Error E = CoverageOrErr.takeError()) { error("Failed to load coverage: " + toString(std::move(E))); return nullptr; @@ -643,6 +653,15 @@ cl::opt DebugDump("dump", cl::Optional, cl::desc("Show internal debug dump")); + cl::list DebugFileDirectory( + "debug-file-directory", cl::Optional, + cl::desc("Directories to search for object files by build ID")); + cl::opt Debuginfod( + "debuginfod", cl::Optional, + cl::desc("Use debuginfod to look up object files from profile."), + cl::init(HTTPClient::isAvailable() && + ExitOnErr(getDefaultDebuginfodUrls()).empty())); + cl::opt Format( "format", cl::desc("Output format for line-based coverage reports"), cl::values(clEnumValN(CoverageViewOptions::OutputFormat::Text, "text", @@ -745,12 +764,16 @@ auto commandLineParser = [&, this](int argc, const char **argv) -> int { cl::ParseCommandLineOptions(argc, argv, "LLVM code coverage tool\n"); ViewOpts.Debug = DebugDump; + if (Debuginfod) + BIDFetcher = std::make_unique(DebugFileDirectory); + else + BIDFetcher = std::make_unique(DebugFileDirectory); if (!CovFilename.empty()) ObjectFilenames.emplace_back(CovFilename); for (const std::string &Filename : CovFilenames) ObjectFilenames.emplace_back(Filename); - if (ObjectFilenames.empty()) { + if (ObjectFilenames.empty() && !Debuginfod) { errs() << "No filenames specified!\n"; ::exit(1); } @@ -863,10 +886,8 @@ } CoverageArches.emplace_back(Arch); } - if (CoverageArches.size() == 1) - CoverageArches.insert(CoverageArches.end(), ObjectFilenames.size() - 1, - CoverageArches[0]); - if (CoverageArches.size() != ObjectFilenames.size()) { + if (CoverageArches.size() != 1 && + CoverageArches.size() != ObjectFilenames.size()) { error("Number of architectures doesn't match the number of objects"); return 1; }