Index: llvm/include/llvm/Passes/StandardInstrumentations.h =================================================================== --- llvm/include/llvm/Passes/StandardInstrumentations.h +++ llvm/include/llvm/Passes/StandardInstrumentations.h @@ -257,22 +257,19 @@ bool same(const std::string &Before, const std::string &After) override; }; -// The following classes hold a representation of the IR for a change -// reporter that uses string comparisons of the basic blocks -// that are created using print (ie, similar to dump()). -// These classes respect the filtering of passes and functions using -// -filter-passes and -filter-print-funcs. -// // Information that needs to be saved for a basic block in order to compare // before and after the pass to determine if it was changed by a pass. -class ChangedBlockData { +template class BlockDataTemplate { public: - ChangedBlockData(const BasicBlock &B); + BlockDataTemplate(const BasicBlock &B) : Label(B.getName().str()), Data(B) { + raw_string_ostream SS(Body); + B.print(SS, nullptr, true, true); + } - bool operator==(const ChangedBlockData &That) const { + bool operator==(const BlockDataTemplate &That) const { return Body == That.Body; } - bool operator!=(const ChangedBlockData &That) const { + bool operator!=(const BlockDataTemplate &That) const { return Body != That.Body; } @@ -281,22 +278,28 @@ // Return the string representation of the basic block. StringRef getBody() const { return Body; } + // Return the associated data + const BlockDataT &getData() const { return Data; } + protected: std::string Label; std::string Body; + + // Extra data associated with a basic block + BlockDataT Data; }; -template class OrderedChangedData { +template class OrderedChangedData { public: // Return the names in the order they were saved std::vector &getOrder() { return Order; } const std::vector &getOrder() const { return Order; } // Return a map of names to saved representations - StringMap &getData() { return Data; } - const StringMap &getData() const { return Data; } + StringMap &getData() { return Data; } + const StringMap &getData() const { return Data; } - bool operator==(const OrderedChangedData &That) const { + bool operator==(const OrderedChangedData &That) const { return Data == That.getData(); } @@ -307,34 +310,62 @@ // based on how the data is ordered in LLVM. static void report(const OrderedChangedData &Before, const OrderedChangedData &After, - function_ref HandlePair); + function_ref HandlePair); protected: std::vector Order; - StringMap Data; + StringMap Data; +}; + +// Do not need extra information for patch-style change reporter. +class EmptyData { +public: + EmptyData(const BasicBlock &) {} }; +// The following classes hold a representation of the IR for a change +// reporter that uses string comparisons of the basic blocks +// that are created using print (ie, similar to dump()). +// These classes respect the filtering of passes and functions using +// -filter-passes and -filter-print-funcs +// +// The data saved for comparing basic blocks. +using ChangedBlockData = BlockDataTemplate; + // The data saved for comparing functions. -using ChangedFuncData = OrderedChangedData; +template +class OrderedFuncDataTemplate : public OrderedChangedData { +public: + OrderedFuncDataTemplate(std::string S) : EntryBlockName(S) {} + + // Return the name of the entry block + std::string getEntryBlockName() const { return EntryBlockName; } + +protected: + std::string EntryBlockName; +}; + +using ChangedFuncData = OrderedFuncDataTemplate; // A map of names to the saved data. using ChangedIRData = OrderedChangedData; -// A class that compares two IRs and does a diff between them. The -// added lines are prefixed with a '+', the removed lines are prefixed -// with a '-' and unchanged lines are prefixed with a space (to have -// things line up). -class ChangedIRComparer { +// Abstract template base class for a class that compares two IRs. The +// class is created with the 2 IRs to compare and then compare is called. +// The static function analyzeIR is used to build up the IR representation. +template class IRComparer { +protected: + IRComparer(const IRDataT &Before, const IRDataT &After) + : Before(Before), After(After) {} + public: - ChangedIRComparer(raw_ostream &OS, const ChangedIRData &Before, - const ChangedIRData &After) - : Before(Before), After(After), Out(OS) {} + virtual ~IRComparer() {} // Compare the 2 IRs. void compare(Any IR, StringRef Prefix, StringRef PassID, StringRef Name); // Analyze \p IR and build the IR representation in \p Data. - static void analyzeIR(Any IR, ChangedIRData &Data); + static void analyzeIR(Any IR, IRDataT &Data); protected: // Return the module when that is the appropriate level of @@ -342,16 +373,37 @@ static const Module *getModuleForComparison(Any IR); // Generate the data for \p F into \p Data. - static bool generateFunctionData(ChangedIRData &Data, const Function &F); + static bool generateFunctionData(IRDataT &Data, const Function &F); // Called to handle the compare of a function. When \p InModule is set, // this function is being handled as part of comparing a module. + virtual void handleFunctionCompare(StringRef Name, StringRef Prefix, + StringRef PassID, bool InModule, + unsigned Minor, const FuncDataT &Before, + const FuncDataT &After) = 0; + + const IRDataT &Before; + const IRDataT &After; +}; + +// A class that compares two IRs and does a diff between them. The +// added lines are prefixed with a '+', the removed lines are prefixed +// with a '-' and unchanged lines are prefixed with a space (to have +// things line up). +class ChangedIRComparer : public IRComparer { +public: + ChangedIRComparer(raw_ostream &OS, const ChangedIRData &Before, + const ChangedIRData &After) + : IRComparer(Before, After), Out(OS) {} + + ~ChangedIRComparer() override {} + +protected: void handleFunctionCompare(StringRef Name, StringRef Prefix, StringRef PassID, - bool InModule, const ChangedFuncData &Before, - const ChangedFuncData &After); + bool InModule, unsigned Minor, + const ChangedFuncData &Before, + const ChangedFuncData &After) override; - const ChangedIRData &Before; - const ChangedIRData &After; raw_ostream &Out; }; @@ -390,6 +442,117 @@ void registerCallbacks(PassInstrumentationCallbacks &PIC); }; +// Class that holds transitions between basic blocks. The transitions +// are contained in a map of values to names of basic blocks. +class BlockSuccessors { +public: + // Fill the map with the transitions from basic block \p B. + BlockSuccessors(const BasicBlock &B); + + // Return an iterator to the names of the successor blocks. + StringMap::const_iterator begin() const { + return Successors.begin(); + } + StringMap::const_iterator end() const { + return Successors.end(); + } + + // Return the label of the basic block reached on a transition on \p S. + const StringRef getSuccessorLabel(StringRef S) const { + assert(Successors.count(S) == 1 && "Expected to find successor."); + return Successors.find(S)->getValue(); + } + +protected: + // Add a transition to \p Succ on \p Label + void addSuccessorLabel(StringRef Succ, StringRef Label) { + std::pair SS{Succ, Label}; + Successors.insert(SS); + } + + StringMap Successors; +}; + +using BlockSuccessorsBlockData = BlockDataTemplate; + +using BlockSuccessorsFuncData = + OrderedFuncDataTemplate; + +using BlockSuccessorsIRData = OrderedChangedData; + +// Class that compares two IRs and creates a graphical representation of +// the differences between the two IRs. It produces HTML instructions for +// a web page with links to pdf files showing the differences between +// functions in the IRs. +class DotCfgComparer + : public IRComparer { + +public: + // Output html on \p OS, storing the file in \p Directory, using a suffix + // of \p N in the filename. + DotCfgComparer(llvm::raw_fd_ostream &OS, unsigned N, + llvm::StringRef Directory, const BlockSuccessorsIRData &Before, + const BlockSuccessorsIRData &After); + + // Replace '<' and '>' in \p SR with "<" and ">", respectively + static std::string makeHTMLReady(llvm::StringRef SR); + +protected: + void handleFunctionCompare(StringRef Name, StringRef Prefix, StringRef PassID, + bool InModule, unsigned Minor, + const BlockSuccessorsFuncData &Before, + const BlockSuccessorsFuncData &After) override; + + // Generate the pdf file into \p Dir / \p PDFFileName using \p DotFile as + // input and return the html tag with \Text as the content, possibly + // indented, based on \p Indent. + static std::string genHTML(llvm::StringRef Text, llvm::StringRef DotFile, + llvm::StringRef PDFFileName, llvm::StringRef Dir, + bool Indent); + + std::string Directory; + llvm::raw_fd_ostream &HTML; + unsigned N; +}; + +// A change reporter that builds a website with links to pdf files showing +// dot control flow graphs with changed instructions shown in colour. +class DotCfgChangeReporter : public ChangeReporter { +public: + DotCfgChangeReporter(bool Verbose) + : ChangeReporter(Verbose) {} + ~DotCfgChangeReporter() override; + void registerCallbacks(PassInstrumentationCallbacks &PIC); + +protected: + // Initialize the HTML file and output the header. + void initializeHTML(); + + // Called on the first IR processed. + void handleInitialIR(llvm::Any IR) override; + // Called before and after a pass to get the representation of the IR. + void generateIRRepresentation(llvm::Any IR, llvm::StringRef PassID, + BlockSuccessorsIRData &Output) override; + // Called when the pass is not iteresting. + void omitAfter(llvm::StringRef PassID, std::string &Name) override; + // Called when an interesting IR has changed. + void handleAfter(llvm::StringRef PassID, std::string &Name, + const BlockSuccessorsIRData &Before, + const BlockSuccessorsIRData &After, llvm::Any) override; + // Called when an interesting pass is invalidated. + void handleInvalidated(llvm::StringRef PassID) override; + // Called when the IR or pass is not interesting. + void handleFiltered(llvm::StringRef PassID, std::string &Name) override; + // Called when an ignored pass is encountered. + void handleIgnored(llvm::StringRef PassID, std::string &Name) override; + // Called to compare the before and after representations of the IR. + bool same(const BlockSuccessorsIRData &Before, + const BlockSuccessorsIRData &After) override; + + unsigned N = 0; + raw_fd_ostream *HTML = nullptr; +}; + /// This class provides an interface to register all the standard pass /// instrumentations and manages their state (if any). class StandardInstrumentations { @@ -403,6 +566,7 @@ PseudoProbeVerifier PseudoProbeVerification; InLineChangePrinter PrintChangedDiff; VerifyInstrumentation Verify; + DotCfgChangeReporter DotCfgChangeReporter; bool VerifyEach; @@ -417,8 +581,12 @@ extern template class ChangeReporter; extern template class TextChangeReporter; +extern template class BlockDataTemplate; +extern template class OrderedChangedData; +extern template class OrderedChangedData; extern template class ChangeReporter; extern template class TextChangeReporter; +extern template class IRComparer; } // namespace llvm Index: llvm/include/llvm/Support/DOTGraphTraits.h =================================================================== --- llvm/include/llvm/Support/DOTGraphTraits.h +++ llvm/include/llvm/Support/DOTGraphTraits.h @@ -65,6 +65,11 @@ return false; } + // renderNodeUsingHTML - If the function returns true, this node will be + // rendered using HTML-like labels which allows colors, etc in the node + // and the edge source labels. + static bool renderNodeUsingHTML(const void *) { return false; } + /// getNodeLabel - Given a node and a pointer to the top level graph, return /// the label to print in the node. template Index: llvm/include/llvm/Support/GraphWriter.h =================================================================== --- llvm/include/llvm/Support/GraphWriter.h +++ llvm/include/llvm/Support/GraphWriter.h @@ -86,6 +86,9 @@ child_iterator EE = GTraits::child_end(Node); bool hasEdgeSourceLabels = false; + if (DTraits.renderNodeUsingHTML(Node)) + O << ""; + for (unsigned i = 0; EI != EE && i != 64; ++EI, ++i) { std::string label = DTraits.getEdgeSourceLabel(Node, EI); @@ -94,14 +97,22 @@ hasEdgeSourceLabels = true; - if (i) - O << "|"; + if (DTraits.renderNodeUsingHTML(Node)) + O << "" << label << ""; + else { + if (i) + O << "|"; - O << "" << DOT::EscapeString(label); + O << "" << DOT::EscapeString(label); + } } - if (EI != EE && hasEdgeSourceLabels) - O << "|truncated..."; + if (EI != EE && hasEdgeSourceLabels) { + if (DTraits.renderNodeUsingHTML(Node)) + O << "truncated..."; + else + O << "|truncated..."; + } return hasEdgeSourceLabels; } @@ -163,12 +174,39 @@ void writeNode(NodeRef Node) { std::string NodeAttributes = DTraits.getNodeAttributes(Node, G); - O << "\tNode" << static_cast(Node) << " [shape=record,"; + O << "\tNode" << static_cast(Node) << " [shape="; + if (DTraits.renderNodeUsingHTML(Node)) + O << "none,"; + else + O << "record,"; + if (!NodeAttributes.empty()) O << NodeAttributes << ","; - O << "label=\"{"; + O << "label="; + + if (DTraits.renderNodeUsingHTML(Node)) { + // Count the numbewr of edges out of the node to determine how + // many columns to span (max 64) + unsigned ColSpan = 0; + child_iterator EI = GTraits::child_begin(Node); + child_iterator EE = GTraits::child_end(Node); + for (; EI != EE && ColSpan != 64; ++EI, ++ColSpan) + ; + if (ColSpan == 0) + ColSpan = 1; + // Include truncated messages when counting. + if (EI != EE) + ++ColSpan; + O << "<"; + else + O << DOT::EscapeString(DTraits.getNodeLabel(Node, G)); // If we should include the address of the node in the label, do so now. std::string Id = DTraits.getNodeIdentifierLabel(Node, G); @@ -185,15 +223,25 @@ bool hasEdgeSourceLabels = getEdgeSourceLabels(EdgeSourceLabels, Node); if (hasEdgeSourceLabels) { - if (!DTraits.renderGraphFromBottomUp()) O << "|"; - - O << "{" << EdgeSourceLabels.str() << "}"; - - if (DTraits.renderGraphFromBottomUp()) O << "|"; + if (!DTraits.renderGraphFromBottomUp()) + if (!DTraits.renderNodeUsingHTML(Node)) + O << "|"; + + if (DTraits.renderNodeUsingHTML(Node)) + O << EdgeSourceLabels.str(); + else + O << "{" << EdgeSourceLabels.str() << "}"; + + if (DTraits.renderGraphFromBottomUp()) + if (!DTraits.renderNodeUsingHTML(Node)) + O << "|"; } if (DTraits.renderGraphFromBottomUp()) { - O << DOT::EscapeString(DTraits.getNodeLabel(Node, G)); + if (DTraits.renderNodeUsingHTML(Node)) + O << DTraits.getNodeLabel(Node, G); + else + O << DOT::EscapeString(DTraits.getNodeLabel(Node, G)); // If we should include the address of the node in the label, do so now. std::string Id = DTraits.getNodeIdentifierLabel(Node, G); @@ -215,12 +263,17 @@ << DOT::EscapeString(DTraits.getEdgeDestLabel(Node, i)); } - if (i != e) - O << "|truncated..."; - O << "}"; + if (DTraits.renderNodeUsingHTML(Node)) + O << ""; + else if (i != e) + O << "|truncated...}"; } - O << "}\"];\n"; // Finish printing the "node" line + if (DTraits.renderNodeUsingHTML(Node)) + O << "
"; + } else + O << "\"{"; if (!DTraits.renderGraphFromBottomUp()) { - O << DOT::EscapeString(DTraits.getNodeLabel(Node, G)); + if (DTraits.renderNodeUsingHTML(Node)) + O << DTraits.getNodeLabel(Node, G) << "... truncated
>"; + else + O << "}\""; + O << "];\n"; // Finish printing the "node" line // Output all of the edges now child_iterator EI = GTraits::child_begin(Node); Index: llvm/lib/Passes/StandardInstrumentations.cpp =================================================================== --- llvm/lib/Passes/StandardInstrumentations.cpp +++ llvm/lib/Passes/StandardInstrumentations.cpp @@ -27,10 +27,14 @@ #include "llvm/Support/CommandLine.h" #include "llvm/Support/Debug.h" #include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/GraphWriter.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Program.h" #include "llvm/Support/raw_ostream.h" +#include +#include #include +#include #include using namespace llvm; @@ -82,19 +86,26 @@ PrintChangedVerbose, PrintChangedQuiet, PrintChangedDiffVerbose, - PrintChangedDiffQuiet + PrintChangedDiffQuiet, + PrintChangedDotCfgVerbose, + PrintChangedDotCfgQuiet }; static cl::opt PrintChanged( "print-changed", cl::desc("Print changed IRs"), cl::Hidden, cl::ValueOptional, cl::init(ChangePrinter::NoChangePrinter), - cl::values(clEnumValN(ChangePrinter::PrintChangedQuiet, "quiet", - "Run in quiet mode"), - clEnumValN(ChangePrinter::PrintChangedDiffVerbose, "diff", - "Display patch-like changes"), - clEnumValN(ChangePrinter::PrintChangedDiffQuiet, "diff-quiet", - "Display patch-like changes in quiet mode"), - // Sentinel value for unspecified option. - clEnumValN(ChangePrinter::PrintChangedVerbose, "", ""))); + cl::values( + clEnumValN(ChangePrinter::PrintChangedQuiet, "quiet", + "Run in quiet mode"), + clEnumValN(ChangePrinter::PrintChangedDiffVerbose, "diff", + "Display patch-like changes"), + clEnumValN(ChangePrinter::PrintChangedDiffQuiet, "diff-quiet", + "Display patch-like changes in quiet mode"), + clEnumValN(ChangePrinter::PrintChangedDotCfgVerbose, "dot-cfg", + "Create a website with graphical changes"), + clEnumValN(ChangePrinter::PrintChangedDotCfgQuiet, "dot-cfg-quiet", + "Create a website with graphical changes in quiet mode"), + // Sentinel value for unspecified option. + clEnumValN(ChangePrinter::PrintChangedVerbose, "", ""))); // An option that supports the -print-changed option. See // the description for -print-changed for an explanation of the use @@ -117,6 +128,32 @@ DiffBinary("print-changed-diff-path", cl::Hidden, cl::init("diff"), cl::desc("system diff used by change reporters")); +// An option that determines the colour used for elements that are only +// in the before part. Must be a colour named in appendix J of +// https://graphviz.org/pdf/dotguide.pdf +cl::opt BeforeColour("before-color", + cl::desc("Color for before elements."), + cl::Hidden, cl::init("red")); +// An option that determines the colour used for elements that are only +// in the after part. Must be a colour named in appendix J of +// https://graphviz.org/pdf/dotguide.pdf +cl::opt AfterColour("after-color", + cl::desc("Color for after elements."), + cl::Hidden, cl::init("forestgreen")); +// An option that determines the colour used for elements that are in both +// the before and after parts. Must be a colour named in appendix J of +// https://graphviz.org/pdf/dotguide.pdf +cl::opt CommonColour("common-color", + cl::desc("Color for common elements."), + cl::Hidden, cl::init("black")); + +// An option that determines where the generated website file (named +// passes.html) and the associated pdf files (named diff_*.pdf) are saved. +static cl::opt DotCfgDir( + "dot-cfg-dir", + cl::desc("Generate dot files into specified directory for changed IRs"), + cl::Hidden, cl::init("./")); + namespace { // Perform a system based diff between \p Before and \p After, using @@ -463,12 +500,6 @@ }); } -ChangedBlockData::ChangedBlockData(const BasicBlock &B) - : Label(B.getName().str()) { - raw_string_ostream SS(Body); - B.print(SS, nullptr, true, true); -} - template TextChangeReporter::TextChangeReporter(bool Verbose) : ChangeReporter(Verbose), Out(dbgs()) {} @@ -631,32 +662,33 @@ handleNewIRData(NewIRDataQueue); } -void ChangedIRComparer::compare(Any IR, StringRef Prefix, StringRef PassID, - StringRef Name) { +template +void IRComparer::compare(Any IR, StringRef Prefix, + StringRef PassID, StringRef Name) { if (!getModuleForComparison(IR)) { // Not a module so just handle the single function. assert(Before.getData().size() == 1 && "Expected only one function."); assert(After.getData().size() == 1 && "Expected only one function."); - handleFunctionCompare(Name, Prefix, PassID, false, + handleFunctionCompare(Name, Prefix, PassID, false, 0, Before.getData().begin()->getValue(), After.getData().begin()->getValue()); return; } - ChangedIRData::report( - Before, After, [&](const ChangedFuncData *B, const ChangedFuncData *A) { - ChangedFuncData Missing; - if (!B) - B = &Missing; - else if (!A) - A = &Missing; - assert(B != &Missing && A != &Missing && - "Both functions cannot be missing."); - handleFunctionCompare(Name, Prefix, PassID, true, *B, *A); - }); + unsigned Minor = 0; + FuncDataT Missing(""); + IRDataT::report(Before, After, [&](const FuncDataT *B, const FuncDataT *A) { + assert((B || A) && "Both functions cannot be missing."); + if (!B) + B = &Missing; + if (!A) + A = &Missing; + handleFunctionCompare(Name, Prefix, PassID, true, Minor++, *B, *A); + }); } -void ChangedIRComparer::analyzeIR(Any IR, ChangedIRData &Data) { +template +void IRComparer::analyzeIR(Any IR, IRDataT &Data) { if (const Module *M = getModuleForComparison(IR)) { // Create data for each existing/interesting function in the module. for (const Function &F : *M) @@ -676,7 +708,8 @@ generateFunctionData(Data, *F); } -const Module *ChangedIRComparer::getModuleForComparison(Any IR) { +template +const Module *IRComparer::getModuleForComparison(Any IR) { if (any_isa(IR)) return any_cast(IR); if (any_isa(IR)) @@ -687,16 +720,17 @@ return nullptr; } -bool ChangedIRComparer::generateFunctionData(ChangedIRData &Data, - const Function &F) { +template +bool IRComparer::generateFunctionData(IRDataT &Data, + const Function &F) { if (!F.isDeclaration() && isFunctionInPrintList(F.getName())) { - ChangedFuncData CFD; + FuncDataT FD(F.getEntryBlock().getName().str()); for (const auto &B : F) { - CFD.getOrder().emplace_back(B.getName()); - CFD.getData().insert({B.getName(), B}); + FD.getOrder().emplace_back(B.getName()); + FD.getData().insert({B.getName(), B}); } Data.getOrder().emplace_back(F.getName()); - Data.getData().insert({F.getName(), CFD}); + Data.getData().insert({F.getName(), FD}); return true; } return false; @@ -1131,6 +1165,7 @@ void ChangedIRComparer::handleFunctionCompare(StringRef Name, StringRef Prefix, StringRef PassID, bool InModule, + unsigned Minor, const ChangedFuncData &Before, const ChangedFuncData &After) { // Print a banner when this is being shown in the context of a module @@ -1154,12 +1189,925 @@ TextChangeReporter::registerRequiredCallbacks(PIC); } +namespace { + +enum IRChangeDiffType { InBefore, InAfter, IsCommon, NumIRChangeDiffTypes }; + +// Describe where a given element exists. +std::string Colours[NumIRChangeDiffTypes]; + +class DisplayNode; +class DotCfgDiffDisplayGraph; + +// Base class for a node or edge in the dot-cfg-changes graph. +class DisplayElement { +public: + // Is this in before, after, or both? + IRChangeDiffType getType() const { return Type; } + +protected: + DisplayElement(IRChangeDiffType T) : Type(T) {} + const IRChangeDiffType Type; +}; + +// An edge representing a transition between basic blocks in the +// dot-cfg-changes graph. +class DisplayEdge : public DisplayElement { +public: + DisplayEdge(std::string V, DisplayNode &Node, IRChangeDiffType T) + : DisplayElement(T), Value(V), Node(Node) {} + // The value on which the transition is made. + std::string getValue() const { return Value; } + // The node (representing a basic block) reached by this transition. + const DisplayNode &getDestinationNode() const { return Node; } + +protected: + std::string Value; + const DisplayNode &Node; +}; + +// A node in the dot-cfg-changes graph which represents a basic block. +class DisplayNode : public DisplayElement { +public: + // \p C is the content for the node, \p T indicates the colour for the + // outline of the node + DisplayNode(std::string C, IRChangeDiffType T) + : DisplayElement(T), Content(C) {} + + // Iterator to the child nodes. Required by GraphWriter. + using ChildIterator = std::unordered_set::const_iterator; + ChildIterator children_begin() const { return Children.cbegin(); } + ChildIterator children_end() const { return Children.cend(); } + + // Iterator for the edges. Required by GraphWriter. + using EdgeIterator = std::vector::const_iterator; + EdgeIterator edges_begin() const { return EdgePtrs.cbegin(); } + EdgeIterator edges_end() const { return EdgePtrs.cend(); } + + // Create an edge to \p Node on value \p V, with type \p T. + void createEdge(StringRef V, DisplayNode &Node, IRChangeDiffType T); + + // Return the content of this node. + std::string getContent() const { return Content; } + + // Return the type of the edge to node \p S. + const DisplayEdge &getEdge(const DisplayNode &To) const { + assert(EdgeMap.find(&To) != EdgeMap.end() && "Expected to find edge."); + return *EdgeMap.find(&To)->second; + } + + // Return the value for the transition to basic block \p S. + // Required by GraphWriter. + std::string getEdgeSourceLabel(const DisplayNode &Sink) const { + return getEdge(Sink).getValue(); + } + + void createEdgeMap(); + +protected: + const std::string Content; + + // Place to collect all of the edges. Once they are all in the vector, + // the vector will not reallocate so then we can use pointers to them, + // which are required by the graph writing routines. + std::vector Edges; + + std::vector EdgePtrs; + std::unordered_set Children; + std::unordered_map EdgeMap; + + // Safeguard adding of edges. + bool AllEdgesCreated = false; +}; + +// Class representing a difference display (corresponds to a pdf file). +class DotCfgDiffDisplayGraph { +public: + DotCfgDiffDisplayGraph(std::string Name) { + NodeGenerationComplete = false; + GraphName = Name; + assert(Nodes.size() == 0 && "Expected Nodes to be empty."); + assert(NodePtrs.size() == 0 && "Expected NodePtrs to be empty."); + assert(EntryNode == nullptr && "Expected EntryNode to be null."); + } + ~DotCfgDiffDisplayGraph() { + // Due to opt bug we have static members. Clear them out. + GraphName = ""; + Nodes.clear(); + NodePtrs.clear(); + EntryNode = nullptr; + } + + // Generate the file into \p DotFile. + void generateDotFile(StringRef DotFile); + + // Iterator to the nodes. Required by GraphWriter. + using NodeIterator = std::vector::const_iterator; + NodeIterator nodes_begin() const { + assert(NodeGenerationComplete && "Unexpected children iterator creation"); + return NodePtrs.cbegin(); + } + NodeIterator nodes_end() const { + assert(NodeGenerationComplete && "Unexpected children iterator creation"); + return NodePtrs.cend(); + } + + // Record the index of the entry node. At this point, we can build up + // vectors of pointers that are required by the graph routines. + void setEntryNode(unsigned N) { + // At this point, there will be no new nodes. + assert(!NodeGenerationComplete && "Unexpected node creation"); + NodeGenerationComplete = true; + for (auto &N : Nodes) + NodePtrs.emplace_back(&N); + + EntryNode = NodePtrs[N]; + } + + // Create a node. + void createNode(std::string C, IRChangeDiffType T) { + assert(!NodeGenerationComplete && "Unexpected node creation"); + Nodes.emplace_back(C, T); + } + // Return the node at index \p N to avoid problems with vectors reallocating. + DisplayNode &getNode(unsigned N) { + assert(N < Nodes.size() && "Node is out of bounds"); + return Nodes[N]; + } + unsigned size() const { + assert(NodeGenerationComplete && "Unexpected children iterator creation"); + return Nodes.size(); + } + + // Return the name of the graph. Required by GraphWriter. + std::string getGraphName() const { return GraphName; } + + // Return the string representing the differences for basic block \p Node. + // Required by GraphWriter. + std::string getNodeLabel(const DisplayNode &Node) const { + return Node.getContent(); + } + + // Return a string with colour information for Dot. Required by GraphWriter. + std::string getNodeAttributes(const DisplayNode &Node) const { + return attribute(Node.getType()); + } + + // Return a string with colour information for Dot. Required by GraphWriter. + std::string getEdgeColorAttr(const DisplayNode &From, + const DisplayNode &To) const { + return attribute(From.getEdge(To).getType()); + } + + // Get the starting basic block. Required by GraphWriter. + DisplayNode *getEntryNode() const { + assert(NodeGenerationComplete && "Unexpected children iterator creation"); + return EntryNode; + } + +protected: + // Return the string containing the colour to use as a Dot attribute. + std::string attribute(IRChangeDiffType T) const; + + // There appears to be an optimization bug in the build compiler in + // that when begin() and end() are called from GraphWriter, the pointer to + // DotCfgDiff (the G member of GraphWriter) is changed to refer to the + // stack. This does not happen in a non-release build and I do not see + // any code that should cause a copy to be made (in fact, the copy + // constructor and operator= are deleted). Because of this, these members + // have to be static or else the program will crash. + // It can be shown by adding + // "dbgs() << __FUNCTION__ << " " << (void*)this << "\n";" into the ctor + // and begin/end functions. They should match because only 1 object is + // created. + // TODO verify the bug at the proper level of build compiler and make a + // bug report if necessary. + // TODO These should be made members and workarounds removed when the + // bug is fixed. + static bool NodeGenerationComplete; + static std::string GraphName; + static std::vector Nodes; + static std::vector NodePtrs; + static DisplayNode *EntryNode; +}; + +bool DotCfgDiffDisplayGraph::NodeGenerationComplete; +std::string DotCfgDiffDisplayGraph::GraphName; +std::vector DotCfgDiffDisplayGraph::Nodes; +std::vector DotCfgDiffDisplayGraph::NodePtrs; +DisplayNode *DotCfgDiffDisplayGraph::EntryNode; + +void DisplayNode::createEdge(StringRef V, DisplayNode &Node, + IRChangeDiffType T) { + assert(!AllEdgesCreated && "Expected to be able to still create edges."); + Edges.emplace_back(V.str(), Node, T); + Children.insert(&Node); +} + +void DisplayNode::createEdgeMap() { + // No more edges will be added so we can now use pointers to the edges + // as the vector will not grow and reallocate. + AllEdgesCreated = true; + for (auto &E : Edges) + EdgeMap.insert({&E.getDestinationNode(), &E}); +} + +class DotCfgDiffNode; +class DotCfgDiff; + +// A class representing a basic block in the Dot difference graph. +class DotCfgDiffNode { +public: + DotCfgDiffNode() = delete; + + // Create a node in Dot difference graph \p G representing the basic block + // represented by \p BD with type \p T (where it exists). + DotCfgDiffNode(DotCfgDiff &G, unsigned N, const BlockSuccessorsBlockData &BD, + IRChangeDiffType T) + : Graph(G), N(N), Data{&BD, nullptr}, Type(T) {} + DotCfgDiffNode(const DotCfgDiffNode &DN) + : Graph(DN.Graph), N(DN.N), Data{DN.Data[0], DN.Data[1]}, Type(DN.Type), + EdgesMap(DN.EdgesMap), Children(DN.Children), Edges(DN.Edges) {} + + unsigned getIndex() const { return N; } + + // The label of the basic block + StringRef getLabel() const { + assert(Data[0] && "Expected Data[0] to be set."); + return Data[0]->getLabel(); + } + // Return where this block exists. + IRChangeDiffType getType() const { return Type; } + // Change this basic block from being only in before to being common. + // Save the pointer to \p Other. + void setCommon(const BlockSuccessorsBlockData &Other) { + assert(!Data[1] && "Expected only one block datum"); + Data[1] = &Other; + Type = IsCommon; + } + // Add an edge to \p E of type {\p Value, \p T}. + void addEdge(unsigned E, StringRef Value, IRChangeDiffType T) { + // This is a new edge or it is an edge being made common. + assert((EdgesMap.count(E) == 0 || T == IsCommon) && + "Unexpected edge count and type."); + EdgesMap[E] = {Value.str(), T}; + } + // Record the children and create edges. + void finalize(DotCfgDiff &G); + + // Return the type of the edge to node \p S. + std::pair getEdge(const unsigned S) const { + assert(EdgesMap.count(S) == 1 && "Expected to find edge."); + return EdgesMap.at(S); + } + + // Return the string representing the basic block. + std::string getBodyContent() const; + + void createDisplayEdges(DotCfgDiffDisplayGraph &Graph, unsigned DisplayNode, + std::map &NodeMap); + +protected: + DotCfgDiff &Graph; + const unsigned N; + const BlockSuccessorsBlockData *Data[2]; + IRChangeDiffType Type; + std::map> EdgesMap; + std::vector Children; + std::vector Edges; +}; + +// Class representing the difference graph between two functions. +class DotCfgDiff { +public: + // \p Title is the title given to the graph. \p EntryNodeName is the + // entry node for the function. \p Before and \p After are the before + // after versions of the function, respectively. \p Dir is the directory + // in which to store the results. + DotCfgDiff(StringRef Title, const BlockSuccessorsFuncData &Before, + const BlockSuccessorsFuncData &After, StringRef Dir); + // Only needed because of opt bug... + ~DotCfgDiff() { + // Due to opt bug we have static members. Clear them out. + Nodes.clear(); + NodePosition.clear(); + GraphName = ""; + } + + DotCfgDiff(const DotCfgDiff &) = delete; + DotCfgDiff &operator=(const DotCfgDiff &) = delete; + + DotCfgDiffDisplayGraph createDisplayGraph(StringRef Title, + StringRef EntryNodeName); + + // Return a string consisting of the labels for the \p Source and \p Sink. + // The combination allows distinguishing changing transitions on the + // same value (ie, a transition went to X before and goes to Y after). + // Required by GraphWriter. + StringRef getEdgeSourceLabel(const unsigned &Source, + const unsigned &Sink) const { + std::string S = + getNode(Source).getLabel().str() + " " + getNode(Sink).getLabel().str(); + assert(EdgeLabels.count(S) == 1 && "Expected to find edge label."); + return EdgeLabels.find(S)->getValue(); + } + + // Return the number of basic blocks (nodes). Required by GraphWriter. + unsigned size() const { return Nodes.size(); } + + DotCfgDiffNode &getNode(unsigned N) const { + assert(N < Nodes.size() && "Unexpected index for node reference"); + return Nodes[N]; + } + +protected: + // Return the string surrounded by HTML to make it the appropriate colour. + std::string colourize(std::string S, IRChangeDiffType T) const; + // Return the string containing the colour to use as a Dot attribute. + std::string attribute(IRChangeDiffType T) const; + + void createNode(StringRef Label, const BlockSuccessorsBlockData &BD, + IRChangeDiffType T) { + unsigned Pos = Nodes.size(); + Nodes.emplace_back(*this, Pos, BD, T); + NodePosition.insert({Label, Pos}); + } + + // There appears to be an optimization bug in the build compiler in + // that when begin() and end() are called from GraphWriter, the pointer to + // DotCfgDiff (the G member of GraphWriter) is changed to refer to the + // stack. This does not happen in a non-release build and I do not see + // any code that should cause a copy to be made (in fact, the copy + // constructor and operator= are deleted). Because of this, these members + // have to be static or else the program will crash. + // It can be shown by adding + // "dbgs() << __FUNCTION__ << " " << (void*)this << "\n";" into the ctor + // and begin/end functions. They should match because only 1 object is + // created. + // TODO verify the bug and make a bug report. + static std::vector Nodes; + static StringMap NodePosition; + static std::string GraphName; + + DotCfgDiffNode *EntryNode; + StringMap EdgeLabels; +}; + +std::string DotCfgDiffNode::getBodyContent() const { + if (Type == IsCommon) { + assert(Data[1] && "Expected Data[1] to be set."); + + StringRef SR[2]; + for (unsigned I = 0; I < 2; ++I) { + SR[I] = Data[I]->getBody(); + // drop initial '\n' if present + if (SR[I][0] == '\n') + SR[I] = SR[I].drop_front(); + // drop predecessors as they can be big and are redundant + SR[I] = SR[I].drop_until([](char c) { return c == '\n'; }).drop_front(); + } + + SmallString<80> OldLineFormat = formatv( + "%l
", Colours[InBefore]); + SmallString<80> NewLineFormat = formatv( + "%l
", Colours[InAfter]); + SmallString<80> UnchangedLineFormat = formatv( + "%l
", Colours[IsCommon]); + std::string Diff = Data[0]->getLabel().str(); + Diff += ":\n
" + + doSystemDiff(DotCfgComparer::makeHTMLReady(SR[0]), + DotCfgComparer::makeHTMLReady(SR[1]), OldLineFormat, + NewLineFormat, UnchangedLineFormat); + + // Diff adds in some empty colour changes which are not valid HTML + // so remove them. Colours are all lowercase alpha characters (as + // listed in https://graphviz.org/pdf/dotguide.pdf). + return std::regex_replace(Diff, std::regex(""), + std::string("")); + } + + // Put node out in the appropriate colour. + assert(!Data[1] && "Data[1] is set unexpectedly."); + std::string Body = DotCfgComparer::makeHTMLReady(Data[0]->getBody()); + const StringRef BS = Body; + StringRef BS1 = BS; + // Drop leading newline, if present. + if (BS.front() == '\n') + BS1 = BS1.drop_front(1); + // Get label. + StringRef Label = BS1.take_until([](char c) { return c == ':'; }); + // drop predecessors as they can be big and are redundant + BS1 = BS1.drop_until([](char c) { return c == '\n'; }).drop_front(); + + std::string S = "" + Label.str() + ":"; + + // align each line to the left. + while (BS1.size()) { + S.append("
"); + StringRef Line = BS1.take_until([](char c) { return c == '\n'; }); + S.append(Line.str()); + BS1 = BS1.drop_front(Line.size() + 1); + } + S.append("
"); + return S; +} + +std::string DotCfgDiff::colourize(std::string S, IRChangeDiffType T) const { + if (S.length() == 0) + return S; + return "" + S + ""; +} + +std::string DotCfgDiff::attribute(IRChangeDiffType T) const { + return "color=" + Colours[T]; +} + +std::string DotCfgDiffDisplayGraph::attribute(IRChangeDiffType T) const { + return "color=" + Colours[T]; +} + +DotCfgDiff::DotCfgDiff(StringRef Title, const BlockSuccessorsFuncData &Before, + const BlockSuccessorsFuncData &After, StringRef Dir) { + // Due to opt bug we have static members. Ensure that they are empty. + assert(Nodes.empty() && "Expected Nodes to be empty."); + assert(NodePosition.empty() && "Expected NodePosition to be empty."); + assert(GraphName.size() == 0 && "Expected GraphName to be empty."); + + GraphName = Title.str(); + StringMap EdgesMap; + + // Handle each basic block in the before IR. + for (auto &B : Before.getData()) { + StringRef Label = B.getKey(); + const BlockSuccessorsBlockData &BD = B.getValue(); + createNode(Label, BD, InBefore); + + // Create transitions with names made up of the from block label, the value + // on which the transition is made and the to block label. + for (StringMap::const_iterator Sink = BD.getData().begin(), + E = BD.getData().end(); + Sink != E; ++Sink) { + std::string Key = (Label + " " + Sink->getKey().str()).str() + " " + + BD.getData().getSuccessorLabel(Sink->getKey()).str(); + EdgesMap.insert({Key, InBefore}); + } + } + + // Handle each basic block in the after IR + for (auto &A : After.getData()) { + StringRef Label = A.getKey(); + const BlockSuccessorsBlockData &BD = A.getValue(); + unsigned C = NodePosition.count(Label); + if (C == 0) + // This only exists in the after IR. Create the node. + createNode(Label, BD, InAfter); + else { + assert(C == 1 && "Unexpected multiple nodes."); + Nodes[NodePosition[Label]].setCommon(BD); + } + // Add in the edges between the nodes (as common or only in after). + for (StringMap::const_iterator Sink = BD.getData().begin(), + E = BD.getData().end(); + Sink != E; ++Sink) { + std::string Key = (Label + " " + Sink->getKey().str()).str() + " " + + BD.getData().getSuccessorLabel(Sink->getKey()).str(); + unsigned C = EdgesMap.count(Key); + if (C == 0) + EdgesMap.insert({Key, InAfter}); + else { + EdgesMap[Key] = IsCommon; + } + } + } + + // Now go through the map of edges and add them to the node. + for (auto &E : EdgesMap) { + // Extract the source, sink and value from the edge key. + StringRef S = E.getKey(); + auto SP1 = S.rsplit(' '); + auto &SourceSink = SP1.first; + auto SP2 = SourceSink.split(' '); + StringRef Source = SP2.first; + StringRef Sink = SP2.second; + StringRef Value = SP1.second; + + assert(NodePosition.count(Source) == 1 && "Expected to find node."); + DotCfgDiffNode &SourceNode = Nodes[NodePosition[Source]]; + assert(NodePosition.count(Sink) == 1 && "Expected to find node."); + unsigned SinkNode = NodePosition[Sink]; + IRChangeDiffType T = E.second; + + // Look for an edge from Source to Sink + if (EdgeLabels.count(SourceSink) == 0) + EdgeLabels.insert({SourceSink, colourize(Value.str(), T)}); + else { + StringRef V = EdgeLabels.find(SourceSink)->getValue(); + std::string NV = colourize(V.str() + " " + Value.str(), T); + T = IsCommon; + EdgeLabels[SourceSink] = NV; + } + SourceNode.addEdge(SinkNode, Value, T); + } + for (auto &I : Nodes) + I.finalize(*this); +} + +DotCfgDiffDisplayGraph DotCfgDiff::createDisplayGraph(StringRef Title, + StringRef EntryNodeName) { + assert(NodePosition.count(EntryNodeName) == 1 && + "Expected to find entry block in map."); + unsigned Entry = NodePosition[EntryNodeName]; + assert(Entry < Nodes.size() && "Expected to find entry node"); + DotCfgDiffDisplayGraph G(Title.str()); + + std::map NodeMap; + + int EntryIndex = -1; + unsigned Index = 0; + for (auto &I : Nodes) { + if (I.getIndex() == Entry) + EntryIndex = Index; + G.createNode(I.getBodyContent(), I.getType()); + NodeMap.insert({I.getIndex(), Index++}); + } + assert(EntryIndex >= 0 && "Expected entry node index to be set."); + G.setEntryNode(EntryIndex); + + for (auto &I : NodeMap) { + unsigned SourceNode = I.first; + unsigned DisplayNode = I.second; + getNode(SourceNode).createDisplayEdges(G, DisplayNode, NodeMap); + } + return G; +} + +void DotCfgDiffNode::createDisplayEdges( + DotCfgDiffDisplayGraph &DisplayGraph, unsigned DisplayNodeIndex, + std::map &NodeMap) { + + DisplayNode &SourceDisplayNode = DisplayGraph.getNode(DisplayNodeIndex); + + for (auto I : Edges) { + unsigned SinkNodeIndex = I; + IRChangeDiffType Type = getEdge(SinkNodeIndex).second; + const DotCfgDiffNode *SinkNode = &Graph.getNode(SinkNodeIndex); + + StringRef Label = Graph.getEdgeSourceLabel(getIndex(), SinkNodeIndex); + DisplayNode &SinkDisplayNode = DisplayGraph.getNode(SinkNode->getIndex()); + SourceDisplayNode.createEdge(Label, SinkDisplayNode, Type); + } + SourceDisplayNode.createEdgeMap(); +} + +// TODO Nodes should probably be a StringMap after the +// display graph is separated out, which would remove the need for NodePosition. +std::vector DotCfgDiff::Nodes; +StringMap DotCfgDiff::NodePosition; +std::string DotCfgDiff::GraphName; + +void DotCfgDiffNode::finalize(DotCfgDiff &G) { + for (auto E : EdgesMap) { + Children.emplace_back(E.first); + Edges.emplace_back(E.first); + } +} + +} // namespace + +namespace llvm { + +template <> struct GraphTraits { + using NodeRef = const DisplayNode *; + using ChildIteratorType = DisplayNode::ChildIterator; + using nodes_iterator = DotCfgDiffDisplayGraph::NodeIterator; + using EdgeRef = const DisplayEdge *; + using ChildEdgeIterator = DisplayNode::EdgeIterator; + + static NodeRef getEntryNode(const DotCfgDiffDisplayGraph *G) { + return G->getEntryNode(); + } + static ChildIteratorType child_begin(NodeRef N) { + return N->children_begin(); + } + static ChildIteratorType child_end(NodeRef N) { return N->children_end(); } + static nodes_iterator nodes_begin(const DotCfgDiffDisplayGraph *G) { + return G->nodes_begin(); + } + static nodes_iterator nodes_end(const DotCfgDiffDisplayGraph *G) { + return G->nodes_end(); + } + static ChildEdgeIterator child_edge_begin(NodeRef N) { + return N->edges_begin(); + } + static ChildEdgeIterator child_edge_end(NodeRef N) { return N->edges_end(); } + static NodeRef edge_dest(EdgeRef E) { return &E->getDestinationNode(); } + static unsigned size(const DotCfgDiffDisplayGraph *G) { return G->size(); } +}; + +template <> +struct DOTGraphTraits : public DefaultDOTGraphTraits { + explicit DOTGraphTraits(bool simple = false) + : DefaultDOTGraphTraits(simple) {} + + static bool renderNodeUsingHTML(const DisplayNode *Node) { return true; } + static std::string getGraphName(const DotCfgDiffDisplayGraph *DiffData) { + return DiffData->getGraphName(); + } + static std::string + getGraphProperties(const DotCfgDiffDisplayGraph *DiffData) { + return "\tsize=\"190, 190\";\n"; + } + static std::string getNodeLabel(const DisplayNode *Node, + const DotCfgDiffDisplayGraph *DiffData) { + return DiffData->getNodeLabel(*Node); + } + static std::string getNodeAttributes(const DisplayNode *Node, + const DotCfgDiffDisplayGraph *DiffData) { + return DiffData->getNodeAttributes(*Node); + } + static std::string getEdgeSourceLabel(const DisplayNode *From, + DisplayNode::ChildIterator &To) { + return From->getEdgeSourceLabel(**To); + } + static std::string getEdgeAttributes(const DisplayNode *From, + DisplayNode::ChildIterator &To, + const DotCfgDiffDisplayGraph *DiffData) { + return DiffData->getEdgeColorAttr(*From, **To); + } +}; + +} // namespace llvm + +namespace { + +void DotCfgDiffDisplayGraph::generateDotFile(StringRef DotFile) { + std::error_code EC; + llvm::raw_fd_ostream OutStream(DotFile, EC); + if (EC) { + errs() << "Error: " << EC.message() << "\n"; + return; + } + GraphWriter W(OutStream, this, false); + W.writeGraph(""); + OutStream.flush(); + OutStream.close(); +} + +} // namespace + +namespace llvm { + +BlockSuccessors::BlockSuccessors(const BasicBlock &B) { + // Build up transition labels. + const Instruction *Term = B.getTerminator(); + if (const BranchInst *Br = dyn_cast(Term)) + if (Br->isUnconditional()) + addSuccessorLabel(Br->getSuccessor(0)->getName().str(), ""); + else { + addSuccessorLabel(Br->getSuccessor(0)->getName().str(), "true"); + addSuccessorLabel(Br->getSuccessor(1)->getName().str(), "false"); + } + else if (const SwitchInst *Sw = dyn_cast(Term)) { + addSuccessorLabel(Sw->case_default()->getCaseSuccessor()->getName().str(), + "default"); + for (auto &C : Sw->cases()) { + assert(C.getCaseValue() && "Expected to find case value."); + SmallString<20> Value = formatv("{0}", C.getCaseValue()->getSExtValue()); + addSuccessorLabel(C.getCaseSuccessor()->getName().str(), Value); + } + } else + for (const_succ_iterator I = succ_begin(&B), E = succ_end(&B); I != E; ++I) + addSuccessorLabel((*I)->getName().str(), ""); +} + +std::string DotCfgComparer::makeHTMLReady(StringRef SR) { + std::string S; + while (true) { + StringRef Clean = + SR.take_until([](char C) { return C == '<' || C == '>'; }); + S.append(Clean.str()); + SR = SR.drop_front(Clean.size()); + if (SR.size() == 0) + return S; + S.append(SR[0] == '<' ? "<" : ">"); + SR = SR.drop_front(); + } + llvm_unreachable("problems converting string to HTML"); +} + +DotCfgComparer::DotCfgComparer(raw_fd_ostream &OS, unsigned N, StringRef Dir, + const BlockSuccessorsIRData &Before, + const BlockSuccessorsIRData &After) + : IRComparer(Before, After), Directory(Dir.str()), HTML(OS), N(N) { + // Set up the colours based on the hidden options. + Colours[InBefore] = BeforeColour; + Colours[InAfter] = AfterColour; + Colours[IsCommon] = CommonColour; +} + +void DotCfgComparer::handleFunctionCompare( + StringRef Name, StringRef Prefix, StringRef PassID, bool InModule, + unsigned Minor, const BlockSuccessorsFuncData &Before, + const BlockSuccessorsFuncData &After) { + SmallString<8> Extender; + SmallString<8> Number; + // Handle numbering and file names. + if (InModule) { + Extender = formatv("{0}_{1}", N, Minor); + Number = formatv("{0}.{1}", N, Minor); + } else { + Extender = formatv("{0}", N); + Number = formatv("{0}", N); + } + // Create a temporary file name for the dot file. + SmallVector SV; + sys::fs::createUniquePath("cfgdot-%%%%%%.dot", SV, true); + std::string DotFile = Twine(SV).str(); + + SmallString<20> PDFFileName = formatv("diff_{0}.pdf", Extender); + SmallString<200> Text; + + Text = formatv("{0}.{1}{2} {3}", Number, Prefix, makeHTMLReady(PassID), Name); + + DotCfgDiff Diff(Text, Before, After, Directory); + std::string EntryBlockName = After.getEntryBlockName(); + // Use the before entry block if the after entry block was removed. + if (EntryBlockName == "") + EntryBlockName = Before.getEntryBlockName(); + assert(EntryBlockName != "" && "Expected to find entry block"); + + DotCfgDiffDisplayGraph DG = Diff.createDisplayGraph(Text, EntryBlockName); + DG.generateDotFile(DotFile); + + HTML << genHTML(Text, DotFile, PDFFileName, Directory, InModule); + std::error_code EC = sys::fs::remove(DotFile); + if (EC) + errs() << "Error: " << EC.message() << "\n"; +} + +std::string DotCfgComparer::genHTML(StringRef Text, StringRef DotFile, + StringRef PDFFileName, StringRef Directory, + bool Indent) { + SmallString<20> PDFFile = formatv("{0}/{1}", Directory, PDFFileName); + // Create the PDF file. + SmallString<200> Command = formatv("dot -Tpdf -o {0} {1}", PDFFile, DotFile); + system(Command.c_str()); + // Create the HTML tag refering to the PDF file. + SmallString<200> S = + formatv("
{1}
\n", + PDFFileName, Text, Indent ? "style=\"margin-left:2em\" " : ""); + return S.c_str(); +} + +void DotCfgChangeReporter::handleInitialIR(Any IR) { + assert(HTML && "Expected outstream to be set"); + *HTML << "\n" + << "
\n" + << "

\n"; + // Create representation of IR + BlockSuccessorsIRData Data; + DotCfgComparer::analyzeIR(IR, Data); + // Now compare it against itself, which will have everything the + // same and will generate the files. + DotCfgComparer Comparer(*HTML, N, DotCfgDir, Data, Data); + Comparer.compare(IR, " ", "Initial IR", ""); + *HTML << "

\n" + << "

\n"; + ++N; +} + +void DotCfgChangeReporter::generateIRRepresentation( + Any IR, StringRef PassID, BlockSuccessorsIRData &Data) { + DotCfgComparer::analyzeIR(IR, Data); +} + +void DotCfgChangeReporter::omitAfter(StringRef PassID, std::string &Name) { + assert(HTML && "Expected outstream to be set"); + SmallString<20> Banner = + formatv(" {0}. After {1}{2} omitted because no change
\n", N, + DotCfgComparer::makeHTMLReady(PassID), Name); + *HTML << Banner; + ++N; +} + +void DotCfgChangeReporter::handleAfter(StringRef PassID, std::string &Name, + const BlockSuccessorsIRData &Before, + const BlockSuccessorsIRData &After, + Any IR) { + assert(HTML && "Expected outstream to be set"); + DotCfgComparer Comparer(*HTML, N, DotCfgDir, Before, After); + Comparer.compare(IR, " After ", PassID, Name); + *HTML << "

\n"; + ++N; +} + +void DotCfgChangeReporter::handleInvalidated(StringRef PassID) { + assert(HTML && "Expected outstream to be set"); + SmallString<20> Banner = formatv(" {0}. {1} invalidated
\n", N, + DotCfgComparer::makeHTMLReady(PassID)); + *HTML << Banner; + ++N; +} + +void DotCfgChangeReporter::handleFiltered(StringRef PassID, std::string &Name) { + assert(HTML && "Expected outstream to be set"); + SmallString<20> Banner = + formatv(" {0}. After {1}{2} filtered out
\n", N, + DotCfgComparer::makeHTMLReady(PassID), Name); + *HTML << Banner; + ++N; +} + +void DotCfgChangeReporter::handleIgnored(StringRef PassID, std::string &Name) { + assert(HTML && "Expected outstream to be set"); + SmallString<20> Banner = formatv(" {0}. {1}{2} ignored
\n", N, + DotCfgComparer::makeHTMLReady(PassID), Name); + *HTML << Banner; + ++N; +} + +bool DotCfgChangeReporter::same(const BlockSuccessorsIRData &Before, + const BlockSuccessorsIRData &After) { + return Before == After; +} + +void DotCfgChangeReporter::initializeHTML() { + std::error_code EC; + HTML = new raw_fd_ostream(DotCfgDir + "/passes.html", EC); + assert(!EC && "Unable to open output stream for -cfg-dot-changed"); + + *HTML << "" + << "" + << "" + << "" + << "passes.html" + << "\n" + << ""; +} + +DotCfgChangeReporter::~DotCfgChangeReporter() { + if (!HTML) + return; + *HTML + << "" + << "" + << "\n"; + HTML->flush(); + HTML->close(); + delete HTML; +} + +void DotCfgChangeReporter::registerCallbacks( + PassInstrumentationCallbacks &PIC) { + if (PrintChanged == ChangePrinter::PrintChangedDotCfgVerbose || + PrintChanged == ChangePrinter::PrintChangedDotCfgQuiet) { + ChangeReporter::registerRequiredCallbacks(PIC); + initializeHTML(); + } +} + StandardInstrumentations::StandardInstrumentations(bool DebugLogging, bool VerifyEach) : PrintPass(DebugLogging), OptNone(DebugLogging), PrintChangedIR(PrintChanged == ChangePrinter::PrintChangedVerbose), PrintChangedDiff(PrintChanged == ChangePrinter::PrintChangedDiffVerbose), - Verify(DebugLogging), VerifyEach(VerifyEach) {} + Verify(DebugLogging), + DotCfgChangeReporter(PrintChanged == + ChangePrinter::PrintChangedDotCfgVerbose), + VerifyEach(VerifyEach) {} void StandardInstrumentations::registerCallbacks( PassInstrumentationCallbacks &PIC) { @@ -1174,14 +2122,17 @@ if (VerifyEach) Verify.registerCallbacks(PIC); PrintChangedDiff.registerCallbacks(PIC); + DotCfgChangeReporter.registerCallbacks(PIC); } -namespace llvm { - template class ChangeReporter; template class TextChangeReporter; +template class BlockDataTemplate; +template class OrderedChangedData; +template class OrderedChangedData; template class ChangeReporter; template class TextChangeReporter; +template class IRComparer; } // namespace llvm Index: llvm/test/Other/ChangePrinters/print-changed-dot-cfg.ll =================================================================== --- /dev/null +++ llvm/test/Other/ChangePrinters/print-changed-dot-cfg.ll @@ -0,0 +1,332 @@ +; Simple checks of -print-changed=dot-cfg +; +; Note that (mostly) only the banners are checked. +; +; Simple functionality check. +; RUN: rm -f diff_*.pdf +; RUN: opt -S -print-changed=dot-cfg -passes=instsimplify 2>&1 -o /dev/null < %s 2>&1 > /dev/null +; RUN: ls *.pdf | count 4 +; RUN: FileCheck %s -input-file=passes.html --check-prefix=CHECK-DOT-CFG-SIMPLE +; +; Check that only the passes that change the IR are printed and that the +; others (including g) are filtered out. +; RUN: rm -f diff_*.pdf +; RUN: opt -S -print-changed=dot-cfg -passes=instsimplify -filter-print-funcs=f 2>&1 -o /dev/null < %s 2>&1 > /dev/null +; RUN: ls *.pdf | count 2 +; RUN: FileCheck %s -input-file=passes.html --check-prefix=CHECK-DOT-CFG-FUNC-FILTER +; +; Check that the reporting of IRs respects is not affected by +; -print-module-scope +; RUN: rm -f diff_*.pdf +; RUN: opt -S -print-changed=dot-cfg -passes=instsimplify -print-module-scope 2>&1 -o /dev/null < %s 2>&1 > /dev/null +; RUN: ls *.pdf | count 4 +; RUN: FileCheck %s -input-file=passes.html --check-prefix=CHECK-DOT-CFG-PRINT-MOD-SCOPE +; +; Check that reporting of multiple functions happens +; RUN: rm -f diff_*.pdf +; RUN: opt -S -print-changed=dot-cfg -passes=instsimplify -filter-print-funcs="f,g" 2>&1 -o /dev/null < %s 2>&1 > /dev/null +; RUN: ls *.pdf | count 4 +; RUN: FileCheck %s -input-file=passes.html --check-prefix=CHECK-DOT-CFG-FILTER-MULT-FUNC +; +; Check that the reporting of IRs respects -filter-passes +; RUN: rm -f diff_*.pdf +; RUN: opt -S -print-changed=dot-cfg -passes="instsimplify,no-op-function" -filter-passes="NoOpFunctionPass" 2>&1 -o /dev/null < %s 2>&1 > /dev/null +; RUN: ls *.pdf | count 1 +; RUN: FileCheck %s -input-file=passes.html --check-prefix=CHECK-DOT-CFG-FILTER-PASSES +; +; Check that the reporting of IRs respects -filter-passes with multiple passes +; RUN: rm -f diff_*.pdf +; RUN: opt -S -print-changed=dot-cfg -passes="instsimplify,no-op-function" -filter-passes="NoOpFunctionPass,InstSimplifyPass" 2>&1 -o /dev/null < %s 2>&1 > /dev/null +; RUN: ls *.pdf | count 3 +; RUN: FileCheck %s -input-file=passes.html --check-prefix=CHECK-DOT-CFG-FILTER-MULT-PASSES +; +; Check that the reporting of IRs respects both -filter-passes and -filter-print-funcs +; RUN: rm -f diff_*.pdf +; RUN: opt -S -print-changed=dot-cfg -passes="instsimplify,no-op-function" -filter-passes="NoOpFunctionPass,InstSimplifyPass" -filter-print-funcs=f 2>&1 -o /dev/null < %s 2>&1 > /dev/null +; RUN: ls *.pdf | count 2 +; RUN: FileCheck %s -input-file=passes.html --check-prefix=CHECK-DOT-CFG-FILTER-FUNC-PASSES +; +; Check that repeated passes that change the IR are printed and that the +; others (including g) are filtered out. Note that only the first time +; instsimplify is run on f will result in changes +; RUN: rm -f diff_*.pdf +; RUN: opt -S -print-changed=dot-cfg -passes="instsimplify,instsimplify" -filter-print-funcs=f 2>&1 -o /dev/null < %s 2>&1 > /dev/null +; RUN: ls *.pdf | count 2 +; RUN: FileCheck %s -input-file=passes.html --check-prefix=CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC +; +; Simple checks of -print-changed=dot-cfg-quiet +; +; Note that (mostly) only the banners are checked. +; +; Simple functionality check. +; RUN: rm -f diff_*.pdf +; RUN: opt -S -print-changed=dot-cfg-quiet -passes=instsimplify 2>&1 -o /dev/null < %s 2>&1 > /dev/null +; RUN: ls *.pdf | count 2 +; RUN: FileCheck %s -input-file=passes.html --check-prefix=CHECK-DOT-CFG-QUIET-SIMPLE --allow-empty +; +; Check that only the passes that change the IR are printed and that the +; others (including g) are filtered out. +; RUN: rm -f diff_*.pdf +; RUN: opt -S -print-changed=dot-cfg-quiet -passes=instsimplify -filter-print-funcs=f 2>&1 -o /dev/null < %s 2>&1 > /dev/null +; RUN: ls *.pdf | count 1 +; RUN: FileCheck %s -input-file=passes.html --check-prefix=CHECK-DOT-CFG-QUIET-FUNC-FILTER +; +; Check that the reporting of IRs respects is not affected by +; -print-module-scope +; RUN: rm -f diff_*.pdf +; RUN: opt -S -print-changed=dot-cfg-quiet -passes=instsimplify -print-module-scope 2>&1 -o /dev/null < %s 2>&1 > /dev/null +; RUN: ls *.pdf | count 2 +; RUN: FileCheck %s -input-file=passes.html --check-prefix=CHECK-DOT-CFG-QUIET-PRINT-MOD-SCOPE +; +; Check that reporting of multiple functions happens +; RUN: rm -f diff_*.pdf +; RUN: opt -S -print-changed=dot-cfg-quiet -passes=instsimplify -filter-print-funcs="f,g" 2>&1 -o /dev/null < %s 2>&1 > /dev/null +; RUN: ls *.pdf | count 2 +; RUN: FileCheck %s -input-file=passes.html --check-prefix=CHECK-DOT-CFG-QUIET-FILTER-MULT-FUNC +; +; Check that the reporting of IRs respects -filter-passes +; RUN: rm -f diff_*.pdf +; RUN: opt -S -print-changed=dot-cfg-quiet -passes="instsimplify,no-op-function" -filter-passes="NoOpFunctionPass" 2>&1 -o /dev/null < %s 2>&1 > /dev/null +; RUN: FileCheck %s -input-file=passes.html --check-prefix=CHECK-DOT-CFG-QUIET-FILTER-PASSES-NONE --allow-empty +; +; Check that the reporting of IRs respects -filter-passes with multiple passes +; RUN: rm -f diff_*.pdf +; RUN: opt -S -print-changed=dot-cfg-quiet -passes="instsimplify,no-op-function" -filter-passes="NoOpFunctionPass,InstSimplifyPass" 2>&1 -o /dev/null < %s 2>&1 > /dev/null +; RUN: ls *.pdf | count 2 +; RUN: FileCheck %s -input-file=passes.html --check-prefix=CHECK-DOT-CFG-QUIET-FILTER-MULT-PASSES +; +; Check that the reporting of IRs respects both -filter-passes and -filter-print-funcs +; RUN: rm -f diff_*.pdf +; RUN: opt -S -print-changed=dot-cfg-quiet -passes="instsimplify,no-op-function" -filter-passes="NoOpFunctionPass,InstSimplifyPass" -filter-print-funcs=f 2>&1 -o /dev/null < %s 2>&1 > /dev/null +; RUN: ls *.pdf | count 1 +; RUN: FileCheck %s -input-file=passes.html --check-prefix=CHECK-DOT-CFG-QUIET-FILTER-FUNC-PASSES +; +; Check that repeated passes that change the IR are printed and that the +; others (including g) are filtered out. Note that only the first time +; instsimplify is run on f will result in changes +; RUN: rm -f diff_*.pdf +; RUN: opt -S -print-changed=dot-cfg-quiet -passes="instsimplify,instsimplify" -filter-print-funcs=f 2>&1 -o /dev/null < %s 2>&1 > /dev/null +; RUN: ls *.pdf | count 1 +; RUN: FileCheck %s -input-file=passes.html --check-prefix=CHECK-DOT-CFG-QUIET-MULT-PASSES-FILTER-FUNC + +define i32 @g() { +entry: + %a = add i32 2, 3 + ret i32 %a +} + +define i32 @f() { +entry: + %a = add i32 2, 3 + ret i32 %a +} + +; CHECK-DOT-CFG-SIMPLE-FILES: passes.html diff_0_0.pdf diff_0_1.pdf diff_2.pdf diff_4.pdf +; CHECK-DOT-CFG-SIMPLE: passes.html +; CHECK-DOT-CFG-SIMPLE-NEXT: +; CHECK-DOT-CFG-SIMPLE-NEXT:
+; CHECK-DOT-CFG-SIMPLE-NEXT:

+; CHECK-DOT-CFG-SIMPLE-NEXT: 0.0. Initial IR
+; CHECK-DOT-CFG-SIMPLE-NEXT: 0.1. Initial IR
+; CHECK-DOT-CFG-SIMPLE-NEXT:

+; CHECK-DOT-CFG-SIMPLE-NEXT:

+; CHECK-DOT-CFG-SIMPLE-NEXT: 1. After VerifierPass (module) omitted because no change
+; CHECK-DOT-CFG-SIMPLE-NEXT: 2. After InstSimplifyPass (function: g)
+; CHECK-DOT-CFG-SIMPLE-NEXT:

+; CHECK-DOT-CFG-SIMPLE-NEXT: 3. PassManager<llvm::Function> (function: g) ignored
+; CHECK-DOT-CFG-SIMPLE-NEXT: 4. After InstSimplifyPass (function: f)
+; CHECK-DOT-CFG-SIMPLE-NEXT:

+; CHECK-DOT-CFG-SIMPLE-NEXT: 5. PassManager<llvm::Function> (function: f) ignored
+; CHECK-DOT-CFG-SIMPLE-NEXT: 6. ModuleToFunctionPassAdaptor (module) ignored
+; CHECK-DOT-CFG-SIMPLE-NEXT: 7. After VerifierPass (module) omitted because no change
+; CHECK-DOT-CFG-SIMPLE-NEXT: 8. After PrintModulePass (module) omitted because no change
+; CHECK-DOT-CFG-SIMPLE-NEXT: + +; CHECK-DOT-CFG-FUNC-FILTER: passes.html +; CHECK-DOT-CFG-FUNC-FILTER-NEXT: +; CHECK-DOT-CFG-FUNC-FILTER-NEXT:
+; CHECK-DOT-CFG-FUNC-FILTER-NEXT:

+; CHECK-DOT-CFG-FUNC-FILTER-NEXT: 0.0. Initial IR
+; CHECK-DOT-CFG-FUNC-FILTER-NEXT:

+; CHECK-DOT-CFG-FUNC-FILTER-NEXT:

+; CHECK-DOT-CFG-FUNC-FILTER-NEXT: 1. After VerifierPass (module) omitted because no change
+; CHECK-DOT-CFG-FUNC-FILTER-NEXT: 2. After InstSimplifyPass (function: g) filtered out
+; CHECK-DOT-CFG-FUNC-FILTER-NEXT: 3. PassManager<llvm::Function> (function: g) ignored
+; CHECK-DOT-CFG-FUNC-FILTER-NEXT: 4. After InstSimplifyPass (function: f)
+; CHECK-DOT-CFG-FUNC-FILTER-NEXT:

+; CHECK-DOT-CFG-FUNC-FILTER-NEXT: 5. PassManager<llvm::Function> (function: f) ignored
+; CHECK-DOT-CFG-FUNC-FILTER-NEXT: 6. ModuleToFunctionPassAdaptor (module) ignored
+; CHECK-DOT-CFG-FUNC-FILTER-NEXT: 7. After VerifierPass (module) omitted because no change
+; CHECK-DOT-CFG-FUNC-FILTER-NEXT: 8. After PrintModulePass (module) omitted because no change
+; CHECK-DOT-CFG-FUNC-FILTER-NEXT: + +; CHECK-DOT-CFG-PRINT-MOD-SCOPE: passes.html +; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT: +; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT:
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT:

+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT: 0.0. Initial IR
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT: 0.1. Initial IR
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT:

+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT:

+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT: 1. After VerifierPass (module) omitted because no change
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT: 2. After InstSimplifyPass (function: g)
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT:

+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT: 3. PassManager<llvm::Function> (function: g) ignored
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT: 4. After InstSimplifyPass (function: f)
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT:

+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT: 5. PassManager<llvm::Function> (function: f) ignored
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT: 6. ModuleToFunctionPassAdaptor (module) ignored
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT: 7. After VerifierPass (module) omitted because no change
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT: 8. After PrintModulePass (module) omitted because no change
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT: + +; CHECK-DOT-CFG-FILTER-MULT-FUNC: passes.html +; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT: +; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT:
+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT:

+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT: 0.0. Initial IR
+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT: 0.1. Initial IR
+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT:

+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT:

+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT: 1. After VerifierPass (module) omitted because no change
+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT: 2. After InstSimplifyPass (function: g)
+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT:

+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT: 3. PassManager<llvm::Function> (function: g) ignored
+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT: 4. After InstSimplifyPass (function: f)
+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT:

+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT: 5. PassManager<llvm::Function> (function: f) ignored
+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT: 6. ModuleToFunctionPassAdaptor (module) ignored
+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT: 7. After VerifierPass (module) omitted because no change
+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT: 8. After PrintModulePass (module) omitted because no change
+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT: + +; CHECK-DOT-CFG-FILTER-PASSES: passes.html +; CHECK-DOT-CFG-FILTER-PASSES-NEXT: 0. After VerifierPass (module) filtered out
+; CHECK-DOT-CFG-FILTER-PASSES-NEXT: 1. After InstSimplifyPass (function: g) filtered out
+; CHECK-DOT-CFG-FILTER-PASSES-NEXT: +; CHECK-DOT-CFG-FILTER-PASSES-NEXT:
+; CHECK-DOT-CFG-FILTER-PASSES-NEXT:

+; CHECK-DOT-CFG-FILTER-PASSES-NEXT: 2. Initial IR
+; CHECK-DOT-CFG-FILTER-PASSES-NEXT:

+; CHECK-DOT-CFG-FILTER-PASSES-NEXT:

+; CHECK-DOT-CFG-FILTER-PASSES-NEXT: 3. After NoOpFunctionPass (function: g) omitted because no change
+; CHECK-DOT-CFG-FILTER-PASSES-NEXT: 4. PassManager<llvm::Function> (function: g) ignored
+; CHECK-DOT-CFG-FILTER-PASSES-NEXT: 5. After InstSimplifyPass (function: f) filtered out
+; CHECK-DOT-CFG-FILTER-PASSES-NEXT: 6. After NoOpFunctionPass (function: f) omitted because no change
+; CHECK-DOT-CFG-FILTER-PASSES-NEXT: 7. PassManager<llvm::Function> (function: f) ignored
+; CHECK-DOT-CFG-FILTER-PASSES-NEXT: 8. ModuleToFunctionPassAdaptor (module) ignored
+; CHECK-DOT-CFG-FILTER-PASSES-NEXT: 9. After VerifierPass (module) filtered out
+; CHECK-DOT-CFG-FILTER-PASSES-NEXT: 10. After PrintModulePass (module) filtered out
+; CHECK-DOT-CFG-FILTER-PASSES-NEXT: + + + +; CHECK-DOT-CFG-FILTER-MULT-PASSES: passes.html +; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT: 0. After VerifierPass (module) filtered out
+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT: +; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT:
+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT:

+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT: 1. Initial IR
+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT:

+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT:

+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT: 2. After InstSimplifyPass (function: g)
+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT:

+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT: 3. After NoOpFunctionPass (function: g) omitted because no change
+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT: 4. PassManager<llvm::Function> (function: g) ignored
+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT: 5. After InstSimplifyPass (function: f)
+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT:

+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT: 6. After NoOpFunctionPass (function: f) omitted because no change
+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT: 7. PassManager<llvm::Function> (function: f) ignored
+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT: 8. ModuleToFunctionPassAdaptor (module) ignored
+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT: 9. After VerifierPass (module) filtered out
+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT: 10. After PrintModulePass (module) filtered out
+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT: + + + +; CHECK-DOT-CFG-FILTER-FUNC-PASSES: passes.html +; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT: 0. After VerifierPass (module) filtered out
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT: 1. After InstSimplifyPass (function: g) filtered out
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT: 2. After NoOpFunctionPass (function: g) filtered out
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT: 3. PassManager<llvm::Function> (function: g) ignored
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT: +; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT:
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT:

+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT: 4. Initial IR
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT:

+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT:

+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT: 5. After InstSimplifyPass (function: f)
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT:

+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT: 6. After NoOpFunctionPass (function: f) omitted because no change
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT: 7. PassManager<llvm::Function> (function: f) ignored
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT: 8. ModuleToFunctionPassAdaptor (module) ignored
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT: 9. After VerifierPass (module) filtered out
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT: 10. After PrintModulePass (module) filtered out
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT: + + +; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC: passes.html +; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT: +; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT:
+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT:

+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT: 0.0. Initial IR
+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT:

+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT:

+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT: 1. After VerifierPass (module) omitted because no change
+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT: 2. After InstSimplifyPass (function: g) filtered out
+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT: 3. After InstSimplifyPass (function: g) filtered out
+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT: 4. PassManager<llvm::Function> (function: g) ignored
+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT: 5. After InstSimplifyPass (function: f)
+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT:

+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT: 6. After InstSimplifyPass (function: f) omitted because no change
+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT: 7. PassManager<llvm::Function> (function: f) ignored
+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT: 8. ModuleToFunctionPassAdaptor (module) ignored
+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT: 9. After VerifierPass (module) omitted because no change
+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT: 10. After PrintModulePass (module) omitted because no change
+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT: + +; CHECK-DOT-CFG-QUIET-SIMPLE: passes.html +; CHECK-DOT-CFG-QUIET-SIMPLE-NEXT: 0. After InstSimplifyPass (function: g)
+; CHECK-DOT-CFG-QUIET-SIMPLE-NEXT:

+; CHECK-DOT-CFG-QUIET-SIMPLE-NEXT: 1. After InstSimplifyPass (function: f)
+; CHECK-DOT-CFG-QUIET-SIMPLE-NEXT:

+; CHECK-DOT-CFG-QUIET-SIMPLE-NEXT: + +; CHECK-DOT-CFG-QUIET-FUNC-FILTER: passes.html +; CHECK-DOT-CFG-QUIET-FUNC-FILTER-NEXT: 0. After InstSimplifyPass (function: f)
+; CHECK-DOT-CFG-QUIET-FUNC-FILTER-NEXT:

+; CHECK-DOT-CFG-QUIET-FUNC-FILTER-NEXT: + +; CHECK-DOT-CFG-QUIET-PRINT-MOD-SCOPE: passes.html +; CHECK-DOT-CFG-QUIET-PRINT-MOD-SCOPE-NEXT: 0. After InstSimplifyPass (function: g)
+; CHECK-DOT-CFG-QUIET-PRINT-MOD-SCOPE-NEXT:

+; CHECK-DOT-CFG-QUIET-PRINT-MOD-SCOPE-NEXT: 1. After InstSimplifyPass (function: f)
+; CHECK-DOT-CFG-QUIET-PRINT-MOD-SCOPE-NEXT:

+; CHECK-DOT-CFG-QUIET-PRINT-MOD-SCOPE-NEXT: + +; CHECK-DOT-CFG-QUIET-FILTER-MULT-FUNC: passes.html +; CHECK-DOT-CFG-QUIET-FILTER-MULT-FUNC-NEXT: 0. After InstSimplifyPass (function: g)
+; CHECK-DOT-CFG-QUIET-FILTER-MULT-FUNC-NEXT:

+; CHECK-DOT-CFG-QUIET-FILTER-MULT-FUNC-NEXT: 1. After InstSimplifyPass (function: f)
+; CHECK-DOT-CFG-QUIET-FILTER-MULT-FUNC-NEXT:

+; CHECK-DOT-CFG-QUIET-FILTER-MULT-FUNC-NEXT: + +; CHECK-DOT-CFG-QUIET-FILTER-PASSES-NONE: passes.html +; CHECK-DOT-CFG-QUIET-FILTER-PASSES-NONE-NEXT: + +; CHECK-DOT-CFG-QUIET-FILTER-MULT-PASSES: passes.html +; CHECK-DOT-CFG-QUIET-FILTER-MULT-PASSES-NEXT: 0. After InstSimplifyPass (function: g)
+; CHECK-DOT-CFG-QUIET-FILTER-MULT-PASSES-NEXT:

+; CHECK-DOT-CFG-QUIET-FILTER-MULT-PASSES-NEXT: 1. After InstSimplifyPass (function: f)
+; CHECK-DOT-CFG-QUIET-FILTER-MULT-PASSES-NEXT:

+; CHECK-DOT-CFG-QUIET-FILTER-MULT-PASSES-NEXT: + +; CHECK-DOT-CFG-QUIET-FILTER-FUNC-PASSES: passes.html +; CHECK-DOT-CFG-QUIET-FILTER-FUNC-PASSES-NEXT: 0. After InstSimplifyPass (function: f)
+; CHECK-DOT-CFG-QUIET-FILTER-FUNC-PASSES-NEXT:

+; CHECK-DOT-CFG-QUIET-FILTER-FUNC-PASSES-NEXT: + +; CHECK-DOT-CFG-QUIET-MULT-PASSES-FILTER-FUNC: passes.html +; CHECK-DOT-CFG-QUIET-MULT-PASSES-FILTER-FUNC-NEXT: 0. After InstSimplifyPass (function: f)
+; CHECK-DOT-CFG-QUIET-MULT-PASSES-FILTER-FUNC-NEXT:

+; CHECK-DOT-CFG-QUIET-MULT-PASSES-FILTER-FUNC-NEXT: