Index: test/tools/llvm-cov/Inputs/multithreaded_report/abs.h =================================================================== --- /dev/null +++ test/tools/llvm-cov/Inputs/multithreaded_report/abs.h @@ -0,0 +1,7 @@ +template +T abs(T x) { + if (x < 0) { + return -x; + } + return x; +} Index: test/tools/llvm-cov/Inputs/multithreaded_report/bytes.h =================================================================== --- /dev/null +++ test/tools/llvm-cov/Inputs/multithreaded_report/bytes.h @@ -0,0 +1,8 @@ +#include +#include + +inline double logarithm(uint8_t v) { + return log(v); +} + +bool loopBytes(); Index: test/tools/llvm-cov/Inputs/multithreaded_report/bytes.cc =================================================================== --- /dev/null +++ test/tools/llvm-cov/Inputs/multithreaded_report/bytes.cc @@ -0,0 +1,15 @@ +#include "abs.h" +#include "bytes.h" +#include "pow.h" + +bool loopBytes() { + uint64_t totalInt = 0; + double totalFloat = 0; + for (uint8_t i = 1; i != 0; ++i) { + double a = logarithm(i); + a = abs(a); + totalInt += abs(pow(i, static_cast(a))); + totalFloat += pow(static_cast(i), a); + } + return totalInt > totalFloat; +} Index: test/tools/llvm-cov/Inputs/multithreaded_report/main.cc =================================================================== --- /dev/null +++ test/tools/llvm-cov/Inputs/multithreaded_report/main.cc @@ -0,0 +1,15 @@ +#include "bytes.h" +#include "words.h" + +int main() { + bool result = false; + if (loopBytes()) + result |= true; + if (loopWords()) + result |= true; + + if (result) + return 0; + + return result; +} Index: test/tools/llvm-cov/Inputs/multithreaded_report/pow.h =================================================================== --- /dev/null +++ test/tools/llvm-cov/Inputs/multithreaded_report/pow.h @@ -0,0 +1,11 @@ +template +T pow(T b, T p) { + if (!p) + return 1; + + while (--p) { + b *= b; + } + + return b; +} Index: test/tools/llvm-cov/Inputs/multithreaded_report/words.h =================================================================== --- /dev/null +++ test/tools/llvm-cov/Inputs/multithreaded_report/words.h @@ -0,0 +1,8 @@ +#include +#include + +inline double logarithm(uint16_t v) { + return log(v); +} + +bool loopWords(); Index: test/tools/llvm-cov/Inputs/multithreaded_report/words.cc =================================================================== --- /dev/null +++ test/tools/llvm-cov/Inputs/multithreaded_report/words.cc @@ -0,0 +1,15 @@ +#include "abs.h" +#include "bytes.h" +#include "pow.h" + +bool loopWords() { + uint64_t totalInt = 0; + double totalFloat = 0; + for (uint16_t i = 1; i != 0; ++i) { + double a = logarithm(i); + a = abs(a); + totalInt += abs(pow(i, static_cast(a))); + totalFloat += pow(static_cast(i), a); + } + return totalInt > totalFloat; +} Index: test/tools/llvm-cov/multithreaded-report.test =================================================================== --- /dev/null +++ test/tools/llvm-cov/multithreaded-report.test @@ -0,0 +1,94 @@ +# Test "report" command with and without multiple threads. +RUN: llvm-cov report -num-threads=1 \ +RUN: -path-equivalence=/tmp,%S/Inputs \ +RUN: -instr-profile %S/Inputs/multithreaded_report/main.profdata \ +RUN: %S/Inputs/multithreaded_report/main.covmapping > %t.1.report + +RUN: llvm-cov report -num-threads=10 \ +RUN: -path-equivalence=/tmp,%S/Inputs \ +RUN: -instr-profile %S/Inputs/multithreaded_report/main.profdata \ +RUN: %S/Inputs/multithreaded_report/main.covmapping > %t.2.report + +RUN: diff %t.1.report %t.2.report + +# Test "export" command with and without multiple threads. +RUN: llvm-cov export -num-threads=1 \ +RUN: -path-equivalence=/tmp,%S/Inputs \ +RUN: -instr-profile %S/Inputs/multithreaded_report/main.profdata \ +RUN: %S/Inputs/multithreaded_report/main.covmapping > %t.1.json + +RUN: llvm-cov export -num-threads=10 \ +RUN: -path-equivalence=/tmp,%S/Inputs \ +RUN: -instr-profile %S/Inputs/multithreaded_report/main.profdata \ +RUN: %S/Inputs/multithreaded_report/main.covmapping > %t.2.json + +RUN: diff %t.1.json %t.2.json + + +# Test "show" command with and without multiple threads, single text file. +RUN: llvm-cov show -format=text -num-threads=1 \ +RUN: -path-equivalence=/tmp,%S/Inputs \ +RUN: -instr-profile %S/Inputs/multithreaded_report/main.profdata \ +RUN: %S/Inputs/multithreaded_report/main.covmapping > %t.1.text + +RUN: llvm-cov show -format=text -num-threads=10 \ +RUN: -path-equivalence=/tmp,%S/Inputs \ +RUN: -instr-profile %S/Inputs/multithreaded_report/main.profdata \ +RUN: %S/Inputs/multithreaded_report/main.covmapping > %t.2.text + +RUN: diff %t.1.text %t.2.text + +# Test "show" command with and without multiple threads, single HTML file. +RUN: llvm-cov show -format=html -num-threads=1 \ +RUN: -path-equivalence=/tmp,%S/Inputs \ +RUN: -instr-profile %S/Inputs/multithreaded_report/main.profdata \ +RUN: %S/Inputs/multithreaded_report/main.covmapping > %t.1.html + +RUN: llvm-cov show -format=html -num-threads=10 \ +RUN: -path-equivalence=/tmp,%S/Inputs \ +RUN: -instr-profile %S/Inputs/multithreaded_report/main.profdata \ +RUN: %S/Inputs/multithreaded_report/main.covmapping > %t.2.html + +RUN: diff %t.1.html %t.2.html + +# Test "show" command with and without multiple threads, text directory. +RUN: llvm-cov show -format=text -num-threads=1 \ +RUN: -path-equivalence=/tmp,%S/Inputs \ +RUN: -instr-profile %S/Inputs/multithreaded_report/main.profdata \ +RUN: %S/Inputs/multithreaded_report/main.covmapping -o %t.1.text_dir + +RUN: llvm-cov show -format=text -num-threads=10 \ +RUN: -path-equivalence=/tmp,%S/Inputs \ +RUN: -instr-profile %S/Inputs/multithreaded_report/main.profdata \ +RUN: %S/Inputs/multithreaded_report/main.covmapping -o %t.2.text_dir + +RUN: diff -r %t.1.text_dir %t.2.text_dir + +# Test "show" command with and without multiple threads, HTML directory. +RUN: llvm-cov show -format=html -num-threads=1 \ +RUN: -path-equivalence=/tmp,%S/Inputs \ +RUN: -instr-profile %S/Inputs/multithreaded_report/main.profdata \ +RUN: %S/Inputs/multithreaded_report/main.covmapping -o %t.1.html_dir + +RUN: llvm-cov show -format=html -num-threads=10 \ +RUN: -path-equivalence=/tmp,%S/Inputs \ +RUN: -instr-profile %S/Inputs/multithreaded_report/main.profdata \ +RUN: %S/Inputs/multithreaded_report/main.covmapping -o %t.2.html_dir + +RUN: diff -r %t.1.html_dir %t.2.html_dir + + +Instructions for regenerating the test: + +# cd %S/Inputs/multithreaded_report + +cp -r . /tmp/multithreaded_report + +clang++ -std=c++11 -mllvm -enable-name-compression=false \ + -fprofile-instr-generate -fcoverage-mapping \ + /tmp/multithreaded_report/*.cc -o main + +LLVM_PROFILE_FILE="main.profraw" ./main +llvm-profdata merge main.profraw -o main.profdata +llvm-cov convert-for-testing ./main -o ./main.covmapping +rm main main.profraw Index: tools/llvm-cov/CodeCoverage.cpp =================================================================== --- tools/llvm-cov/CodeCoverage.cpp +++ tools/llvm-cov/CodeCoverage.cpp @@ -32,8 +32,8 @@ #include "llvm/Support/Process.h" #include "llvm/Support/Program.h" #include "llvm/Support/ScopedPrinter.h" -#include "llvm/Support/Threading.h" #include "llvm/Support/ThreadPool.h" +#include "llvm/Support/Threading.h" #include "llvm/Support/ToolOutputFile.h" #include @@ -635,6 +635,13 @@ "summary-only", cl::Optional, cl::desc("Export only summary information for each source file")); + cl::opt NumThreads( + "num-threads", cl::init(0), + cl::desc("Number of merge threads to use (default: autodetect)")); + cl::alias NumThreadsA("j", cl::desc("Alias for --num-threads"), + cl::aliasopt(NumThreads)); + + auto commandLineParser = [&, this](int argc, const char **argv) -> int { cl::ParseCommandLineOptions(argc, argv, "LLVM code coverage tool\n"); ViewOpts.Debug = DebugDump; @@ -748,6 +755,7 @@ ViewOpts.ShowRegionSummary = RegionSummary; ViewOpts.ShowInstantiationSummary = InstantiationSummary; ViewOpts.ExportSummaryOnly = SummaryOnly; + ViewOpts.NumThreads = NumThreads; return 0; }; @@ -807,12 +815,6 @@ "project-title", cl::Optional, cl::desc("Set project title for the coverage report")); - cl::opt NumThreads( - "num-threads", cl::init(0), - cl::desc("Number of merge threads to use (default: autodetect)")); - cl::alias NumThreadsA("j", cl::desc("Alias for --num-threads"), - cl::aliasopt(NumThreads)); - auto Err = commandLineParser(argc, argv); if (Err) return Err; @@ -910,6 +912,7 @@ (ViewOpts.Format == CoverageViewOptions::OutputFormat::HTML); // If NumThreads is not specified, auto-detect a good default. + auto NumThreads = ViewOpts.NumThreads; if (NumThreads == 0) NumThreads = std::max(1U, std::min(llvm::heavyweight_hardware_concurrency(), Index: tools/llvm-cov/CoverageReport.h =================================================================== --- tools/llvm-cov/CoverageReport.h +++ tools/llvm-cov/CoverageReport.h @@ -44,6 +44,14 @@ const CoverageViewOptions &Options, const CoverageFilter &Filters = CoverageFiltersMatchAll()); + static void + prepareSingleFileReport(const StringRef Filename, + const coverage::CoverageMapping *Coverage, + const CoverageViewOptions &Options, + const unsigned LCP, + FileCoverageSummary *FileReport, + const CoverageFilter *Filters); + /// Render file reports for every unique file in the coverage mapping. void renderFileReports(raw_ostream &OS) const; Index: tools/llvm-cov/CoverageReport.cpp =================================================================== --- tools/llvm-cov/CoverageReport.cpp +++ tools/llvm-cov/CoverageReport.cpp @@ -16,6 +16,8 @@ #include "llvm/ADT/DenseMap.h" #include "llvm/Support/Format.h" #include "llvm/Support/Path.h" +#include "llvm/Support/ThreadPool.h" +#include "llvm/Support/Threading.h" #include using namespace llvm; @@ -319,42 +321,60 @@ } } +void CoverageReport::prepareSingleFileReport(const StringRef Filename, + const coverage::CoverageMapping *Coverage, + const CoverageViewOptions &Options, const unsigned LCP, + FileCoverageSummary *FileReport, const CoverageFilter *Filters) { + for (const auto &Group : Coverage->getInstantiationGroups(Filename)) { + std::vector InstantiationSummaries; + for (const coverage::FunctionRecord *F : Group.getInstantiations()) { + if (!Filters->matches(*Coverage, *F)) + continue; + auto InstantiationSummary = FunctionCoverageSummary::get(*Coverage, *F); + FileReport->addInstantiation(InstantiationSummary); + InstantiationSummaries.push_back(InstantiationSummary); + } + if (InstantiationSummaries.empty()) + continue; + + auto GroupSummary = + FunctionCoverageSummary::get(Group, InstantiationSummaries); + + if (Options.Debug) + outs() << "InstantiationGroup: " << GroupSummary.Name << " with " + << "size = " << Group.size() << "\n"; + + FileReport->addFunction(GroupSummary); + } +} + std::vector CoverageReport::prepareFileReports( const coverage::CoverageMapping &Coverage, FileCoverageSummary &Totals, ArrayRef Files, const CoverageViewOptions &Options, const CoverageFilter &Filters) { - std::vector FileReports; unsigned LCP = getRedundantPrefixLen(Files); + auto NumThreads = Options.NumThreads; - for (StringRef Filename : Files) { - FileCoverageSummary Summary(Filename.drop_front(LCP)); - - for (const auto &Group : Coverage.getInstantiationGroups(Filename)) { - std::vector InstantiationSummaries; - for (const coverage::FunctionRecord *F : Group.getInstantiations()) { - if (!Filters.matches(Coverage, *F)) - continue; - auto InstantiationSummary = FunctionCoverageSummary::get(Coverage, *F); - Summary.addInstantiation(InstantiationSummary); - Totals.addInstantiation(InstantiationSummary); - InstantiationSummaries.push_back(InstantiationSummary); - } - if (InstantiationSummaries.empty()) - continue; - - auto GroupSummary = - FunctionCoverageSummary::get(Group, InstantiationSummaries); + // If NumThreads is not specified, auto-detect a good default. + if (NumThreads == 0) + NumThreads = + std::max(1U, std::min(llvm::heavyweight_hardware_concurrency(), + unsigned(Files.size()))); - if (Options.Debug) - outs() << "InstantiationGroup: " << GroupSummary.Name << " with " - << "size = " << Group.size() << "\n"; + ThreadPool Pool(NumThreads); - Summary.addFunction(GroupSummary); - Totals.addFunction(GroupSummary); - } + std::vector FileReports; + FileReports.reserve(Files.size()); - FileReports.push_back(Summary); + for (StringRef Filename : Files) { + FileReports.emplace_back(Filename.drop_front(LCP)); + Pool.async(&CoverageReport::prepareSingleFileReport, Filename, + &Coverage, Options, LCP, &FileReports.back(), &Filters); } + Pool.wait(); + + for (const auto &FileReport : FileReports) + Totals += FileReport; return FileReports; } Index: tools/llvm-cov/CoverageSummaryInfo.h =================================================================== --- tools/llvm-cov/CoverageSummaryInfo.h +++ tools/llvm-cov/CoverageSummaryInfo.h @@ -116,6 +116,12 @@ FunctionCoverageInfo(size_t Executed, size_t NumFunctions) : Executed(Executed), NumFunctions(NumFunctions) {} + FunctionCoverageInfo &operator+=(const FunctionCoverageInfo &RHS) { + Executed += RHS.Executed; + NumFunctions += RHS.NumFunctions; + return *this; + } + void addFunction(bool Covered) { if (Covered) ++Executed; @@ -176,6 +182,14 @@ : Name(Name), RegionCoverage(), LineCoverage(), FunctionCoverage(), InstantiationCoverage() {} + FileCoverageSummary &operator+=(const FileCoverageSummary &RHS) { + RegionCoverage += RHS.RegionCoverage; + LineCoverage += RHS.LineCoverage; + FunctionCoverage += RHS.FunctionCoverage; + InstantiationCoverage += RHS.InstantiationCoverage; + return *this; + } + void addFunction(const FunctionCoverageSummary &Function) { RegionCoverage += Function.RegionCoverage; LineCoverage += Function.LineCoverage; Index: tools/llvm-cov/CoverageViewOptions.h =================================================================== --- tools/llvm-cov/CoverageViewOptions.h +++ tools/llvm-cov/CoverageViewOptions.h @@ -39,6 +39,7 @@ uint32_t TabSize; std::string ProjectTitle; std::string CreatedTimeStr; + unsigned NumThreads; /// \brief Change the output's stream color if the colors are enabled. ColoredRawOstream colored_ostream(raw_ostream &OS,