Index: llvm/include/llvm/IR/ChangeReporters.h =================================================================== --- llvm/include/llvm/IR/ChangeReporters.h +++ llvm/include/llvm/IR/ChangeReporters.h @@ -12,8 +12,8 @@ /// //===----------------------------------------------------------------------===// -#ifndef LLVM_SUPPORT_CHANGEREPORTERS_H -#define LLVM_SUPPORT_CHANGEREPORTERS_H +#ifndef LLVM_IR_CHANGEREPORTERS_H +#define LLVM_IR_CHANGEREPORTERS_H #include "llvm/ADT/Any.h" #include "llvm/ADT/SmallVector.h" @@ -218,7 +218,15 @@ bool operator!=(const IRDataTemplate &that) const { return !(*this == that); } }; -enum IRChangeDiffType { InBefore, InAfter, IsCommon }; +enum IRChangeDiffType { InBefore, InAfter, IsCommon, NumIRChangeDiffTypes }; + +// Perform a linux based diff between \p Before and \p After, using +// \p OldLineFormat, \p NewLineFormat, and \p UnchangedLineFormat +// to control the formatting of the output. +std::string doLinuxDiff(llvm::StringRef Before, llvm::StringRef After, + llvm::StringRef OldLineFormat, + llvm::StringRef NewLineFormat, + llvm::StringRef UnchangedLineFormat); // 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 @@ -301,6 +309,84 @@ llvm::raw_ostream &Out; }; +class BlockSuccessors : private llvm::StringMap { +public: + BlockSuccessors() {} + void populate(const llvm::BasicBlock &B); + + // Return an iterator to the names of the successor blocks. + using llvm::StringMap::begin; + using llvm::StringMap::end; + + // Return the label of the basic block reached on a transition on \p S. + const llvm::StringRef getSuccessorLabel(llvm::StringRef S) const { + assert(count(S) == 1 && "Expected to find successor."); + return find(S)->getValue(); + } + + static void initialize(BlockSuccessors &S, const llvm::BasicBlock &B) { + S.populate(B); + } + +protected: + // Add a transition to \p Succ on \p Label + void addSuccessorLabel(llvm::StringRef Succ, llvm::StringRef Label) { + std::pair SS{Succ, Label}; + insert(SS); + } +}; + +using BlockSuccessorsBlockData = BlockDataTemplate; +using BlockSuccessorsFuncData = FuncDataTemplate; +using BlockSuccessorsIRData = IRDataTemplate; + +// Class that compares two IRs and creates a graphical representation of +// the differences between the two IRs. It produces a web page with links +// to pdf files showing the differences between functions in the IRs. +class CFGDotComparer + : public IRComparer { + +public: + // Output html on \p OS, storing the file in \p Directory, using a suffix + // of \p N in the filename. + CFGDotComparer(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); + + // Compare \p Before and \After and create the pdf name using + // \p Major and \p Minor (if present) as part of the name. Call genHTML + // to create the html using \p Name and \PassID. + void handleSingleFunctionCompare(llvm::StringRef Name, llvm::StringRef Prefix, + llvm::StringRef PassID, unsigned, unsigned *, + const BlockSuccessorsFuncData &Before, + const BlockSuccessorsFuncData &After); + // Called by base class + void handleFunctionInModuleCompare(llvm::StringRef Name, + llvm::StringRef Prefix, + llvm::StringRef PassID, unsigned Major, + unsigned *Minor, + const BlockSuccessorsFuncData &Before, + const BlockSuccessorsFuncData &After) { + handleSingleFunctionCompare(Name, Prefix, PassID, Major, Minor, Before, + After); + } + +protected: + // 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; +}; + extern template class ChangeReporter; extern template class TextChangeReporter; @@ -312,6 +398,14 @@ extern template class IRComparer; +extern template class BlockDataTemplate; +extern template class FuncDataTemplate; +extern template class IRDataTemplate; +extern template class ChangeReporter; +extern template class IRComparer; + } // namespace cr #endif Index: llvm/include/llvm/Passes/StandardInstrumentations.h =================================================================== --- llvm/include/llvm/Passes/StandardInstrumentations.h +++ llvm/include/llvm/Passes/StandardInstrumentations.h @@ -28,7 +28,6 @@ namespace llvm { -class Function; class Module; class BasicBlock; class Any; @@ -200,67 +199,56 @@ // this change reporter. class InLineChangePrinter : public cr::TextChangeReporter { public: - InLineChangePrinter() {} ~InLineChangePrinter() override; void registerCallbacks(PassInstrumentationCallbacks &PIC); -}; - -// A class that compares two IRs of type EmptyDataIRData and does a -// linux 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). The functions -// handleCompare and handleFunctionCompare are called by the base -// class with the appropriate parameters. -class InLineComparer - : public cr::IRComparer { -public: - InLineComparer(llvm::raw_ostream &OS, const cr::EmptyDataIRData &Before, - const cr::EmptyDataIRData &After) - : IRComparer(Before, After, 0), Out(OS) {} - - // Called by base class - void handleSingleFunctionCompare(llvm::StringRef Name, llvm::StringRef Prefix, - llvm::StringRef PassID, unsigned, unsigned *, - const cr::EmptyDataFuncData &Before, - const cr::EmptyDataFuncData &After); - // Called by base class - void handleFunctionInModuleCompare(llvm::StringRef Name, - llvm::StringRef Prefix, - llvm::StringRef PassID, unsigned, - unsigned *, - const cr::EmptyDataFuncData &Before, - const cr::EmptyDataFuncData &After) { - Out << "\n*** IR for function " << Name << " ***\n"; - handleSingleFunctionCompare(Name, Prefix, PassID, 0, nullptr, Before, - After); - } protected: // Create a representation of the IR. - virtual void generateIRRepresentation(llvm::Any IR, llvm::StringRef PassID, - cr::EmptyDataIRData &Output) override; + void generateIRRepresentation(llvm::Any IR, llvm::StringRef PassID, + cr::EmptyDataIRData &Output) override; // Called when an interesting IR has changed. - virtual void handleAfter(StringRef PassID, std::string &Name, - const cr::EmptyDataIRData &Before, - const cr::EmptyDataIRData &After, Any) override; + void handleAfter(llvm::StringRef PassID, std::string &Name, + const cr::EmptyDataIRData &Before, + const cr::EmptyDataIRData &After, llvm::Any) override; // Called to compare the before and after representations of the IR. - virtual bool same(const cr::EmptyDataIRData &Before, - const cr::EmptyDataIRData &After) override; - llvm::raw_ostream &Out; + bool same(const cr::EmptyDataIRData &Before, + const cr::EmptyDataIRData &After) override; }; -// A change printer that prints out in-line differences in the basic -// blocks of functions. When a pass changes the module, the changes to -// all basic blocks for each function in the module are reported. -// Changes to the IR that do not affect basic blocks are not reported as -// having changed the IR. The option -print-module-scope does not affect -// this change reporter. -class InLineChangePrinter : public cr::TextChangeReporter { +class CFGDotChangeReporter + : public cr::ChangeReporter { public: - InLineChangePrinter(); + ~CFGDotChangeReporter() 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, + cr::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 cr::BlockSuccessorsIRData &Before, + const cr::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 cr::BlockSuccessorsIRData &Before, + const cr::BlockSuccessorsIRData &After) override; + + unsigned N = 0; + raw_fd_ostream *HTML = nullptr; }; /// This class provides an interface to register all the standard pass @@ -273,6 +261,7 @@ PreservedCFGCheckerInstrumentation PreservedCFGChecker; IRChangedPrinter PrintChangedIR; InLineChangePrinter PrintChanges; + CFGDotChangeReporter CFGDotDiffGenerator; public: StandardInstrumentations(bool DebugLogging) : PrintPass(DebugLogging) {} Index: llvm/lib/IR/ChangeReporters.cpp =================================================================== --- llvm/lib/IR/ChangeReporters.cpp +++ llvm/lib/IR/ChangeReporters.cpp @@ -26,14 +26,18 @@ #include "llvm/Support/CommandLine.h" #include "llvm/Support/Debug.h" #include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/GraphWriter.h" #include "llvm/Support/raw_ostream.h" +#include +#include #include #include #include using namespace llvm; +using namespace cr; -// A hidden option that supports the -print-changed option. See +// An option that supports the -print-changed option. See // the description for -print-changed for an explanation of the use // of this option. Note that this option has no effect without -print-changed. static cl::list @@ -42,6 +46,25 @@ "match for the print-changed option"), cl::CommaSeparated, cl::Hidden); +// 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")); + namespace cr { template @@ -350,6 +373,791 @@ return false; } +// Perform a linux based diff between \p Before and \p After, using +// \p OldLineFormat, \p NewLineFormat, and \p UnchangedLineFormat +// to control the formatting of the output. +std::string doLinuxDiff(StringRef Before, StringRef After, + StringRef OldLineFormat, StringRef NewLineFormat, + StringRef UnchangedLineFormat) { + StringRef SR[2]{Before, After}; + // Store the 2 bodies into temporary files and call diff on them + // to get the body of the node. + static std::string FileName[2]; + static int FD[2]{-1, -1}; + for (unsigned I = 0; I < 2; ++I) { + if (FD[I] == -1) { + SmallVector SV; + std::error_code EC = + sys::fs::createTemporaryFile("tmpdiff", "txt", FD[I], SV); + assert(!EC && "Unable to create temporary file."); + FileName[I] = Twine(SV).str(); + } + + std::error_code EC = sys::fs::openFileForWrite(FileName[I], FD[I]); + assert(!EC && "Unable to open temporary file for writing."); + + llvm::raw_fd_ostream OutStream(FD[I], /*shouldClose=*/true); + assert(FD[I] != -1 && "Error opening file for writing."); + OutStream << SR[I]; + } + + SmallString<512> DiffCall = + formatv("diff -w -d --old-line-format='{2}' --new-line-format='{3}' " + "--unchanged-line-format='{4}' {0} {1}", + FileName[0], FileName[1], OldLineFormat, NewLineFormat, + UnchangedLineFormat); + std::string Diff; + + std::array Buffer; + FILE *Pipe = popen(DiffCall.c_str(), "r"); + assert(Pipe && "Expected pipe to open."); + while (fgets(Buffer.data(), Buffer.size(), Pipe) != nullptr) + Diff += Buffer.data(); + pclose(Pipe); + + // Clean up. + for (unsigned I = 0; I < 2; ++I) { + std::error_code EC = sys::fs::remove(FileName[I]); + assert(!EC && "Unable to remove temporary file."); + } + return Diff; +} + +} // namespace cr + +namespace { + +// Describe where a given element exists. +std::string Colours[NumIRChangeDiffTypes]; + +class DisplayNode; +class CFGDotDiffDisplayGraph; + +class DisplayElement { +public: + IRChangeDiffType getType() const { return Type; } + +protected: + DisplayElement(IRChangeDiffType T) : Type(T) {} + const IRChangeDiffType Type; +}; + +class DisplayEdge : public DisplayElement { +public: + DisplayEdge(std::string V, DisplayNode &Node, IRChangeDiffType T) + : DisplayElement(T), Value(V), Node(Node) {} + std::string getValue() const { return Value; } + const DisplayNode &getDestinationNode() const { return Node; } + +protected: + std::string Value; + const DisplayNode &Node; +}; + +class DisplayNode : public DisplayElement { +public: + DisplayNode(std::string C, IRChangeDiffType T, CFGDotDiffDisplayGraph &G) + : 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(); } + + using EdgeIterator = std::vector::const_iterator; + EdgeIterator edges_begin() const { return EdgePtrs.cbegin(); } + EdgeIterator edges_end() const { return EdgePtrs.cend(); } + + void createEdge(StringRef V, DisplayNode &Node, IRChangeDiffType T); + 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; + + // TODO Needed? + std::vector Edges; + + std::vector EdgePtrs; + std::unordered_set Children; + std::unordered_map EdgeMap; +}; + +class CFGDotDiffDisplayGraph { +public: + CFGDotDiffDisplayGraph(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."); + } + ~CFGDotDiffDisplayGraph() { + // Due to opt bug we have static members. Clear them out. + GraphName = ""; + Nodes.clear(); + NodePtrs.clear(); + EntryNode = nullptr; + } + + 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(); + } + + 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]; + } + + void createNode(std::string C, IRChangeDiffType T) { + assert(!NodeGenerationComplete && "Unexpected node creation"); + Nodes.emplace_back(C, T, *this); + } + 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 + // CFGDotDiff (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 bool NodeGenerationComplete; + static std::string GraphName; + static std::vector Nodes; + static std::vector NodePtrs; + static DisplayNode *EntryNode; +}; + +bool CFGDotDiffDisplayGraph::NodeGenerationComplete; +std::string CFGDotDiffDisplayGraph::GraphName; +std::vector CFGDotDiffDisplayGraph::Nodes; +std::vector CFGDotDiffDisplayGraph::NodePtrs; +DisplayNode *CFGDotDiffDisplayGraph::EntryNode; + +void DisplayNode::createEdge(StringRef V, DisplayNode &Node, + IRChangeDiffType T) { + Edges.emplace_back(V.str(), Node, T); + Children.insert(&Node); +} + +void DisplayNode::createEdgeMap() { + for (auto &E : Edges) + EdgeMap.insert({&E.getDestinationNode(), &E}); +} + +class CFGDotDiffNode; +class CFGDotDiff; +class CFGDotDiff; + +// A class representing a basic block in the Dot difference graph. +class CFGDotDiffNode { +public: + CFGDotDiffNode() = 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). + CFGDotDiffNode(CFGDotDiff &G, unsigned N, const BlockSuccessorsBlockData &BD, + IRChangeDiffType T) + : Graph(G), N(N), Data{&BD, nullptr}, Type(T) {} + CFGDotDiffNode(const CFGDotDiffNode &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(CFGDotDiff &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(CFGDotDiffDisplayGraph &Graph, unsigned DisplayNode, + std::map &NodeMap); + +protected: + CFGDotDiff &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 CFGDotDiff { +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. + CFGDotDiff(StringRef Title, const BlockSuccessorsFuncData &Before, + const BlockSuccessorsFuncData &After, StringRef Dir); + // Only needed because of opt bug... + ~CFGDotDiff() { + // Due to opt bug we have static members. Clear them out. + Nodes.clear(); + NodePosition.clear(); + GraphName = ""; + } + + CFGDotDiff(const CFGDotDiff &) = delete; + CFGDotDiff &operator=(const CFGDotDiff &) = delete; + + CFGDotDiffDisplayGraph 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(); } + + CFGDotDiffNode &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 + // CFGDotDiff (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; + + CFGDotDiffNode *EntryNode; + StringMap EdgeLabels; +}; + +std::string CFGDotDiffNode::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
" + + doLinuxDiff(CFGDotComparer::makeHTMLReady(SR[0]), + CFGDotComparer::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 = CFGDotComparer::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 CFGDotDiff::colourize(std::string S, IRChangeDiffType T) const { + if (S.length() == 0) + return S; + return "" + S + ""; +} + +std::string CFGDotDiff::attribute(IRChangeDiffType T) const { + return "color=" + Colours[T]; +} + +std::string CFGDotDiffDisplayGraph::attribute(IRChangeDiffType T) const { + return "color=" + Colours[T]; +} + +CFGDotDiff::CFGDotDiff(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) { + 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) { + 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."); + CFGDotDiffNode &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); +} + +CFGDotDiffDisplayGraph CFGDotDiff::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"); + CFGDotDiffDisplayGraph 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 CFGDotDiffNode::createDisplayEdges( + CFGDotDiffDisplayGraph &DisplayGraph, unsigned DisplayNodeIndex, + std::map &NodeMap) { + + DisplayNode &SourceDisplayNode = DisplayGraph.getNode(DisplayNodeIndex); + + for (auto I : Edges) { + unsigned SinkNodeIndex = I; + IRChangeDiffType Type = getEdge(SinkNodeIndex).second; + const CFGDotDiffNode *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 CFGDotDiff::Nodes; +StringMap CFGDotDiff::NodePosition; +std::string CFGDotDiff::GraphName; + +void CFGDotDiffNode::finalize(CFGDotDiff &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 = CFGDotDiffDisplayGraph::NodeIterator; + using EdgeRef = const DisplayEdge *; + using ChildEdgeIterator = DisplayNode::EdgeIterator; + + static NodeRef getEntryNode(const CFGDotDiffDisplayGraph *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 CFGDotDiffDisplayGraph *G) { + return G->nodes_begin(); + } + static nodes_iterator nodes_end(const CFGDotDiffDisplayGraph *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 CFGDotDiffDisplayGraph *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 CFGDotDiffDisplayGraph *DiffData) { + return DiffData->getGraphName(); + } + static std::string + getGraphProperties(const CFGDotDiffDisplayGraph *DiffData) { + return "\tsize=\"190, 190\";\n"; + } + static std::string getNodeLabel(const DisplayNode *Node, + const CFGDotDiffDisplayGraph *DiffData) { + return DiffData->getNodeLabel(*Node); + } + static std::string getNodeAttributes(const DisplayNode *Node, + const CFGDotDiffDisplayGraph *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 CFGDotDiffDisplayGraph *DiffData) { + return DiffData->getEdgeColorAttr(*From, **To); + } +}; + +} // namespace llvm + +namespace { + +void CFGDotDiffDisplayGraph::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 cr { + +void BlockSuccessors::populate(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 CFGDotComparer::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"); +} + +CFGDotComparer::CFGDotComparer(raw_fd_ostream &OS, unsigned N, StringRef Dir, + const BlockSuccessorsIRData &Before, + const BlockSuccessorsIRData &After) + : IRComparer(Before, After, N), Directory(Dir.str()), HTML(OS) { + // Set up the colours based on the hidden options. + Colours[InBefore] = BeforeColour; + Colours[InAfter] = AfterColour; + Colours[IsCommon] = CommonColour; +} + +void CFGDotComparer::handleSingleFunctionCompare( + StringRef Name, StringRef Prefix, StringRef PassID, unsigned Major, + unsigned *Minor, const BlockSuccessorsFuncData &Before, + const BlockSuccessorsFuncData &After) { + SmallString<8> Extender; + SmallString<8> Number; + // Handle numbering and file names. + if (Minor) { + Extender = formatv("{0}_{1}", Major, *Minor); + Number = formatv("{0}.{1}", Major, *Minor); + } else { + Extender = formatv("{0}", Major); + Number = formatv("{0}", Major); + } + // 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); + + CFGDotDiff Diff(Text, Before, After, Directory); + CFGDotDiffDisplayGraph DG = + Diff.createDisplayGraph(Text, After.getEntryBlockName()); + DG.generateDotFile(DotFile); + + HTML << genHTML(Text, DotFile, PDFFileName, Directory, Minor != nullptr); + std::error_code EC = sys::fs::remove(DotFile); + if (EC) + errs() << "Error: " << EC.message() << "\n"; +} + +std::string CFGDotComparer::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(); +} + template class ChangeReporter; template class TextChangeReporter; @@ -361,4 +1169,11 @@ template class IRComparer; +template class BlockDataTemplate; +template class FuncDataTemplate; +template class IRDataTemplate; +template class ChangeReporter; +template class IRComparer; + } // namespace cr Index: llvm/lib/Passes/StandardInstrumentations.cpp =================================================================== --- llvm/lib/Passes/StandardInstrumentations.cpp +++ llvm/lib/Passes/StandardInstrumentations.cpp @@ -84,6 +84,11 @@ "print-changes", cl::Hidden, cl::init(false), cl::desc("Print in-line differences of changes made by a pass")); +static cl::opt DotCFGChanges( + "dot-cfg-changes", + cl::desc("Generate dot files into specified directory for changed IRs"), + cl::Hidden, cl::init("")); + namespace { void printIR(raw_ostream &OS, const Function *F, StringRef Banner, @@ -149,56 +154,6 @@ llvm::printLoop(const_cast(*L), OS, std::string(Banner)); } -// Perform a linux based diff between \p Before and \p After, using -// \p OldLineFormat, \p NewLineFormat, and \p UnchangedLineFormat -// to control the formatting of the output. -std::string doLinuxDiff(StringRef Before, StringRef After, - StringRef OldLineFormat, StringRef NewLineFormat, - StringRef UnchangedLineFormat) { - StringRef SR[2]{Before, After}; - // Store the 2 bodies into temporary files and call diff on them - // to get the body of the node. - static std::string FileName[2]; - static int FD[2]{-1, -1}; - for (unsigned I = 0; I < 2; ++I) { - if (FD[I] == -1) { - SmallVector SV; - std::error_code EC = - sys::fs::createTemporaryFile("tmpdiff", "txt", FD[I], SV); - assert(!EC && "Unable to create temporary file."); - FileName[I] = Twine(SV).str(); - } - - std::error_code EC = sys::fs::openFileForWrite(FileName[I], FD[I]); - assert(!EC && "Unable to open temporary file for writing."); - - llvm::raw_fd_ostream OutStream(FD[I], /*shouldClose=*/true); - assert(FD[I] != -1 && "Error opening file for writing."); - OutStream << SR[I]; - } - - SmallString<512> DiffCall = - formatv("diff -w -d --old-line-format='{2}' --new-line-format='{3}' " - "--unchanged-line-format='{4}' {0} {1}", - FileName[0], FileName[1], OldLineFormat, NewLineFormat, - UnchangedLineFormat); - std::string Diff; - - std::array Buffer; - FILE *Pipe = popen(DiffCall.c_str(), "r"); - assert(Pipe && "Expected pipe to open."); - while (fgets(Buffer.data(), Buffer.size(), Pipe) != nullptr) - Diff += Buffer.data(); - pclose(Pipe); - - // Clean up. - for (unsigned I = 0; I < 2; ++I) { - std::error_code EC = sys::fs::remove(FileName[I]); - assert(!EC && "Unable to remove temporary file."); - } - return Diff; -} - } // namespace Optional> @@ -588,36 +543,6 @@ IRChangedPrinter::~IRChangedPrinter() {} -IRChangedPrinter::IRChangedPrinter() - : TextChangeReporter( - [](Any IR, StringRef PassID, std::string &Output) -> void { - raw_string_ostream OS(Output); - // use the after banner for all cases so it will match - SmallString<20> Banner = - formatv("*** IR Dump After {0} ***", PassID); - unwrapAndPrint(OS, IR, Banner, llvm::forcePrintModuleIR()); - OS.str(); - }, - [this](StringRef PassID, std::string &Name, const std::string &Before, - const std::string &After, Any IR) -> void { - assert(After.find("*** IR Dump") == 0 && - "Unexpected banner format."); - StringRef S = After; - StringRef Banner = - S.take_until([](char C) -> bool { return C == '\n'; }); - Out << Banner; - - // LazyCallGraph::SCC already has "(scc:..." in banner so only - // add in the name if it isn't already there. - if (Name.substr(0, 6).compare(" (scc:") != 0 && - !llvm::forcePrintModuleIR()) - Out << Name; - Out << S.substr(Banner.size()); - }, - [](const std::string &S1, const std::string &S2) -> bool { - return S1.compare(S2) == 0; - }) {} - void IRChangedPrinter::registerCallbacks(PassInstrumentationCallbacks &PIC) { if (PrintChanged) TextChangeReporter::registerCallbacks(PIC); @@ -657,7 +582,9 @@ void InLineChangePrinter::generateIRRepresentation(Any IR, StringRef PassID, EmptyDataIRData &D) { - InLineComparer::analyzeIR(IR, D); + bool B = InLineComparer::analyzeIR(IR, D); + (void)B; + assert(B && "Problems generating data for initial IR"); } void InLineChangePrinter::handleAfter(StringRef PassID, std::string &Name, @@ -712,31 +639,156 @@ Before, After); } -InLineChangePrinter::InLineChangePrinter() - : TextChangeReporter( - [](Any IR, StringRef PassID, EmptyDataIRData &D) -> void { - InLineComparer::analyzeIR(IR, D); - }, - [this](StringRef PassID, std::string &Name, - const EmptyDataIRData &Before, const EmptyDataIRData &After, - Any IR) -> void { - if (Name == "") - Name = " (module)"; - SmallString<20> Banner = - formatv("*** IR Dump After {0} ***{1}\n", PassID, Name); - Out << Banner; - InLineComparer(Out, Before, After).compare(IR, "", PassID, Name); - Out << "\n"; - }, - [](const EmptyDataIRData &D1, const EmptyDataIRData &D2) -> bool { - return D1 == D2; - }) {} - void InLineChangePrinter::registerCallbacks(PassInstrumentationCallbacks &PIC) { if (PrintChanges) TextChangeReporter::registerCallbacks(PIC); } +void CFGDotChangeReporter::handleInitialIR(Any IR) { + assert(HTML && "Expected outstream to be set"); + *HTML << "\n" + << "
\n" + << "

