Index: llvm/include/llvm/Passes/StandardInstrumentations.h =================================================================== --- llvm/include/llvm/Passes/StandardInstrumentations.h +++ llvm/include/llvm/Passes/StandardInstrumentations.h @@ -273,46 +273,45 @@ bool same(const std::string &Before, const std::string &After) override; }; -// The following classes hold a representation of the IR for a change -// reporter that uses string comparisons of the basic blocks -// that are created using print (ie, similar to dump()). -// These classes respect the filtering of passes and functions using -// -filter-passes and -filter-print-funcs. -// // Information that needs to be saved for a basic block in order to compare // before and after the pass to determine if it was changed by a pass. -class ChangedBlockData { +template class BlockDataT { public: - ChangedBlockData(const BasicBlock &B); - - bool operator==(const ChangedBlockData &That) const { - return Body == That.Body; - } - bool operator!=(const ChangedBlockData &That) const { - return Body != That.Body; + BlockDataT(const BasicBlock &B) : Label(B.getName().str()), Data(B) { + raw_string_ostream SS(Body); + B.print(SS, nullptr, true, true); } + bool operator==(const BlockDataT &That) const { return Body == That.Body; } + bool operator!=(const BlockDataT &That) const { return Body != That.Body; } + // Return the label of the represented basic block. StringRef getLabel() const { return Label; } // Return the string representation of the basic block. StringRef getBody() const { return Body; } + // Return the associated data + const DataT &getData() const { return Data; } + protected: std::string Label; std::string Body; + + // Extra data associated with a basic block + DataT Data; }; -template class OrderedChangedData { +template class OrderedChangedData { public: // Return the names in the order they were saved std::vector &getOrder() { return Order; } const std::vector &getOrder() const { return Order; } // Return a map of names to saved representations - StringMap &getData() { return Data; } - const StringMap &getData() const { return Data; } + StringMap &getData() { return Data; } + const StringMap &getData() const { return Data; } - bool operator==(const OrderedChangedData &That) const { + bool operator==(const OrderedChangedData &That) const { return Data == That.getData(); } @@ -323,53 +322,68 @@ // based on how the data is ordered in LLVM. static void report(const OrderedChangedData &Before, const OrderedChangedData &After, - function_ref HandlePair); + function_ref HandlePair); protected: std::vector Order; - StringMap Data; + StringMap Data; }; +// Do not need extra information for patch-style change reporter. +class EmptyData { +public: + EmptyData(const BasicBlock &) {} +}; + +// The following classes hold a representation of the IR for a change +// reporter that uses string comparisons of the basic blocks +// that are created using print (ie, similar to dump()). +// These classes respect the filtering of passes and functions using +// -filter-passes and -filter-print-funcs + // The data saved for comparing functions. -using ChangedFuncData = OrderedChangedData; +template +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; +}; -// A map of names to the saved data. -using ChangedIRData = OrderedChangedData; +template +class IRDataT : public OrderedChangedData> {}; -// A class that compares two IRs and does a diff between them. The -// added lines are prefixed with a '+', the removed lines are prefixed -// with a '-' and unchanged lines are prefixed with a space (to have -// things line up). -class ChangedIRComparer { +// Abstract template base class for a class that compares two IRs. The +// class is created with the 2 IRs to compare and then compare is called. +// The static function analyzeIR is used to build up the IR representation. +template class IRComparer { public: - ChangedIRComparer(raw_ostream &OS, const ChangedIRData &Before, - const ChangedIRData &After, bool ColourMode) - : Before(Before), After(After), Out(OS), UseColour(ColourMode) {} + IRComparer(const IRDataT &Before, const IRDataT &After) + : Before(Before), After(After) {} - // Compare the 2 IRs. - void compare(Any IR, StringRef Prefix, StringRef PassID, StringRef Name); + // 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 &Before, + const FuncDataT &After)> + CompareFunc); // Analyze \p IR and build the IR representation in \p Data. - static void analyzeIR(Any IR, ChangedIRData &Data); + static void analyzeIR(Any IR, IRDataT &Data); protected: - // Return the module when that is the appropriate level of - // comparison for \p IR. - static const Module *getModuleForComparison(Any IR); - // Generate the data for \p F into \p Data. - static bool generateFunctionData(ChangedIRData &Data, const Function &F); - - // Called to handle the compare of a function. When \p InModule is set, - // this function is being handled as part of comparing a module. - void handleFunctionCompare(StringRef Name, StringRef Prefix, StringRef PassID, - bool InModule, const ChangedFuncData &Before, - const ChangedFuncData &After); + static bool generateFunctionData(IRDataT &Data, const Function &F); - const ChangedIRData &Before; - const ChangedIRData &After; - raw_ostream &Out; - bool UseColour; + const IRDataT &Before; + const IRDataT &After; }; // A change printer that prints out in-line differences in the basic @@ -378,25 +392,31 @@ // and added, respectively. 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 TextChangeReporter { +class InLineChangePrinter : public TextChangeReporter> { public: InLineChangePrinter(bool VerboseMode, bool ColourMode) - : TextChangeReporter(VerboseMode), UseColour(ColourMode) {} + : TextChangeReporter>(VerboseMode), + UseColour(ColourMode) {} ~InLineChangePrinter() override; void registerCallbacks(PassInstrumentationCallbacks &PIC); protected: // Create a representation of the IR. virtual void generateIRRepresentation(Any IR, StringRef PassID, - ChangedIRData &Output) override; + IRDataT &Output) override; // Called when an interesting IR has changed. virtual void handleAfter(StringRef PassID, std::string &Name, - const ChangedIRData &Before, - const ChangedIRData &After, Any) override; + const IRDataT &Before, + const IRDataT &After, Any) override; // Called to compare the before and after representations of the IR. - virtual bool same(const ChangedIRData &Before, - const ChangedIRData &After) override; + virtual bool same(const IRDataT &Before, + const IRDataT &After) override; + + void handleFunctionCompare(StringRef Name, StringRef Prefix, StringRef PassID, + StringRef Divider, bool InModule, unsigned Minor, + const FuncDataT &Before, + const FuncDataT &After); bool UseColour; }; @@ -409,6 +429,84 @@ void registerCallbacks(PassInstrumentationCallbacks &PIC); }; +// Class that holds transitions between basic blocks. The transitions +// are contained in a map of values to names of basic blocks. +class DCData { +public: + // Fill the map with the transitions from basic block \p B. + DCData(const BasicBlock &B); + + // Return an iterator to the names of the successor blocks. + StringMap::const_iterator begin() const { + return Successors.begin(); + } + StringMap::const_iterator end() const { + return Successors.end(); + } + + // Return the label of the basic block reached on a transition on \p S. + const StringRef getSuccessorLabel(StringRef S) const { + assert(Successors.count(S) == 1 && "Expected to find successor."); + return Successors.find(S)->getValue(); + } + +protected: + // Add a transition to \p Succ on \p Label + void addSuccessorLabel(StringRef Succ, StringRef Label) { + std::pair SS{Succ, Label}; + Successors.insert(SS); + } + + StringMap Successors; +}; + +// 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; + // Called to compare the before and after representations of the IR. + bool same(const IRDataT &Before, + const IRDataT &After) override; + + // Generate the pdf file into \p Dir / \p PDFFileName using \p DotFile as + // input and return the html tag with \Text as the content. + 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 { @@ -421,6 +519,7 @@ IRChangedPrinter PrintChangedIR; PseudoProbeVerifier PseudoProbeVerification; InLineChangePrinter PrintChangedDiff; + DotCfgChangeReporter WebsiteChangeReporter; VerifyInstrumentation Verify; bool VerifyEach; @@ -440,8 +539,12 @@ extern template class ChangeReporter; extern template class TextChangeReporter; -extern template class ChangeReporter; -extern template class TextChangeReporter; +extern template class BlockDataT; +extern template class FuncDataT; +extern template class IRDataT; +extern template class ChangeReporter>; +extern template class TextChangeReporter>; +extern template class IRComparer; } // namespace llvm Index: llvm/include/llvm/Support/DOTGraphTraits.h =================================================================== --- llvm/include/llvm/Support/DOTGraphTraits.h +++ llvm/include/llvm/Support/DOTGraphTraits.h @@ -65,6 +65,11 @@ return false; } + // 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 Index: llvm/include/llvm/Support/GraphWriter.h =================================================================== --- llvm/include/llvm/Support/GraphWriter.h +++ 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); Index: llvm/lib/Passes/StandardInstrumentations.cpp =================================================================== --- llvm/lib/Passes/StandardInstrumentations.cpp +++ 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 @@ -166,7 +210,8 @@ SmallString<128> ULF = formatv("--unchanged-line-format={0}", UnchangedLineFormat); - StringRef Args[] = {"-w", "-d", OLF, NLF, ULF, FileName[0], FileName[1]}; + StringRef Args[] = {DiffBinary, "-w", "-d", OLF, + NLF, ULF, FileName[0], FileName[1]}; Optional Redirects[] = {None, StringRef(FileName[2]), None}; int Result = sys::ExecuteAndWait(*DiffExe, Args, None, Redirects); if (Result < 0) @@ -322,7 +367,7 @@ } /// Generic IR-printing helper that unpacks a pointer to IRUnit wrapped into -/// llvm::Any and does actual print job. +/// Any and does actual print job. void unwrapAndPrint(raw_ostream &OS, Any IR, bool ShouldPreserveUseListOrder = false) { if (!shouldPrintIR(IR)) @@ -368,20 +413,46 @@ "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)) + return any_cast(IR); + if (any_isa(IR)) + return any_cast(IR) + ->begin() + ->getFunction() + .getParent(); + return nullptr; +} + } // namespace -template -ChangeReporter::~ChangeReporter() { +template ChangeReporter::~ChangeReporter() { assert(BeforeStack.empty() && "Problem with Change Printer stack."); } -template -bool ChangeReporter::isInterestingFunction(const Function &F) { +template +bool ChangeReporter::isInterestingFunction(const Function &F) { return isFunctionInPrintList(F.getName()); } -template -bool ChangeReporter::isInterestingPass(StringRef PassID) { +template +bool ChangeReporter::isInterestingPass(StringRef PassID) { if (isIgnored(PassID)) return false; @@ -392,8 +463,8 @@ // Return true when this is a pass on IR for which printing // of changes is desired. -template -bool ChangeReporter::isInteresting(Any IR, StringRef PassID) { +template +bool ChangeReporter::isInteresting(Any IR, StringRef PassID) { if (!isInterestingPass(PassID)) return false; if (any_isa(IR)) @@ -401,8 +472,8 @@ return true; } -template -void ChangeReporter::saveIRBeforePass(Any IR, StringRef PassID) { +template +void ChangeReporter::saveIRBeforePass(Any IR, StringRef PassID) { // Always need to place something on the stack because invalidated passes // are not given the IR so it cannot be determined whether the pass was for // something that was filtered out. @@ -418,12 +489,12 @@ } // Save the IR representation on the stack. - IRUnitT &Data = BeforeStack.back(); + DataT &Data = BeforeStack.back(); generateIRRepresentation(IR, PassID, Data); } -template -void ChangeReporter::handleIRAfterPass(Any IR, StringRef PassID) { +template +void ChangeReporter::handleIRAfterPass(Any IR, StringRef PassID) { assert(!BeforeStack.empty() && "Unexpected empty stack encountered."); std::string Name = getIRName(IR); @@ -436,9 +507,9 @@ handleFiltered(PassID, Name); } else { // Get the before rep from the stack - IRUnitT &Before = BeforeStack.back(); + DataT &Before = BeforeStack.back(); // Create the after rep - IRUnitT After; + DataT After; generateIRRepresentation(IR, PassID, After); // Was there a change in IR? @@ -451,8 +522,8 @@ BeforeStack.pop_back(); } -template -void ChangeReporter::handleInvalidatedPass(StringRef PassID) { +template +void ChangeReporter::handleInvalidatedPass(StringRef PassID) { assert(!BeforeStack.empty() && "Unexpected empty stack encountered."); // Always flag it as invalidated as we cannot determine when @@ -464,8 +535,8 @@ BeforeStack.pop_back(); } -template -void ChangeReporter::registerRequiredCallbacks( +template +void ChangeReporter::registerRequiredCallbacks( PassInstrumentationCallbacks &PIC) { PIC.registerBeforeNonSkippedPassCallback( [this](StringRef P, Any IR) { saveIRBeforePass(IR, P); }); @@ -480,18 +551,12 @@ }); } -ChangedBlockData::ChangedBlockData(const BasicBlock &B) - : Label(B.getName().str()) { - raw_string_ostream SS(Body); - B.print(SS, nullptr, true, true); -} - -template -TextChangeReporter::TextChangeReporter(bool Verbose) - : ChangeReporter(Verbose), Out(dbgs()) {} +template +TextChangeReporter::TextChangeReporter(bool Verbose) + : ChangeReporter(Verbose), Out(dbgs()) {} -template -void TextChangeReporter::handleInitialIR(Any IR) { +template +void TextChangeReporter::handleInitialIR(Any IR) { // Always print the module. // Unwrap and print directly to avoid filtering problems in general routines. auto *M = unwrapModule(IR, /*Force=*/true); @@ -501,29 +566,28 @@ /*ShouldPreserveUseListOrder=*/true); } -template -void TextChangeReporter::omitAfter(StringRef PassID, - std::string &Name) { +template +void TextChangeReporter::omitAfter(StringRef PassID, std::string &Name) { Out << formatv("*** IR Dump After {0} on {1} omitted because no change ***\n", PassID, Name); } -template -void TextChangeReporter::handleInvalidated(StringRef PassID) { +template +void TextChangeReporter::handleInvalidated(StringRef PassID) { Out << formatv("*** IR Pass {0} invalidated ***\n", PassID); } -template -void TextChangeReporter::handleFiltered(StringRef PassID, - std::string &Name) { +template +void TextChangeReporter::handleFiltered(StringRef PassID, + std::string &Name) { SmallString<20> Banner = formatv("*** IR Dump After {0} on {1} filtered out ***\n", PassID, Name); Out << Banner; } -template -void TextChangeReporter::handleIgnored(StringRef PassID, - std::string &Name) { +template +void TextChangeReporter::handleIgnored(StringRef PassID, + std::string &Name) { Out << formatv("*** IR Pass {0} on {1} ignored ***\n", PassID, Name); } @@ -565,10 +629,10 @@ return S1 == S2; } -template -void OrderedChangedData::report( +template +void OrderedChangedData::report( const OrderedChangedData &Before, const OrderedChangedData &After, - function_ref HandlePair) { + function_ref HandlePair) { const auto &BFD = Before.getData(); const auto &AFD = After.getData(); std::vector::const_iterator BI = Before.getOrder().begin(); @@ -576,21 +640,21 @@ std::vector::const_iterator AI = After.getOrder().begin(); std::vector::const_iterator AE = After.getOrder().end(); - auto handlePotentiallyRemovedIRData = [&](std::string S) { + auto HandlePotentiallyRemovedDataT = [&](std::string S) { // The order in LLVM may have changed so check if still exists. if (!AFD.count(S)) { // This has been removed. HandlePair(&BFD.find(*BI)->getValue(), nullptr); } }; - auto handleNewIRData = [&](std::vector &Q) { + auto HandleNewDataT = [&](std::vector &Q) { // Print out any queued up new sections - for (const IRData *NBI : Q) + for (const DataT *NBI : Q) HandlePair(nullptr, NBI); Q.clear(); }; - // Print out the IRData in the after order, with before ones interspersed + // Print out the DataT in the after order, with before ones interspersed // appropriately (ie, somewhere near where they were in the before list). // Start at the beginning of both lists. Loop through the // after list. If an element is common, then advance in the before list @@ -599,26 +663,26 @@ // common, then enqueue it for reporting. When the after list is exhausted, // loop through the before list, reporting any removed ones. Finally, // report the rest of the enqueued new ones. - std::vector NewIRDataQueue; + std::vector NewDataTQueue; while (AI != AE) { if (!BFD.count(*AI)) { // This section is new so place it in the queue. This will cause it // to be reported after deleted sections. - NewIRDataQueue.emplace_back(&AFD.find(*AI)->getValue()); + NewDataTQueue.emplace_back(&AFD.find(*AI)->getValue()); ++AI; continue; } // This section is in both; advance and print out any before-only // until we get to it. while (*BI != *AI) { - handlePotentiallyRemovedIRData(*BI); + HandlePotentiallyRemovedDataT(*BI); ++BI; } // Report any new sections that were queued up and waiting. - handleNewIRData(NewIRDataQueue); + HandleNewDataT(NewDataTQueue); - const IRData &AData = AFD.find(*AI)->getValue(); - const IRData &BData = BFD.find(*AI)->getValue(); + const DataT &AData = AFD.find(*AI)->getValue(); + const DataT &BData = BFD.find(*AI)->getValue(); HandlePair(&BData, &AData); ++BI; ++AI; @@ -626,38 +690,43 @@ // Check any remaining before sections to see if they have been removed while (BI != BE) { - handlePotentiallyRemovedIRData(*BI); + HandlePotentiallyRemovedDataT(*BI); ++BI; } - handleNewIRData(NewIRDataQueue); + HandleNewDataT(NewDataTQueue); } -void ChangedIRComparer::compare(Any IR, StringRef Prefix, StringRef PassID, - StringRef Name) { - if (!getModuleForComparison(IR)) { - // Not a module so just handle the single function. - assert(Before.getData().size() == 1 && "Expected only one function."); - assert(After.getData().size() == 1 && "Expected only one function."); - handleFunctionCompare(Name, Prefix, PassID, false, - Before.getData().begin()->getValue(), - After.getData().begin()->getValue()); +template +void IRComparer::compare( + bool CompareModule, std::function &Before, + const FuncDataT &After)> + CompareFunc) { + if (!CompareModule) { + // Just handle the single function. + assert(Before.getData().size() == 1 && After.getData().size() == 1 && + "Expected only one function."); + CompareFunc(false, 0, Before.getData().begin()->getValue(), + After.getData().begin()->getValue()); return; } - ChangedIRData::report( - Before, After, [&](const ChangedFuncData *B, const ChangedFuncData *A) { + unsigned Minor = 0; + FuncDataT Missing(""); + IRDataT::report( + Before, After, [&](const FuncDataT *B, const FuncDataT *A) { assert((B || A) && "Both functions cannot be missing."); - ChangedFuncData Missing; if (!B) B = &Missing; else if (!A) A = &Missing; - handleFunctionCompare(Name, Prefix, PassID, true, *B, *A); + CompareFunc(true, Minor++, *B, *A); }); } -void ChangedIRComparer::analyzeIR(Any IR, ChangedIRData &Data) { +template +void IRComparer::analyzeIR(Any IR, IRDataT &Data) { if (const Module *M = getModuleForComparison(IR)) { // Create data for each existing/interesting function in the module. for (const Function &F : *M) @@ -677,27 +746,17 @@ generateFunctionData(Data, *F); } -const Module *ChangedIRComparer::getModuleForComparison(Any IR) { - if (any_isa(IR)) - return any_cast(IR); - if (any_isa(IR)) - return any_cast(IR) - ->begin() - ->getFunction() - .getParent(); - return nullptr; -} - -bool ChangedIRComparer::generateFunctionData(ChangedIRData &Data, +template +bool IRComparer::generateFunctionData(IRDataT &Data, const Function &F) { if (!F.isDeclaration() && isFunctionInPrintList(F.getName())) { - ChangedFuncData CFD; + FuncDataT FD(F.getEntryBlock().getName().str()); for (const auto &B : F) { - CFD.getOrder().emplace_back(B.getName()); - CFD.getData().insert({B.getName(), B}); + FD.getOrder().emplace_back(B.getName()); + FD.getData().insert({B.getName(), B}); } Data.getOrder().emplace_back(F.getName()); - Data.getData().insert({F.getName(), CFD}); + Data.getData().insert({F.getName(), FD}); return true; } return false; @@ -791,7 +850,7 @@ return true; StringRef PassName = PIC->getPassNameForClassName(PassID); - return llvm::is_contained(printBeforePasses(), PassName); + return is_contained(printBeforePasses(), PassName); } bool PrintIRInstrumentation::shouldPrintAfterPass(StringRef PassID) { @@ -799,7 +858,7 @@ return true; StringRef PassName = PIC->getPassNameForClassName(PassID); - return llvm::is_contained(printAfterPasses(), PassName); + return is_contained(printAfterPasses(), PassName); } void PrintIRInstrumentation::registerCallbacks( @@ -873,14 +932,13 @@ SpecialPasses.emplace_back("PassAdaptor"); } - PIC.registerBeforeSkippedPassCallback( - [this, SpecialPasses](StringRef PassID, Any IR) { - assert(!isSpecialPass(PassID, SpecialPasses) && - "Unexpectedly skipping special pass"); + PIC.registerBeforeSkippedPassCallback([this, SpecialPasses](StringRef PassID, + Any IR) { + assert(!isSpecialPass(PassID, SpecialPasses) && + "Unexpectedly skipping special pass"); - print() << "Skipping pass: " << PassID << " on " << getIRName(IR) - << "\n"; - }); + print() << "Skipping pass: " << PassID << " on " << getIRName(IR) << "\n"; + }); PIC.registerBeforeNonSkippedPassCallback([this, SpecialPasses]( StringRef PassID, Any IR) { if (isSpecialPass(PassID, SpecialPasses)) @@ -1078,19 +1136,18 @@ report_fatal_error(Twine("CFG unexpectedly changed by ", Pass)); }; - PIC.registerBeforeNonSkippedPassCallback( - [this, &FAM](StringRef P, Any IR) { + PIC.registerBeforeNonSkippedPassCallback([this, &FAM](StringRef P, Any IR) { #ifdef LLVM_ENABLE_ABI_BREAKING_CHECKS - assert(&PassStack.emplace_back(P)); + assert(&PassStack.emplace_back(P)); #endif - (void)this; - if (!any_isa(IR)) - return; + (void)this; + if (!any_isa(IR)) + return; - const auto *F = any_cast(IR); - // Make sure a fresh CFG snapshot is available before the pass. - FAM.getResult(*const_cast(F)); - }); + const auto *F = any_cast(IR); + // Make sure a fresh CFG snapshot is available before the pass. + FAM.getResult(*const_cast(F)); + }); PIC.registerAfterPassInvalidatedCallback( [this](StringRef P, const PreservedAnalyses &PassPA) { @@ -1164,36 +1221,43 @@ InLineChangePrinter::~InLineChangePrinter() {} void InLineChangePrinter::generateIRRepresentation(Any IR, StringRef PassID, - ChangedIRData &D) { - ChangedIRComparer::analyzeIR(IR, D); + IRDataT &D) { + IRComparer::analyzeIR(IR, D); } void InLineChangePrinter::handleAfter(StringRef PassID, std::string &Name, - const ChangedIRData &Before, - const ChangedIRData &After, Any IR) { + const IRDataT &Before, + const IRDataT &After, Any IR) { SmallString<20> Banner = formatv("*** IR Dump After {0} on {1} ***\n", PassID, Name); Out << Banner; - ChangedIRComparer(Out, Before, After, UseColour) - .compare(IR, "", PassID, Name); + IRComparer(Before, After) + .compare(getModuleForComparison(IR), + [&](bool InModule, unsigned Minor, + const FuncDataT &Before, + const FuncDataT &After) -> void { + handleFunctionCompare(Name, "", PassID, " on ", InModule, + Minor, Before, After); + }); Out << "\n"; } -bool InLineChangePrinter::same(const ChangedIRData &D1, - const ChangedIRData &D2) { +bool InLineChangePrinter::same(const IRDataT &D1, + const IRDataT &D2) { return D1 == D2; } -void ChangedIRComparer::handleFunctionCompare(StringRef Name, StringRef Prefix, - StringRef PassID, bool InModule, - const ChangedFuncData &Before, - const ChangedFuncData &After) { +void InLineChangePrinter::handleFunctionCompare( + StringRef Name, StringRef Prefix, StringRef PassID, StringRef Divider, + bool InModule, unsigned Minor, const FuncDataT &Before, + const FuncDataT &After) { // Print a banner when this is being shown in the context of a module if (InModule) Out << "\n*** IR for function " << Name << " ***\n"; - ChangedFuncData::report( - Before, After, [&](const ChangedBlockData *B, const ChangedBlockData *A) { + FuncDataT::report( + Before, After, + [&](const BlockDataT *B, const BlockDataT *A) { StringRef BStr = B ? B->getBody() : "\n"; StringRef AStr = A ? A->getBody() : "\n"; const std::string Removed = @@ -1209,7 +1273,874 @@ PrintChanged == ChangePrinter::PrintChangedDiffQuiet || PrintChanged == ChangePrinter::PrintChangedColourDiffVerbose || PrintChanged == ChangePrinter::PrintChangedColourDiffQuiet) - TextChangeReporter::registerRequiredCallbacks(PIC); + 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::same(const IRDataT &Before, + const IRDataT &After) { + return Before == After; +} + +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( @@ -1221,6 +2152,8 @@ PrintChanged == ChangePrinter::PrintChangedColourDiffVerbose, PrintChanged == ChangePrinter::PrintChangedColourDiffVerbose || PrintChanged == ChangePrinter::PrintChangedColourDiffQuiet), + WebsiteChangeReporter(PrintChanged == + ChangePrinter::PrintChangedDotCfgVerbose), Verify(DebugLogging), VerifyEach(VerifyEach) {} void StandardInstrumentations::registerCallbacks( @@ -1237,14 +2170,17 @@ if (VerifyEach) Verify.registerCallbacks(PIC); PrintChangedDiff.registerCallbacks(PIC); + WebsiteChangeReporter.registerCallbacks(PIC); } -namespace llvm { - template class ChangeReporter; template class TextChangeReporter; -template class ChangeReporter; -template class TextChangeReporter; +template class BlockDataT; +template class FuncDataT; +template class IRDataT; +template class ChangeReporter>; +template class TextChangeReporter>; +template class IRComparer; } // namespace llvm Index: llvm/test/Other/ChangePrinters/DotCfg/lit.local.cfg =================================================================== --- /dev/null +++ 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 Index: llvm/test/Other/ChangePrinters/DotCfg/print-changed-dot-cfg.ll =================================================================== --- /dev/null +++ llvm/test/Other/ChangePrinters/DotCfg/print-changed-dot-cfg.ll @@ -0,0 +1,332 @@ +; Simple checks of -print-changed=dot-cfg +; +; Note that (mostly) only the banners are checked. +; +; Simple functionality check. +; RUN: rm -rf %t && mkdir -p %t +; RUN: opt -S -print-changed=dot-cfg -passes=instsimplify -dot-cfg-dir=%t < %s -o /dev/null +; RUN: ls %t/*.pdf %t/passes.html | count 5 +; 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 -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 -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 5 +; 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 -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 5 +; 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 -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 -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 -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 -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_0.pdf diff_0_1.pdf diff_2.pdf diff_4.pdf +; CHECK-DOT-CFG-SIMPLE: passes.html +; CHECK-DOT-CFG-SIMPLE-NEXT: +; CHECK-DOT-CFG-SIMPLE-NEXT:
+; CHECK-DOT-CFG-SIMPLE-NEXT:

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

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

