Index: CMakeLists.txt =================================================================== --- CMakeLists.txt +++ CMakeLists.txt @@ -796,6 +796,7 @@ add_subdirectory(utils/count) add_subdirectory(utils/not) add_subdirectory(utils/llvm-lit) + add_subdirectory(utils/opt-viewer) add_subdirectory(utils/yaml-bench) add_subdirectory(utils/unittest) else() Index: utils/opt-viewer/CMakeLists.txt =================================================================== --- /dev/null +++ utils/opt-viewer/CMakeLists.txt @@ -0,0 +1,7 @@ +add_llvm_utility(opt-viewer + opt-viewer.cpp + HTMLWriter.cpp + YAMLProcessor.cpp + ) + +target_link_libraries(opt-viewer LLVMSupport) Index: utils/opt-viewer/HTMLWriter.h =================================================================== --- /dev/null +++ utils/opt-viewer/HTMLWriter.h @@ -0,0 +1,48 @@ +//===- HTMLWriter.h - Write HTML reports from optimizer remarks -----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_UTILS_OPT_VIEWER_HTML_H +#define LLVM_UTILS_OPT_VIEWER_HTML_H + +#include "Remark.h" + +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/Twine.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/raw_ostream.h" + +#include + +namespace llvm { +namespace opt_viewer { + +/// Creates a URI for a path. +std::string pathToURI(const Twine &Path); + +/// Writes Text, properly encoded as HTML, to Stream. +void writeHTML(StringRef Text, raw_ostream &Stream); + +/// Generates an HTML report from the given remark in the given output +/// directory. +Error writeReport(const Twine &OutputDirectory, + SmallVectorImpl &Remarks, + unsigned long long MaxHotness); + +/// Writes an HTML link for the given location, using the location as the text. +void writeLocationLink(const Location &L, raw_ostream &Stream); + +/// Writes an HTML link for the given location, using the given text. +void writeLocationLink(const Location &L, StringRef Text, raw_ostream &Stream); + +} // namespace opt_viewer +} // namespace llvm + +#endif Index: utils/opt-viewer/HTMLWriter.cpp =================================================================== --- /dev/null +++ utils/opt-viewer/HTMLWriter.cpp @@ -0,0 +1,287 @@ +//===- HTMLWriter.cpp - Write HTML reports from optimizer remarks ---------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "HTMLWriter.h" +#include "Location.h" +#include "Remark.h" + +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/Twine.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/MD5.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/raw_ostream.h" + +#include +#include +#include + +namespace llvm { +namespace opt_viewer { + +static const char uri_suffix[] = ".html"; +static const size_t max_uri_length = 64; +static const size_t uri_hash_length = 16; +static const size_t uri_suffix_length = sizeof(uri_suffix); +static const size_t uri_filename_length = + max_uri_length - uri_hash_length - uri_suffix_length; + +/// Function-like class that sorts remarks by hotness, descending. +class SortRemarksByHotness { + public: + bool operator()(const Remark *L, const Remark *R) { + return L->Hotness < R->Hotness; + } +}; + +/// Function-like class that sorts remarks by line and column, ascending. +class SortRemarksByPosition { + public: + bool operator()(const Remark *L, const Remark *R) { + auto &LL = L->Location; + auto &RL = R->Location; + return LL.Line > RL.Line || (LL.Line == RL.Line && LL.Column > RL.Column); + } +}; + +typedef std::priority_queue, + SortRemarksByHotness> + RemarksByHotnessQueue; +typedef std::priority_queue, + SortRemarksByPosition> + RemarksByPositionQueue; +typedef DenseMap PathRemarksMap; + +/// Maps Remark::Kind values to CSS classes used by the stylesheet. +static const char *colorClass(enum Remark::Kind Kind) { + switch (Kind) { + case Remark::Kind::PASSED: + return "column-entry-green"; + case Remark::Kind::MISSED: + return "column-entry-red"; + default: + return "column-entry-white"; + } +} + +/// Returns Hotness as a percentage of MaxHotness. +static unsigned long long relativeHotness(unsigned long long Hotness, + unsigned long long MaxHotness) { + // Convert the inputs to floating point numbers to avoid overflowing the + // fixed-precision integer type. A double has fewer mantissa bits than an + // unsigned long long, but since we are only going to return an integer in the + // range [0,100], there should be plenty of precision there. Similarly, we + // cast back to an unsigned long long, which cannot represent all values of a + // double, but we only want the integer value in the range [0,100], so it's + // fine. The static_casts are there to prevent the compiler from warning us + // about throwing away bits we don't need. + return static_cast( + 100.0 * static_cast(Hotness) / static_cast(MaxHotness) + + 0.5); +} + +static void writeLocationLinkEpilogue(raw_ostream &Stream) { Stream << ""; } + +static void writeLocationLinkPrologue(const Location &L, raw_ostream &Stream) { + auto URI = pathToURI(L.File); + Stream << ""; +} + +static Error writeRemark(const Remark &R, unsigned long long MaxHotness, + raw_ostream &Stream) { + Stream << "\n"; + writeLocationLink(R.Location, Stream); + Stream << "\n" << relativeHotness(R.Hotness, MaxHotness) + << "%\n"; + writeHTML(R.Function, Stream); + Stream << "\n" << R.Pass + << "\n\n"; + return Error::success(); +} + +static Error writeIndex(RemarksByHotnessQueue &HQ, + unsigned long long MaxHotness, raw_ostream &Stream) { + Stream << "\n\n" + << "\n" + << "\n\n
\n\n" + << "\n\n\n" + << "\n\n\n"; + while (!HQ.empty()) { + if (auto E = writeRemark(*HQ.top(), MaxHotness, Stream)) return E; + HQ.pop(); + } + Stream << "
Source LocationHotnessFunctionPass
\n
\n\n\n"; + Stream.flush(); + return Error::success(); +} + +static Error WriteSourceFile(const Twine &SourcePath, + const Twine &OutputDirectory, + RemarksByPositionQueue &Remarks, + unsigned long long MaxHotness) { + size_t Lineno = 0; + SmallString<255> OutputPath; + OutputDirectory.toVector(OutputPath); + sys::path::append(OutputPath, pathToURI(SourcePath)); + std::ifstream Source(SourcePath.str()); + std::error_code EC; + raw_fd_ostream Output(OutputPath, EC, sys::fs::OpenFlags::F_None); + if (EC) return errorCodeToError(EC); + Output << "\n\n\n" + << ""; + writeHTML(SourcePath.str(), Output); + Output << "\n" + << "\n\n
\n\n\n" + << "\n"; + std::string Line; + const Remark *NextRemark; + size_t NextRemarkLine = std::numeric_limits::max(); + if (!Remarks.empty()) { + NextRemark = Remarks.top(); + Remarks.pop(); + NextRemarkLine = NextRemark->Location.Line; + } + while (std::getline(Source, Line)) { + ++Lineno; + Output << "\n\n" + << "\n\n"; + while (Lineno >= NextRemarkLine) { + Output << "\n\n\n" + << "\n\n\n"; + if (!Remarks.empty()) { + NextRemark = Remarks.top(); + Remarks.pop(); + NextRemarkLine = NextRemark->Location.Line; + } else { + NextRemarkLine = std::numeric_limits::max(); + } + } + } + Output << "
LineHotnessOptimizationSource
" << Lineno + << "
";
+    writeHTML(Line, Output);
+    Output << "
" + << relativeHotness(NextRemark->Hotness, MaxHotness) << "%Kind) << "\">"; + writeHTML(NextRemark->Pass, Output); + Output << "" + << NextRemark->Message << "
\n
\n\n\n"; + Output.close(); + return Error::success(); +} + +std::string pathToURI(const Twine &Path) { + SmallString PathStr; + SmallVectorImpl::iterator PI; + std::string URI; + std::string::iterator UI; + Path.toVector(PathStr); + if (PathStr.size() <= max_uri_length - uri_suffix_length) { + URI.assign(PathStr.size(), 0); + PI = PathStr.begin(); + UI = URI.begin(); + } else { + MD5 Digest; + MD5::MD5Result Result; + Digest.update(PathStr); + Digest.final(Result); + SmallString<32> Hexed; + MD5::stringifyResult(Result, Hexed); + URI.assign(Hexed.begin(), Hexed.begin() + uri_hash_length); + URI.append(uri_filename_length, 0); + PI = PathStr.end() - uri_filename_length; + UI = URI.end() - uri_filename_length; + } + std::string::iterator UE = URI.end(); + while (UI != UE) { + char c = *PI; + if (c == '/' || c == '\\') c = '_'; + *UI = c; + ++UI; + ++PI; + } + URI.append(uri_suffix); + return URI; +} + +void writeHTML(StringRef Text, raw_ostream &Stream) { + for (char C : Text) { + switch (C) { + case '<': + Stream << "<"; + break; + case '>': + Stream << ">"; + break; + case '&': + Stream << "&"; + break; + default: + Stream << C; + } + } +} + +Error writeReport(const Twine &OutputDirectory, + SmallVectorImpl &Remarks, + unsigned long long MaxHotness) { + // Sort remarks by hotness for the index, and by file and location for the + // source files. + RemarksByHotnessQueue HQ; + PathRemarksMap RemarksByPath; + for (const auto &R : Remarks) { + HQ.push(&R); + RemarksByPositionQueue &RQ = + RemarksByPath.FindAndConstruct(R.Location.File).getSecond(); + RQ.push(&R); + } + + // Create the output directory if it does not already exist. + std::error_code EC; + sys::fs::create_directory(OutputDirectory); + + // Write the index. + SmallString<255> IndexPath; + OutputDirectory.toVector(IndexPath); + sys::path::append(IndexPath, "index.html"); + raw_fd_ostream Index(IndexPath, EC, sys::fs::OpenFlags::F_None); + if (EC) return errorCodeToError(EC); + Error E = writeIndex(HQ, MaxHotness, Index); + Index.close(); + if (E) return E; + + // Write the source files. + for (auto &Entry : RemarksByPath) { + E = WriteSourceFile(Entry.getFirst(), OutputDirectory, Entry.getSecond(), + MaxHotness); + if (E) return E; + } + + return E; +} + +void writeLocationLink(const Location &L, raw_ostream &Stream) { + writeLocationLinkPrologue(L, Stream); + writeHTML(L.File, Stream); + Stream << ":" << L.Line << ":" << L.Column; + writeLocationLinkEpilogue(Stream); +} + +void writeLocationLink(const Location &L, StringRef Text, raw_ostream &Stream) { + writeLocationLinkPrologue(L, Stream); + writeHTML(Text, Stream); + writeLocationLinkEpilogue(Stream); +} + +} // namespace opt_viewer +} // namespace llvm Index: utils/opt-viewer/Location.h =================================================================== --- /dev/null +++ utils/opt-viewer/Location.h @@ -0,0 +1,27 @@ +//===- Location.h - Source location ---------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_UTILS_OPT_VIEWER_LOCATION_H +#define LLVM_UTILS_OPT_VIEWER_LOCATION_H + +#include + +namespace llvm { +namespace opt_viewer { + +struct Location { + std::string File; + size_t Line; + size_t Column; +}; + +} // namespace opt_viewer +} // namespace llvm + +#endif Index: utils/opt-viewer/Remark.h =================================================================== --- /dev/null +++ utils/opt-viewer/Remark.h @@ -0,0 +1,37 @@ +//===- Remark.h - Optimizer remark ----------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_UTILS_OPT_VIEWER_REMARK_H +#define LLVM_UTILS_OPT_VIEWER_REMARK_H + +#include "Location.h" + +#include + +namespace llvm { +namespace opt_viewer { + +struct Remark { + std::string Pass; + std::string Name; + Location Location; + std::string Function; + unsigned long long Hotness; + enum Kind { + NOTE, + MISSED, + PASSED, + } Kind; + std::string Message; +}; + +} // namespace opt_viewer +} // namespace llvm + +#endif Index: utils/opt-viewer/YAMLProcessor.h =================================================================== --- /dev/null +++ utils/opt-viewer/YAMLProcessor.h @@ -0,0 +1,68 @@ +//===- YAMLProcessor.h - Parses YAML files into Remarks -------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_UTILS_OPT_VIEWER_YAMLPROCESSOR_H +#define LLVM_UTILS_OPT_VIEWER_YAMLPROCESSOR_H + +#include "Location.h" +#include "Remark.h" + +#include "llvm/ADT/Twine.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/MemoryBuffer.h" + +#include + +namespace llvm { +namespace yaml { +class Node; +} + +namespace opt_viewer { + +/// Parse YAML files into Remarks. The Process* methods are used to process +/// inputs, +/// after which the get* methods can be used to get the results. +class YAMLProcessor { + public: + YAMLProcessor() : MaxHotness(1) {} + + /// Finds files matching *.opt.yaml in the given directory and its + /// subdirectories, + /// then processes the remarks in them. + Error processDirectory(const Twine &Path); + + /// Processes remarks from the file at Path. + Error processFile(const Twine &Path); + + /// Processes remarks from the file or directory at Path. + Error processPath(const Twine &Path); + + /// Returns a reference to the parsed Remarks. + SmallVectorImpl &getRemarks() { return Remarks; } + + /// Returns the highest hotness value seen during processing. + unsigned long long getMaxHotness() const { return MaxHotness; } + + private: + void argToHTML(yaml::Node *Node, raw_ostream &stream); + std::string argsToHTML(yaml::Node *Node); + Expected parseDebugLoc(yaml::Node *Node); + Expected parseRemark(yaml::Node *Node); + Error processMemoryBuffer(std::unique_ptr Buffer); + Error processNode(yaml::Node *Node); + + unsigned long long MaxHotness; + SmallVector Remarks; +}; + +} // namespace opt_viewer +} // namespace llvm + +#endif Index: utils/opt-viewer/YAMLProcessor.cpp =================================================================== --- /dev/null +++ utils/opt-viewer/YAMLProcessor.cpp @@ -0,0 +1,215 @@ +//===- YAMLProcessor.cpp - Parses YAML files into Remarks -----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "YAMLProcessor.h" +#include "HTMLWriter.h" + +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Demangle/Demangle.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/SourceMgr.h" +#include "llvm/Support/YAMLParser.h" +#include "llvm/Support/raw_os_ostream.h" +#include "llvm/Support/raw_ostream.h" + +#include +#include +#include +#include + +namespace llvm { +namespace opt_viewer { + +static const unsigned long long InvalidHotness = + std::numeric_limits::max(); + +/// Attempts to demangle a function name. +/// If successful, returns the demangled name. +/// Else, returns a copy of the original name. +static std::string demangle(const std::string &Name) { + // itaniumDemangle has an API that is quite unlike how LLVM normally works. + // It will call realloc on the buffer if the buffer is too small. + // Because of this, the buffer needs to be something that realloc can be + // called on. To ensure this, we allocate it using malloc (and call + // free before leaving this function). + size_t BufferSize = 64; + char *Buffer = reinterpret_cast(std::malloc(BufferSize)); + int Status; + Buffer = itaniumDemangle(Name.c_str(), Buffer, &BufferSize, &Status); + std::string Result((Status == 0) ? Buffer : Name); + std::free(Buffer); + return Result; +} + +/// Given a yaml::ScalarNode, gets the string it contains. +static StringRef YAMLToString(yaml::Node *Node, + SmallVectorImpl &Storage) { + return cast(Node)->getValue(Storage); +} + +/// Given a yaml::ScalarNode, gets its value as an unsigned long long. +static unsigned long long YAMLToUnsigned(yaml::Node *Node) { + SmallString<20> Storage; + auto Str = cast(Node)->getValue(Storage); + return std::stoull(Str); +} + +/// Converts an Arg from a YAML optimizatin report to HTML. +void YAMLProcessor::argToHTML(yaml::Node *Node, raw_ostream &Stream) { + auto *MN = cast(Node); + std::string Demangled, Key; + Location Location; + SmallString<255> Storage; + yaml::Node *Value; + for (auto &It : *MN) { + Key = YAMLToString(It.getKey(), Storage); + Value = It.getValue(); + + if (Key == "DebugLoc") { + // If we have a demangled name, emit it, with a link to the debug + // location. + // Else, skip the debug location. + if (Demangled.empty()) continue; + auto DL = parseDebugLoc(Value); + if (DL) { + writeLocationLink(*DL, Demangled, Stream); + Demangled.clear(); + } + } + + // If we have a demangled name at this point, just emit it - we'll do + // without the link to the source. + if (!Demangled.empty()) { + Stream << Demangled; + Demangled.clear(); + } + + if (Key == "Callee") { + // Save the function name for later, so that we can emit it + // together with a link to the source. + Demangled = demangle(YAMLToString(Value, Storage).str()); + } else if (Key == "Caller") { + writeHTML(demangle(YAMLToString(Value, Storage).str()), Stream); + } else { + if (Value->getType() == yaml::Node::NodeKind::NK_Scalar) + writeHTML(YAMLToString(Value, Storage), Stream); + } + } +} + +/// Converts the Args from a YAML optimization report to HTML. +std::string YAMLProcessor::argsToHTML(yaml::Node *Node) { + auto *SN = cast(Node); + std::ostringstream Result; + raw_os_ostream Stream(Result); + for (auto &N : *SN) { + argToHTML(&N, Stream); + } + Stream.flush(); + return Result.str(); +} + +/// Parses a DebugLoc from a YAML optimization report. +Expected YAMLProcessor::parseDebugLoc(yaml::Node *Node) { + auto *MN = cast(Node); + SmallString<255> Storage; + Location D; + for (auto &It : *MN) { + auto Key = YAMLToString(It.getKey(), Storage); + if (Key == "File") D.File = YAMLToString(It.getValue(), Storage); + else if (Key == "Line") D.Line = YAMLToUnsigned(It.getValue()); + else if (Key == "Column") D.Column = YAMLToUnsigned(It.getValue()); + } + return D; +} + +/// Parses a remark from a YAML optimization report. +Expected YAMLProcessor::parseRemark(yaml::Node *Node) { + // Ignore Nodes that aren't KeyValue. + auto *MN = dyn_cast(Node); + Remark R; + R.Hotness = InvalidHotness; + if (!MN) return R; + if (MN->getVerbatimTag() == "!Passed") + R.Kind = Remark::Kind::PASSED; + else if (MN->getVerbatimTag() == "!Missed") + R.Kind = Remark::Kind::MISSED; + SmallString<255> Storage; + for (auto &It : *MN) { + auto *Key = cast(It.getKey()); + auto *Value = It.getValue(); + auto KeyStr = Key->getValue(Storage); + if (KeyStr == "DebugLoc") { + auto D = parseDebugLoc(Value); + if (!D) return D.takeError(); + R.Location = *D; + } else if (KeyStr == "Name") { + R.Name = YAMLToString(Value, Storage); + } else if (KeyStr == "Function") { + R.Function = demangle(YAMLToString(Value, Storage)); + } else if (KeyStr == "Hotness") { + R.Hotness = YAMLToUnsigned(Value); + if (R.Hotness == InvalidHotness) --R.Hotness; + if (R.Hotness > MaxHotness) MaxHotness = R.Hotness; + } else if (KeyStr == "Pass") { + R.Pass = YAMLToString(Value, Storage); + } else if (KeyStr == "Args") { + R.Message = argsToHTML(Value); + } + } + return R; +} + +Error YAMLProcessor::processNode(yaml::Node *Node) { + auto R = parseRemark(Node); + if (!R) return R.takeError(); + if (R->Hotness != InvalidHotness) Remarks.push_back(*R); + return Error::success(); +} + +Error YAMLProcessor::processMemoryBuffer(std::unique_ptr Buffer) { + SourceMgr SM; + yaml::Stream Stream(MemoryBufferRef(*Buffer), SM); + for (auto &Document : Stream) { + yaml::Node *Node = Document.getRoot(); + if (Node) + if (auto E = processNode(Node)) return E; + } + return Error::success(); +} + +Error YAMLProcessor::processFile(const Twine &path) { + auto Buffer = MemoryBuffer::getFile(path); + if (auto EC = Buffer.getError()) return errorCodeToError(EC); + return processMemoryBuffer(std::move(*Buffer)); +} + +Error YAMLProcessor::processDirectory(const Twine &path) { + std::error_code EC; + for (sys::fs::recursive_directory_iterator I(path, EC), EI; + I != EI && !EC; + I.increment(EC)) { + const StringRef Name = I->path(); + if (Name.endswith(".opt.yaml")) + if (auto E = processFile(Name)) return E; + } + return errorCodeToError(EC); +} + +Error YAMLProcessor::processPath(const Twine &path) { + if (sys::fs::is_directory(path)) + return processDirectory(path); + else + return processFile(path); +} + +} // namespace opt_viewer +} // namespace llvm Index: utils/opt-viewer/opt-viewer.cpp =================================================================== --- /dev/null +++ utils/opt-viewer/opt-viewer.cpp @@ -0,0 +1,56 @@ +//===- opt-viewer.cpp - Generates HTML optimization reports ---------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Generate HTML output to visualize optimization records from the YAML files +// generated with -fsave-optimization-record and -fdiagnostics-show-hotness. +// +// Usage: opt-viewer -o +// +//===----------------------------------------------------------------------===// + +#include "HTMLWriter.h" +#include "YAMLProcessor.h" + +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/Error.h" + +#include +#include + +using namespace llvm; +using namespace llvm::opt_viewer; + +static cl::opt OutputDirectory( + "output-directory", cl::desc("Directory to generate output in."), + cl::Required); +static cl::alias OutputDirectoryA("o", cl::desc("Short for -output-directory"), + cl::aliasopt(OutputDirectory)); +static cl::list Inputs(cl::Positional, cl::desc(""), + cl::OneOrMore); + +static Error do_main(int argc, char **argv) { + cl::ParseCommandLineOptions(argc, argv); + YAMLProcessor P; + for (auto &Input : Inputs) + if (auto E = P.processPath(Input)) return E; + + if (auto E = writeReport(OutputDirectory, P.getRemarks(), P.getMaxHotness())) + return E; + + return Error::success(); +} + +int main(int argc, char *argv[]) { + Error E = do_main(argc, argv); + if (E) { + logAllUnhandledErrors(std::move(E), errs(), "opt-viewer: "); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +}