[PATCH][RFC] llvm-cov HTML generation
Harlan Haskins via llvm-commits
llvm-commits at lists.llvm.org
Wed Mar 2 11:09:40 PST 2016
Thanks for the review!
I implemented those changes and attached an updated patch.
I’m gonna tackle generating an index later today.
— Harlan
-------------- next part --------------
A non-text attachment was scrubbed...
Name: llvm-cov-html.diff
Type: application/octet-stream
Size: 41121 bytes
Desc: not available
URL: <http://lists.llvm.org/pipermail/llvm-commits/attachments/20160302/9d1f74ea/attachment.obj>
-------------- next part --------------
> On Mar 1, 2016, at 11:18 PM, Justin Bogner via llvm-commits <llvm-commits at lists.llvm.org> wrote:
>
> Harlan Haskins via llvm-commits <llvm-commits at lists.llvm.org> writes:
>> I’ve got a preliminary implementation of HTML generation for llvm-cov’s
>> coverage reports.
>>
>> The patch adds 2 flags to llvm-cov show:
>>
>> * -format [html | console]
>> * -output-dir <dirname>
>
> This looks really useful. I've cc'd a couple of people I think will be
> interested.
>
> Commments inline, mostly nitpicks.
>
>> Specifying -format=console will perform the current behavior (now the
>> default), and -format=html will generate an HTML report.
>> If -output-dir is specified, then the output is split into one html or txt
>> file per source file, named <source-name>.<ext>, with a directory structure
>> that mimics the file system structure.
>>
>> If neither are provided, the behavior remains the same.
>>
>> I’m hoping to add an index with a browsable list of files within their
>> directories, but for now I’ve attached the patch and a sample HTML file (In
>> this case, AliasAnalysis.h, as included by swiftc).
>
> Something as simple as a list of files with clickable links would
> probably be good enough for a start. You could also go further and do
> something like the output of llvm-cov report with clickable links, but
> that's probably better as a followup.
>
> Harlan Haskins via llvm-commits <llvm-commits at lists.llvm.org> writes:
>> Oops, forgot to add a file to the patch.
>>
>> New patch attached.
>>
>> Index: CodeCoverage.cpp
>
> Better to generate diffs from the top level llvm source directory, for
> future reference. Then people don't need to figure out where to cd first
> (tools/llvm-cov) to apply the patch.
>
>> ===================================================================
>> --- CodeCoverage.cpp (revision 262364)
>> +++ CodeCoverage.cpp (working copy)
>> @@ -33,6 +33,7 @@
>> #include "llvm/Support/Signals.h"
>> #include <functional>
>> #include <system_error>
>> +#include <unistd.h>
>
> We should probably avoid unistd here, it's not portable.
>
>>
>> using namespace llvm;
>> using namespace coverage;
>> @@ -47,10 +48,17 @@
>> /// \brief The report command.
>> Report
>> };
>> -
>> +
>> + /// \brief Print an error message and exit.
>> + void exitWithErrorCode(std::error_code Error, StringRef Whence);
>> +
>> /// \brief Print the error message to the error output stream.
>> void error(const Twine &Message, StringRef Whence = "");
>>
>> + /// \brief Return a pointer to a stream for the given file.
>> + /// Passing "" yields stdout.
>> + std::unique_ptr<raw_ostream> streamForFile(std::string Filename);
>> +
>> /// \brief Return a memory buffer for the given source file.
>> ErrorOr<const MemoryBuffer &> getSourceFile(StringRef SourceFile);
>>
>> @@ -135,7 +143,7 @@
>> continue;
>>
>> auto SubViewExpansions = ExpansionCoverage.getExpansions();
>> - auto SubView = llvm::make_unique<SourceCoverageView>(
>> + auto SubView = SourceCoverageView::create(
>> SourceBuffer.get(), ViewOpts, std::move(ExpansionCoverage));
>> attachExpansionSubViews(*SubView, SubViewExpansions, Coverage);
>> View.addExpansion(Expansion.Region, std::move(SubView));
>> @@ -153,7 +161,7 @@
>> return nullptr;
>>
>> auto Expansions = FunctionCoverage.getExpansions();
>> - auto View = llvm::make_unique<SourceCoverageView>(
>> + auto View = SourceCoverageView::create(
>> SourceBuffer.get(), ViewOpts, std::move(FunctionCoverage));
>> attachExpansionSubViews(*View, Expansions, Coverage);
>>
>> @@ -171,14 +179,14 @@
>> return nullptr;
>>
>> auto Expansions = FileCoverage.getExpansions();
>> - auto View = llvm::make_unique<SourceCoverageView>(
>> + auto View = SourceCoverageView::create(
>> SourceBuffer.get(), ViewOpts, std::move(FileCoverage));
>> attachExpansionSubViews(*View, Expansions, Coverage);
>>
>> for (auto Function : Coverage.getInstantiations(SourceFile)) {
>> auto SubViewCoverage = Coverage.getCoverageForFunction(*Function);
>> auto SubViewExpansions = SubViewCoverage.getExpansions();
>> - auto SubView = llvm::make_unique<SourceCoverageView>(
>> + auto SubView = SourceCoverageView::create(
>> SourceBuffer.get(), ViewOpts, std::move(SubViewCoverage));
>> attachExpansionSubViews(*SubView, SubViewExpansions, Coverage);
>>
>> @@ -377,6 +385,26 @@
>> return 0;
>> }
>>
>> +std::unique_ptr<raw_ostream>
>> +CodeCoverageTool::streamForFile(std::string Filename) {
>
> Functions should usually be verb phrases, like getStreamForFile.
>
>> + if (Filename == "") {
>> + return make_unique<raw_fd_ostream>(STDOUT_FILENO, false);
>> + } else {
>
> The LLVM style avoids the "else" after an early return. Saves some
> indentation.
>
> This applies to a few places later in the patch too.
>
>> + std::error_code Error;
>> + auto OS = make_unique<raw_fd_ostream>(Filename,
>> + Error, sys::fs::F_RW);
>> + if (Error)
>> + exitWithErrorCode(Error, __PRETTY_FUNCTION__);
>
> Why __PRETTY_FUNCTION__?
>
> I'm not a big fan of calling exit here - I think it'd be better to just
> call error(...) and return nullptr, then CodeCoverageTool::view can
> just return 1 or whatever.
>
>> + return move(OS);
>
> This is std::move, right? Are we "using namespace std" somewhere? We
> shouldn't be.
>
>> + }
>> +}
>> +
>> +void CodeCoverageTool::exitWithErrorCode(std::error_code Error,
>> + StringRef Whence = "") {
>> + error(Error.message(), Whence);
>> + exit(Error.value());
>> +}
>> +
>> int CodeCoverageTool::show(int argc, const char **argv,
>> CommandLineParserType commandLineParser) {
>>
>> @@ -405,6 +433,20 @@
>> cl::opt<bool> ShowInstantiations("show-instantiations", cl::Optional,
>> cl::desc("Show function instantiations"),
>> cl::cat(ViewCategory));
>> +
>> + cl::opt<std::string> OutputDirectory("o", "output-dir", cl::Optional,
>> + cl::desc("Directory to output individual files"));
>> +
>> + cl::opt<CoverageViewOptions::OutputFormat> Format("f", "format",
>> + cl::desc("Format to output comparison"),
>> + cl::values(clEnumValN(CoverageViewOptions::HTML,
>> + "html",
>> + "HTML output"),
>> + clEnumValN(CoverageViewOptions::Console,
>> + "console",
>> + "Console table output"),
>> + clEnumValEnd),
>> + cl::init(CoverageViewOptions::Console));
>>
>> auto Err = commandLineParser(argc, argv);
>> if (Err)
>> @@ -417,12 +459,23 @@
>> ViewOpts.ShowLineStatsOrRegionMarkers = ShowBestLineRegionsCounts;
>> ViewOpts.ShowExpandedRegions = ShowExpansions;
>> ViewOpts.ShowFunctionInstantiations = ShowInstantiations;
>> + ViewOpts.Format = Format;
>> + ViewOpts.Colors = ViewOpts.Colors && Format == CoverageViewOptions::Console;
>>
>> auto Coverage = load();
>> if (!Coverage)
>> return 1;
>> -
>> +
>> +
>> if (!Filters.empty()) {
>> + std::string outputPath = OutputDirectory;
>> + if (outputPath != "") {
>> + std::string ext = Format == CoverageViewOptions::HTML ? "html" : "txt";
>> + outputPath += "/functions." + ext;
>> + }
>> + auto os = streamForFile(outputPath);
>
> Variables should start with capital letters. Ie, `OutputPath`, `Ext`,
> and `OS`.
>
>> + SourceCoverageViewHTML::renderFileHeader(*os);
>> +
>> // Show functions
>> for (const auto &Function : Coverage->getCoveredFunctions()) {
>> if (!Filters.matches(Function))
>> @@ -435,12 +488,12 @@
>> outs() << "\n";
>> continue;
>> }
>> - ViewOpts.colored_ostream(outs(), raw_ostream::CYAN) << Function.Name
>> - << ":";
>> - outs() << "\n";
>> - mainView->render(outs(), /*WholeFile=*/false);
>> - outs() << "\n";
>> + mainView->renderTitle(*os, Function.Name);
>> + *os << "\n";
>> + mainView->render(*os, /*WholeFile=*/false);
>> + *os << "\n";
>> }
>> + SourceCoverageViewHTML::renderFileFooter(*os);
>> return 0;
>> }
>>
>> @@ -453,21 +506,28 @@
>> SourceFiles.push_back(Filename);
>>
>> for (const auto &SourceFile : SourceFiles) {
>> + auto basename = sys::path::parent_path(SourceFile);
>> + sys::fs::create_directories(OutputDirectory + basename);
>> + auto ext = Format == CoverageViewOptions::HTML ? "html" : "txt";
>> + std::string outputPath = OutputDirectory;
>> + if (outputPath != "") {
>> + outputPath += "/" + std::string(SourceFile) + "." + ext;
>> + }
>> + std::unique_ptr<raw_ostream> os = streamForFile(outputPath);
>
> The code duplication's kind of gross here. To be fair, that was true
> before this patch, but we should probably try to clean that up in a
> separate NFC patch either before or after this one.
>
>> auto mainView = createSourceFileView(SourceFile, *Coverage);
>> if (!mainView) {
>> - ViewOpts.colored_ostream(outs(), raw_ostream::RED)
>> + ViewOpts.colored_ostream(errs(), raw_ostream::RED)
>> << "warning: The file '" << SourceFile << "' isn't covered.";
>> - outs() << "\n";
>> + errs() << "\n";
>> continue;
>> }
>> -
>> +
>> if (ShowFilenames) {
>> - ViewOpts.colored_ostream(outs(), raw_ostream::CYAN) << SourceFile << ":";
>> - outs() << "\n";
>> + mainView->renderTitle(*os, SourceFile);
>> }
>> - mainView->render(outs(), /*Wholefile=*/true);
>> + mainView->render(*os, /*Wholefile=*/true);
>> if (SourceFiles.size() > 1)
>> - outs() << "\n";
>> + *os << "\n";
>> }
>>
>> return 0;
>> Index: CoverageViewOptions.h
>> ===================================================================
>> --- CoverageViewOptions.h (revision 262364)
>> +++ CoverageViewOptions.h (working copy)
>> @@ -16,6 +16,12 @@
>>
>> /// \brief The options for displaying the code coverage information.
>> struct CoverageViewOptions {
>> + enum OutputFormat {
>> + /// \brief ASCII table output.
>> + Console,
>> + /// \brief An HTML directory.
>> + HTML
>
> These should probably be named something like OutputFormatConsole or
> OFConsole to avoid name collisions.
>
>> + };
>> bool Debug;
>> bool Colors;
>> bool ShowLineNumbers;
>> @@ -25,6 +31,7 @@
>> bool ShowExpandedRegions;
>> bool ShowFunctionInstantiations;
>> bool ShowFullFilenames;
>> + OutputFormat Format;
>>
>> /// \brief Change the output's stream color if the colors are enabled.
>> ColoredRawOstream colored_ostream(raw_ostream &OS,
>> Index: SourceCoverageView.cpp
>> ===================================================================
>> --- SourceCoverageView.cpp (revision 262364)
>> +++ SourceCoverageView.cpp (working copy)
>> @@ -12,13 +12,99 @@
>> //===----------------------------------------------------------------------===//
>>
>> #include "SourceCoverageView.h"
>> -#include "llvm/ADT/Optional.h"
>> #include "llvm/ADT/SmallString.h"
>> #include "llvm/ADT/StringExtras.h"
>> -#include "llvm/Support/LineIterator.h"
>>
>> using namespace llvm;
>>
>> +namespace html {
>> + using namespace std;
>
> Please don't "using namespace std".
>
>> + /// \returns An HTML-escaped string.
>
> I don't know what doxygen does with a \returns with no \brief. If you're
> sure it's reasonable this is fine, but otherwise I'd just drop the
> \returns and reword these like "Escapes the string for HTML".
>
>> + string escape(string s) {
>> + string result;
>> + for (size_t i = 0; i < s.size(); ++i) {
>> + string token = s.substr(i,1);
>> + if (token == "&")
>> + token = "&";
>> + else if (token == "<")
>> + token = "<";
>> + else if (token == "\"")
>> + token = """;
>> + else if (token == ">")
>> + token = ">";
>> + result += token;
>> + }
>> + return result;
>> + }
>> +
>> + /// \returns A tag around the provided text
>> + /// (expects the value to be properly escaped).
>> + string tag(string name, string text, string _class = "") {
>
> More variables that should start with capital letters, but note that
> _Class would be a reserved name (_Capital always is), so you'd have to
> go with Class_.
>
>> + string tag = "<" + name;
>> + if (_class != "") {
>> + tag += " class='" + _class + "'";
>> + }
>> + return tag + ">" + text + "</" + name + ">";
>> + }
>> +}
>> +
>> +std::string classForColor(raw_ostream::Colors Color) {
>> + switch (Color) {
>> + case raw_ostream::RED: return "red";
>> + case raw_ostream::CYAN: return "cyan";
>> + case raw_ostream::BLUE: return "blue";
>> + case raw_ostream::BLACK: return "black";
>> + case raw_ostream::GREEN: return "green";
>> + case raw_ostream::MAGENTA: return "magenta";
>> + default: return "clear";
>> + }
>> +}
>> +
>> +/// Format a count using engineering notation with 3 significant digits.
>> +static std::string formatCount(uint64_t N) {
>> + std::string Number = utostr(N);
>> + int Len = Number.size();
>> + if (Len <= 3)
>> + return Number;
>> + int IntLen = Len % 3 == 0 ? 3 : Len % 3;
>> + std::string Result(Number.data(), IntLen);
>> + if (IntLen != 3) {
>> + Result.push_back('.');
>> + Result += Number.substr(IntLen, 3 - IntLen);
>> + }
>> + Result.push_back(" kMGTPEZY"[(Len - 1) / 3]);
>> + return Result;
>> +}
>> +
>> +void SourceCoverageViewHTML::output(raw_ostream &OS, std::string Text,
>> + Optional<raw_ostream::Colors> Highlight,
>> + const coverage::CoverageSegment
>> + *Segment) {
>
> This is formatted funny. I recommend clang-format. You can use the
> clang-format-diff or git-clang-format script in the clang repo to just
> format your changes (and avoid arbitrary reformatting changes elsewhere).
>
>> + auto Color = Highlight ? *Highlight : raw_ostream::SAVEDCOLOR;
>> + Text = html::escape(Text);
>> + if (Highlight) {
>> + Text = html::tag("span", Text, classForColor(Color));
>> + }
>> + if (Options.ShowRegionMarkers && Segment && Segment->IsRegionEntry) {
>> + std::string C = formatCount(Segment->Count);
>> + auto countTag = html::tag("span", html::escape(C));
>> + Text = html::tag("span", Text + countTag, "tooltips");
>> + }
>> + OS << Text;
>> +}
>> +
>> +void SourceCoverageViewConsole::output(raw_ostream &OS,
>> + std::string Text,
>> + Optional<raw_ostream::Colors>
>> + Highlight,
>> + const coverage::CoverageSegment
>> + *Segment) {
>> + auto Color = Highlight ? *Highlight : raw_ostream::SAVEDCOLOR;
>> + colored_ostream(OS, Color,
>> + Options.Colors && Highlight, /*Bold=*/false, /*BG=*/true)
>> + << Text;
>> +}
>> +
>> void SourceCoverageView::renderLine(
>> raw_ostream &OS, StringRef Line, int64_t LineNumber,
>> const coverage::CoverageSegment *WrappedSegment,
>> @@ -30,14 +116,13 @@
>> // The first segment overlaps from a previous line, so we treat it specially.
>> if (WrappedSegment && WrappedSegment->HasCount && WrappedSegment->Count == 0)
>> Highlight = raw_ostream::RED;
>> -
>> +
>> // Output each segment of the line, possibly highlighted.
>> unsigned Col = 1;
>> for (const auto *S : Segments) {
>> unsigned End = std::min(S->Col, static_cast<unsigned>(Line.size()) + 1);
>> - colored_ostream(OS, Highlight ? *Highlight : raw_ostream::SAVEDCOLOR,
>> - Options.Colors && Highlight, /*Bold=*/false, /*BG=*/true)
>> - << Line.substr(Col - 1, End - Col);
>> + auto Text = Line.substr(Col - 1, End - Col);
>> + output(OS, Text, Highlight, S);
>> if (Options.Debug && Highlight)
>> HighlightedRanges.push_back(std::make_pair(Col, End));
>> Col = End;
>> @@ -48,13 +133,10 @@
>> else
>> Highlight = None;
>> }
>> +
>> + auto Rest = Line.substr(Col - 1, Line.size() - Col + 1);
>> + output(OS, Rest, Highlight, nullptr);
>>
>> - // Show the rest of the line
>> - colored_ostream(OS, Highlight ? *Highlight : raw_ostream::SAVEDCOLOR,
>> - Options.Colors && Highlight, /*Bold=*/false, /*BG=*/true)
>> - << Line.substr(Col - 1, Line.size() - Col + 1);
>> - OS << "\n";
>> -
>> if (Options.Debug) {
>> for (const auto &Range : HighlightedRanges)
>> errs() << "Highlighted line " << LineNumber << ", " << Range.first
>> @@ -64,13 +146,60 @@
>> }
>> }
>>
>> -void SourceCoverageView::renderIndent(raw_ostream &OS, unsigned Level) {
>> +void SourceCoverageViewHTML::renderTitle(raw_ostream &OS,
>> + StringRef Name) {
>> + OS << "<div class='function-title'><pre>"
>> + << std::string(Name) << ":</pre></div>";
>> +}
>> +
>> +void SourceCoverageViewConsole::renderTitle(raw_ostream &OS,
>> + StringRef Name) {
>> + Options.colored_ostream(OS, raw_ostream::CYAN) << Name << ":\n";
>> +}
>> +
>> +void SourceCoverageViewHTML::renderLine(raw_ostream &OS,
>> + StringRef Line,
>> + int64_t LineNumber,
>> + const coverage::CoverageSegment
>> + *WrappedSegment,
>> + ArrayRef<const
>> + coverage::CoverageSegment *>
>> + Segments,
>> + unsigned int ExpansionCol) {
>> + OS << "<td><pre>\n";
>> + SourceCoverageView::renderLine(OS, Line, LineNumber,
>> + WrappedSegment, Segments,
>> + ExpansionCol);
>> + OS << "</pre>\n";
>> + if (ExpansionSubViews.empty() && InstantiationSubViews.empty()) {
>> + // don't end the source line if we're going to embed sub-tables.
>> + OS << "</td>\n";
>> + }
>> +}
>> +
>> +void SourceCoverageViewConsole::renderLine(raw_ostream &OS,
>> + StringRef Line,
>> + int64_t LineNumber,
>> + const coverage::CoverageSegment
>> + *WrappedSegment,
>> + ArrayRef<const
>> + coverage::CoverageSegment *>
>> + Segments,
>> + unsigned int ExpansionCol) {
>> + SourceCoverageView::renderLine(OS, Line, LineNumber,
>> + WrappedSegment, Segments,
>> + ExpansionCol);
>> + OS << "\n";
>> +}
>> +
>> +void SourceCoverageViewConsole::renderIndent(raw_ostream &OS, unsigned Level) {
>> for (unsigned I = 0; I < Level; ++I)
>> OS << " |";
>> }
>>
>> -void SourceCoverageView::renderViewDivider(unsigned Level, unsigned Length,
>> - raw_ostream &OS) {
>> +void SourceCoverageViewConsole::renderViewDivider(unsigned Level,
>> + unsigned Length,
>> + raw_ostream &OS) {
>> assert(Level != 0 && "Cannot render divider at top level");
>> renderIndent(OS, Level - 1);
>> OS.indent(2);
>> @@ -78,50 +207,61 @@
>> OS << "-";
>> }
>>
>> -/// Format a count using engineering notation with 3 significant digits.
>> -static std::string formatCount(uint64_t N) {
>> - std::string Number = utostr(N);
>> - int Len = Number.size();
>> - if (Len <= 3)
>> - return Number;
>> - int IntLen = Len % 3 == 0 ? 3 : Len % 3;
>> - std::string Result(Number.data(), IntLen);
>> - if (IntLen != 3) {
>> - Result.push_back('.');
>> - Result += Number.substr(IntLen, 3 - IntLen);
>> - }
>> - Result.push_back(" kMGTPEZY"[(Len - 1) / 3]);
>> - return Result;
>> -}
>> -
>> -void
>> -SourceCoverageView::renderLineCoverageColumn(raw_ostream &OS,
>> - const LineCoverageInfo &Line) {
>> +void SourceCoverageViewConsole::renderLineCoverageColumn(raw_ostream &OS,
>> + const LineCoverageInfo
>> + &Line) {
>> +
>> + std::string C = formatCount(Line.ExecutionCount);
>> if (!Line.isMapped()) {
>> OS.indent(LineCoverageColumnWidth) << '|';
>> return;
>> }
>> - std::string C = formatCount(Line.ExecutionCount);
>> OS.indent(LineCoverageColumnWidth - C.size());
>> colored_ostream(OS, raw_ostream::MAGENTA,
>> Line.hasMultipleRegions() && Options.Colors)
>> - << C;
>> + << C;
>> OS << '|';
>> }
>>
>> -void SourceCoverageView::renderLineNumberColumn(raw_ostream &OS,
>> - unsigned LineNo) {
>> +void SourceCoverageViewHTML::renderLineCoverageColumn(raw_ostream &OS,
>> + const LineCoverageInfo
>> + &Line) {
>> + if (Line.isMapped()) {
>> + std::string C = formatCount(Line.ExecutionCount);
>> + OS << html::tag("td",
>> + html::tag("pre", html::escape(C)),
>> + "numeric") << "\n";
>> + } else {
>> + OS << html::tag("td", "", "numeric") << "\n";
>> + }
>> +}
>> +
>> +void SourceCoverageViewHTML::renderLineNumberColumn(raw_ostream &OS,
>> + unsigned int LineNo) {
>> SmallString<32> Buffer;
>> raw_svector_ostream BufferOS(Buffer);
>> BufferOS << LineNo;
>> auto Str = BufferOS.str();
>> + OS << html::tag("td",
>> + html::tag("pre", Str),
>> + "numeric") << "\n";
>> +}
>> +
>> +void SourceCoverageViewConsole::renderLineNumberColumn(raw_ostream &OS,
>> + unsigned LineNo) {
>> + SmallString<32> Buffer;
>> + raw_svector_ostream BufferOS(Buffer);
>> + BufferOS << LineNo;
>> + auto Str = BufferOS.str();
>> // Trim and align to the right
>> Str = Str.substr(0, std::min(Str.size(), (size_t)LineNumberColumnWidth));
>> OS.indent(LineNumberColumnWidth - Str.size()) << Str << '|';
>> }
>>
>> -void SourceCoverageView::renderRegionMarkers(
>> - raw_ostream &OS, ArrayRef<const coverage::CoverageSegment *> Segments) {
>> +void SourceCoverageView::renderRegionMarkers(raw_ostream &OS,
>> + ArrayRef<const coverage::CoverageSegment *> Segments,
>> + unsigned IndentLevel, unsigned CombinedColumnWidth) {
>> + OS.indent(CombinedColumnWidth);
>> unsigned PrevColumn = 1;
>> for (const auto *S : Segments) {
>> if (!S->IsRegionEntry)
>> @@ -135,23 +275,158 @@
>> OS << '^' << C;
>> }
>> OS << "\n";
>> +}
>>
>> - if (Options.Debug)
>> - for (const auto *S : Segments)
>> - errs() << "Marker at " << S->Line << ":" << S->Col << " = "
>> - << formatCount(S->Count) << (S->IsRegionEntry ? "\n" : " (pop)\n");
>> +void SourceCoverageViewConsole::renderRegionMarkers(raw_ostream &OS,
>> + ArrayRef<const coverage::CoverageSegment *> Segments,
>> + unsigned IndentLevel, unsigned CombinedColumnWidth) {
>> + renderIndent(OS, IndentLevel);
>> + SourceCoverageView::renderRegionMarkers(OS, Segments,
>> + IndentLevel, CombinedColumnWidth);
>> }
>>
>> +void SourceCoverageViewConsole::startLine(raw_ostream &OS,
>> + unsigned IndentLevel) {
>> + renderIndent(OS, IndentLevel);
>> +}
>> +
>> +void SourceCoverageViewHTML::renderFileHeader(raw_ostream &OS) {
>> +#include "css.inc"
>
> A more specific name than css.inc would probably be better. This
> probably deserves a comment too.
>
>> + OS << "<!doctype html>\n"
>> + "<html>\n"
>> + " <head>\n"
>> + " <meta name='viewport'"
>> + "content='width=device-width,initial-scale=1'>\n"
>> + " <style>\n" << css <<
>> + " </style>\n"
>> + " </head>\n"
>> + " <body>\n";
>> +}
>> +
>> +void SourceCoverageViewHTML::renderFileFooter(raw_ostream &OS) {
>> + OS << " </body>\n"
>> + "</html>\n";
>> +}
>> +
>> +void SourceCoverageViewHTML::render(raw_ostream &OS, bool WholeFile,
>> + unsigned IndentLevel) {
>> + if (WholeFile) {
>> + renderFileHeader(OS);
>> + OS << " <table class='centered'>\n";
>> + } else {
>> + OS << " <table>\n";
>> + }
>> + SourceCoverageView::render(OS, WholeFile, IndentLevel);
>> + OS << " </table>\n";
>> + if (WholeFile) {
>> + renderFileFooter(OS);
>> + }
>> +}
>> +
>> +void SourceCoverageViewHTML::startLine(raw_ostream &OS, unsigned IndentLevel) {
>> + OS << "<tr>\n";
>> +}
>> +
>> +void SourceCoverageViewHTML::endLine(raw_ostream &OS) {
>> + OS << "</tr>\n";
>> +}
>> +
>> +void SourceCoverageViewConsole::renderSubviews(raw_ostream &OS,
>> + line_iterator &LI,
>> + std::vector<ExpansionView>::iterator &NextESV,
>> + std::vector<ExpansionView>::iterator &EndESV,
>> + std::vector<InstantiationView>::iterator &NextISV,
>> + std::vector<InstantiationView>::iterator &EndISV,
>> + const coverage::CoverageSegment *WrappedSegment,
>> + SmallVector<const coverage::CoverageSegment *, 8> LineSegments,
>> + unsigned IndentLevel, unsigned ExpansionColumn,
>> + unsigned CombinedColumnWidth) {
>> +
>> + // The width of the line that is used to divide between the view and the
>> + // subviews.
>> + unsigned DividerWidth = CombinedColumnWidth + 4;
>> +
>> + // Show the expansions and instantiations for this line.
>> + unsigned NestedIndent = IndentLevel + 1;
>> + bool RenderedSubView = false;
>> + for (; NextESV != EndESV && NextESV->getLine() == LI.line_number();
>> + ++NextESV) {
>> +
>> + renderViewDivider(NestedIndent, DividerWidth, OS);
>> + OS << "\n";
>> + if (RenderedSubView) {
>> + // Re-render the current line and highlight the expansion range for
>> + // this subview.
>> + ExpansionColumn = NextESV->getStartCol();
>> + renderIndent(OS, IndentLevel);
>> + OS.indent(CombinedColumnWidth + (IndentLevel == 0 ? 0 : 1));
>> + renderLine(OS, *LI, LI.line_number(), WrappedSegment, LineSegments,
>> + ExpansionColumn);
>> + renderViewDivider(NestedIndent, DividerWidth, OS);
>> + OS << "\n";
>> + }
>> + // Render the child subview
>> + if (Options.Debug)
>> + errs() << "Expansion at line " << NextESV->getLine() << ", "
>> + << NextESV->getStartCol() << " -> " << NextESV->getEndCol()
>> + << "\n";
>> + NextESV->View->render(OS, false, NestedIndent);
>> + RenderedSubView = true;
>> + }
>> + for (; NextISV != EndISV && NextISV->Line == LI.line_number(); ++NextISV) {
>> + renderViewDivider(NestedIndent, DividerWidth, OS);
>> + OS << "\n";
>> + renderIndent(OS, NestedIndent);
>> + OS << ' ';
>> + Options.colored_ostream(OS, raw_ostream::CYAN) << NextISV->FunctionName
>> + << ":";
>> + OS << "\n";
>> + NextISV->View->render(OS, false, NestedIndent);
>> + RenderedSubView = true;
>> + }
>> + if (RenderedSubView) {
>> + renderViewDivider(NestedIndent, DividerWidth, OS);
>> + OS << "\n";
>> + }
>> +}
>> +
>> +void SourceCoverageViewHTML::renderSubviews(raw_ostream &OS,
>> + line_iterator &LI,
>> + std::vector<ExpansionView>::iterator &NextESV,
>> + std::vector<ExpansionView>::iterator &EndESV,
>> + std::vector<InstantiationView>::iterator &NextISV,
>> + std::vector<InstantiationView>::iterator &EndISV,
>> + const coverage::CoverageSegment *WrappedSegment,
>> + SmallVector<const coverage::CoverageSegment *, 8>
>> + LineSegments, unsigned IndentLevel,
>> + unsigned ExpansionColumn, unsigned CombinedColumnWidth) {
>> + for (; NextESV != EndESV && NextESV->getLine() == LI.line_number();
>> + ++NextESV) {
>> + OS << "<div class='expansion-view'>\n";
>> + // Render the child subview
>> + if (Options.Debug)
>> + errs() << "Expansion at line " << NextESV->getLine() << ", "
>> + << NextESV->getStartCol() << " -> " << NextESV->getEndCol()
>> + << "\n";
>> + NextESV->View->render(OS, false);
>> + OS << "</div>\n";
>> + }
>> +
>> + for (; NextISV != EndISV && NextISV->Line == LI.line_number(); ++NextISV) {
>> + OS << "<div class='expansion-view'>\n";
>> + NextISV->View->renderTitle(OS, NextISV->FunctionName);
>> + NextISV->View->render(OS, false);
>> + OS << "</div>\n";
>> + }
>> + OS << "</td>\n";
>> +}
>> +
>> void SourceCoverageView::render(raw_ostream &OS, bool WholeFile,
>> unsigned IndentLevel) {
>> // The width of the leading columns
>> unsigned CombinedColumnWidth =
>> (Options.ShowLineStats ? LineCoverageColumnWidth + 1 : 0) +
>> (Options.ShowLineNumbers ? LineNumberColumnWidth + 1 : 0);
>> - // The width of the line that is used to divide between the view and the
>> - // subviews.
>> - unsigned DividerWidth = CombinedColumnWidth + 4;
>> -
>> // We need the expansions and instantiations sorted so we can go through them
>> // while we iterate lines.
>> std::sort(ExpansionSubViews.begin(), ExpansionSubViews.end());
>> @@ -193,8 +468,8 @@
>> if (S->HasCount && S->IsRegionEntry)
>> LineCount.addRegionStartCount(S->Count);
>>
>> - // Render the line prefix.
>> - renderIndent(OS, IndentLevel);
>> + startLine(OS, IndentLevel);
>> +
>> if (Options.ShowLineStats)
>> renderLineCoverageColumn(OS, LineCount);
>> if (Options.ShowLineNumbers)
>> @@ -214,51 +489,30 @@
>> if (Options.ShowRegionMarkers && (!Options.ShowLineStatsOrRegionMarkers ||
>> LineCount.hasMultipleRegions()) &&
>> !LineSegments.empty()) {
>> - renderIndent(OS, IndentLevel);
>> - OS.indent(CombinedColumnWidth);
>> - renderRegionMarkers(OS, LineSegments);
>> - }
>> + renderRegionMarkers(OS, LineSegments, IndentLevel, CombinedColumnWidth);
>>
>> - // Show the expansions and instantiations for this line.
>> - unsigned NestedIndent = IndentLevel + 1;
>> - bool RenderedSubView = false;
>> - for (; NextESV != EndESV && NextESV->getLine() == LI.line_number();
>> - ++NextESV) {
>> - renderViewDivider(NestedIndent, DividerWidth, OS);
>> - OS << "\n";
>> - if (RenderedSubView) {
>> - // Re-render the current line and highlight the expansion range for
>> - // this subview.
>> - ExpansionColumn = NextESV->getStartCol();
>> - renderIndent(OS, IndentLevel);
>> - OS.indent(CombinedColumnWidth + (IndentLevel == 0 ? 0 : 1));
>> - renderLine(OS, *LI, LI.line_number(), WrappedSegment, LineSegments,
>> - ExpansionColumn);
>> - renderViewDivider(NestedIndent, DividerWidth, OS);
>> - OS << "\n";
>> - }
>> - // Render the child subview
>> if (Options.Debug)
>> - errs() << "Expansion at line " << NextESV->getLine() << ", "
>> - << NextESV->getStartCol() << " -> " << NextESV->getEndCol()
>> - << "\n";
>> - NextESV->View->render(OS, false, NestedIndent);
>> - RenderedSubView = true;
>> + for (const auto *S : LineSegments)
>> + errs() << "Marker at " << S->Line << ":" << S->Col << " = "
>> + << formatCount(S->Count) << (S->IsRegionEntry ? "\n" : " (pop)\n");
>> }
>> - for (; NextISV != EndISV && NextISV->Line == LI.line_number(); ++NextISV) {
>> - renderViewDivider(NestedIndent, DividerWidth, OS);
>> - OS << "\n";
>> - renderIndent(OS, NestedIndent);
>> - OS << ' ';
>> - Options.colored_ostream(OS, raw_ostream::CYAN) << NextISV->FunctionName
>> - << ":";
>> - OS << "\n";
>> - NextISV->View->render(OS, false, NestedIndent);
>> - RenderedSubView = true;
>> - }
>> - if (RenderedSubView) {
>> - renderViewDivider(NestedIndent, DividerWidth, OS);
>> - OS << "\n";
>> - }
>> + renderSubviews(OS, LI, NextESV, EndESV, NextISV, EndISV,
>> + WrappedSegment, LineSegments, IndentLevel,
>> + ExpansionColumn, CombinedColumnWidth);
>> +
>> + endLine(OS);
>> }
>> }
>> +
>> +std::unique_ptr<SourceCoverageView>
>> +SourceCoverageView::create(const MemoryBuffer &File,
>> + const CoverageViewOptions &Options,
>> + coverage::CoverageData &&CoverageInfo) {
>> + if (Options.Format == CoverageViewOptions::HTML) {
>> + return make_unique<SourceCoverageViewHTML>(File, Options,
>> + std::move(CoverageInfo));
>> + } else {
>> + return make_unique<SourceCoverageViewConsole>(File, Options,
>> + std::move(CoverageInfo));
>
> Better to use a switch statement - it'll give a warning if we add a
> value to the enum and forget to update here.
>
>> + }
>> +}
>> Index: SourceCoverageView.h
>> ===================================================================
>> --- SourceCoverageView.h (revision 262364)
>> +++ SourceCoverageView.h (working copy)
>> @@ -17,6 +17,9 @@
>> #include "CoverageViewOptions.h"
>> #include "llvm/ProfileData/CoverageMapping.h"
>> #include "llvm/Support/MemoryBuffer.h"
>> +#include "llvm/Support/LineIterator.h"
>> +#include "llvm/ADT/Optional.h"
>> +#include "llvm/ADT/STLExtras.h"
>> #include <vector>
>>
>> namespace llvm {
>> @@ -76,7 +79,7 @@
>> /// \brief A code coverage view of a specific source file.
>> /// It can have embedded coverage views.
>> class SourceCoverageView {
>> -private:
>> +protected:
>> /// \brief Coverage information for a single line.
>> struct LineCoverageInfo {
>> uint64_t ExecutionCount;
>> @@ -108,25 +111,39 @@
>> std::vector<InstantiationView> InstantiationSubViews;
>>
>> /// \brief Render a source line with highlighting.
>> - void renderLine(raw_ostream &OS, StringRef Line, int64_t LineNumber,
>> - const coverage::CoverageSegment *WrappedSegment,
>> - ArrayRef<const coverage::CoverageSegment *> Segments,
>> - unsigned ExpansionCol);
>> + virtual void renderLine(raw_ostream &OS, StringRef Line, int64_t LineNumber,
>> + const coverage::CoverageSegment *WrappedSegment,
>> + ArrayRef<const coverage::CoverageSegment *> Segments,
>> + unsigned ExpansionCol);
>>
>> - void renderIndent(raw_ostream &OS, unsigned Level);
>> -
>> - void renderViewDivider(unsigned Offset, unsigned Length, raw_ostream &OS);
>> -
>> /// \brief Render the line's execution count column.
>> - void renderLineCoverageColumn(raw_ostream &OS, const LineCoverageInfo &Line);
>> + virtual void renderLineCoverageColumn(raw_ostream &OS,
>> + const LineCoverageInfo &Line) = 0;
>>
>> /// \brief Render the line number column.
>> - void renderLineNumberColumn(raw_ostream &OS, unsigned LineNo);
>> + virtual void renderLineNumberColumn(raw_ostream &OS, unsigned LineNo) = 0;
>>
>> /// \brief Render all the region's execution counts on a line.
>> - void
>> - renderRegionMarkers(raw_ostream &OS,
>> - ArrayRef<const coverage::CoverageSegment *> Segments);
>> + virtual void renderRegionMarkers(raw_ostream &OS,
>> + ArrayRef<const coverage::CoverageSegment *> Segments,
>> + unsigned IndentLevel,
>> + unsigned CombinedColumnWidth);
>> +
>> + virtual void renderSubviews(llvm::raw_ostream &OS,
>> + line_iterator &LI,
>> + std::vector<ExpansionView>::iterator &NextESV,
>> + std::vector<ExpansionView>::iterator &EndESV,
>> + std::vector<InstantiationView>::iterator &NextISV,
>> + std::vector<InstantiationView>::iterator &EndISV,
>> + const coverage::CoverageSegment *WrappedSegment,
>> + SmallVector<const coverage::CoverageSegment *, 8>
>> + LineSegments,
>> + unsigned IndentLevel,
>> + unsigned ExpansionColumn,
>> + unsigned CombinedColumnWidth) = 0;
>> +
>> + virtual void startLine(raw_ostream &OS, unsigned IndentLevel) {}
>> + virtual void endLine(raw_ostream &OS) {}
>>
>> static const unsigned LineCoverageColumnWidth = 7;
>> static const unsigned LineNumberColumnWidth = 5;
>> @@ -136,6 +153,11 @@
>> const CoverageViewOptions &Options,
>> coverage::CoverageData &&CoverageInfo)
>> : File(File), Options(Options), CoverageInfo(std::move(CoverageInfo)) {}
>> +
>> +
>> + static std::unique_ptr<SourceCoverageView> create(const MemoryBuffer &File,
>> + const CoverageViewOptions &Options,
>> + coverage::CoverageData &&CoverageInfo);
>>
>> const CoverageViewOptions &getOptions() const { return Options; }
>>
>> @@ -150,12 +172,139 @@
>> std::unique_ptr<SourceCoverageView> View) {
>> InstantiationSubViews.emplace_back(FunctionName, Line, std::move(View));
>> }
>> +
>> + virtual void output(llvm::raw_ostream &OS, std::string Text,
>> + llvm::Optional<llvm::raw_ostream::Colors> Highlight,
>> + const coverage::CoverageSegment *Segment = nullptr) = 0;
>>
>> /// \brief Print the code coverage information for a specific
>> /// portion of a source file to the output stream.
>> - void render(raw_ostream &OS, bool WholeFile, unsigned IndentLevel = 0);
>> + virtual void render(raw_ostream &OS, bool WholeFile,
>> + unsigned IndentLevel = 0);
>> +
>> + /// \brief Render the title for a given source region (function or file name)
>> + virtual void renderTitle(raw_ostream &OS, StringRef Name) = 0;
>> +
>> + virtual ~SourceCoverageView() {}
>> };
>> +
>> +class SourceCoverageViewHTML : public SourceCoverageView {
>> +
>> + /// \brief Render a source line with highlighting.
>> + void renderLine(raw_ostream &OS, StringRef Line, int64_t LineNumber,
>> + const coverage::CoverageSegment *WrappedSegment,
>> + ArrayRef<const coverage::CoverageSegment *> Segments,
>> + unsigned ExpansionCol) override;
>> +
>> + void startLine(raw_ostream &OS, unsigned IndentLevel) override;
>> + void endLine(raw_ostream &OS) override;
>> +
>> + /// \brief Render the line's execution count column.
>> + void renderLineCoverageColumn(raw_ostream &OS,
>> + const LineCoverageInfo &Line) override;
>> +
>> + void output(llvm::raw_ostream &OS, std::string Text,
>> + llvm::Optional<llvm::raw_ostream::Colors> Highlight,
>> + const coverage::CoverageSegment *Segment = nullptr) override;
>> +
>> + /// \brief Render the line number column.
>> + void renderLineNumberColumn(raw_ostream &OS,
>> + unsigned LineNo) override;
>> +
>> + /// \brief Render all the region's execution counts on a line.
>> + void renderRegionMarkers(raw_ostream &OS,
>> + ArrayRef<const coverage::CoverageSegment *> Segments,
>> + unsigned IndentLevel,
>> + unsigned CombinedColumnWidth) override {
>> + // empty implementation (region markers are tooltips).
>> + }
>> +
>> + void renderSubviews(llvm::raw_ostream &OS,
>> + line_iterator &LI,
>> + std::vector<ExpansionView>::iterator &NextESV,
>> + std::vector<ExpansionView>::iterator &EndESV,
>> + std::vector<InstantiationView>::iterator &NextISV,
>> + std::vector<InstantiationView>::iterator &EndISV,
>> + const coverage::CoverageSegment *WrappedSegment,
>> + SmallVector<const coverage::CoverageSegment *, 8>
>> + LineSegments,
>> + unsigned IndentLevel,
>> + unsigned ExpansionColumn,
>> + unsigned CombinedColumnWidth) override;
>> +
>> + /// \brief Print the code coverage information for a specific
>> + /// portion of a source file to the output stream.
>> + void render(raw_ostream &OS, bool WholeFile,
>> + unsigned IndentLevel = 0) override;
>> +public:
>> + SourceCoverageViewHTML(const MemoryBuffer &File,
>> + const CoverageViewOptions &Options,
>> + coverage::CoverageData &&CoverageInfo)
>> + : SourceCoverageView(File, Options, std::move(CoverageInfo)) {}
>> +
>> + // \brief Render the standard boilerplate HTML header.
>> + static void renderFileHeader(raw_ostream &OS);
>> +
>> + // \brief Render the standard boilerplate HTML footer.
>> + static void renderFileFooter(raw_ostream &OS);
>> +
>> + /// \brief Render the title for a given source region (function or file name)
>> + void renderTitle(raw_ostream &OS, StringRef Name) override;
>> +};
>> +
>> +class SourceCoverageViewConsole : public SourceCoverageView {
>> +
>> + /// \brief Render a source line with highlighting.
>> + void renderLine(raw_ostream &OS, StringRef Line, int64_t LineNumber,
>> + const coverage::CoverageSegment *WrappedSegment,
>> + ArrayRef<const coverage::CoverageSegment *> Segments,
>> + unsigned ExpansionCol) override;
>>
>> + void startLine(raw_ostream &OS, unsigned IndentLevel) override;
>> +
>> + /// \brief Render the line's execution count column.
>> + void renderLineCoverageColumn(raw_ostream &OS,
>> + const LineCoverageInfo &Line) override;
>> +
>> + void output(llvm::raw_ostream &OS, std::string Text,
>> + llvm::Optional<llvm::raw_ostream::Colors> Highlight,
>> + const coverage::CoverageSegment *Segment = nullptr) override;
>> +
>> + /// \brief Render the line number column.
>> + void renderLineNumberColumn(raw_ostream &OS, unsigned LineNo) override;
>> +
>> + /// \brief Render all the region's execution counts on a line.
>> + void renderRegionMarkers(raw_ostream &OS,
>> + ArrayRef<const coverage::CoverageSegment *> Segments,
>> + unsigned IndentLevel,
>> + unsigned CombinedColumnWidth) override;
>> +
>> + void renderSubviews(raw_ostream &OS,
>> + line_iterator &LI,
>> + std::vector<ExpansionView>::iterator &NextESV,
>> + std::vector<ExpansionView>::iterator &EndESV,
>> + std::vector<InstantiationView>::iterator &NextISV,
>> + std::vector<InstantiationView>::iterator &EndISV,
>> + const coverage::CoverageSegment *WrappedSegment,
>> + SmallVector<const coverage::CoverageSegment *, 8>
>> + LineSegments,
>> + unsigned IndentLevel,
>> + unsigned ExpansionColumn,
>> + unsigned CombinedColumnWidth) override;
>> +
>> + void renderIndent(raw_ostream &OS, unsigned Level);
>> +
>> + void renderViewDivider(unsigned Offset, unsigned Length, raw_ostream &OS);
>> +public:
>> + SourceCoverageViewConsole(const MemoryBuffer &File,
>> + const CoverageViewOptions &Options,
>> + coverage::CoverageData &&CoverageInfo)
>> + : SourceCoverageView(File, Options, std::move(CoverageInfo)) {}
>> +
>> + /// \brief Render the title for a given source region (function or file name)
>> + void renderTitle(raw_ostream &OS, StringRef Name) override;
>> +};
>> +
>> } // namespace llvm
>>
>> #endif // LLVM_COV_SOURCECOVERAGEVIEW_H
>> Index: css.inc
>> ===================================================================
>> --- css.inc (revision 0)
>> +++ css.inc (working copy)
>> @@ -0,0 +1,108 @@
>> +//===------------ css.inc - css file for HTML coverage reports ------------===//
>> +//
>> +// The LLVM Compiler Infrastructure
>> +//
>> +// This file is distributed under the University of Illinois Open Source
>> +// License. See LICENSE.TXT for details.
>> +//
>> +//===----------------------------------------------------------------------===//
>> +//
>> +// This file has a raw string literal that is inserted as a CSS style into
>> +// HTML documents generated by llvm-cov.
>> +//
>> +// This file includes the necessary styling for centered tables of code and
>> +// tooltip generation.
>> +//
>> +//===----------------------------------------------------------------------===//
>> +
>> +StringRef css = R"(.red {
>> + background-color: #FFD0D0;
>> +}
>> +.cyan {
>> + background-color: cyan;
>> +}
>> +.black {
>> + background-color: black;
>> + color: white;
>> +}
>> +.green {
>> + background-color: #98FFA6;
>> + color: white;
>> +}
>> +.magenta {
>> + background-color: #F998FF;
>> + color: white;
>> +}
>> +tr:nth-child(odd) {
>> + background-color: #fafafa;
>> +}
>> +tr:nth-child(even) {
>> + background-color: #f0f0f0;
>> +}
>> +pre {
>> + margin-top: 0px !important;
>> + margin-bottom: 0px !important;
>> +}
>> +.function-title {
>> + margin-top: 15px;
>> + margin-bottom: 5px;
>> +}
>> +.centered {
>> + margin-left: auto;
>> + margin-right: auto;
>> +}
>> +.expansion-view {
>> + border: 2px solid #d8d8d8;
>> + padding-left: 10px;
>> + padding-right: 10px;
>> + padding-bottom: 10px;
>> + margin-bottom: 5px;
>> +}
>> +table {
>> + border: 2px solid #dbdbdb;
>> + border-collapse: collapse;
>> +}
>> +.numeric {
>> + text-align: right;
>> +}
>> +.tooltips {
>> + position: relative;
>> + display: inline;
>> + background-color: #fffee6;
>> + text-decoration: none;
>> +}
>> +.tooltips span {
>> + position: absolute;
>> + width:140px;
>> + color: #FFFFFF;
>> + background: #000000;
>> + height: 30px;
>> + line-height: 30px;
>> + text-align: center;
>> + visibility: hidden;
>> + border-radius: 6px;
>> +}
>> +.tooltips span:after {
>> + content: '';
>> + position: absolute;
>> + top: 100%;
>> + left: 50%;
>> + margin-left: -8px;
>> + width: 0; height: 0;
>> + border-top: 8px solid #000000;
>> + border-right: 8px solid transparent;
>> + border-left: 8px solid transparent;
>> +}
>> +:hover.tooltips span {
>> + visibility: visible;
>> + opacity: 0.8;
>> + bottom: 30px;
>> + left: 50%;
>> + margin-left: -76px;
>> + z-index: 999;
>> +}
>> +th, td {
>> + vertical-align: top;
>> + padding: 2px 5px;
>> + border-collapse: collapse;
>> +})";
> _______________________________________________
> llvm-commits mailing list
> llvm-commits at lists.llvm.org
> http://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-commits
More information about the llvm-commits
mailing list