diff tools/llvm-cov/CodeCoverage.cpp @ 121:803732b1fca8

LLVM 5.0
author kono
date Fri, 27 Oct 2017 17:07:41 +0900
parents 1172e4bd9c6f
children 3a76565eade5
line wrap: on
line diff
--- a/tools/llvm-cov/CodeCoverage.cpp	Fri Nov 25 19:14:25 2016 +0900
+++ b/tools/llvm-cov/CodeCoverage.cpp	Fri Oct 27 17:07:41 2017 +0900
@@ -15,6 +15,7 @@
 
 #include "CoverageFilters.h"
 #include "CoverageReport.h"
+#include "CoverageSummaryInfo.h"
 #include "CoverageViewOptions.h"
 #include "RenderingSupport.h"
 #include "SourceCoverageView.h"
@@ -31,15 +32,19 @@
 #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/ToolOutputFile.h"
+
 #include <functional>
+#include <map>
 #include <system_error>
 
 using namespace llvm;
 using namespace coverage;
 
 void exportCoverageDataToJson(const coverage::CoverageMapping &CoverageMapping,
+                              const CoverageViewOptions &Options,
                               raw_ostream &OS);
 
 namespace {
@@ -92,15 +97,16 @@
   /// \brief Load the coverage mapping data. Return nullptr if an error occurred.
   std::unique_ptr<CoverageMapping> load();
 
+  /// \brief Create a mapping from files in the Coverage data to local copies
+  /// (path-equivalence).
+  void remapPathNames(const CoverageMapping &Coverage);
+
   /// \brief Remove input source files which aren't mapped by \p Coverage.
   void removeUnmappedInputs(const CoverageMapping &Coverage);
 
   /// \brief If a demangler is available, demangle all symbol names.
   void demangleSymbols(const CoverageMapping &Coverage);
 
-  /// \brief Demangle \p Sym if possible. Otherwise, just return \p Sym.
-  StringRef getSymbolForHumans(StringRef Sym) const;
-
   /// \brief Write out a source file view to the filesystem.
   void writeSourceFileView(StringRef SourceFile, CoverageMapping *Coverage,
                            CoveragePrinter *Printer, bool ShowFilenames);
@@ -126,26 +132,30 @@
   /// A list of input source files.
   std::vector<std::string> SourceFiles;
 
-  /// Whether or not we're in -filename-equivalence mode.
-  bool CompareFilenamesOnly;
-
-  /// In -filename-equivalence mode, this maps absolute paths from the
-  /// coverage mapping data to input source files.
+  /// In -path-equivalence mode, this maps the absolute paths from the coverage
+  /// mapping data to the input source files.
   StringMap<std::string> RemappedFilenames;
 
-  /// The architecture the coverage mapping data targets.
-  std::string CoverageArch;
+  /// The coverage data path to be remapped from, and the source path to be
+  /// remapped to, when using -path-equivalence.
+  Optional<std::pair<std::string, std::string>> PathRemapping;
 
-  /// A cache for demangled symbol names.
-  StringMap<std::string> DemangledNames;
+  /// The architecture the coverage mapping data targets.
+  std::vector<StringRef> CoverageArches;
 
-  /// Errors and warnings which have not been printed.
+  /// A cache for demangled symbols.
+  DemangleCache DC;
+
+  /// A lock which guards printing to stderr.
   std::mutex ErrsLock;
 
   /// A container for input source file buffers.
   std::mutex LoadedSourceFilesLock;
   std::vector<std::pair<std::string, std::unique_ptr<MemoryBuffer>>>
       LoadedSourceFiles;
+
+  /// Whitelist from -name-whitelist to be used for filtering.
+  std::unique_ptr<SpecialCaseList> NameWhitelist;
 };
 }
 
