diff --git a/llvm/include/llvm/Passes/StandardInstrumentations.h b/llvm/include/llvm/Passes/StandardInstrumentations.h --- a/llvm/include/llvm/Passes/StandardInstrumentations.h +++ b/llvm/include/llvm/Passes/StandardInstrumentations.h @@ -333,7 +333,16 @@ // The data saved for comparing functions. template -class FuncDataT : public OrderedChangedData> {}; +class FuncDataT : public OrderedChangedData> { +public: + FuncDataT(std::string S) : EntryBlockName(S) {} + + // Return the name of the entry block + std::string getEntryBlockName() const { return EntryBlockName; } + +protected: + std::string EntryBlockName; +}; // The data saved for comparing IRs. template @@ -350,7 +359,6 @@ // Compare the 2 IRs. \p handleFunctionCompare is called to handle the // compare of a function. When \p InModule is set, // this function is being handled as part of comparing a module. - void compare( bool CompareModule, std::function::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; +}; + +// 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); + ~DotCfgChangeReporter() override; + void registerCallbacks(PassInstrumentationCallbacks &PIC); + +protected: + // Initialize the HTML file and output the header. + bool initializeHTML(); + + // Called on the first IR processed. + void handleInitialIR(Any IR) override; + // Called before and after a pass to get the representation of the IR. + void generateIRRepresentation(Any IR, StringRef PassID, + IRDataT &Output) override; + // Called when the pass is not iteresting. + void omitAfter(StringRef PassID, std::string &Name) override; + // Called when an interesting IR has changed. + void handleAfter(StringRef PassID, std::string &Name, + const IRDataT &Before, const IRDataT &After, + Any) override; + // Called when an interesting pass is invalidated. + void handleInvalidated(StringRef PassID) override; + // Called when the IR or pass is not interesting. + void handleFiltered(StringRef PassID, std::string &Name) override; + // Called when an ignored pass is encountered. + void handleIgnored(StringRef PassID, std::string &Name) 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. + static std::string genHTML(StringRef Text, StringRef DotFile, + StringRef PDFFileName); + + void handleFunctionCompare(StringRef Name, StringRef Prefix, StringRef PassID, + StringRef Divider, bool InModule, unsigned Minor, + const FuncDataT &Before, + const FuncDataT &After); + + unsigned N = 0; + std::unique_ptr HTML; +}; + /// This class provides an interface to register all the standard pass /// instrumentations and manages their state (if any). class StandardInstrumentations { @@ -420,6 +503,7 @@ IRChangedPrinter PrintChangedIR; PseudoProbeVerifier PseudoProbeVerification; InLineChangePrinter PrintChangedDiff; + DotCfgChangeReporter WebsiteChangeReporter; VerifyInstrumentation Verify; bool VerifyEach; diff --git a/llvm/include/llvm/Support/DOTGraphTraits.h b/llvm/include/llvm/Support/DOTGraphTraits.h --- a/llvm/include/llvm/Support/DOTGraphTraits.h +++ b/llvm/include/llvm/Support/DOTGraphTraits.h @@ -65,6 +65,11 @@ return false; } + // renderNodesUsingHTML - If the function returns true, nodes will be + // rendered using HTML-like labels which allows colors, etc in the nodes + // and the edge source labels. + static bool renderNodesUsingHTML() { return false; } + /// getNodeLabel - Given a node and a pointer to the top level graph, return /// the label to print in the node. template diff --git a/llvm/include/llvm/Support/GraphWriter.h b/llvm/include/llvm/Support/GraphWriter.h --- a/llvm/include/llvm/Support/GraphWriter.h +++ b/llvm/include/llvm/Support/GraphWriter.h @@ -66,6 +66,7 @@ class GraphWriter { raw_ostream &O; const GraphType &G; + bool RenderUsingHTML = false; using DOTTraits = DOTGraphTraits; using GTraits = GraphTraits; @@ -86,6 +87,9 @@ child_iterator EE = GTraits::child_end(Node); bool hasEdgeSourceLabels = false; + if (RenderUsingHTML) + O << ""; + for (unsigned i = 0; EI != EE && i != 64; ++EI, ++i) { std::string label = DTraits.getEdgeSourceLabel(Node, EI); @@ -94,14 +98,22 @@ hasEdgeSourceLabels = true; - if (i) - O << "|"; + if (RenderUsingHTML) + 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 (RenderUsingHTML) + O << "truncated..."; + else + O << "|truncated..."; + } return hasEdgeSourceLabels; } @@ -109,6 +121,7 @@ public: GraphWriter(raw_ostream &o, const GraphType &g, bool SN) : O(o), G(g) { DTraits = DOTTraits(SN); + RenderUsingHTML = DTraits.renderNodesUsingHTML(); } void writeGraph(const std::string &Title = "") { @@ -163,12 +176,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 (RenderUsingHTML) + O << "none,"; + else + O << "record,"; + if (!NodeAttributes.empty()) O << NodeAttributes << ","; - O << "label=\"{"; + O << "label="; + + if (RenderUsingHTML) { + // 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 +225,25 @@ bool hasEdgeSourceLabels = getEdgeSourceLabels(EdgeSourceLabels, Node); if (hasEdgeSourceLabels) { - if (!DTraits.renderGraphFromBottomUp()) O << "|"; - - O << "{" << EdgeSourceLabels.str() << "}"; - - if (DTraits.renderGraphFromBottomUp()) O << "|"; + if (!DTraits.renderGraphFromBottomUp()) + if (!RenderUsingHTML) + O << "|"; + + if (RenderUsingHTML) + O << EdgeSourceLabels.str(); + else + O << "{" << EdgeSourceLabels.str() << "}"; + + if (DTraits.renderGraphFromBottomUp()) + if (!RenderUsingHTML) + O << "|"; } if (DTraits.renderGraphFromBottomUp()) { - O << DOT::EscapeString(DTraits.getNodeLabel(Node, G)); + if (RenderUsingHTML) + 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 +265,17 @@ << DOT::EscapeString(DTraits.getEdgeDestLabel(Node, i)); } - if (i != e) - O << "|truncated..."; - O << "}"; + if (RenderUsingHTML) + O << ""; + else if (i != e) + O << "|truncated...}"; } - O << "}\"];\n"; // Finish printing the "node" line + if (RenderUsingHTML) + O << "
"; + } else + O << "\"{"; if (!DTraits.renderGraphFromBottomUp()) { - O << DOT::EscapeString(DTraits.getNodeLabel(Node, G)); + if (RenderUsingHTML) + 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); diff --git a/llvm/lib/Passes/StandardInstrumentations.cpp b/llvm/lib/Passes/StandardInstrumentations.cpp --- a/llvm/lib/Passes/StandardInstrumentations.cpp +++ b/llvm/lib/Passes/StandardInstrumentations.cpp @@ -29,10 +29,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/Regex.h" #include "llvm/Support/raw_ostream.h" +#include #include +#include #include using namespace llvm; @@ -79,7 +83,9 @@ PrintChangedDiffVerbose, PrintChangedDiffQuiet, PrintChangedColourDiffVerbose, - PrintChangedColourDiffQuiet + PrintChangedColourDiffQuiet, + PrintChangedDotCfgVerbose, + PrintChangedDotCfgQuiet }; static cl::opt PrintChanged( "print-changed", cl::desc("Print changed IRs"), cl::Hidden, @@ -95,6 +101,10 @@ "Display patch-like changes with color"), clEnumValN(ChangePrinter::PrintChangedColourDiffQuiet, "cdiff-quiet", "Display patch-like changes in quiet mode with color"), + 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, "", ""))); @@ -119,6 +129,40 @@ DiffBinary("print-changed-diff-path", cl::Hidden, cl::init("diff"), cl::desc("system diff used by change reporters")); +// An option for specifying the dot used by +// print-changed=[dot-cfg | dot-cfg-quiet] +static cl::opt + DotBinary("print-changed-dot-path", cl::Hidden, cl::init("dot"), + cl::desc("system dot 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("dot-cfg-before-color", + cl::desc("Color for dot-cfg 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("dot-cfg-after-color", + cl::desc("Color for dot-cfg 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("dot-cfg-common-color", + cl::desc("Color for dot-cfg 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 @@ -367,6 +411,21 @@ "DevirtSCCRepeatedPass", "ModuleInlinerWrapperPass"}); } +std::string 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"); +} + // Return the module when that is the appropriate level of comparison for \p IR. const Module *getModuleForComparison(Any IR) { if (any_isa(IR)) @@ -644,7 +703,7 @@ } unsigned Minor = 0; - FuncDataT Missing; + FuncDataT Missing(""); IRDataT::report(Before, After, [&](const FuncDataT *B, const FuncDataT *A) { assert((B || A) && "Both functions cannot be missing."); @@ -679,7 +738,7 @@ template bool IRComparer::generateFunctionData(IRDataT &Data, const Function &F) { if (!F.isDeclaration() && isFunctionInPrintList(F.getName())) { - FuncDataT FD; + FuncDataT FD(F.getEntryBlock().getName().str()); for (const auto &B : F) { FD.getOrder().emplace_back(B.getName()); FD.getData().insert({B.getName(), B}); @@ -1200,8 +1259,868 @@ 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) : GraphName(Name) {} + + // 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; + + bool NodeGenerationComplete = false; + const std::string GraphName; + std::vector Nodes; + std::vector NodePtrs; + DisplayNode *EntryNode = nullptr; +}; + +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 BlockDataT &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 BlockDataT &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) const; + +protected: + DotCfgDiff &Graph; + const unsigned N; + const BlockDataT *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 FuncDataT &Before, + const FuncDataT &After); + + 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(); } + + const 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 BlockDataT &BD, + IRChangeDiffType T) { + unsigned Pos = Nodes.size(); + Nodes.emplace_back(*this, Pos, BD, T); + NodePosition.insert({Label, Pos}); + } + + // TODO Nodes should probably be a StringMap after the + // display graph is separated out, which would remove the need for + // NodePosition. + std::vector Nodes; + StringMap NodePosition; + const std::string GraphName; + + 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(makeHTMLReady(SR[0]), 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). + Regex R(""); + while (true) { + std::string Error; + std::string S = R.sub("", Diff, &Error); + if (Error != "") + return Error; + if (S == Diff) + return Diff; + Diff = S; + } + llvm_unreachable("Should not get here"); + } + + // Put node out in the appropriate colour. + assert(!Data[1] && "Data[1] is set unexpectedly."); + std::string Body = 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 FuncDataT &Before, + const FuncDataT &After) + : GraphName(Title.str()) { + StringMap EdgesMap; + + // Handle each basic block in the before IR. + for (auto &B : Before.getData()) { + StringRef Label = B.getKey(); + const BlockDataT &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 BlockDataT &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) const { + + 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(); +} + +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 renderNodesUsingHTML() { 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; + raw_fd_ostream OutStream(DotFile, EC); + if (EC) { + errs() << "Error: " << EC.message() << "\n"; + return; + } + WriteGraph(OutStream, this, false); + OutStream.flush(); + OutStream.close(); +} + +} // namespace + namespace llvm { +DCData::DCData(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(), ""); +} + +DotCfgChangeReporter::DotCfgChangeReporter(bool Verbose) + : ChangeReporter>(Verbose) { + // Set up the colours based on the hidden options. + Colours[InBefore] = BeforeColour; + Colours[InAfter] = AfterColour; + Colours[IsCommon] = CommonColour; +} + +void DotCfgChangeReporter::handleFunctionCompare( + StringRef Name, StringRef Prefix, StringRef PassID, StringRef Divider, + bool InModule, unsigned Minor, const FuncDataT &Before, + const FuncDataT &After) { + assert(HTML && "Expected outstream to be set"); + 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}{4}", Number, Prefix, makeHTMLReady(PassID), + Divider, Name); + + DotCfgDiff Diff(Text, Before, After); + 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); + std::error_code EC = sys::fs::remove(DotFile); + if (EC) + errs() << "Error: " << EC.message() << "\n"; +} + +std::string DotCfgChangeReporter::genHTML(StringRef Text, StringRef DotFile, + StringRef PDFFileName) { + SmallString<20> PDFFile = formatv("{0}/{1}", DotCfgDir, PDFFileName); + // Create the PDF file. + static ErrorOr DotExe = sys::findProgramByName(DotBinary); + if (!DotExe) + return "Unable to find dot executable."; + + StringRef Args[] = {DotBinary, "-Tpdf", "-o", PDFFile, DotFile}; + int Result = sys::ExecuteAndWait(*DotExe, Args, None); + if (Result < 0) + return "Error executing system dot."; + + // Create the HTML tag refering to the PDF file. + SmallString<200> S = formatv( + "
{1}
\n", PDFFileName, Text); + return S.c_str(); +} + +void DotCfgChangeReporter::handleInitialIR(Any IR) { + assert(HTML && "Expected outstream to be set"); + *HTML << "\n" + << "
\n" + << "

\n"; + // Create representation of IR + IRDataT Data; + IRComparer::analyzeIR(IR, Data); + // Now compare it against itself, which will have everything the + // same and will generate the files. + IRComparer(Data, Data) + .compare(getModuleForComparison(IR), + [&](bool InModule, unsigned Minor, + const FuncDataT &Before, + const FuncDataT &After) -> void { + handleFunctionCompare("", " ", "Initial IR", "", InModule, + Minor, Before, After); + }); + *HTML << "

\n" + << "

\n"; + ++N; +} + +void DotCfgChangeReporter::generateIRRepresentation(Any IR, StringRef PassID, + IRDataT &Data) { + IRComparer::analyzeIR(IR, Data); +} + +void DotCfgChangeReporter::omitAfter(StringRef PassID, std::string &Name) { + assert(HTML && "Expected outstream to be set"); + SmallString<20> Banner = + formatv(" {0}. Pass {1} on {2} omitted because no change
\n", + N, makeHTMLReady(PassID), Name); + *HTML << Banner; + ++N; +} + +void DotCfgChangeReporter::handleAfter(StringRef PassID, std::string &Name, + const IRDataT &Before, + const IRDataT &After, Any IR) { + assert(HTML && "Expected outstream to be set"); + IRComparer(Before, After) + .compare(getModuleForComparison(IR), + [&](bool InModule, unsigned Minor, + const FuncDataT &Before, + const FuncDataT &After) -> void { + handleFunctionCompare(Name, " Pass ", PassID, " on ", InModule, + Minor, Before, After); + }); + *HTML << "

\n"; + ++N; +} + +void DotCfgChangeReporter::handleInvalidated(StringRef PassID) { + assert(HTML && "Expected outstream to be set"); + SmallString<20> Banner = + formatv(" {0}. {1} invalidated
\n", N, 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}. Pass {1} on {2} filtered out
\n", N, + 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} on {2} ignored
\n", N, + makeHTMLReady(PassID), Name); + *HTML << Banner; + ++N; +} + +bool DotCfgChangeReporter::initializeHTML() { + std::error_code EC; + HTML = std::make_unique(DotCfgDir + "/passes.html", EC); + if (EC) { + HTML = nullptr; + return false; + } + + *HTML << "" + << "" + << "" + << "" + << "passes.html" + << "\n" + << ""; + return true; +} + +DotCfgChangeReporter::~DotCfgChangeReporter() { + if (!HTML) + return; + *HTML + << "" + << "" + << "\n"; + HTML->flush(); + HTML->close(); +} + +void DotCfgChangeReporter::registerCallbacks( + PassInstrumentationCallbacks &PIC) { + if ((PrintChanged == ChangePrinter::PrintChangedDotCfgVerbose || + PrintChanged == ChangePrinter::PrintChangedDotCfgQuiet)) { + SmallString<128> OutputDir; + sys::fs::expand_tilde(DotCfgDir, OutputDir); + sys::fs::make_absolute(OutputDir); + assert(!OutputDir.empty() && "expected output dir to be non-empty"); + DotCfgDir = OutputDir.c_str(); + if (initializeHTML()) { + ChangeReporter>::registerRequiredCallbacks(PIC); + return; + } + dbgs() << "Unable to open output stream for -cfg-dot-changed\n"; + } +} + StandardInstrumentations::StandardInstrumentations( bool DebugLogging, bool VerifyEach, PrintPassOptions PrintPassOpts) : PrintPass(DebugLogging, PrintPassOpts), OptNone(DebugLogging), @@ -1211,6 +2130,8 @@ PrintChanged == ChangePrinter::PrintChangedColourDiffVerbose, PrintChanged == ChangePrinter::PrintChangedColourDiffVerbose || PrintChanged == ChangePrinter::PrintChangedColourDiffQuiet), + WebsiteChangeReporter(PrintChanged == + ChangePrinter::PrintChangedDotCfgVerbose), Verify(DebugLogging), VerifyEach(VerifyEach) {} void StandardInstrumentations::registerCallbacks( @@ -1227,6 +2148,7 @@ if (VerifyEach) Verify.registerCallbacks(PIC); PrintChangedDiff.registerCallbacks(PIC); + WebsiteChangeReporter.registerCallbacks(PIC); } template class ChangeReporter; diff --git a/llvm/test/Other/ChangePrinters/DotCfg/lit.local.cfg b/llvm/test/Other/ChangePrinters/DotCfg/lit.local.cfg new file mode 100644 --- /dev/null +++ b/llvm/test/Other/ChangePrinters/DotCfg/lit.local.cfg @@ -0,0 +1,4 @@ +import os + +if not os.path.exists('/usr/bin/dot'): + config.unsupported = True diff --git a/llvm/test/Other/ChangePrinters/DotCfg/print-changed-dot-cfg.ll b/llvm/test/Other/ChangePrinters/DotCfg/print-changed-dot-cfg.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Other/ChangePrinters/DotCfg/print-changed-dot-cfg.ll @@ -0,0 +1,311 @@ +; Simple checks of -print-changed=dot-cfg +; +; Note that (mostly) only the banners are checked. +; +; Simple functionality check. +; RUN: rm -rf %t && mkdir -p %t +; RUN: opt -disable-verify -S -print-changed=dot-cfg -passes=instsimplify -dot-cfg-dir=%t < %s -o /dev/null +; RUN: ls %t/*.pdf %t/passes.html | count 4 +; RUN: FileCheck %s -input-file=%t/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 -rf %t && mkdir -p %t +; RUN: opt -disable-verify -S -print-changed=dot-cfg -passes=instsimplify -filter-print-funcs=f -dot-cfg-dir=%t < %s -o /dev/null +; RUN: ls %t/*.pdf %t/passes.html | count 3 +; RUN: FileCheck %s -input-file=%t/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 -rf %t && mkdir -p %t +; RUN: opt -disable-verify -S -print-changed=dot-cfg -passes=instsimplify -print-module-scope -dot-cfg-dir=%t < %s -o /dev/null +; RUN: ls %t/*.pdf %t/passes.html | count 4 +; RUN: FileCheck %s -input-file=%t/passes.html --check-prefix=CHECK-DOT-CFG-PRINT-MOD-SCOPE +; +; Check that reporting of multiple functions happens +; RUN: rm -rf %t && mkdir -p %t +; RUN: opt -disable-verify -S -print-changed=dot-cfg -passes=instsimplify -filter-print-funcs="f,g" -dot-cfg-dir=%t < %s -o /dev/null +; RUN: ls %t/*.pdf %t/passes.html | count 4 +; RUN: FileCheck %s -input-file=%t/passes.html --check-prefix=CHECK-DOT-CFG-FILTER-MULT-FUNC +; +; Check that the reporting of IRs respects -filter-passes +; RUN: rm -rf %t && mkdir -p %t +; RUN: opt -disable-verify -S -print-changed=dot-cfg -passes="instsimplify,no-op-function" -filter-passes="NoOpFunctionPass" -dot-cfg-dir=%t < %s -o /dev/null +; RUN: ls %t/*.pdf %t/passes.html | count 2 +; RUN: FileCheck %s -input-file=%t/passes.html --check-prefix=CHECK-DOT-CFG-FILTER-PASSES +; +; Check that the reporting of IRs respects -filter-passes with multiple passes +; RUN: rm -rf %t && mkdir -p %t +; RUN: opt -disable-verify -S -print-changed=dot-cfg -passes="instsimplify,no-op-function" -filter-passes="NoOpFunctionPass,InstSimplifyPass" -dot-cfg-dir=%t < %s -o /dev/null +; RUN: ls %t/*.pdf %t/passes.html | count 4 +; RUN: FileCheck %s -input-file=%t/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 -rf %t && mkdir -p %t +; RUN: opt -disable-verify -S -print-changed=dot-cfg -passes="instsimplify,no-op-function" -filter-passes="NoOpFunctionPass,InstSimplifyPass" -filter-print-funcs=f -dot-cfg-dir=%t < %s -o /dev/null +; RUN: ls %t/*.pdf %t/passes.html | count 3 +; RUN: FileCheck %s -input-file=%t/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 -rf %t && mkdir -p %t +; RUN: opt -disable-verify -S -print-changed=dot-cfg -passes="instsimplify,instsimplify" -filter-print-funcs=f -dot-cfg-dir=%t < %s -o /dev/null +; RUN: ls %t/*.pdf %t/passes.html | count 3 +; RUN: FileCheck %s -input-file=%t/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 -rf %t && mkdir -p %t +; RUN: opt -S -print-changed=dot-cfg-quiet -passes=instsimplify -dot-cfg-dir=%t < %s -o /dev/null +; RUN: ls %t/*.pdf %t/passes.html | count 3 +; RUN: FileCheck %s -input-file=%t/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 -rf %t && mkdir -p %t +; RUN: opt -S -print-changed=dot-cfg-quiet -passes=instsimplify -filter-print-funcs=f -dot-cfg-dir=%t < %s -o /dev/null +; RUN: ls %t/*.pdf %t/passes.html | count 2 +; RUN: FileCheck %s -input-file=%t/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 -rf %t && mkdir -p %t +; RUN: opt -S -print-changed=dot-cfg-quiet -passes=instsimplify -print-module-scope -dot-cfg-dir=%t < %s -o /dev/null +; RUN: ls %t/*.pdf %t/passes.html | count 3 +; RUN: FileCheck %s -input-file=%t/passes.html --check-prefix=CHECK-DOT-CFG-QUIET-PRINT-MOD-SCOPE +; +; Check that reporting of multiple functions happens +; RUN: rm -rf %t && mkdir -p %t +; RUN: opt -S -print-changed=dot-cfg-quiet -passes=instsimplify -filter-print-funcs="f,g" -dot-cfg-dir=%t < %s -o /dev/null +; RUN: ls %t/*.pdf %t/passes.html | count 3 +; RUN: FileCheck %s -input-file=%t/passes.html --check-prefix=CHECK-DOT-CFG-QUIET-FILTER-MULT-FUNC +; +; Check that the reporting of IRs respects -filter-passes +; RUN: rm -rf %t && mkdir -p %t +; RUN: opt -S -print-changed=dot-cfg-quiet -passes="instsimplify,no-op-function" -filter-passes="NoOpFunctionPass" -dot-cfg-dir=%t < %s -o /dev/null +; RUN: FileCheck %s -input-file=%t/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 -rf %t && mkdir -p %t +; RUN: opt -S -print-changed=dot-cfg-quiet -passes="instsimplify,no-op-function" -filter-passes="NoOpFunctionPass,InstSimplifyPass" -dot-cfg-dir=%t < %s -o /dev/null +; RUN: ls %t/*.pdf %t/passes.html | count 3 +; RUN: FileCheck %s -input-file=%t/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 -rf %t && mkdir -p %t +; RUN: opt -S -print-changed=dot-cfg-quiet -passes="instsimplify,no-op-function" -filter-passes="NoOpFunctionPass,InstSimplifyPass" -filter-print-funcs=f -dot-cfg-dir=%t < %s -o /dev/null +; RUN: ls %t/*.pdf %t/passes.html | count 2 +; RUN: FileCheck %s -input-file=%t/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 -rf %t && mkdir -p %t +; RUN: opt -S -print-changed=dot-cfg-quiet -passes="instsimplify,instsimplify" -filter-print-funcs=f -dot-cfg-dir=%t < %s -o /dev/null +; RUN: ls %t/*.pdf %t/passes.html | count 2 +; RUN: FileCheck %s -input-file=%t/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.pdf diff_1.pdf diff_3.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. Initial IR
+; CHECK-DOT-CFG-SIMPLE-NEXT:

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

+; CHECK-DOT-CFG-SIMPLE-NEXT: 1. Pass InstSimplifyPass on g
+; CHECK-DOT-CFG-SIMPLE-NEXT:

+; CHECK-DOT-CFG-SIMPLE-NEXT: 2. PassManager<llvm::Function> on g ignored
+; CHECK-DOT-CFG-SIMPLE-NEXT: 3. Pass InstSimplifyPass on f
+; CHECK-DOT-CFG-SIMPLE-NEXT:

+; CHECK-DOT-CFG-SIMPLE-NEXT: 4. PassManager<llvm::Function> on f ignored
+; CHECK-DOT-CFG-SIMPLE-NEXT: 5. ModuleToFunctionPassAdaptor on [module] ignored
+; CHECK-DOT-CFG-SIMPLE-NEXT: 6. Pass PrintModulePass on [module] omitted because no change
+; CHECK-DOT-CFG-SIMPLE-NEXT: + +; CHECK-DOT-CFG-FUNC-FILTER: passes.html +; CHECK-DOT-CFG-FUNC-FILTER-NEXT: 0. Pass InstSimplifyPass on g filtered out
+; CHECK-DOT-CFG-FUNC-FILTER-NEXT: 1. PassManager<llvm::Function> on g ignored
+; CHECK-DOT-CFG-FUNC-FILTER-NEXT: +; CHECK-DOT-CFG-FUNC-FILTER-NEXT:
+; CHECK-DOT-CFG-FUNC-FILTER-NEXT:

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

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

+; CHECK-DOT-CFG-FUNC-FILTER-NEXT: 3. Pass InstSimplifyPass on f
+; CHECK-DOT-CFG-FUNC-FILTER-NEXT:

+; CHECK-DOT-CFG-FUNC-FILTER-NEXT: 4. PassManager<llvm::Function> on f ignored
+; CHECK-DOT-CFG-FUNC-FILTER-NEXT: 5. ModuleToFunctionPassAdaptor on [module] ignored
+; CHECK-DOT-CFG-FUNC-FILTER-NEXT: 6. Pass PrintModulePass on [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. Initial IR
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT:

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

+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT: 1. Pass InstSimplifyPass on g
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT:

+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT: 2. PassManager<llvm::Function> on g ignored
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT: 3. Pass InstSimplifyPass on f
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT:

+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT: 4. PassManager<llvm::Function> on f ignored
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT: 5. ModuleToFunctionPassAdaptor on [module] ignored
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT: 6. Pass PrintModulePass on [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. Initial IR
+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT:

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

+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT: 1. Pass InstSimplifyPass on g
+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT:

+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT: 2. PassManager<llvm::Function> on g ignored
+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT: 3. Pass InstSimplifyPass on f
+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT:

+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT: 4. PassManager<llvm::Function> on f ignored
+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT: 5. ModuleToFunctionPassAdaptor on [module] ignored
+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT: 6. Pass PrintModulePass on [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. Pass InstSimplifyPass on 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: 1. Initial IR
+; CHECK-DOT-CFG-FILTER-PASSES-NEXT:

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

+; CHECK-DOT-CFG-FILTER-PASSES-NEXT: 2. Pass NoOpFunctionPass on g omitted because no change
+; CHECK-DOT-CFG-FILTER-PASSES-NEXT: 3. PassManager<llvm::Function> on g ignored
+; CHECK-DOT-CFG-FILTER-PASSES-NEXT: 4. Pass InstSimplifyPass on f filtered out
+; CHECK-DOT-CFG-FILTER-PASSES-NEXT: 5. Pass NoOpFunctionPass on f omitted because no change
+; CHECK-DOT-CFG-FILTER-PASSES-NEXT: 6. PassManager<llvm::Function> on f ignored
+; CHECK-DOT-CFG-FILTER-PASSES-NEXT: 7. ModuleToFunctionPassAdaptor on [module] ignored
+; CHECK-DOT-CFG-FILTER-PASSES-NEXT: 8. Pass PrintModulePass on [module] filtered out
+; CHECK-DOT-CFG-FILTER-PASSES-NEXT: + + + +; CHECK-DOT-CFG-FILTER-MULT-PASSES: passes.html +; 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: 0. Initial IR
+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT:

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

+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT: 1. Pass InstSimplifyPass on g
+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT:

+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT: 2. Pass NoOpFunctionPass on g omitted because no change
+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT: 3. PassManager<llvm::Function> on g ignored
+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT: 4. Pass InstSimplifyPass on f
+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT:

+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT: 5. Pass NoOpFunctionPass on f omitted because no change
+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT: 6. PassManager<llvm::Function> on f ignored
+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT: 7. ModuleToFunctionPassAdaptor on [module] ignored
+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT: 8. Pass PrintModulePass on [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. Pass InstSimplifyPass on g filtered out
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT: 1. Pass NoOpFunctionPass on g filtered out
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT: 2. PassManager<llvm::Function> on 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: 3. Initial IR
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT:

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

+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT: 4. Pass InstSimplifyPass on f
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT:

+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT: 5. Pass NoOpFunctionPass on f omitted because no change
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT: 6. PassManager<llvm::Function> on f ignored
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT: 7. ModuleToFunctionPassAdaptor on [module] ignored
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT: 8. Pass PrintModulePass on [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: 0. Pass InstSimplifyPass on g filtered out
+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT: 1. Pass InstSimplifyPass on g filtered out
+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT: 2. PassManager<llvm::Function> on g ignored
+; 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: 3. 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: 4. Pass InstSimplifyPass on f
+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT:

+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT: 5. Pass InstSimplifyPass on f omitted because no change
+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT: 6. PassManager<llvm::Function> on f ignored
+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT: 7. ModuleToFunctionPassAdaptor on [module] ignored
+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT: 8. Pass PrintModulePass on [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. Pass InstSimplifyPass on g
+; CHECK-DOT-CFG-QUIET-SIMPLE-NEXT:

+; CHECK-DOT-CFG-QUIET-SIMPLE-NEXT: 1. Pass InstSimplifyPass on 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. Pass InstSimplifyPass on 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. Pass InstSimplifyPass on g
+; CHECK-DOT-CFG-QUIET-PRINT-MOD-SCOPE-NEXT:

+; CHECK-DOT-CFG-QUIET-PRINT-MOD-SCOPE-NEXT: 1. Pass InstSimplifyPass on 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. Pass InstSimplifyPass on g
+; CHECK-DOT-CFG-QUIET-FILTER-MULT-FUNC-NEXT:

+; CHECK-DOT-CFG-QUIET-FILTER-MULT-FUNC-NEXT: 1. Pass InstSimplifyPass on 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. Pass InstSimplifyPass on g
+; CHECK-DOT-CFG-QUIET-FILTER-MULT-PASSES-NEXT:

+; CHECK-DOT-CFG-QUIET-FILTER-MULT-PASSES-NEXT: 1. Pass InstSimplifyPass on 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. Pass InstSimplifyPass on 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. Pass InstSimplifyPass on f
+; CHECK-DOT-CFG-QUIET-MULT-PASSES-FILTER-FUNC-NEXT:

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