\n"; + // Create representation of IR + BlockSuccessorsIRData Data; + bool B = CFGDotComparer::analyzeIR(IR, Data); + (void)B; + assert(B && "Problems generating data for initial IR"); + // Now compare it against itself, which will have everything the + // same and will generate the files. + CFGDotComparer Comparer(*HTML, N, DotCFGChanges, Data, Data); + Comparer.compare(IR, " ", "Initial IR", ""); + *HTML << "

\n" + << "

\n"; + ++N; +} + +void CFGDotChangeReporter::generateIRRepresentation( + Any IR, StringRef PassID, BlockSuccessorsIRData &Data) { + bool B = CFGDotComparer::analyzeIR(IR, Data); + (void)B; + assert(B && "Problems generating data for initial IR"); +} + +void CFGDotChangeReporter::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, + CFGDotComparer::makeHTMLReady(PassID), Name); + *HTML << Banner; + ++N; +} + +void CFGDotChangeReporter::handleAfter(StringRef PassID, std::string &Name, + const BlockSuccessorsIRData &Before, + const BlockSuccessorsIRData &After, + Any IR) { + assert(HTML && "Expected outstream to be set"); + CFGDotComparer Comparer(*HTML, N, DotCFGChanges, Before, After); + Comparer.compare(IR, " After ", PassID, Name); + *HTML << "