@@ -172,24 +182,20 @@
 }
 
 void CodeCoverageTool::addCollectedPath(const std::string &Path) {
-  if (CompareFilenamesOnly) {
-    SourceFiles.emplace_back(Path);
-  } else {
-    SmallString<128> EffectivePath(Path);
-    if (std::error_code EC = sys::fs::make_absolute(EffectivePath)) {
-      error(EC.message(), Path);
-      return;
-    }
-    sys::path::remove_dots(EffectivePath, /*remove_dot_dots=*/true);
-    SourceFiles.emplace_back(EffectivePath.str());
+  SmallString<128> EffectivePath(Path);
+  if (std::error_code EC = sys::fs::make_absolute(EffectivePath)) {
+    error(EC.message(), Path);
+    return;
   }
+  sys::path::remove_dots(EffectivePath, /*remove_dot_dots=*/true);
+  SourceFiles.emplace_back(EffectivePath.str());
 }
 
 void CodeCoverageTool::collectPaths(const std::string &Path) {
   llvm::sys::fs::file_status Status;
   llvm::sys::fs::status(Path, Status);
   if (!llvm::sys::fs::exists(Status)) {
-    if (CompareFilenamesOnly)
+    if (PathRemapping)
       addCollectedPath(Path);
     else
       error("Missing source file", Path);
@@ -267,7 +273,7 @@
     return nullptr;
 
   auto Expansions = FunctionCoverage.getExpansions();
-  auto View = SourceCoverageView::create(getSymbolForHumans(Function.Name),
+  auto View = SourceCoverageView::create(DC.demangle(Function.Name),
                                          SourceBuffer.get(), ViewOpts,
                                          std::move(FunctionCoverage));
   attachExpansionSubViews(*View, Expansions, Coverage);
@@ -289,26 +295,34 @@
   auto View = SourceCoverageView::create(SourceFile, SourceBuffer.get(),
                                          ViewOpts, std::move(FileCoverage));
   attachExpansionSubViews(*View, Expansions, Coverage);
+  if (!ViewOpts.ShowFunctionInstantiations)
+    return View;
 
-  for (const auto *Function : Coverage.getInstantiations(SourceFile)) {
-    std::unique_ptr<SourceCoverageView> SubView{nullptr};
+  for (const auto &Group : Coverage.getInstantiationGroups(SourceFile)) {
+    // Skip functions which have a single instantiation.
+    if (Group.size() < 2)
+      continue;
 
-    StringRef Funcname = getSymbolForHumans(Function->Name);
+    for (const FunctionRecord *Function : Group.getInstantiations()) {
+      std::unique_ptr<SourceCoverageView> SubView{nullptr};
+
+      StringRef Funcname = DC.demangle(Function->Name);
 
-    if (Function->ExecutionCount > 0) {
-      auto SubViewCoverage = Coverage.getCoverageForFunction(*Function);
-      auto SubViewExpansions = SubViewCoverage.getExpansions();
-      SubView = SourceCoverageView::create(
-          Funcname, SourceBuffer.get(), ViewOpts, std::move(SubViewCoverage));
-      attachExpansionSubViews(*SubView, SubViewExpansions, Coverage);
+      if (Function->ExecutionCount > 0) {
+        auto SubViewCoverage = Coverage.getCoverageForFunction(*Function);
+        auto SubViewExpansions = SubViewCoverage.getExpansions();
+        SubView = SourceCoverageView::create(
+            Funcname, SourceBuffer.get(), ViewOpts, std::move(SubViewCoverage));
+        attachExpansionSubViews(*SubView, SubViewExpansions, Coverage);
+      }
+
+      unsigned FileID = Function->CountedRegions.front().FileID;
+      unsigned Line = 0;
+      for (const auto &CR : Function->CountedRegions)
+        if (CR.FileID == FileID)
+          Line = std::max(CR.LineEnd, Line);
+      View->addInstantiation(Funcname, Line, std::move(SubView));
     }
-
-    unsigned FileID = Function->CountedRegions.front().FileID;
-    unsigned Line = 0;
-    for (const auto &CR : Function->CountedRegions)
-      if (CR.FileID == FileID)
-        Line = std::max(CR.LineEnd, Line);
-    View->addInstantiation(Funcname, Line, std::move(SubView));
   }
   return View;
 }
@@ -330,7 +344,7 @@
       warning("profile data may be out of date - object is newer",
               ObjectFilename);
   auto CoverageOrErr =
-      CoverageMapping::load(ObjectFilenames, PGOFilename, CoverageArch);
+      CoverageMapping::load(ObjectFilenames, PGOFilename, CoverageArches);
   if (Error E = CoverageOrErr.takeError()) {
     error("Failed to load coverage: " + toString(std::move(E)),
           join(ObjectFilenames.begin(), ObjectFilenames.end(), ", "));
@@ -338,9 +352,25 @@
   }
   auto Coverage = std::move(CoverageOrErr.get());
   unsigned Mismatched = Coverage->getMismatchedCount();
-  if (Mismatched)
+  if (Mismatched) {
     warning(utostr(Mismatched) + " functions have mismatched data");
 
+    if (ViewOpts.Debug) {
+      for (const auto &HashMismatch : Coverage->getHashMismatches())
+        errs() << "hash-mismatch: "
+               << "No profile record found for '" << HashMismatch.first << "'"
+               << " with hash = 0x" << utohexstr(HashMismatch.second) << "\n";
+
+      for (const auto &CounterMismatch : Coverage->getCounterMismatches())
+        errs() << "counter-mismatch: "
+               << "Coverage mapping for " << CounterMismatch.first
+               << " only has " << CounterMismatch.second
+               << " valid counter expressions\n";
+    }
+  }
+
+  remapPathNames(*Coverage);
+
   if (!SourceFiles.empty())
     removeUnmappedInputs(*Coverage);
 
@@ -349,33 +379,58 @@
   return Coverage;
 }
 
+void CodeCoverageTool::remapPathNames(const CoverageMapping &Coverage) {
+  if (!PathRemapping)
+    return;
+
+  // Convert remapping paths to native paths with trailing seperators.
+  auto nativeWithTrailing = [](StringRef Path) -> std::string {
+    if (Path.empty())
+      return "";
+    SmallString<128> NativePath;
+    sys::path::native(Path, NativePath);
+    if (!sys::path::is_separator(NativePath.back()))
+      NativePath += sys::path::get_separator();
+    return NativePath.c_str();
+  };
+  std::string RemapFrom = nativeWithTrailing(PathRemapping->first);
+  std::string RemapTo = nativeWithTrailing(PathRemapping->second);
+
+  // Create a mapping from coverage data file paths to local paths.
+  for (StringRef Filename : Coverage.getUniqueSourceFiles()) {
+    SmallString<128> NativeFilename;
+    sys::path::native(Filename, NativeFilename);
+    if (NativeFilename.startswith(RemapFrom)) {
+      RemappedFilenames[Filename] =
+          RemapTo + NativeFilename.substr(RemapFrom.size()).str();
+    }
+  }
+
+  // Convert input files from local paths to coverage data file paths.
+  StringMap<std::string> InvRemappedFilenames;
+  for (const auto &RemappedFilename : RemappedFilenames)
+    InvRemappedFilenames[RemappedFilename.getValue()] = RemappedFilename.getKey();
+
+  for (std::string &Filename : SourceFiles) {
+    SmallString<128> NativeFilename;
+    sys::path::native(Filename, NativeFilename);
+    auto CovFileName = InvRemappedFilenames.find(NativeFilename);
+    if (CovFileName != InvRemappedFilenames.end())
+      Filename = CovFileName->second;
+  }
+}
+
 void CodeCoverageTool::removeUnmappedInputs(const CoverageMapping &Coverage) {
   std::vector<StringRef> CoveredFiles = Coverage.getUniqueSourceFiles();
 
   auto UncoveredFilesIt = SourceFiles.end();
-  if (!CompareFilenamesOnly) {
-    // The user may have specified source files which aren't in the coverage
-    // mapping. Filter these files away.
-    UncoveredFilesIt = std::remove_if(
-        SourceFiles.begin(), SourceFiles.end(), [&](const std::string &SF) {
-          return !std::binary_search(CoveredFiles.begin(), CoveredFiles.end(),
-                                     SF);
-        });
-  } else {
-    for (auto &SF : SourceFiles) {
-      StringRef SFBase = sys::path::filename(SF);
-      for (const auto &CF : CoveredFiles) {
-        if (SFBase == sys::path::filename(CF)) {
-          RemappedFilenames[CF] = SF;
-          SF = CF;
-          break;
-        }
-      }
-    }
-    UncoveredFilesIt = std::remove_if(
-        SourceFiles.begin(), SourceFiles.end(),
-        [&](const std::string &SF) { return !RemappedFilenames.count(SF); });
-  }
+  // The user may have specified source files which aren't in the coverage
+  // mapping. Filter these files away.
+  UncoveredFilesIt = std::remove_if(
+      SourceFiles.begin(), SourceFiles.end(), [&](const std::string &SF) {
+        return !std::binary_search(CoveredFiles.begin(), CoveredFiles.end(),
+                                   SF);
+      });
 
   SourceFiles.erase(UncoveredFilesIt, SourceFiles.end());
 }
@@ -393,7 +448,7 @@
     error(InputPath, EC.message());
     return;
   }
-  tool_output_file InputTOF{InputPath, InputFD};
+  ToolOutputFile InputTOF{InputPath, InputFD};
 
   unsigned NumSymbols = 0;
   for (const auto &Function : Coverage.getCoveredFunctions()) {
@@ -411,7 +466,7 @@
     error(OutputPath, EC.message());
     return;
   }
-  tool_output_file OutputTOF{OutputPath, OutputFD};
+  ToolOutputFile OutputTOF{OutputPath, OutputFD};
   OutputTOF.os().close();
 
   // Invoke the demangler.
@@ -419,10 +474,7 @@
   for (const std::string &Arg : ViewOpts.DemanglerOpts)
     ArgsV.push_back(Arg.c_str());
   ArgsV.push_back(nullptr);
-  StringRef InputPathRef = InputPath.str();
-  StringRef OutputPathRef = OutputPath.str();
-  StringRef StderrRef;
-  const StringRef *Redirects[] = {&InputPathRef, &OutputPathRef, &StderrRef};
+  Optional<StringRef> Redirects[] = {InputPath.str(), OutputPath.str(), {""}};
   std::string ErrMsg;
   int RC = sys::ExecuteAndWait(ViewOpts.DemanglerOpts[0], ArgsV.data(),
                                /*env=*/nullptr, Redirects, /*secondsToWait=*/0,
@@ -453,14 +505,9 @@
   // Cache the demangled names.
   unsigned I = 0;
   for (const auto &Function : Coverage.getCoveredFunctions())
-    DemangledNames[Function.Name] = Symbols[I++];
-}
-
-StringRef CodeCoverageTool::getSymbolForHumans(StringRef Sym) const {
-  const auto DemangledName = DemangledNames.find(Sym);
-  if (DemangledName == DemangledNames.end())
-    return Sym;
-  return DemangledName->getValue();
+    // On Windows, lines in the demangler's output file end with "\r\n".
+    // Splitting by '\n' keeps '\r's, so cut them now.
+    DC.DemangledNames[Function.Name] = Symbols[I++].rtrim();
 }
 
 void CodeCoverageTool::writeSourceFileView(StringRef SourceFile,
@@ -481,7 +528,8 @@
   auto OS = std::move(OSOrErr.get());
 
   View->print(*OS.get(), /*Wholefile=*/true,
-              /*ShowSourceName=*/ShowFilenames);
+              /*ShowSourceName=*/ShowFilenames,
+              /*ShowTitle=*/ViewOpts.hasOutputDirectory());
   Printer->closeViewFile(std::move(OS));
 }
 
@@ -505,8 +553,8 @@
       cl::desc(
           "File with the profile data obtained after an instrumented run"));
 
-  cl::opt<std::string> Arch(
-      "arch", cl::desc("architecture of the coverage mapping binary"));
+  cl::list<std::string> Arches(
+      "arch", cl::desc("architectures of the coverage mapping binaries"));
 
   cl::opt<bool> DebugDump("dump", cl::Optional,
                           cl::desc("Show internal debug dump"));
@@ -519,10 +567,10 @@
                             "HTML output")),
       cl::init(CoverageViewOptions::OutputFormat::Text));
 
