[llvm] 816761f - Add new choices dot-cfg and dot-cfg-quiet to print-changed.

Jamie Schmeiser via llvm-commits llvm-commits at lists.llvm.org
Tue Nov 2 09:06:36 PDT 2021


Author: Jamie Schmeiser
Date: 2021-11-02T12:06:25-04:00
New Revision: 816761f04484bc7096755e8351a1ff46a3594398

URL: https://github.com/llvm/llvm-project/commit/816761f04484bc7096755e8351a1ff46a3594398
DIFF: https://github.com/llvm/llvm-project/commit/816761f04484bc7096755e8351a1ff46a3594398.diff

LOG: Add new choices dot-cfg and dot-cfg-quiet to print-changed.

Summary:
Add new options -print-changed=[dot-cfg | dot-cfg-quiet] which create
a website of DOT files showing colourized changes as the IR is changed
by passes in the new pass manager pipeline.

A new change reporter is introduced that creates a website of changes made
by passes in the opt pipeline that change the IR. The hidden option
-dot-cfg-dir=<dir> specifies a directory (defaulting to "./") into which the
website will be created.

A file passes.html is created that contains a list of all the passes that
act on the IR. Those that do not change the IR are listed as omitted
because of no change, ignored or filtered out (using -filter-print-func
and -filter-passes) or not listed in quiet mode. Those that
do change the IR are listed as a link to a DOT file which contains a
CFG depiction of the IR (ala -dot-cfg) except that the instructions,
basic blocks and links that are only in the IR before the pass (ie, removed)
and those that are only in the IR after the pass (ie, added) are shown in
red and green, respectively, while the aspects of the CFG that do not change
are shown in black. Additional hidden options
-dot-cfg-before-color=<dot named color>,
-dot-cfg-after-color=<dot named color> and
-dot-cfg-common-color=<dot named color> are defined that allow the
customization of the colors used in colorizing the CFG.
-change-printer-dot-path=<path to dot exe> is also added.

Author: Jamie Schmeiser <schmeise at ca.ibm.com>
Reviewed By: aeubanks (Arthur Eubanks)
Differential Revision: https://reviews.llvm.org/D87202

Added: 
    llvm/test/Other/ChangePrinters/DotCfg/lit.local.cfg
    llvm/test/Other/ChangePrinters/DotCfg/print-changed-dot-cfg.ll

Modified: 
    llvm/include/llvm/Passes/StandardInstrumentations.h
    llvm/include/llvm/Support/DOTGraphTraits.h
    llvm/include/llvm/Support/GraphWriter.h
    llvm/lib/Passes/StandardInstrumentations.cpp

Removed: 
    