+; CHECK-DOT-CFG-SIMPLE-NEXT: 1. Pass VerifierPass on [module] omitted because no change
+; CHECK-DOT-CFG-SIMPLE-NEXT: 2. Pass InstSimplifyPass on g
+; CHECK-DOT-CFG-SIMPLE-NEXT:

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

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

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

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

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

+; CHECK-DOT-CFG-FUNC-FILTER-NEXT: 5. PassManager<llvm::Function> on f ignored
+; CHECK-DOT-CFG-FUNC-FILTER-NEXT: 6. ModuleToFunctionPassAdaptor on [module] ignored
+; CHECK-DOT-CFG-FUNC-FILTER-NEXT: 7. Pass VerifierPass on [module] omitted because no change
+; CHECK-DOT-CFG-FUNC-FILTER-NEXT: 8. 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.0. Initial IR
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT: 0.1. Initial IR
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT:

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

+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT: 1. Pass VerifierPass on [module] omitted because no change
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT: 2. Pass InstSimplifyPass on g
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT:

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

+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT: 5. PassManager<llvm::Function> on f ignored
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT: 6. ModuleToFunctionPassAdaptor on [module] ignored
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT: 7. Pass VerifierPass on [module] omitted because no change
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT: 8. 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.0. Initial IR
+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT: 0.1. Initial IR
+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT:

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

+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT: 1. Pass VerifierPass on [module] omitted because no change
+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT: 2. Pass InstSimplifyPass on g
+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT:

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

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

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

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

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

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

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

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

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

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

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

+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT: 6. Pass NoOpFunctionPass on f omitted because no change
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT: 7. PassManager<llvm::Function> on f ignored
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT: 8. ModuleToFunctionPassAdaptor on [module] ignored
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT: 9. Pass VerifierPass on [module] filtered out
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT: 10. 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: +; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT:
+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT:

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

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

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

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