-  cl::opt<bool> FilenameEquivalence(
-      "filename-equivalence", cl::Optional,
-      cl::desc("Treat source files as equivalent to paths in the coverage data "
-               "when the file names match, even if the full paths do not"));
+  cl::opt<std::string> PathRemap(
+      "path-equivalence", cl::Optional,
+      cl::desc("<from>,<to> Map coverage data paths to local source file "
+               "paths"));
 
   cl::OptionCategory FilteringCategory("Function filtering options");
 
@@ -531,6 +579,12 @@
       cl::desc("Show code coverage only for functions with the given name"),
       cl::ZeroOrMore, cl::cat(FilteringCategory));
 
+  cl::list<std::string> NameFilterFiles(
+      "name-whitelist", cl::Optional,
+      cl::desc("Show code coverage only for functions listed in the given "
+               "file"),
+      cl::ZeroOrMore, cl::cat(FilteringCategory));
+
   cl::list<std::string> NameRegexFilters(
       "name-regex", cl::Optional,
       cl::desc("Show code coverage only for functions that match the given "
@@ -568,10 +622,18 @@
   cl::list<std::string> DemanglerOpts(
       "Xdemangler", cl::desc("<demangler-path>|<demangler-option>"));
 
+  cl::opt<bool> RegionSummary(
+      "show-region-summary", cl::Optional,
+      cl::desc("Show region statistics in summary table"),
+      cl::init(true));
+
+  cl::opt<bool> InstantiationSummary(
+      "show-instantiation-summary", cl::Optional,
+      cl::desc("Show instantiation statistics in summary table"));
+
   auto commandLineParser = [&, this](int argc, const char **argv) -> int {
     cl::ParseCommandLineOptions(argc, argv, "LLVM code coverage tool\n");
     ViewOpts.Debug = DebugDump;
-    CompareFilenamesOnly = FilenameEquivalence;
 
     if (!CovFilename.empty())
       ObjectFilenames.emplace_back(CovFilename);
@@ -596,6 +658,12 @@
       break;
     }
 
+    // If path-equivalence was given and is a comma seperated pair then set
+    // PathRemapping.
+    auto EquivPair = StringRef(PathRemap).split(',');
+    if (!(EquivPair.first.empty() && EquivPair.second.empty()))
+      PathRemapping = EquivPair;
+
     // If a demangler is supplied, check if it exists and register it.
     if (DemanglerOpts.size()) {
       auto DemanglerPathOrErr = sys::findProgramByName(DemanglerOpts[0]);
@@ -608,21 +676,33 @@
       ViewOpts.DemanglerOpts.swap(DemanglerOpts);
     }
 
+    // Read in -name-whitelist files.
+    if (!NameFilterFiles.empty()) {
+      std::string SpecialCaseListErr;
+      NameWhitelist =
+          SpecialCaseList::create(NameFilterFiles, SpecialCaseListErr);
+      if (!NameWhitelist)
+        error(SpecialCaseListErr);
+    }
+
     // Create the function filters
-    if (!NameFilters.empty() || !NameRegexFilters.empty()) {
-      auto NameFilterer = new CoverageFilters;
+    if (!NameFilters.empty() || NameWhitelist || !NameRegexFilters.empty()) {
+      auto NameFilterer = llvm::make_unique<CoverageFilters>();
       for (const auto &Name : NameFilters)
         NameFilterer->push_back(llvm::make_unique<NameCoverageFilter>(Name));
+      if (NameWhitelist)
+        NameFilterer->push_back(
+            llvm::make_unique<NameWhitelistCoverageFilter>(*NameWhitelist));
       for (const auto &Regex : NameRegexFilters)
         NameFilterer->push_back(
             llvm::make_unique<NameRegexCoverageFilter>(Regex));
-      Filters.push_back(std::unique_ptr<CoverageFilter>(NameFilterer));
+      Filters.push_back(std::move(NameFilterer));
     }
     if (RegionCoverageLtFilter.getNumOccurrences() ||
         RegionCoverageGtFilter.getNumOccurrences() ||
         LineCoverageLtFilter.getNumOccurrences() ||
         LineCoverageGtFilter.getNumOccurrences()) {
-      auto StatFilterer = new CoverageFilters;
+      auto StatFilterer = llvm::make_unique<CoverageFilters>();
       if (RegionCoverageLtFilter.getNumOccurrences())
         StatFilterer->push_back(llvm::make_unique<RegionCoverageFilter>(
             RegionCoverageFilter::LessThan, RegionCoverageLtFilter));
@@ -635,15 +715,22 @@
       if (LineCoverageGtFilter.getNumOccurrences())
         StatFilterer->push_back(llvm::make_unique<LineCoverageFilter>(
             RegionCoverageFilter::GreaterThan, LineCoverageGtFilter));
-      Filters.push_back(std::unique_ptr<CoverageFilter>(StatFilterer));
+      Filters.push_back(std::move(StatFilterer));
     }
 
-    if (!Arch.empty() &&
-        Triple(Arch).getArch() == llvm::Triple::ArchType::UnknownArch) {
-      error("Unknown architecture: " + Arch);
-      return 1;
+    if (!Arches.empty()) {
+      for (const std::string &Arch : Arches) {
+        if (Triple(Arch).getArch() == llvm::Triple::ArchType::UnknownArch) {
+          error("Unknown architecture: " + Arch);
+          return 1;
+        }
+        CoverageArches.emplace_back(Arch);
+      }
+      if (CoverageArches.size() != ObjectFilenames.size()) {
+        error("Number of architectures doesn't match the number of objects");
+        return 1;
+      }
     }
-    CoverageArch = Arch;
 
     for (const std::string &File : InputSourceFiles)
       collectPaths(File);
@@ -654,6 +741,9 @@
       ::exit(0);
     }
 
+    ViewOpts.ShowRegionSummary = RegionSummary;
+    ViewOpts.ShowInstantiationSummary = InstantiationSummary;
+
     return 0;
   };
 
@@ -695,7 +785,7 @@
 
   cl::opt<bool> ShowInstantiations("show-instantiations", cl::Optional,
                                    cl::desc("Show function instantiations"),
-                                   cl::cat(ViewCategory));
+                                   cl::init(true), cl::cat(ViewCategory));
 
   cl::opt<std::string> ShowOutputDirectory(
       "output-dir", cl::init(""),
@@ -712,6 +802,12 @@
       "project-title", cl::Optional,
       cl::desc("Set project title for the coverage report"));
 
+  cl::opt<unsigned> 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;
@@ -720,7 +816,6 @@
   ViewOpts.ShowLineStats = ShowLineExecutionCounts.getNumOccurrences() != 0 ||
                            !ShowRegions || ShowBestLineRegionsCounts;
   ViewOpts.ShowRegionMarkers = ShowRegions || ShowBestLineRegionsCounts;
-  ViewOpts.ShowLineStatsOrRegionMarkers = ShowBestLineRegionsCounts;
   ViewOpts.ShowExpandedRegions = ShowExpansions;
   ViewOpts.ShowFunctionInstantiations = ShowInstantiations;
   ViewOpts.ShowOutputDirectory = ShowOutputDirectory;
@@ -742,7 +837,7 @@
 
   auto ModifiedTime = Status.getLastModificationTime();
   std::string ModifiedTimeStr = to_string(ModifiedTime);
-  size_t found = ModifiedTimeStr.rfind(":");
+  size_t found = ModifiedTimeStr.rfind(':');
   ViewOpts.CreatedTimeStr = (found != std::string::npos)
                                 ? "Created: " + ModifiedTimeStr.substr(0, found)
                                 : "Created: " + ModifiedTimeStr;
@@ -753,29 +848,54 @@
 
   auto Printer = CoveragePrinter::create(ViewOpts);
 
-  if (!Filters.empty()) {
-    auto OSOrErr = Printer->createViewFile("functions", /*InToplevel=*/true);
-    if (Error E = OSOrErr.takeError()) {
-      error("Could not create view file!", toString(std::move(E)));
+  if (SourceFiles.empty())
+    // Get the source files from the function coverage mapping.
+    for (StringRef Filename : Coverage->getUniqueSourceFiles())
+      SourceFiles.push_back(Filename);
+
+  // Create an index out of the source files.
+  if (ViewOpts.hasOutputDirectory()) {
+    if (Error E = Printer->createIndexFile(SourceFiles, *Coverage, Filters)) {
+      error("Could not create index file!", toString(std::move(E)));
       return 1;
     }
-    auto OS = std::move(OSOrErr.get());
+  }
+
+  if (!Filters.empty()) {
+    // Build the map of filenames to functions.
+    std::map<llvm::StringRef, std::vector<const FunctionRecord *>>
+        FilenameFunctionMap;
+    for (const auto &SourceFile : SourceFiles)
+      for (const auto &Function : Coverage->getCoveredFunctions(SourceFile))
+        if (Filters.matches(*Coverage.get(), Function))
+          FilenameFunctionMap[SourceFile].push_back(&Function);
+
+    // Only print filter matching functions for each file.
+    for (const auto &FileFunc : FilenameFunctionMap) {
+      StringRef File = FileFunc.first;
+      const auto &Functions = FileFunc.second;
 
-    // Show functions.
-    for (const auto &Function : Coverage->getCoveredFunctions()) {
-      if (!Filters.matches(Function))
-        continue;
+      auto OSOrErr = Printer->createViewFile(File, /*InToplevel=*/false);
+      if (Error E = OSOrErr.takeError()) {
+        error("Could not create view file!", toString(std::move(E)));
+        return 1;
+      }
+      auto OS = std::move(OSOrErr.get());
 
-      auto mainView = createFunctionView(Function, *Coverage);
-      if (!mainView) {
-        warning("Could not read coverage for '" + Function.Name + "'.");
-        continue;
+      bool ShowTitle = ViewOpts.hasOutputDirectory();
+      for (const auto *Function : Functions) {
+        auto FunctionView = createFunctionView(*Function, *Coverage);
+        if (!FunctionView) {
+          warning("Could not read coverage for '" + Function->Name + "'.");
+          continue;
+        }
+        FunctionView->print(*OS.get(), /*WholeFile=*/false,
+                            /*ShowSourceName=*/true, ShowTitle);
+        ShowTitle = false;
       }
 
-      mainView->print(*OS.get(), /*WholeFile=*/false, /*ShowSourceName=*/true);
+      Printer->closeViewFile(std::move(OS));
     }
-
-    Printer->closeViewFile(std::move(OS));
     return 0;
   }
 
@@ -784,28 +904,19 @@
       (SourceFiles.size() != 1) || ViewOpts.hasOutputDirectory() ||
       (ViewOpts.Format == CoverageViewOptions::OutputFormat::HTML);
 
-  if (SourceFiles.empty())
-    // Get the source files from the function coverage mapping.
-    for (StringRef Filename : Coverage->getUniqueSourceFiles())
-      SourceFiles.push_back(Filename);
+  // If NumThreads is not specified, auto-detect a good default.
+  if (NumThreads == 0)
+    NumThreads =
+        std::max(1U, std::min(llvm::heavyweight_hardware_concurrency(),
+                              unsigned(SourceFiles.size())));
 
-  // Create an index out of the source files.
-  if (ViewOpts.hasOutputDirectory()) {
-    if (Error E = Printer->createIndexFile(SourceFiles, *Coverage)) {
-      error("Could not create index file!", toString(std::move(E)));
-      return 1;
-    }
-  }
-
-  // FIXME: Sink the hardware_concurrency() == 1 check into ThreadPool.
-  if (!ViewOpts.hasOutputDirectory() ||
-      std::thread::hardware_concurrency() == 1) {
+  if (!ViewOpts.hasOutputDirectory() || NumThreads == 1) {
     for (const std::string &SourceFile : SourceFiles)
       writeSourceFileView(SourceFile, Coverage.get(), Printer.get(),
                           ShowFilenames);
   } else {
     // In -output-dir mode, it's safe to use multiple threads to print files.
-    ThreadPool Pool;
+    ThreadPool Pool(NumThreads);
     for (const std::string &SourceFile : SourceFiles)
       Pool.async(&CodeCoverageTool::writeSourceFileView, this, SourceFile,
                  Coverage.get(), Printer.get(), ShowFilenames);
@@ -817,22 +928,38 @@
 
 int CodeCoverageTool::report(int argc, const char **argv,
                              CommandLineParserType commandLineParser) {
+  cl::opt<bool> ShowFunctionSummaries(
+      "show-functions", cl::Optional, cl::init(false),
+      cl::desc("Show coverage summaries for each function"));
+
   auto Err = commandLineParser(argc, argv);
   if (Err)
     return Err;
 
-  if (ViewOpts.Format == CoverageViewOptions::OutputFormat::HTML)
+  if (ViewOpts.Format == CoverageViewOptions::OutputFormat::HTML) {
     error("HTML output for summary reports is not yet supported.");
+    return 1;
+  }
 
   auto Coverage = load();
   if (!Coverage)
     return 1;
 
   CoverageReport Report(ViewOpts, *Coverage.get());
-  if (SourceFiles.empty())
-    Report.renderFileReports(llvm::outs());
-  else
-    Report.renderFunctionReports(SourceFiles, llvm::outs());
+  if (!ShowFunctionSummaries) {
+    if (SourceFiles.empty())
+      Report.renderFileReports(llvm::outs());
+    else
+      Report.renderFileReports(llvm::outs(), SourceFiles);
+  } else {
+    if (SourceFiles.empty()) {
+      error("Source files must be specified when -show-functions=true is "
+            "specified");
+      return 1;
+    }
+
+    Report.renderFunctionReports(SourceFiles, DC, llvm::outs());
+  }
   return 0;
 }
 
@@ -843,13 +970,18 @@
   if (Err)
     return Err;
 
+  if (ViewOpts.Format != CoverageViewOptions::OutputFormat::Text) {
+    error("Coverage data can only be exported as textual JSON.");
+    return 1;
+  }
+
   auto Coverage = load();
   if (!Coverage) {
     error("Could not load coverage information");
     return 1;
   }
 
-  exportCoverageDataToJson(*Coverage.get(), outs());
+  exportCoverageDataToJson(*Coverage.get(), ViewOpts, outs());
 
   return 0;
 }