################################################################################
diff  --git a/llvm/include/llvm/Passes/StandardInstrumentations.h b/llvm/include/llvm/Passes/StandardInstrumentations.h
index b89a151f88c0b..4340b6242ad4e 100644
--- a/llvm/include/llvm/Passes/StandardInstrumentations.h
+++ b/llvm/include/llvm/Passes/StandardInstrumentations.h
@@ -333,7 +333,16 @@ class EmptyData {
 
 // The data saved for comparing functions.
 template <typename T>
-class FuncDataT : public OrderedChangedData<BlockDataT<T>> {};
+class FuncDataT : public OrderedChangedData<BlockDataT<T>> {
+public:
+  FuncDataT(std::string S) : EntryBlockName(S) {}
+
+  // Return the name of the entry block
+  std::string getEntryBlockName() const { return EntryBlockName; }
+
+protected:
+  std::string EntryBlockName;
+};
 
 // The data saved for comparing IRs.
 template <typename T>
@@ -350,7 +359,6 @@ template <typename T> class IRComparer {
   // 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<void(bool InModule, unsigned Minor,
@@ -408,6 +416,81 @@ class VerifyInstrumentation {
   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<std::string>::const_iterator begin() const {
+    return Successors.begin();
+  }
+  StringMap<std::string>::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<std::string, std::string> SS{Succ, Label};
+    Successors.insert(SS);
+  }
+
+  StringMap<std::string> 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<IRDataT<DCData>> {
+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<DCData> &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<DCData> &Before, const IRDataT<DCData> &After,
+                   Any) override;
+  // Called when an interesting pass is invalidated.
+  void handleInvalidated(StringRef PassID) override;
+  // Called when the IR or pass is not interesting.
+  void handleFiltered(StringRef PassID, std::string &Name) override;
+  // Called when an ignored pass is encountered.
+  void handleIgnored(StringRef PassID, std::string &Name) override;
+
+  // Generate the pdf file into \p Dir / \p PDFFileName using \p DotFile as
+  // input and return the html <a> 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<DCData> &Before,
+                             const FuncDataT<DCData> &After);
+
+  unsigned N = 0;
+  std::unique_ptr<raw_fd_ostream> HTML;
+};
+
 /// This class provides an interface to register all the standard pass
 /// instrumentations and manages their state (if any).
 class StandardInstrumentations {
@@ -420,6 +503,7 @@ class StandardInstrumentations {
   IRChangedPrinter PrintChangedIR;
   PseudoProbeVerifier PseudoProbeVerification;
   InLineChangePrinter PrintChangedDiff;
+  DotCfgChangeReporter WebsiteChangeReporter;
   VerifyInstrumentation Verify;
 
   bool VerifyEach;

diff  --git a/llvm/include/llvm/Support/DOTGraphTraits.h b/llvm/include/llvm/Support/DOTGraphTraits.h
index a73538fa14624..ffa9abe328c83 100644
--- a/llvm/include/llvm/Support/DOTGraphTraits.h
+++ b/llvm/include/llvm/Support/DOTGraphTraits.h
@@ -65,6 +65,11 @@ struct DefaultDOTGraphTraits {
     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<typename GraphType>

diff  --git a/llvm/include/llvm/Support/GraphWriter.h b/llvm/include/llvm/Support/GraphWriter.h
index b886bf45f474b..11a31bf401606 100644
--- a/llvm/include/llvm/Support/GraphWriter.h
+++ b/llvm/include/llvm/Support/GraphWriter.h
@@ -66,6 +66,7 @@ template<typename GraphType>
 class GraphWriter {
   raw_ostream &O;
   const GraphType &G;
+  bool RenderUsingHTML = false;
 
   using DOTTraits = DOTGraphTraits<GraphType>;
   using GTraits = GraphTraits<GraphType>;
@@ -86,6 +87,9 @@ class GraphWriter {
     child_iterator EE = GTraits::child_end(Node);
     bool hasEdgeSourceLabels = false;
 
+    if (RenderUsingHTML)
+      O << "</tr><tr>";
+
     for (unsigned i = 0; EI != EE && i != 64; ++EI, ++i) {
       std::string label = DTraits.getEdgeSourceLabel(Node, EI);
 
@@ -94,14 +98,22 @@ class GraphWriter {
 
       hasEdgeSourceLabels = true;
 
-      if (i)
-        O << "|";
+      if (RenderUsingHTML)
+        O << "<td colspan=\"1\" port=\"s" << i << "\">" << label << "</td>";
+      else {
+        if (i)
+          O << "|";
 
-      O << "<s" << i << ">" << DOT::EscapeString(label);
+        O << "<s" << i << ">" << DOT::EscapeString(label);
+      }
     }
 
-    if (EI != EE && hasEdgeSourceLabels)
-      O << "|<s64>truncated...";
+    if (EI != EE && hasEdgeSourceLabels) {
+      if (RenderUsingHTML)
+        O << "<td colspan=\"1\" port=\"s64\">truncated...</td>";
+      else
+        O << "|<s64>truncated...";
+    }
 
     return hasEdgeSourceLabels;
   }
@@ -109,6 +121,7 @@ class GraphWriter {
 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 @@ class GraphWriter {
   void writeNode(NodeRef Node) {
     std::string NodeAttributes = DTraits.getNodeAttributes(Node, G);
 
-    O << "\tNode" << static_cast<const void*>(Node) << " [shape=record,";
+    O << "\tNode" << static_cast<const void *>(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 << "<<table border=\"0\" cellborder=\"1\" cellspacing=\"0\""
+        << " cellpadding=\"0\"><tr><td align=\"text\" colspan=\"" << ColSpan
+        << "\">";
+    } else
+      O << "\"{";
 
     if (!DTraits.renderGraphFromBottomUp()) {
-      O << DOT::EscapeString(DTraits.getNodeLabel(Node, G));
+      if (RenderUsingHTML)
+        O << DTraits.getNodeLabel(Node, G) << "</td>";
+      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 @@ class GraphWriter {
     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 @@ class GraphWriter {
           << DOT::EscapeString(DTraits.getEdgeDestLabel(Node, i));
       }
 
-      if (i != e)
-        O << "|<d64>truncated...";
-      O << "}";
+      if (RenderUsingHTML)
+        O << "<td colspan=\"1\">... truncated</td>";
+      else if (i != e)
+        O << "|<d64>truncated...}";
     }
 
-    O << "}\"];\n";   // Finish printing the "node" line
+    if (RenderUsingHTML)
+      O << "</tr></table>>";
+    else
+      O << "}\"";
+    O << "];\n"; // Finish printing the "node" line
 
     // Output all of the edges now
     child_iterator EI = GTraits::child_begin(Node);

diff  --git a/llvm/lib/Passes/StandardInstrumentations.cpp b/llvm/lib/Passes/StandardInstrumentations.cpp
index 727d02afea17a..44f3011044858 100644
--- a/llvm/lib/Passes/StandardInstrumentations.cpp
+++ b/llvm/lib/Passes/StandardInstrumentations.cpp
@@ -29,10 +29,14 @@
 #include "llvm/Support/CommandLine.h"
 #include "llvm/Support/Debug.h"
 #include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/GraphWriter.h"
 #include "llvm/Support/MemoryBuffer.h"
 #include "llvm/Support/Program.h"
+#include "llvm/Support/Regex.h"
 #include "llvm/Support/raw_ostream.h"
+#include <unordered_map>
 #include <unordered_set>
+#include <utility>
 #include <vector>
 
 using namespace llvm;
@@ -79,7 +83,9 @@ enum class ChangePrinter {
   PrintChangedDiffVerbose,
   PrintChangedDiffQuiet,
   PrintChangedColourDiffVerbose,
-  PrintChangedColourDiffQuiet
+  PrintChangedColourDiffQuiet,
+  PrintChangedDotCfgVerbose,
+  PrintChangedDotCfgQuiet
 };
 static cl::opt<ChangePrinter> PrintChanged(
     "print-changed", cl::desc("Print changed IRs"), cl::Hidden,
@@ -95,6 +101,10 @@ static cl::opt<ChangePrinter> PrintChanged(
                    "Display patch-like changes with color"),
         clEnumValN(ChangePrinter::PrintChangedColourDiffQuiet, "c
diff -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 @@ static cl::opt<std::string>
     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<std::string>
+    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<std::string>
+    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<std::string> 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<std::string>
+    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<std::string> DotCfgDir(
+    "dot-cfg-dir",
+    cl::desc("Generate dot files into specified directory for changed IRs"),
+    cl::Hidden, cl::init("./"));
+
 namespace {
 
 // Perform a system based 
diff  between \p Before and \p After, using
@@ -367,6 +411,21 @@ bool isIgnored(StringRef PassID) {
                         "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<const Module *>(IR))
@@ -644,7 +703,7 @@ void IRComparer<T>::compare(
   }
 
   unsigned Minor = 0;
-  FuncDataT<T> Missing;
+  FuncDataT<T> Missing("");
   IRDataT<T>::report(Before, After,
                      [&](const FuncDataT<T> *B, const FuncDataT<T> *A) {
                        assert((B || A) && "Both functions cannot be missing.");
@@ -679,7 +738,7 @@ template <typename T> void IRComparer<T>::analyzeIR(Any IR, IRDataT<T> &Data) {
 template <typename T>
 bool IRComparer<T>::generateFunctionData(IRDataT<T> &Data, const Function &F) {
   if (!F.isDeclaration() && isFunctionInPrintList(F.getName())) {
-    FuncDataT<T> FD;
+    FuncDataT<T> FD(F.getEntryBlock().getName().str());
     for (const auto &B : F) {
       FD.getOrder().emplace_back(B.getName());
       FD.getData().insert({B.getName(), B});
@@ -1200,8 +1259,868 @@ void InLineChangePrinter::registerCallbacks(PassInstrumentationCallbacks &PIC) {
     TextChangeReporter<IRDataT<EmptyData>>::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<DisplayNode *>::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<DisplayEdge *>::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<DisplayEdge> Edges;
+
+  std::vector<DisplayEdge *> EdgePtrs;
+  std::unordered_set<DisplayNode *> Children;
+  std::unordered_map<const DisplayNode *, const DisplayEdge *> EdgeMap;
+
+  // Safeguard adding of edges.
+  bool AllEdgesCreated = false;
+};
+
+// Class representing a 
diff erence 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<DisplayNode *>::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 
diff erences 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<DisplayNode> Nodes;
+  std::vector<DisplayNode *> 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 
diff erence graph.
+class DotCfgDiffNode {
+public:
+  DotCfgDiffNode() = delete;
+
+  // Create a node in Dot 
diff erence 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<DCData> &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<DCData> &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<std::string, IRChangeDiffType> 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<const unsigned, unsigned> &NodeMap) const;
+
+protected:
+  DotCfgDiff &Graph;
+  const unsigned N;
+  const BlockDataT<DCData> *Data[2];
+  IRChangeDiffType Type;
+  std::map<const unsigned, std::pair<std::string, IRChangeDiffType>> EdgesMap;
+  std::vector<unsigned> Children;
+  std::vector<unsigned> Edges;
+};
+
+// Class representing the 
diff erence 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<DCData> &Before,
+             const FuncDataT<DCData> &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<DCData> &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<DotCfgDiffNode> after the
+  // display graph is separated out, which would remove the need for
+  // NodePosition.
+  std::vector<DotCfgDiffNode> Nodes;
+  StringMap<unsigned> NodePosition;
+  const std::string GraphName;
+
+  StringMap<std::string> 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(
+        "<FONT COLOR=\"{0}\">%l</FONT><BR align=\"left\"/>", Colours[InBefore]);
+    SmallString<80> NewLineFormat = formatv(
+        "<FONT COLOR=\"{0}\">%l</FONT><BR align=\"left\"/>", Colours[InAfter]);
+    SmallString<80> UnchangedLineFormat = formatv(
+        "<FONT COLOR=\"{0}\">%l</FONT><BR align=\"left\"/>", Colours[IsCommon]);
+    std::string Diff = Data[0]->getLabel().str();
+    Diff += ":\n<BR align=\"left\"/>" +
+            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("<FONT COLOR=\"\\w+\"></FONT>");
+    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 = "<FONT COLOR=\"" + Colours[Type] + "\">" + Label.str() + ":";
+
+  // align each line to the left.
+  while (BS1.size()) {
+    S.append("<BR align=\"left\"/>");
+    StringRef Line = BS1.take_until([](char C) { return C == '\n'; });
+    S.append(Line.str());
+    BS1 = BS1.drop_front(Line.size() + 1);
+  }
+  S.append("<BR align=\"left\"/></FONT>");
+  return S;
+}
+
+std::string DotCfgDiff::colourize(std::string S, IRChangeDiffType T) const {
+  if (S.length() == 0)
+    return S;
+  return "<FONT COLOR=\"" + Colours[T] + "\">" + S + "</FONT>";
+}
+
+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<DCData> &Before,
+                       const FuncDataT<DCData> &After)
+    : GraphName(Title.str()) {
+  StringMap<IRChangeDiffType> EdgesMap;
+
+  // Handle each basic block in the before IR.
+  for (auto &B : Before.getData()) {
+    StringRef Label = B.getKey();
+    const BlockDataT<DCData> &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<std::string>::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<DCData> &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<std::string>::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<const unsigned, unsigned> 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<const unsigned, unsigned> &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<DotCfgDiffDisplayGraph *> {
+  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<DotCfgDiffDisplayGraph *> : 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<const BranchInst>(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<const SwitchInst>(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<IRDataT<DCData>>(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<DCData> &Before,
+    const FuncDataT<DCData> &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<char, 128> 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<std::string> 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(
+      "  <a href=\"{0}\" target=\"_blank\">{1}</a><br/>\n", PDFFileName, Text);
+  return S.c_str();
+}
+
+void DotCfgChangeReporter::handleInitialIR(Any IR) {
+  assert(HTML && "Expected outstream to be set");
+  *HTML << "<button type=\"button\" class=\"collapsible\">0. "
+        << "Initial IR (by function)</button>\n"
+        << "<div class=\"content\">\n"
+        << "  <p>\n";
+  // Create representation of IR
+  IRDataT<DCData> Data;
+  IRComparer<DCData>::analyzeIR(IR, Data);
+  // Now compare it against itself, which will have everything the
+  // same and will generate the files.
+  IRComparer<DCData>(Data, Data)
+      .compare(getModuleForComparison(IR),
+               [&](bool InModule, unsigned Minor,
+                   const FuncDataT<DCData> &Before,
+                   const FuncDataT<DCData> &After) -> void {
+                 handleFunctionCompare("", " ", "Initial IR", "", InModule,
+                                       Minor, Before, After);
+               });
+  *HTML << "  </p>\n"
+        << "</div><br/>\n";
+  ++N;
+}
+
+void DotCfgChangeReporter::generateIRRepresentation(Any IR, StringRef PassID,
+                                                    IRDataT<DCData> &Data) {
+  IRComparer<DCData>::analyzeIR(IR, Data);
+}
+
+void DotCfgChangeReporter::omitAfter(StringRef PassID, std::string &Name) {
+  assert(HTML && "Expected outstream to be set");
+  SmallString<20> Banner =
+      formatv("  <a>{0}. Pass {1} on {2} omitted because no change</a><br/>\n",
+              N, makeHTMLReady(PassID), Name);
+  *HTML << Banner;
+  ++N;
+}
+
+void DotCfgChangeReporter::handleAfter(StringRef PassID, std::string &Name,
+                                       const IRDataT<DCData> &Before,
+                                       const IRDataT<DCData> &After, Any IR) {
+  assert(HTML && "Expected outstream to be set");
+  IRComparer<DCData>(Before, After)
+      .compare(getModuleForComparison(IR),
+               [&](bool InModule, unsigned Minor,
+                   const FuncDataT<DCData> &Before,
+                   const FuncDataT<DCData> &After) -> void {
+                 handleFunctionCompare(Name, " Pass ", PassID, " on ", InModule,
+                                       Minor, Before, After);
+               });
+  *HTML << "    </p></div>\n";
+  ++N;
+}
+
+void DotCfgChangeReporter::handleInvalidated(StringRef PassID) {
+  assert(HTML && "Expected outstream to be set");
+  SmallString<20> Banner =
+      formatv("  <a>{0}. {1} invalidated</a><br/>\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("  <a>{0}. Pass {1} on {2} filtered out</a><br/>\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("  <a>{0}. {1} on {2} ignored</a><br/>\n", N,
+                                   makeHTMLReady(PassID), Name);
+  *HTML << Banner;
+  ++N;
+}
+
+bool DotCfgChangeReporter::initializeHTML() {
+  std::error_code EC;
+  HTML = std::make_unique<raw_fd_ostream>(DotCfgDir + "/passes.html", EC);
+  if (EC) {
+    HTML = nullptr;
+    return false;
+  }
+
+  *HTML << "<!doctype html>"
+        << "<html>"
+        << "<head>"
+        << "<style>.collapsible { "
+        << "background-color: #777;"
+        << " color: white;"
+        << " cursor: pointer;"
+        << " padding: 18px;"
+        << " width: 100%;"
+        << " border: none;"
+        << " text-align: left;"
+        << " outline: none;"
+        << " font-size: 15px;"
+        << "} .active, .collapsible:hover {"
+        << " background-color: #555;"
+        << "} .content {"
+        << " padding: 0 18px;"
+        << " display: none;"
+        << " overflow: hidden;"
+        << " background-color: #f1f1f1;"
+        << "}"
+        << "</style>"
+        << "<title>passes.html</title>"
+        << "</head>\n"
+        << "<body>";
+  return true;
+}
+
+DotCfgChangeReporter::~DotCfgChangeReporter() {
+  if (!HTML)
+    return;
+  *HTML
+      << "<script>var coll = document.getElementsByClassName(\"collapsible\");"
+      << "var i;"
+      << "for (i = 0; i < coll.length; i++) {"
+      << "coll[i].addEventListener(\"click\", function() {"
+      << " this.classList.toggle(\"active\");"
+      << " var content = this.nextElementSibling;"
+      << " if (content.style.display === \"block\"){"
+      << " content.style.display = \"none\";"
+      << " }"
+      << " else {"
+      << " content.style.display= \"block\";"
+      << " }"
+      << " });"
+      << " }"
+      << "</script>"
+      << "</body>"
+      << "</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<IRDataT<DCData>>::registerRequiredCallbacks(PIC);
+      return;
+    }
+    dbgs() << "Unable to open output stream for -cfg-dot-changed\n";
+  }
+}
+
 StandardInstrumentations::StandardInstrumentations(
     bool DebugLogging, bool VerifyEach, PrintPassOptions PrintPassOpts)
     : PrintPass(DebugLogging, PrintPassOpts), OptNone(DebugLogging),
@@ -1211,6 +2130,8 @@ StandardInstrumentations::StandardInstrumentations(
               PrintChanged == ChangePrinter::PrintChangedColourDiffVerbose,
           PrintChanged == ChangePrinter::PrintChangedColourDiffVerbose ||
               PrintChanged == ChangePrinter::PrintChangedColourDiffQuiet),
+      WebsiteChangeReporter(PrintChanged ==
+                            ChangePrinter::PrintChangedDotCfgVerbose),
       Verify(DebugLogging), VerifyEach(VerifyEach) {}
 
 void StandardInstrumentations::registerCallbacks(
@@ -1227,6 +2148,7 @@ void StandardInstrumentations::registerCallbacks(
   if (VerifyEach)
     Verify.registerCallbacks(PIC);
   PrintChangedDiff.registerCallbacks(PIC);
+  WebsiteChangeReporter.registerCallbacks(PIC);
 }
 
 template class ChangeReporter<std::string>;

diff  --git a/llvm/test/Other/ChangePrinters/DotCfg/lit.local.cfg b/llvm/test/Other/ChangePrinters/DotCfg/lit.local.cfg
new file mode 100644
index 0000000000000..a739faf919daa
--- /dev/null
+++ b/llvm/test/Other/ChangePrinters/DotCfg/lit.local.cfg
@@ -0,0 +1,4 @@
+import os
+
+if not os.path.exists('/usr/bin/dot'):
+  config.unsupported = True

diff  --git a/llvm/test/Other/ChangePrinters/DotCfg/print-changed-dot-cfg.ll b/llvm/test/Other/ChangePrinters/DotCfg/print-changed-dot-cfg.ll
new file mode 100644
index 0000000000000..cbd9d3013d97c
--- /dev/null
+++ b/llvm/test/Other/ChangePrinters/DotCfg/print-changed-dot-cfg.ll
@@ -0,0 +1,311 @@
+; Simple checks of -print-changed=dot-cfg
+;
+; Note that (mostly) only the banners are checked.
+;
+; Simple functionality check.
+; RUN: rm -rf %t && mkdir -p %t
+; RUN: opt -disable-verify -S -print-changed=dot-cfg -passes=instsimplify -dot-cfg-dir=%t < %s -o /dev/null
+; RUN: ls %t/*.pdf %t/passes.html | count 4
+; RUN: FileCheck %s -input-file=%t/passes.html --check-prefix=CHECK-DOT-CFG-SIMPLE
+;
+; Check that only the passes that change the IR are printed and that the
+; others (including g) are filtered out.
+; RUN: rm -rf %t && mkdir -p %t
+; RUN: opt -disable-verify -S -print-changed=dot-cfg -passes=instsimplify -filter-print-funcs=f  -dot-cfg-dir=%t < %s -o /dev/null
+; RUN: ls %t/*.pdf %t/passes.html | count 3
+; RUN: FileCheck %s -input-file=%t/passes.html --check-prefix=CHECK-DOT-CFG-FUNC-FILTER
+;
+; Check that the reporting of IRs respects is not affected by
+; -print-module-scope
+; RUN: rm -rf %t && mkdir -p %t
+; RUN: opt -disable-verify -S -print-changed=dot-cfg -passes=instsimplify -print-module-scope -dot-cfg-dir=%t < %s -o /dev/null
+; RUN: ls %t/*.pdf %t/passes.html | count 4
+; RUN: FileCheck %s -input-file=%t/passes.html --check-prefix=CHECK-DOT-CFG-PRINT-MOD-SCOPE
+;
+; Check that reporting of multiple functions happens
+; RUN: rm -rf %t && mkdir -p %t
+; RUN: opt -disable-verify -S -print-changed=dot-cfg -passes=instsimplify -filter-print-funcs="f,g" -dot-cfg-dir=%t < %s -o /dev/null
+; RUN: ls %t/*.pdf %t/passes.html | count 4
+; RUN: FileCheck %s -input-file=%t/passes.html --check-prefix=CHECK-DOT-CFG-FILTER-MULT-FUNC
+;
+; Check that the reporting of IRs respects -filter-passes
+; RUN: rm -rf %t && mkdir -p %t
+; RUN: opt -disable-verify -S -print-changed=dot-cfg -passes="instsimplify,no-op-function" -filter-passes="NoOpFunctionPass" -dot-cfg-dir=%t < %s -o /dev/null
+; RUN: ls %t/*.pdf %t/passes.html | count 2
+; RUN: FileCheck %s -input-file=%t/passes.html --check-prefix=CHECK-DOT-CFG-FILTER-PASSES
+;
+; Check that the reporting of IRs respects -filter-passes with multiple passes
+; RUN: rm -rf %t && mkdir -p %t
+; RUN: opt -disable-verify -S -print-changed=dot-cfg -passes="instsimplify,no-op-function" -filter-passes="NoOpFunctionPass,InstSimplifyPass" -dot-cfg-dir=%t < %s -o /dev/null
+; RUN: ls %t/*.pdf %t/passes.html | count 4
+; RUN: FileCheck %s -input-file=%t/passes.html --check-prefix=CHECK-DOT-CFG-FILTER-MULT-PASSES
+;
+; Check that the reporting of IRs respects both -filter-passes and -filter-print-funcs
+; RUN: rm -rf %t && mkdir -p %t
+; RUN: opt -disable-verify -S -print-changed=dot-cfg -passes="instsimplify,no-op-function" -filter-passes="NoOpFunctionPass,InstSimplifyPass" -filter-print-funcs=f -dot-cfg-dir=%t < %s -o /dev/null
+; RUN: ls %t/*.pdf %t/passes.html | count 3
+; RUN: FileCheck %s -input-file=%t/passes.html --check-prefix=CHECK-DOT-CFG-FILTER-FUNC-PASSES
+;
+; Check that repeated passes that change the IR are printed and that the
+; others (including g) are filtered out.  Note that only the first time
+; instsimplify is run on f will result in changes
+; RUN: rm -rf %t && mkdir -p %t
+; RUN: opt -disable-verify -S -print-changed=dot-cfg -passes="instsimplify,instsimplify" -filter-print-funcs=f  -dot-cfg-dir=%t < %s -o /dev/null
+; RUN: ls %t/*.pdf %t/passes.html | count 3
+; RUN: FileCheck %s -input-file=%t/passes.html --check-prefix=CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC
+;
+; Simple checks of -print-changed=dot-cfg-quiet
+;
+; Note that (mostly) only the banners are checked.
+;
+; Simple functionality check.
+; RUN: rm -rf %t && mkdir -p %t
+; RUN: opt -S -print-changed=dot-cfg-quiet -passes=instsimplify -dot-cfg-dir=%t < %s -o /dev/null
+; RUN: ls %t/*.pdf %t/passes.html | count 3
+; RUN: FileCheck %s -input-file=%t/passes.html --check-prefix=CHECK-DOT-CFG-QUIET-SIMPLE --allow-empty
+;
+; Check that only the passes that change the IR are printed and that the
+; others (including g) are filtered out.
+; RUN: rm -rf %t && mkdir -p %t
+; RUN: opt -S -print-changed=dot-cfg-quiet -passes=instsimplify -filter-print-funcs=f  -dot-cfg-dir=%t < %s -o /dev/null
+; RUN: ls %t/*.pdf %t/passes.html | count 2
+; RUN: FileCheck %s -input-file=%t/passes.html --check-prefix=CHECK-DOT-CFG-QUIET-FUNC-FILTER
+;
+; Check that the reporting of IRs respects is not affected by
+; -print-module-scope
+; RUN: rm -rf %t && mkdir -p %t
+; RUN: opt -S -print-changed=dot-cfg-quiet -passes=instsimplify -print-module-scope -dot-cfg-dir=%t < %s -o /dev/null
+; RUN: ls %t/*.pdf %t/passes.html | count 3
+; RUN: FileCheck %s -input-file=%t/passes.html --check-prefix=CHECK-DOT-CFG-QUIET-PRINT-MOD-SCOPE
+;
+; Check that reporting of multiple functions happens
+; RUN: rm -rf %t && mkdir -p %t
+; RUN: opt -S -print-changed=dot-cfg-quiet -passes=instsimplify -filter-print-funcs="f,g" -dot-cfg-dir=%t < %s -o /dev/null
+; RUN: ls %t/*.pdf %t/passes.html | count 3
+; RUN: FileCheck %s -input-file=%t/passes.html --check-prefix=CHECK-DOT-CFG-QUIET-FILTER-MULT-FUNC
+;
+; Check that the reporting of IRs respects -filter-passes
+; RUN: rm -rf %t && mkdir -p %t
+; RUN: opt -S -print-changed=dot-cfg-quiet -passes="instsimplify,no-op-function" -filter-passes="NoOpFunctionPass" -dot-cfg-dir=%t < %s -o /dev/null
+; RUN: FileCheck %s -input-file=%t/passes.html --check-prefix=CHECK-DOT-CFG-QUIET-FILTER-PASSES-NONE --allow-empty
+;
+; Check that the reporting of IRs respects -filter-passes with multiple passes
+; RUN: rm -rf %t && mkdir -p %t
+; RUN: opt -S -print-changed=dot-cfg-quiet -passes="instsimplify,no-op-function" -filter-passes="NoOpFunctionPass,InstSimplifyPass" -dot-cfg-dir=%t < %s -o /dev/null
+; RUN: ls %t/*.pdf %t/passes.html | count 3
+; RUN: FileCheck %s -input-file=%t/passes.html --check-prefix=CHECK-DOT-CFG-QUIET-FILTER-MULT-PASSES
+;
+; Check that the reporting of IRs respects both -filter-passes and -filter-print-funcs
+; RUN: rm -rf %t && mkdir -p %t
+; RUN: opt -S -print-changed=dot-cfg-quiet -passes="instsimplify,no-op-function" -filter-passes="NoOpFunctionPass,InstSimplifyPass" -filter-print-funcs=f -dot-cfg-dir=%t < %s -o /dev/null
+; RUN: ls %t/*.pdf %t/passes.html | count 2
+; RUN: FileCheck %s -input-file=%t/passes.html --check-prefix=CHECK-DOT-CFG-QUIET-FILTER-FUNC-PASSES
+;
+; Check that repeated passes that change the IR are printed and that the
+; others (including g) are filtered out.  Note that only the first time
+; instsimplify is run on f will result in changes
+; RUN: rm -rf %t && mkdir -p %t
+; RUN: opt -S -print-changed=dot-cfg-quiet -passes="instsimplify,instsimplify" -filter-print-funcs=f  -dot-cfg-dir=%t < %s -o /dev/null
+; RUN: ls %t/*.pdf %t/passes.html | count 2
+; RUN: FileCheck %s -input-file=%t/passes.html --check-prefix=CHECK-DOT-CFG-QUIET-MULT-PASSES-FILTER-FUNC
+
+define i32 @g() {
+entry:
+  %a = add i32 2, 3
+  ret i32 %a
+}
+
+define i32 @f() {
+entry:
+  %a = add i32 2, 3
+  ret i32 %a
+}
+
+; CHECK-DOT-CFG-SIMPLE-FILES: passes.html 
diff _0.pdf 
diff _1.pdf 
diff _3.pdf
+; CHECK-DOT-CFG-SIMPLE: <!doctype html><html><head><style>.collapsible { background-color: #777; color: white; cursor: pointer; padding: 18px; width: 100%; border: none; text-align: left; outline: none; font-size: 15px;} .active, .collapsible:hover { background-color: #555;} .content { padding: 0 18px; display: none; overflow: hidden; background-color: #f1f1f1;}</style><title>passes.html</title></head>
+; CHECK-DOT-CFG-SIMPLE-NEXT: <body><button type="button" class="collapsible">0. Initial IR (by function)</button>
+; CHECK-DOT-CFG-SIMPLE-NEXT: <div class="content">
+; CHECK-DOT-CFG-SIMPLE-NEXT:   <p>
+; CHECK-DOT-CFG-SIMPLE-NEXT:   <a href="
diff _0.pdf" target="_blank">0. Initial IR</a><br/>
+; CHECK-DOT-CFG-SIMPLE-NEXT:   </p>
+; CHECK-DOT-CFG-SIMPLE-NEXT: </div><br/>
+; CHECK-DOT-CFG-SIMPLE-NEXT:   <a href="
diff _1.pdf" target="_blank">1. Pass InstSimplifyPass on g</a><br/>
+; CHECK-DOT-CFG-SIMPLE-NEXT:     </p></div>
+; CHECK-DOT-CFG-SIMPLE-NEXT:   <a>2. PassManager<llvm::Function> on g ignored</a><br/>
+; CHECK-DOT-CFG-SIMPLE-NEXT:   <a href="
diff _3.pdf" target="_blank">3. Pass InstSimplifyPass on f</a><br/>
+; CHECK-DOT-CFG-SIMPLE-NEXT:     </p></div>
+; CHECK-DOT-CFG-SIMPLE-NEXT:   <a>4. PassManager<llvm::Function> on f ignored</a><br/>
+; CHECK-DOT-CFG-SIMPLE-NEXT:   <a>5. ModuleToFunctionPassAdaptor on [module] ignored</a><br/>
+; CHECK-DOT-CFG-SIMPLE-NEXT:   <a>6. Pass PrintModulePass on [module] omitted because no change</a><br/>
+; CHECK-DOT-CFG-SIMPLE-NEXT: <script>var coll = document.getElementsByClassName("collapsible");var i;for (i = 0; i < coll.length; i++) {coll[i].addEventListener("click", function() { this.classList.toggle("active"); var content = this.nextElementSibling; if (content.style.display === "block"){ content.style.display = "none"; } else { content.style.display= "block"; } }); }</script></body></html>
+
+; CHECK-DOT-CFG-FUNC-FILTER: <!doctype html><html><head><style>.collapsible { background-color: #777; color: white; cursor: pointer; padding: 18px; width: 100%; border: none; text-align: left; outline: none; font-size: 15px;} .active, .collapsible:hover { background-color: #555;} .content { padding: 0 18px; display: none; overflow: hidden; background-color: #f1f1f1;}</style><title>passes.html</title></head>
+; CHECK-DOT-CFG-FUNC-FILTER-NEXT: <body>  <a>0. Pass InstSimplifyPass on g filtered out</a><br/>
+; CHECK-DOT-CFG-FUNC-FILTER-NEXT:   <a>1. PassManager<llvm::Function> on g ignored</a><br/>
+; CHECK-DOT-CFG-FUNC-FILTER-NEXT: <button type="button" class="collapsible">0. Initial IR (by function)</button>
+; CHECK-DOT-CFG-FUNC-FILTER-NEXT: <div class="content">
+; CHECK-DOT-CFG-FUNC-FILTER-NEXT:   <p>
+; CHECK-DOT-CFG-FUNC-FILTER-NEXT:   <a href="
diff _2.pdf" target="_blank">2. Initial IR</a><br/>
+; CHECK-DOT-CFG-FUNC-FILTER-NEXT:   </p>
+; CHECK-DOT-CFG-FUNC-FILTER-NEXT: </div><br/>
+; CHECK-DOT-CFG-FUNC-FILTER-NEXT:   <a href="
diff _3.pdf" target="_blank">3. Pass InstSimplifyPass on f</a><br/>
+; CHECK-DOT-CFG-FUNC-FILTER-NEXT:     </p></div>
+; CHECK-DOT-CFG-FUNC-FILTER-NEXT:   <a>4. PassManager<llvm::Function> on f ignored</a><br/>
+; CHECK-DOT-CFG-FUNC-FILTER-NEXT:   <a>5. ModuleToFunctionPassAdaptor on [module] ignored</a><br/>
+; CHECK-DOT-CFG-FUNC-FILTER-NEXT:   <a>6. Pass PrintModulePass on [module] omitted because no change</a><br/>
+; CHECK-DOT-CFG-FUNC-FILTER-NEXT: <script>var coll = document.getElementsByClassName("collapsible");var i;for (i = 0; i < coll.length; i++) {coll[i].addEventListener("click", function() { this.classList.toggle("active"); var content = this.nextElementSibling; if (content.style.display === "block"){ content.style.display = "none"; } else { content.style.display= "block"; } }); }</script></body></html>
+
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE: <!doctype html><html><head><style>.collapsible { background-color: #777; color: white; cursor: pointer; padding: 18px; width: 100%; border: none; text-align: left; outline: none; font-size: 15px;} .active, .collapsible:hover { background-color: #555;} .content { padding: 0 18px; display: none; overflow: hidden; background-color: #f1f1f1;}</style><title>passes.html</title></head>
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT: <body><button type="button" class="collapsible">0. Initial IR (by function)</button>
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT: <div class="content">
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT:   <p>
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT:   <a href="
diff _0.pdf" target="_blank">0. Initial IR</a><br/>
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT:   </p>
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT: </div><br/>
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT:   <a href="
diff _1.pdf" target="_blank">1. Pass InstSimplifyPass on g</a><br/>
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT:     </p></div>
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT:   <a>2. PassManager<llvm::Function> on g ignored</a><br/>
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT:   <a href="
diff _3.pdf" target="_blank">3. Pass InstSimplifyPass on f</a><br/>
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT:     </p></div>
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT:   <a>4. PassManager<llvm::Function> on f ignored</a><br/>
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT:   <a>5. ModuleToFunctionPassAdaptor on [module] ignored</a><br/>
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT:   <a>6. Pass PrintModulePass on [module] omitted because no change</a><br/>
+; CHECK-DOT-CFG-PRINT-MOD-SCOPE-NEXT: <script>var coll = document.getElementsByClassName("collapsible");var i;for (i = 0; i < coll.length; i++) {coll[i].addEventListener("click", function() { this.classList.toggle("active"); var content = this.nextElementSibling; if (content.style.display === "block"){ content.style.display = "none"; } else { content.style.display= "block"; } }); }</script></body></html>
+
+; CHECK-DOT-CFG-FILTER-MULT-FUNC: <!doctype html><html><head><style>.collapsible { background-color: #777; color: white; cursor: pointer; padding: 18px; width: 100%; border: none; text-align: left; outline: none; font-size: 15px;} .active, .collapsible:hover { background-color: #555;} .content { padding: 0 18px; display: none; overflow: hidden; background-color: #f1f1f1;}</style><title>passes.html</title></head>
+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT: <body><button type="button" class="collapsible">0. Initial IR (by function)</button>
+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT: <div class="content">
+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT:   <p>
+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT:   <a href="
diff _0.pdf" target="_blank">0. Initial IR</a><br/>
+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT:   </p>
+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT: </div><br/>
+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT:   <a href="
diff _1.pdf" target="_blank">1. Pass InstSimplifyPass on g</a><br/>
+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT:     </p></div>
+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT:   <a>2. PassManager<llvm::Function> on g ignored</a><br/>
+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT:   <a href="
diff _3.pdf" target="_blank">3. Pass InstSimplifyPass on f</a><br/>
+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT:     </p></div>
+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT:   <a>4. PassManager<llvm::Function> on f ignored</a><br/>
+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT:   <a>5. ModuleToFunctionPassAdaptor on [module] ignored</a><br/>
+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT:   <a>6. Pass PrintModulePass on [module] omitted because no change</a><br/>
+; CHECK-DOT-CFG-FILTER-MULT-FUNC-NEXT: <script>var coll = document.getElementsByClassName("collapsible");var i;for (i = 0; i < coll.length; i++) {coll[i].addEventListener("click", function() { this.classList.toggle("active"); var content = this.nextElementSibling; if (content.style.display === "block"){ content.style.display = "none"; } else { content.style.display= "block"; } }); }</script></body></html>
+
+; CHECK-DOT-CFG-FILTER-PASSES: <!doctype html><html><head><style>.collapsible { background-color: #777; color: white; cursor: pointer; padding: 18px; width: 100%; border: none; text-align: left; outline: none; font-size: 15px;} .active, .collapsible:hover { background-color: #555;} .content { padding: 0 18px; display: none; overflow: hidden; background-color: #f1f1f1;}</style><title>passes.html</title></head>
+; CHECK-DOT-CFG-FILTER-PASSES-NEXT: <body>  <a>0. Pass InstSimplifyPass on g filtered out</a><br/>
+; CHECK-DOT-CFG-FILTER-PASSES-NEXT: <button type="button" class="collapsible">0. Initial IR (by function)</button>
+; CHECK-DOT-CFG-FILTER-PASSES-NEXT: <div class="content">
+; CHECK-DOT-CFG-FILTER-PASSES-NEXT:   <p>
+; CHECK-DOT-CFG-FILTER-PASSES-NEXT:   <a href="
diff _1.pdf" target="_blank">1. Initial IR</a><br/>
+; CHECK-DOT-CFG-FILTER-PASSES-NEXT:   </p>
+; CHECK-DOT-CFG-FILTER-PASSES-NEXT: </div><br/>
+; CHECK-DOT-CFG-FILTER-PASSES-NEXT:   <a>2. Pass NoOpFunctionPass on g omitted because no change</a><br/>
+; CHECK-DOT-CFG-FILTER-PASSES-NEXT:   <a>3. PassManager<llvm::Function> on g ignored</a><br/>
+; CHECK-DOT-CFG-FILTER-PASSES-NEXT:   <a>4. Pass InstSimplifyPass on f filtered out</a><br/>
+; CHECK-DOT-CFG-FILTER-PASSES-NEXT:   <a>5. Pass NoOpFunctionPass on f omitted because no change</a><br/>
+; CHECK-DOT-CFG-FILTER-PASSES-NEXT:   <a>6. PassManager<llvm::Function> on f ignored</a><br/>
+; CHECK-DOT-CFG-FILTER-PASSES-NEXT:   <a>7. ModuleToFunctionPassAdaptor on [module] ignored</a><br/>
+; CHECK-DOT-CFG-FILTER-PASSES-NEXT:   <a>8. Pass PrintModulePass on [module] filtered out</a><br/>
+; CHECK-DOT-CFG-FILTER-PASSES-NEXT: <script>var coll = document.getElementsByClassName("collapsible");var i;for (i = 0; i < coll.length; i++) {coll[i].addEventListener("click", function() { this.classList.toggle("active"); var content = this.nextElementSibling; if (content.style.display === "block"){ content.style.display = "none"; } else { content.style.display= "block"; } }); }</script></body></html>
+
+
+
+; CHECK-DOT-CFG-FILTER-MULT-PASSES: <!doctype html><html><head><style>.collapsible { background-color: #777; color: white; cursor: pointer; padding: 18px; width: 100%; border: none; text-align: left; outline: none; font-size: 15px;} .active, .collapsible:hover { background-color: #555;} .content { padding: 0 18px; display: none; overflow: hidden; background-color: #f1f1f1;}</style><title>passes.html</title></head>
+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT: <button type="button" class="collapsible">0. Initial IR (by function)</button>
+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT: <div class="content">
+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT:   <p>
+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT:   <a href="
diff _0.pdf" target="_blank">0. Initial IR</a><br/>
+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT:   </p>
+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT: </div><br/>
+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT:   <a href="
diff _1.pdf" target="_blank">1. Pass InstSimplifyPass on g</a><br/>
+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT:     </p></div>
+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT:   <a>2. Pass NoOpFunctionPass on g omitted because no change</a><br/>
+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT:   <a>3. PassManager<llvm::Function> on g ignored</a><br/>
+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT:   <a href="
diff _4.pdf" target="_blank">4. Pass InstSimplifyPass on f</a><br/>
+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT:     </p></div>
+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT:   <a>5. Pass NoOpFunctionPass on f omitted because no change</a><br/>
+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT:   <a>6. PassManager<llvm::Function> on f ignored</a><br/>
+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT:   <a>7. ModuleToFunctionPassAdaptor on [module] ignored</a><br/>
+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT:   <a>8. Pass PrintModulePass on [module] filtered out</a><br/>
+; CHECK-DOT-CFG-FILTER-MULT-PASSES-NEXT: <script>var coll = document.getElementsByClassName("collapsible");var i;for (i = 0; i < coll.length; i++) {coll[i].addEventListener("click", function() { this.classList.toggle("active"); var content = this.nextElementSibling; if (content.style.display === "block"){ content.style.display = "none"; } else { content.style.display= "block"; } }); }</script></body></html>
+
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES: <!doctype html><html><head><style>.collapsible { background-color: #777; color: white; cursor: pointer; padding: 18px; width: 100%; border: none; text-align: left; outline: none; font-size: 15px;} .active, .collapsible:hover { background-color: #555;} .content { padding: 0 18px; display: none; overflow: hidden; background-color: #f1f1f1;}</style><title>passes.html</title></head>
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT: <body>  <a>0. Pass InstSimplifyPass on g filtered out</a><br/>
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT:   <a>1. Pass NoOpFunctionPass on g filtered out</a><br/>
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT:   <a>2. PassManager<llvm::Function> on g ignored</a><br/>
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT: <button type="button" class="collapsible">0. Initial IR (by function)</button>
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT: <div class="content">
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT:   <p>
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT:   <a href="
diff _3.pdf" target="_blank">3. Initial IR</a><br/>
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT:   </p>
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT: </div><br/>
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT:   <a href="
diff _4.pdf" target="_blank">4. Pass InstSimplifyPass on f</a><br/>
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT:     </p></div>
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT:   <a>5. Pass NoOpFunctionPass on f omitted because no change</a><br/>
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT:   <a>6. PassManager<llvm::Function> on f ignored</a><br/>
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT:   <a>7. ModuleToFunctionPassAdaptor on [module] ignored</a><br/>
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT:   <a>8. Pass PrintModulePass on [module] filtered out</a><br/>
+; CHECK-DOT-CFG-FILTER-FUNC-PASSES-NEXT: <script>var coll = document.getElementsByClassName("collapsible");var i;for (i = 0; i < coll.length; i++) {coll[i].addEventListener("click", function() { this.classList.toggle("active"); var content = this.nextElementSibling; if (content.style.display === "block"){ content.style.display = "none"; } else { content.style.display= "block"; } }); }</script></body></html>
+
+
+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC: <!doctype html><html><head><style>.collapsible { background-color: #777; color: white; cursor: pointer; padding: 18px; width: 100%; border: none; text-align: left; outline: none; font-size: 15px;} .active, .collapsible:hover { background-color: #555;} .content { padding: 0 18px; display: none; overflow: hidden; background-color: #f1f1f1;}</style><title>passes.html</title></head>
+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT: <body>  <a>0. Pass InstSimplifyPass on g filtered out</a><br/>
+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT:   <a>1. Pass InstSimplifyPass on g filtered out</a><br/>
+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT:   <a>2. PassManager<llvm::Function> on g ignored</a><br/>
+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT: <button type="button" class="collapsible">0. Initial IR (by function)</button>
+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT: <div class="content">
+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT:   <p>
+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT:   <a href="
diff _3.pdf" target="_blank">3. Initial IR</a><br/>
+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT:   </p>
+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT: </div><br/>
+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT:   <a href="
diff _4.pdf" target="_blank">4. Pass InstSimplifyPass on f</a><br/>
+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT:     </p></div>
+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT:   <a>5. Pass InstSimplifyPass on f omitted because no change</a><br/>
+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT:   <a>6. PassManager<llvm::Function> on f ignored</a><br/>
+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT:   <a>7. ModuleToFunctionPassAdaptor on [module] ignored</a><br/>
+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT:   <a>8. Pass PrintModulePass on [module] omitted because no change</a><br/>
+; CHECK-DOT-CFG-MULT-PASSES-FILTER-FUNC-NEXT: <script>var coll = document.getElementsByClassName("collapsible");var i;for (i = 0; i < coll.length; i++) {coll[i].addEventListener("click", function() { this.classList.toggle("active"); var content = this.nextElementSibling; if (content.style.display === "block"){ content.style.display = "none"; } else { content.style.display= "block"; } }); }</script></body></html>
+
+; CHECK-DOT-CFG-QUIET-SIMPLE: <!doctype html><html><head><style>.collapsible { background-color: #777; color: white; cursor: pointer; padding: 18px; width: 100%; border: none; text-align: left; outline: none; font-size: 15px;} .active, .collapsible:hover { background-color: #555;} .content { padding: 0 18px; display: none; overflow: hidden; background-color: #f1f1f1;}</style><title>passes.html</title></head>
+; CHECK-DOT-CFG-QUIET-SIMPLE-NEXT: <body>  <a href="
diff _0.pdf" target="_blank">0. Pass InstSimplifyPass on g</a><br/>
+; CHECK-DOT-CFG-QUIET-SIMPLE-NEXT:     </p></div>
+; CHECK-DOT-CFG-QUIET-SIMPLE-NEXT:   <a href="
diff _1.pdf" target="_blank">1. Pass InstSimplifyPass on f</a><br/>
+; CHECK-DOT-CFG-QUIET-SIMPLE-NEXT:     </p></div>
+; CHECK-DOT-CFG-QUIET-SIMPLE-NEXT: <script>var coll = document.getElementsByClassName("collapsible");var i;for (i = 0; i < coll.length; i++) {coll[i].addEventListener("click", function() { this.classList.toggle("active"); var content = this.nextElementSibling; if (content.style.display === "block"){ content.style.display = "none"; } else { content.style.display= "block"; } }); }</script></body></html>
+
+; CHECK-DOT-CFG-QUIET-FUNC-FILTER: <!doctype html><html><head><style>.collapsible { background-color: #777; color: white; cursor: pointer; padding: 18px; width: 100%; border: none; text-align: left; outline: none; font-size: 15px;} .active, .collapsible:hover { background-color: #555;} .content { padding: 0 18px; display: none; overflow: hidden; background-color: #f1f1f1;}</style><title>passes.html</title></head>
+; CHECK-DOT-CFG-QUIET-FUNC-FILTER-NEXT: <body>  <a href="
diff _0.pdf" target="_blank">0. Pass InstSimplifyPass on f</a><br/>
+; CHECK-DOT-CFG-QUIET-FUNC-FILTER-NEXT:     </p></div>
+; CHECK-DOT-CFG-QUIET-FUNC-FILTER-NEXT: <script>var coll = document.getElementsByClassName("collapsible");var i;for (i = 0; i < coll.length; i++) {coll[i].addEventListener("click", function() { this.classList.toggle("active"); var content = this.nextElementSibling; if (content.style.display === "block"){ content.style.display = "none"; } else { content.style.display= "block"; } }); }</script></body></html>
+
+; CHECK-DOT-CFG-QUIET-PRINT-MOD-SCOPE: <!doctype html><html><head><style>.collapsible { background-color: #777; color: white; cursor: pointer; padding: 18px; width: 100%; border: none; text-align: left; outline: none; font-size: 15px;} .active, .collapsible:hover { background-color: #555;} .content { padding: 0 18px; display: none; overflow: hidden; background-color: #f1f1f1;}</style><title>passes.html</title></head>
+; CHECK-DOT-CFG-QUIET-PRINT-MOD-SCOPE-NEXT: <body>  <a href="
diff _0.pdf" target="_blank">0. Pass InstSimplifyPass on g</a><br/>
+; CHECK-DOT-CFG-QUIET-PRINT-MOD-SCOPE-NEXT:     </p></div>
+; CHECK-DOT-CFG-QUIET-PRINT-MOD-SCOPE-NEXT:   <a href="
diff _1.pdf" target="_blank">1. Pass InstSimplifyPass on f</a><br/>
+; CHECK-DOT-CFG-QUIET-PRINT-MOD-SCOPE-NEXT:     </p></div>
+; CHECK-DOT-CFG-QUIET-PRINT-MOD-SCOPE-NEXT: <script>var coll = document.getElementsByClassName("collapsible");var i;for (i = 0; i < coll.length; i++) {coll[i].addEventListener("click", function() { this.classList.toggle("active"); var content = this.nextElementSibling; if (content.style.display === "block"){ content.style.display = "none"; } else { content.style.display= "block"; } }); }</script></body></html>
+
+; CHECK-DOT-CFG-QUIET-FILTER-MULT-FUNC: <!doctype html><html><head><style>.collapsible { background-color: #777; color: white; cursor: pointer; padding: 18px; width: 100%; border: none; text-align: left; outline: none; font-size: 15px;} .active, .collapsible:hover { background-color: #555;} .content { padding: 0 18px; display: none; overflow: hidden; background-color: #f1f1f1;}</style><title>passes.html</title></head>
+; CHECK-DOT-CFG-QUIET-FILTER-MULT-FUNC-NEXT: <body>  <a href="
diff _0.pdf" target="_blank">0. Pass InstSimplifyPass on g</a><br/>
+; CHECK-DOT-CFG-QUIET-FILTER-MULT-FUNC-NEXT:     </p></div>
+; CHECK-DOT-CFG-QUIET-FILTER-MULT-FUNC-NEXT:   <a href="
diff _1.pdf" target="_blank">1. Pass InstSimplifyPass on f</a><br/>
+; CHECK-DOT-CFG-QUIET-FILTER-MULT-FUNC-NEXT:     </p></div>
+; CHECK-DOT-CFG-QUIET-FILTER-MULT-FUNC-NEXT: <script>var coll = document.getElementsByClassName("collapsible");var i;for (i = 0; i < coll.length; i++) {coll[i].addEventListener("click", function() { this.classList.toggle("active"); var content = this.nextElementSibling; if (content.style.display === "block"){ content.style.display = "none"; } else { content.style.display= "block"; } }); }</script></body></html>
+
+; CHECK-DOT-CFG-QUIET-FILTER-PASSES-NONE: <!doctype html><html><head><style>.collapsible { background-color: #777; color: white; cursor: pointer; padding: 18px; width: 100%; border: none; text-align: left; outline: none; font-size: 15px;} .active, .collapsible:hover { background-color: #555;} .content { padding: 0 18px; display: none; overflow: hidden; background-color: #f1f1f1;}</style><title>passes.html</title></head>
+; CHECK-DOT-CFG-QUIET-FILTER-PASSES-NONE-NEXT: <body><script>var coll = document.getElementsByClassName("collapsible");var i;for (i = 0; i < coll.length; i++) {coll[i].addEventListener("click", function() { this.classList.toggle("active"); var content = this.nextElementSibling; if (content.style.display === "block"){ content.style.display = "none"; } else { content.style.display= "block"; } }); }</script></body></html>
+
+; CHECK-DOT-CFG-QUIET-FILTER-MULT-PASSES: <!doctype html><html><head><style>.collapsible { background-color: #777; color: white; cursor: pointer; padding: 18px; width: 100%; border: none; text-align: left; outline: none; font-size: 15px;} .active, .collapsible:hover { background-color: #555;} .content { padding: 0 18px; display: none; overflow: hidden; background-color: #f1f1f1;}</style><title>passes.html</title></head>
+; CHECK-DOT-CFG-QUIET-FILTER-MULT-PASSES-NEXT: <body>  <a href="
diff _0.pdf" target="_blank">0. Pass InstSimplifyPass on g</a><br/>
+; CHECK-DOT-CFG-QUIET-FILTER-MULT-PASSES-NEXT:     </p></div>
+; CHECK-DOT-CFG-QUIET-FILTER-MULT-PASSES-NEXT:   <a href="
diff _1.pdf" target="_blank">1. Pass InstSimplifyPass on f</a><br/>
+; CHECK-DOT-CFG-QUIET-FILTER-MULT-PASSES-NEXT:     </p></div>
+; CHECK-DOT-CFG-QUIET-FILTER-MULT-PASSES-NEXT: <script>var coll = document.getElementsByClassName("collapsible");var i;for (i = 0; i < coll.length; i++) {coll[i].addEventListener("click", function() { this.classList.toggle("active"); var content = this.nextElementSibling; if (content.style.display === "block"){ content.style.display = "none"; } else { content.style.display= "block"; } }); }</script></body></html>
+
+; CHECK-DOT-CFG-QUIET-FILTER-FUNC-PASSES: <!doctype html><html><head><style>.collapsible { background-color: #777; color: white; cursor: pointer; padding: 18px; width: 100%; border: none; text-align: left; outline: none; font-size: 15px;} .active, .collapsible:hover { background-color: #555;} .content { padding: 0 18px; display: none; overflow: hidden; background-color: #f1f1f1;}</style><title>passes.html</title></head>
+; CHECK-DOT-CFG-QUIET-FILTER-FUNC-PASSES-NEXT: <body>  <a href="
diff _0.pdf" target="_blank">0. Pass InstSimplifyPass on f</a><br/>
+; CHECK-DOT-CFG-QUIET-FILTER-FUNC-PASSES-NEXT:     </p></div>
+; CHECK-DOT-CFG-QUIET-FILTER-FUNC-PASSES-NEXT: <script>var coll = document.getElementsByClassName("collapsible");var i;for (i = 0; i < coll.length; i++) {coll[i].addEventListener("click", function() { this.classList.toggle("active"); var content = this.nextElementSibling; if (content.style.display === "block"){ content.style.display = "none"; } else { content.style.display= "block"; } }); }</script></body></html>
+
+; CHECK-DOT-CFG-QUIET-MULT-PASSES-FILTER-FUNC: <!doctype html><html><head><style>.collapsible { background-color: #777; color: white; cursor: pointer; padding: 18px; width: 100%; border: none; text-align: left; outline: none; font-size: 15px;} .active, .collapsible:hover { background-color: #555;} .content { padding: 0 18px; display: none; overflow: hidden; background-color: #f1f1f1;}</style><title>passes.html</title></head>
+; CHECK-DOT-CFG-QUIET-MULT-PASSES-FILTER-FUNC-NEXT: <body>  <a href="
diff _0.pdf" target="_blank">0. Pass InstSimplifyPass on f</a><br/>
+; CHECK-DOT-CFG-QUIET-MULT-PASSES-FILTER-FUNC-NEXT:     </p></div>
+; CHECK-DOT-CFG-QUIET-MULT-PASSES-FILTER-FUNC-NEXT: <script>var coll = document.getElementsByClassName("collapsible");var i;for (i = 0; i < coll.length; i++) {coll[i].addEventListener("click", function() { this.classList.toggle("active"); var content = this.nextElementSibling; if (content.style.display === "block"){ content.style.display = "none"; } else { content.style.display= "block"; } }); }</script></body></html>


        


More information about the llvm-commits mailing list