\n"; + ++N; +} + +void CFGDotChangeReporter::handleInvalidated(StringRef PassID) { + assert(HTML && "Expected outstream to be set"); + SmallString<20> Banner = formatv(" {0}. {1} invalidated
\n", N, + CFGDotComparer::makeHTMLReady(PassID)); + *HTML << Banner; + ++N; +} + +void CFGDotChangeReporter::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, + CFGDotComparer::makeHTMLReady(PassID), Name); + *HTML << Banner; + ++N; +} + +void CFGDotChangeReporter::handleIgnored(StringRef PassID, std::string &Name) { + assert(HTML && "Expected outstream to be set"); + SmallString<20> Banner = formatv(" {0}. {1}{2} ignored
\n", N, + CFGDotComparer::makeHTMLReady(PassID), Name); + *HTML << Banner; + ++N; +} + +bool CFGDotChangeReporter::same(const BlockSuccessorsIRData &Before, + const BlockSuccessorsIRData &After) { + return Before == After; +} + +void CFGDotChangeReporter::initializeHTML() { + std::error_code EC; + HTML = new raw_fd_ostream(DotCFGChanges + "/passes.html", EC); + assert(!EC && "Unable to open output stream for -cfg-dot-changed"); + + *HTML << "" + << "" + << "" + << "" + << "passes.html" + << "\n" + << ""; +} + +CFGDotChangeReporter::~CFGDotChangeReporter() { + if (!HTML) + return; + *HTML + << "" + << "" + << "\n"; + HTML->flush(); + HTML->close(); + delete HTML; +} + +void CFGDotChangeReporter::registerCallbacks( + PassInstrumentationCallbacks &PIC) { + if (DotCFGChanges.length() == 0) + return; + + ChangeReporter::registerCallbacks(PIC); + + initializeHTML(); +} + void StandardInstrumentations::registerCallbacks( PassInstrumentationCallbacks &PIC) { PrintIR.registerCallbacks(PIC); @@ -746,4 +798,5 @@ PreservedCFGChecker.registerCallbacks(PIC); PrintChangedIR.registerCallbacks(PIC); PrintChanges.registerCallbacks(PIC); + CFGDotDiffGenerator.registerCallbacks(PIC); }