ymandel updated this revision to Diff 535397.
ymandel added a comment.

Rebase and address some comments.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D153058/new/

https://reviews.llvm.org/D153058

Files:
  clang/include/clang/Analysis/Analyses/IntervalPartition.h
  clang/lib/Analysis/IntervalPartition.cpp
  clang/unittests/Analysis/IntervalPartitionTest.cpp

Index: clang/unittests/Analysis/IntervalPartitionTest.cpp
===================================================================
--- clang/unittests/Analysis/IntervalPartitionTest.cpp
+++ clang/unittests/Analysis/IntervalPartitionTest.cpp
@@ -8,13 +8,119 @@
 
 #include "clang/Analysis/Analyses/IntervalPartition.h"
 #include "CFGBuildResult.h"
+#include "clang/Analysis/CFG.h"
+#include "llvm/Support/raw_ostream.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
+#include <type_traits>
+#include <variant>
 
 namespace clang {
-namespace analysis {
+
+namespace {
+template <typename T> using NodeData = CFGInterval::NodeData<T>;
+}  // namespace
+
+llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
+                              const CFGInterval::IntervalData &D) {
+  OS << "Nodes{";
+  if (std::holds_alternative<NodeData<CFGBlock>>(D)) {
+    auto &BlockData = std::get<NodeData<CFGBlock>>(D);
+    for (const auto *B : BlockData.Nodes)
+      OS << B->getBlockID() << ", ";
+  } else {
+    assert(std::holds_alternative<NodeData<CFGInterval>>(D));
+    auto &IntervalData = std::get<NodeData<CFGInterval>>(D);
+    for (const auto *B : IntervalData.Nodes)
+      OS << B->ID << ", ";
+  }
+  OS << "}";
+  return OS;
+}
+
+llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const CFGInterval &I) {
+  OS << "Interval{ID = " << I.ID << ", ";
+  OS << I.Data;
+  OS << ", Pre{";
+  for (const auto *P : I.Predecessors)
+    OS << P->ID << ",";
+  OS << "}, Succ{";
+  for (const auto *P : I.Successors)
+    OS << P->ID << ",";
+  OS << "}}\n";
+  return OS;
+}
+
+void PrintTo(const CFGInterval::IntervalData &D, std::ostream *OS) {
+  std::string Result;
+  llvm::raw_string_ostream StringOS(Result);
+  StringOS << D;
+  *OS << Result;
+}
+
+void PrintTo(const CFGInterval &I, std::ostream *OS) {
+  std::string Result;
+  llvm::raw_string_ostream StringOS(Result);
+  StringOS << I;
+  *OS << Result;
+}
+
 namespace {
 
+using ::clang::analysis::BuildCFG;
+using ::clang::analysis::BuildResult;
+using ::testing::ElementsAre;
+using ::testing::Field;
+using ::testing::IsEmpty;
+using ::testing::Optional;
+using ::testing::UnorderedElementsAre;
+
+MATCHER_P(BlockID, ID, "") { return arg->getBlockID == ID; }
+MATCHER_P(IntervalID, ID, "") { return arg->ID == ID; }
+
+template <typename... T>
+auto BlockIDs(T... IDs) {
+  return UnorderedElementsAre(
+      ::testing::Property(&CFGBlock::getBlockID, IDs)...);
+}
+
+template <typename... T>
+auto IntervalIDs(T... IDs) {
+  return UnorderedElementsAre(
+      ::testing::Field(&CFGInterval::ID, IDs)...);
+}
+
+MATCHER_P3(IsBlockInterval, ID, Preds, Succs, "") {
+  if (!std::holds_alternative<NodeData<CFGBlock>>(arg.Data))
+    return false;
+
+  return testing::Matches(ID)(arg.ID) &&
+         testing::Matches(Preds)(arg.Predecessors) &&
+         testing::Matches(Succs)(arg.Successors);
+}
+
+MATCHER_P4(IsBlockInterval, ID, Nodes, Preds, Succs, "") {
+  if (!std::holds_alternative<NodeData<CFGBlock>>(arg.Data))
+    return false;
+  auto &IntervalData = std::get<NodeData<CFGBlock>>(arg.Data);
+
+  return testing::Matches(ID)(arg.ID) &&
+         testing::Matches(Nodes)(IntervalData.Nodes) &&
+         testing::Matches(Preds)(arg.Predecessors) &&
+         testing::Matches(Succs)(arg.Successors);
+}
+
+MATCHER_P4(IsNestedInterval, ID, Nodes, Preds, Succs, "") {
+  if (!std::holds_alternative<NodeData<CFGInterval>>(arg.Data))
+    return false;
+  auto &IntervalData = std::get<NodeData<CFGInterval>>(arg.Data);
+
+  return testing::Matches(ID)(arg.ID) &&
+         testing::Matches(Nodes)(IntervalData.Nodes) &&
+         testing::Matches(Preds)(arg.Predecessors) &&
+         testing::Matches(Succs)(arg.Successors);
+}
+
 TEST(BuildInterval, PartitionSimpleOneInterval) {
 
   const char *Code = R"(void f() {
@@ -32,8 +138,8 @@
 
   auto &EntryBlock = cfg->getEntry();
 
-  CFGInterval I = buildInterval(*cfg, EntryBlock);
-  EXPECT_EQ(I.Blocks.size(), 3u);
+  std::vector<const CFGBlock *> I = buildInterval(&EntryBlock);
+  EXPECT_EQ(I.size(), 3u);
 }
 
 TEST(BuildInterval, PartitionIfThenOneInterval) {
@@ -56,12 +162,10 @@
 
   auto &EntryBlock = cfg->getEntry();
 
-  CFGInterval I = buildInterval(*cfg, EntryBlock);
-  EXPECT_EQ(I.Blocks.size(), 6u);
+  std::vector<const CFGBlock *> I = buildInterval(&EntryBlock);
+  EXPECT_EQ(I.size(), 6u);
 }
 
-using ::testing::UnorderedElementsAre;
-
 TEST(BuildInterval, PartitionWhileMultipleIntervals) {
 
   const char *Code = R"(void f() {
@@ -80,11 +184,11 @@
   CFGBlock *InitXBlock = *EntryBlock->succ_begin();
   CFGBlock *LoopHeadBlock = *InitXBlock->succ_begin();
 
-  CFGInterval I1 = buildInterval(*cfg, *EntryBlock);
-  EXPECT_THAT(I1.Blocks, UnorderedElementsAre(EntryBlock, InitXBlock));
+  std::vector<const CFGBlock *> I1 = buildInterval(EntryBlock);
+  EXPECT_THAT(I1, UnorderedElementsAre(EntryBlock, InitXBlock));
 
-  CFGInterval I2 = buildInterval(*cfg, *LoopHeadBlock);
-  EXPECT_EQ(I2.Blocks.size(), 5u);
+  std::vector<const CFGBlock *> I2 = buildInterval(LoopHeadBlock);
+  EXPECT_EQ(I2.size(), 5u);
 }
 
 TEST(PartitionIntoIntervals, PartitionIfThenOneInterval) {
@@ -99,11 +203,10 @@
   BuildResult Result = BuildCFG(Code);
   ASSERT_EQ(BuildResult::BuiltCFG, Result.getStatus());
 
-  CFG *cfg = Result.getCFG();
-  ASSERT_EQ(cfg->size(), 6u);
-
-  auto Intervals = partitionIntoIntervals(*cfg);
-  EXPECT_EQ(Intervals.size(), 1u);
+  auto Graph = partitionIntoIntervals(*Result.getCFG());
+  EXPECT_EQ(Graph.Intervals.size(), 1u);
+  EXPECT_THAT(Graph.Intervals,
+              ElementsAre(IsBlockInterval(0, IsEmpty(), IsEmpty())));
 }
 
 TEST(PartitionIntoIntervals, PartitionWhileTwoIntervals) {
@@ -116,11 +219,12 @@
   BuildResult Result = BuildCFG(Code);
   ASSERT_EQ(BuildResult::BuiltCFG, Result.getStatus());
 
-  CFG *cfg = Result.getCFG();
-  ASSERT_EQ(cfg->size(), 7u);
-
-  auto Intervals = partitionIntoIntervals(*cfg);
-  EXPECT_EQ(Intervals.size(), 2u);
+  auto Graph = partitionIntoIntervals(*Result.getCFG());
+  EXPECT_THAT(
+      Graph.Intervals,
+      ElementsAre(
+          IsBlockInterval(0, IsEmpty(), UnorderedElementsAre(IntervalID(1u))),
+          IsBlockInterval(1, UnorderedElementsAre(IntervalID(0u)), IsEmpty())));
 }
 
 TEST(PartitionIntoIntervals, PartitionNestedWhileThreeIntervals) {
@@ -136,9 +240,16 @@
   BuildResult Result = BuildCFG(Code);
   ASSERT_EQ(BuildResult::BuiltCFG, Result.getStatus());
 
-  CFG *cfg = Result.getCFG();
-  auto Intervals = partitionIntoIntervals(*cfg);
-  EXPECT_EQ(Intervals.size(), 3u);
+  auto Graph = partitionIntoIntervals(*Result.getCFG());
+  EXPECT_THAT(
+      Graph.Intervals,
+      ElementsAre(
+          IsBlockInterval(0, IsEmpty(), UnorderedElementsAre(IntervalID(1u))),
+          IsBlockInterval(1,
+                          UnorderedElementsAre(IntervalID(0u), IntervalID(2u)),
+                          UnorderedElementsAre(IntervalID(2u))),
+          IsBlockInterval(2, UnorderedElementsAre(IntervalID(1u)),
+                          UnorderedElementsAre(IntervalID(1u)))));
 }
 
 TEST(PartitionIntoIntervals, PartitionSequentialWhileThreeIntervals) {
@@ -154,11 +265,103 @@
   BuildResult Result = BuildCFG(Code);
   ASSERT_EQ(BuildResult::BuiltCFG, Result.getStatus());
 
-  CFG *cfg = Result.getCFG();
-  auto Intervals = partitionIntoIntervals(*cfg);
-  EXPECT_EQ(Intervals.size(), 3u);
+  auto Graph = partitionIntoIntervals(*Result.getCFG());
+  EXPECT_THAT(
+      Graph.Intervals,
+      ElementsAre(
+          IsBlockInterval(0, IsEmpty(), UnorderedElementsAre(IntervalID(1u))),
+          IsBlockInterval(1, UnorderedElementsAre(IntervalID(0u)),
+                          UnorderedElementsAre(IntervalID(2u))),
+          IsBlockInterval(2, UnorderedElementsAre(IntervalID(1u)), IsEmpty())));
+}
+
+TEST(PartitionIntoIntervals, LimitReducibleSequentialWhile) {
+  const char *Code = R"(void f() {
+                          int x = 3;
+                          while (x >= 3) {
+                            --x;
+                          }
+                          x = x + x;
+                          int y = x;
+                          while (y > 0) --y;
+                        })";
+  BuildResult Result = BuildCFG(Code);
+  ASSERT_EQ(BuildResult::BuiltCFG, Result.getStatus());
+
+  auto Graph = partitionIntoIntervals(*Result.getCFG());
+  ASSERT_THAT(
+      Graph.Intervals,
+      ElementsAre(
+          IsBlockInterval(0, IsEmpty(), UnorderedElementsAre(IntervalID(1u))),
+          IsBlockInterval(1, UnorderedElementsAre(IntervalID(0u)),
+                          UnorderedElementsAre(IntervalID(2u))),
+          IsBlockInterval(2, UnorderedElementsAre(IntervalID(1u)), IsEmpty())));
+
+  auto Graph2 = partitionIntoIntervals(Graph);
+  EXPECT_THAT(Graph2.Intervals,
+              ElementsAre(IsNestedInterval(0, IntervalIDs(0, 1, 2),
+                                           IsEmpty(), IsEmpty())));
+  EXPECT_EQ(formatWTO(getWTO(Graph2.Intervals[0])), "9 8 (7 6 4 5) (3 2 0 1)");
+}
+
+TEST(PartitionIntoIntervals, LimitReducibleNestedWhile) {
+  const char *Code = R"(void f() {
+                          int x = 3;
+                          while (x >= 3) {
+                            --x;
+                            int y = x;
+                            while (y > 0) --y;
+                          }
+                          x = x + x;
+                        })";
+  BuildResult Result = BuildCFG(Code);
+  ASSERT_EQ(BuildResult::BuiltCFG, Result.getStatus());
+
+  auto Graph = partitionIntoIntervals(*Result.getCFG());
+  ASSERT_THAT(
+      Graph.Intervals,
+      ElementsAre(
+          IsBlockInterval(0, BlockIDs(9, 8), IsEmpty(),
+                          UnorderedElementsAre(IntervalID(1u))),
+          IsBlockInterval(1, BlockIDs(7, 6, 1, 0),
+                          UnorderedElementsAre(IntervalID(0u), IntervalID(2u)),
+                          UnorderedElementsAre(IntervalID(2u))),
+          IsBlockInterval(2, BlockIDs(5, 4, 3, 2),
+                          UnorderedElementsAre(IntervalID(1u)),
+                          UnorderedElementsAre(IntervalID(1u)))));
+
+  auto Graph2 = partitionIntoIntervals(Graph);
+  EXPECT_THAT(Graph2.Intervals,
+              ElementsAre(IsNestedInterval(0, IntervalIDs(0), IsEmpty(),
+                                           UnorderedElementsAre(IntervalID(1u))),
+                          IsNestedInterval(1, IntervalIDs(1, 2),
+                                           UnorderedElementsAre(IntervalID(0u)),
+                                           IsEmpty())));
+
+  auto Graph3 = partitionIntoIntervals(Graph2);
+  EXPECT_THAT(Graph3.Intervals,
+              ElementsAre(IsNestedInterval(0, IntervalIDs(0, 1), IsEmpty(),
+                                           IsEmpty())));
+  EXPECT_EQ(formatWTO(getWTO(Graph3.Intervals[0])), "9 8 (7 6 1 0 (5 4 2 3))");
+}
+
+TEST(GetWTO, NestedWhile) {
+  const char *Code = R"(void f() {
+                          int x = 3;
+                          while (x >= 3) {
+                            --x;
+                            int y = x;
+                            while (y > 0) --y;
+                          }
+                          x = x + x;
+                        })";
+  BuildResult Result = BuildCFG(Code);
+  ASSERT_EQ(BuildResult::BuiltCFG, Result.getStatus());
+
+  auto WTO = getIntervalWTO(*Result.getCFG());
+  ASSERT_NE(WTO, std::nullopt);
+  ASSERT_EQ(formatWTO(*WTO), "9 8 (7 6 1 0 (5 4 2 3))");
 }
 
 } // namespace
-} // namespace analysis
 } // namespace clang
Index: clang/lib/Analysis/IntervalPartition.cpp
===================================================================
--- clang/lib/Analysis/IntervalPartition.cpp
+++ clang/lib/Analysis/IntervalPartition.cpp
@@ -13,23 +13,49 @@
 #include "clang/Analysis/Analyses/IntervalPartition.h"
 #include "clang/Analysis/CFG.h"
 #include "llvm/ADT/BitVector.h"
+#include <optional>
 #include <queue>
 #include <set>
 #include <vector>
 
 namespace clang {
 
-static CFGInterval buildInterval(llvm::BitVector &Partitioned,
-                                 const CFGBlock &Header) {
-  CFGInterval Interval(&Header);
-  Partitioned.set(Header.getBlockID());
+namespace {
+template <typename Node> using NodeData = CFGInterval::NodeData<Node>;
+} // namespace
+
+// Intermediate data used in constructing a CFGIntervalNode.
+template <typename Node> struct BuildResult {
+  // Use a vector to maintain the insertion order. Given the expected small
+  // number of nodes, vector should be sufficiently efficient.
+  std::vector<const Node *> Nodes;
+  // llvm::SmallDenseSet<const Node *> Nodes;
+  llvm::SmallDenseSet<const Node *> Successors;
+};
+
+template <typename Node>
+bool inInterval(const Node *N, std::vector<const Node *> &Interval) {
+  return std::find(Interval.begin(), Interval.end(), N) != Interval.end();
+}
+
+static unsigned getID(const CFGBlock *B) { return B->getBlockID(); }
+static unsigned getID(const CFGInterval *I) { return I->ID; }
+
+// Requires: `Node::succs()` and `Node::preds()`.
+template <typename Node>
+BuildResult<Node> buildInterval(llvm::BitVector &Partitioned,
+                                const Node *Header) {
+  assert(Header);
+  BuildResult<Node> Interval;
+  Interval.Nodes.push_back(Header);
+  Partitioned.set(getID(Header));
 
   // Elements must not be null. Duplicates are prevented using `Workset`, below.
-  std::queue<const CFGBlock *> Worklist;
-  llvm::BitVector Workset(Header.getParent()->getNumBlockIDs(), false);
-  for (const CFGBlock *S : Header.succs())
+  std::queue<const Node *> Worklist;
+  llvm::BitVector Workset(Partitioned.size(), false);
+  for (const Node *S : Header->succs())
     if (S != nullptr)
-      if (auto SID = S->getBlockID(); !Partitioned.test(SID)) {
+      if (auto SID = getID(S); !Partitioned.test(SID)) {
         // Successors are unique, so we don't test against `Workset` before
         // adding to `Worklist`.
         Worklist.push(S);
@@ -42,31 +68,30 @@
   // yet. In the latter case, we'll revisit the block through some other path
   // from the interval. At the end of processing the worklist, we filter out any
   // that ended up in the interval to produce the output set of interval
-  // successors. It may contain duplicates -- ultimately, all relevant elements
-  // are added to `Interval.Successors`, which is a set.
-  std::vector<const CFGBlock *> MaybeSuccessors;
+  // successors.
+  std::vector<const Node *> MaybeSuccessors;
 
   while (!Worklist.empty()) {
     const auto *B = Worklist.front();
-    auto ID = B->getBlockID();
+    auto ID = getID(B);
     Worklist.pop();
     Workset.reset(ID);
 
     // Check whether all predecessors are in the interval, in which case `B`
     // is included as well.
     bool AllInInterval = true;
-    for (const CFGBlock *P : B->preds())
-      if (Interval.Blocks.find(P) == Interval.Blocks.end()) {
+    for (const Node *P : B->preds())
+      if (!inInterval(P, Interval.Nodes)) {
         MaybeSuccessors.push_back(B);
         AllInInterval = false;
         break;
       }
     if (AllInInterval) {
-      Interval.Blocks.insert(B);
+      Interval.Nodes.push_back(B);
       Partitioned.set(ID);
-      for (const CFGBlock *S : B->succs())
+      for (const Node *S : B->succs())
         if (S != nullptr)
-          if (auto SID = S->getBlockID();
+          if (auto SID = getID(S);
               !Partitioned.test(SID) && !Workset.test(SID)) {
             Worklist.push(S);
             Workset.set(SID);
@@ -75,42 +100,192 @@
   }
 
   // Any block successors not in the current interval are interval successors.
-  for (const CFGBlock *B : MaybeSuccessors)
-    if (Interval.Blocks.find(B) == Interval.Blocks.end())
+  for (const Node *B : MaybeSuccessors)
+    if (!inInterval(B, Interval.Nodes))
       Interval.Successors.insert(B);
 
   return Interval;
 }
 
-CFGInterval buildInterval(const CFG &Cfg, const CFGBlock &Header) {
-  llvm::BitVector Partitioned(Cfg.getNumBlockIDs(), false);
-  return buildInterval(Partitioned, Header);
+template <typename Node>
+void addIntervalNode(CFGIntervalGraph &Graph,
+                     std::map<const Node *, unsigned> &Index,
+                     std::queue<const Node *> &Successors,
+                     llvm::BitVector &Partitioned, const Node *Header) {
+  BuildResult<Node> Data = buildInterval(Partitioned, Header);
+  for (const auto *S : Data.Successors)
+    Successors.push(S);
+
+  // Index the nodes of the new interval.
+  int ID = Graph.Intervals.size();
+  for (const auto *N : Data.Nodes)
+    Index.emplace(N, ID);
+  Graph.Intervals.emplace_back(ID, Header, std::move(Data.Nodes));
 }
 
-std::vector<CFGInterval> partitionIntoIntervals(const CFG &Cfg) {
-  std::vector<CFGInterval> Intervals;
-  llvm::BitVector Partitioned(Cfg.getNumBlockIDs(), false);
-  auto &EntryBlock = Cfg.getEntry();
-  Intervals.push_back(buildInterval(Partitioned, EntryBlock));
+// TODO: for Node = CFGInterval, we should collapse single-node intervals.
+template <typename Node>
+CFGIntervalGraph partitionIntoIntervalsImpl(unsigned NumBlockIDs,
+                                            const Node *EntryBlock) {
+  assert(EntryBlock != nullptr);
+  CFGIntervalGraph Graph;
+  // Records the ID of the header node associated with each node in the graph.
+  std::map<const Node *, unsigned> Index;
+  llvm::BitVector Partitioned(NumBlockIDs, false);
+  std::queue<const Node *> Successors;
 
-  std::queue<const CFGBlock *> Successors;
-  for (const auto *S : Intervals[0].Successors)
-    Successors.push(S);
+  addIntervalNode(Graph, Index, Successors, Partitioned, EntryBlock);
 
   while (!Successors.empty()) {
     const auto *B = Successors.front();
     Successors.pop();
-    if (Partitioned.test(B->getBlockID()))
+    if (Partitioned.test(getID(B)))
       continue;
 
-    // B has not been partitioned, but it has a predecessor that has.
-    CFGInterval I = buildInterval(Partitioned, *B);
-    for (const auto *S : I.Successors)
-      Successors.push(S);
-    Intervals.push_back(std::move(I));
+    // B has not been partitioned, but it has a predecessor that has. Create a
+    // new interval from `B`.
+    addIntervalNode(Graph, Index, Successors, Partitioned, B);
+  }
+
+  // Go back and patch up all the Intervals -- the successors and predecessors.
+  for (auto &I : Graph.Intervals) {
+    assert(std::holds_alternative<NodeData<Node>>(I.Data));
+    auto &Data = std::get<NodeData<Node>>(I.Data);
+    // All external successors must be interval headers, because they
+    // have predecessors in multiple intervals.
+    for (const Node *N : Data.Header->preds()) {
+      auto It = Index.find(N);
+      if (It == Index.end())
+        // Unreachable block.
+        continue;
+      if (It->second == I.ID)
+        // This is a back edge, so mark `I` the head of this edge.
+        I.IsBackedgeHead = true;
+      else if (I.Predecessors.insert(&Graph.Intervals[It->second]).second)
+        Graph.Intervals[It->second].Successors.insert(&I);
+    }
   }
 
-  return Intervals;
+  return Graph;
 }
 
+std::vector<const CFGBlock *> buildInterval(const CFGBlock *Header) {
+  llvm::BitVector Partitioned(Header->getParent()->getNumBlockIDs(), false);
+  return buildInterval(Partitioned, Header).Nodes;
+}
+
+CFGIntervalGraph partitionIntoIntervals(const CFG &Cfg) {
+  return partitionIntoIntervalsImpl(Cfg.getNumBlockIDs(), &Cfg.getEntry());
+}
+
+CFGIntervalGraph partitionIntoIntervals(const CFGIntervalGraph &Graph) {
+  return partitionIntoIntervalsImpl(Graph.Intervals.size(),
+                                    &Graph.Intervals[0]);
+}
+
+static void addToWTO(const CFGInterval &I, WeakTopologicalOrdering &Order);
+
+static void addToWTO(const CFGInterval::IntervalData &D,
+                     WeakTopologicalOrdering &Order) {
+  std::visit(
+      [&Order](auto &&D) {
+        for (const auto *N : D.Nodes) {
+          if constexpr (std::is_same_v<std::decay_t<decltype(D)>,
+                                       NodeData<CFGBlock>>) {
+            Order.Elements.emplace_back(N);
+          } else {
+            static_assert(std::is_same_v<std::decay_t<decltype(D)>,
+                                         NodeData<CFGInterval>>);
+            addToWTO(*N, Order);
+          }
+        }
+      },
+      D);
+}
+
+static void addToWTO(const CFGInterval &I, WeakTopologicalOrdering &Order) {
+  if (I.IsBackedgeHead) {
+    WeakTopologicalOrdering Nested;
+    addToWTO(I.Data, Nested);
+    Order.Elements.push_back(std::move(Nested));
+    return;
+  }
+  addToWTO(I.Data, Order);
+}
+
+WeakTopologicalOrdering getWTO(const CFGInterval &I) {
+  WeakTopologicalOrdering Order;
+  addToWTO(I, Order);
+  return Order;
+}
+
+std::optional<WeakTopologicalOrdering> getIntervalWTO(const CFG &Cfg) {
+  // Backing storage for the allocated nodes in each graph.
+  std::vector<CFGIntervalGraph> Graphs;
+  unsigned PrevSize = Cfg.size();
+  Graphs.push_back(partitionIntoIntervals(Cfg));
+  unsigned Size = Graphs.back().Intervals.size();
+  while (Size > 1 && Size < PrevSize) {
+    PrevSize = Graphs.back().Intervals.size();
+    Graphs.push_back(partitionIntoIntervals(Graphs.back()));
+    Size = Graphs.back().Intervals.size();
+  }
+  if (Size > 1)
+    // Not reducible.
+    return std::nullopt;
+
+  assert(Size != 0);
+  return getWTO(Graphs.back().Intervals[0]);
+}
+
+static void printWTO(const WeakTopologicalOrdering &O, llvm::raw_ostream &OS) {
+  bool First = true;
+  for (auto &E : O.Elements) {
+    if (!First)
+      OS << " ";
+    else
+      First = false;
+
+    std::visit(
+        [&OS](auto &&Value) {
+          if constexpr (std::is_same_v<std::decay_t<decltype(Value)>,
+                                       const CFGBlock *>) {
+            OS << Value->getBlockID();
+          } else {
+            static_assert(std::is_same_v<std::decay_t<decltype(Value)>,
+                                         WeakTopologicalOrdering>);
+            OS << "(";
+            printWTO(Value, OS);
+            OS << ")";
+          }
+        },
+        E);
+  }
+}
+
+std::string formatWTO(const WeakTopologicalOrdering &O) {
+  std::string Result;
+  llvm::raw_string_ostream OS(Result);
+  printWTO(O, OS);
+  return Result;
+}
+
+static void initWithWTO(llvm::DenseMap<const CFGBlock *, unsigned> &BlockOrder,
+                        const WeakTopologicalOrdering &WTO) {
+  for (auto &E : WTO.Elements) {
+    if (std::holds_alternative<WeakTopologicalOrdering>(E)) {
+      initWithWTO(BlockOrder, std::get<WeakTopologicalOrdering>(E));
+      continue;
+    }
+    const auto *B = std::get<const CFGBlock *>(E);
+    // Calculate J first so we don't depend on order of evaluation of the
+    // assignment's operands.
+    auto J = BlockOrder.size() + 1;
+    BlockOrder[B] = J;
+  }
+}
+
+WTOCompare::WTOCompare(const WeakTopologicalOrdering &WTO) {
+  initWithWTO(BlockOrder, WTO);
+}
 } // namespace clang
Index: clang/include/clang/Analysis/Analyses/IntervalPartition.h
===================================================================
--- clang/include/clang/Analysis/Analyses/IntervalPartition.h
+++ clang/include/clang/Analysis/Analyses/IntervalPartition.h
@@ -6,9 +6,13 @@
 //
 //===----------------------------------------------------------------------===//
 //
-//  This file defines functionality for partitioning a CFG into intervals. The
-//  concepts and implementations are based on the presentation in "Compilers" by
-//  Aho, Sethi and Ullman (the "dragon book"), pages 664-666.
+//  This file defines functionality for partitioning a CFG into intervals and
+//  building a weak topological order (WTO) of the nodes, based on the
+//  partitioning. The concepts and implementations for the graph partitioning
+//  are based on the presentation in "Compilers" by Aho, Sethi and Ullman (the
+//  "dragon book"), pages 664-666. The concepts around WTOs is taken from the
+//  paper "Efficient chaotic iteration strategies with widenings," by
+//  F. Bourdoncle ([Bourdoncle1993]).
 //
 //===----------------------------------------------------------------------===//
 
@@ -17,33 +21,125 @@
 
 #include "clang/Analysis/CFG.h"
 #include "llvm/ADT/DenseSet.h"
+#include <map>
+#include <memory>
+#include <set>
+#include <variant>
 #include <vector>
 
 namespace clang {
 
 // An interval is a strongly-connected component of the CFG along with a
-// trailing acyclic structure. The _header_ of the interval is either the CFG
-// entry block or has at least one predecessor outside of the interval. All
-// other blocks in the interval have only predecessors also in the interval.
+// trailing acyclic structure. An interval can be constructed directly from CFG
+// blocks or from a graph of other intervals. The _header_ of the interval is
+// either the graph's entry block or has at least one predecessor outside of the
+// interval. All other blocks in the interval have only predecessors also in the
+// interval.
 struct CFGInterval {
-  CFGInterval(const CFGBlock *Header) : Header(Header), Blocks({Header}) {}
+  CFGInterval() = default;
 
-  // The block from which the interval was constructed. Is either the CFG entry
-  // block or has at least one predecessor outside the interval.
-  const CFGBlock *Header;
+  template <typename Node>
+  CFGInterval(int ID, const Node *Header, std::vector<const Node *> Nodes)
+      : ID(ID), Data(NodeData<Node>{Header, std::move(Nodes)}) {}
 
-  llvm::SmallDenseSet<const CFGBlock *> Blocks;
+  const llvm::SmallDenseSet<const CFGInterval *> &preds() const {
+    return Predecessors;
+  }
+  const llvm::SmallDenseSet<const CFGInterval *> &succs() const {
+    return Successors;
+  }
 
-  // Successor blocks of the *interval*: blocks outside the interval for
-  // reachable (in one edge) from within the interval.
-  llvm::SmallDenseSet<const CFGBlock *> Successors;
+  // Unique identifier of this interval relative to other intervals in the same
+  // graph.
+  unsigned ID;
+
+  template <typename Node> struct NodeData {
+    // The block from which the interval was constructed. It is either the
+    // graph's entry block or has at least one predecessor outside the interval.
+    const Node *Header;
+    std::vector<const Node *> Nodes;
+  };
+  using IntervalData = std::variant<NodeData<CFGBlock>, NodeData<CFGInterval>>;
+
+  IntervalData Data;
+
+  // Predessor intervals of this interval: those intervals for which there
+  // exists an edge from a node in that other interval to the head of this
+  // interval.
+  llvm::SmallDenseSet<const CFGInterval *> Predecessors;
+
+  // Successor intervals of this interval: those intervals for which there
+  // exists an edge from a node in this interval to the head of that other
+  // interval.
+  llvm::SmallDenseSet<const CFGInterval *> Successors;
+
+  // Whether this node is the head of a back edge within the interval.
+  bool IsBackedgeHead = false;
 };
 
-CFGInterval buildInterval(const CFG &Cfg, const CFGBlock &Header);
+struct CFGIntervalGraph {
+  std::vector<CFGInterval> Intervals;
+};
+
+std::vector<const CFGBlock *> buildInterval(const CFGBlock *Header);
 
-// Partitions `Cfg` into intervals and constructs a graph of the intervals,
+// Partitions `Cfg` into intervals and constructs a the graph of the intervals
 // based on the edges between nodes in these intervals.
-std::vector<CFGInterval> partitionIntoIntervals(const CFG &Cfg);
+CFGIntervalGraph partitionIntoIntervals(const CFG &Cfg);
+
+// (Further) partitions `Graph` into intervals and constructs a the graph of the
+// intervals based on the edges between nodes (themselves intervals) in these
+// intervals.
+CFGIntervalGraph partitionIntoIntervals(const CFGIntervalGraph &Graph);
+
+/// A _weak topological ordering_ (WTO) of CFG nodes provides a total order over
+/// the CFG (defined in `WTOCompare`, below), which can guide the order in which
+/// to visit nodes in fixpoint computations over the CFG.
+///
+/// Roughly, a WTO a) groups the blocks so that loop heads are grouped with
+/// their bodies and any nodes they dominate after the loop and b) orders the
+/// groups topologically. As a result, the blocks in a series of loops are
+/// ordered such that all nodes in loop `i` are earlier in the order than nodes
+/// in loop `j`. This ordering, when combined with widening, bounds the number
+/// of times a node must be visited for a dataflow algorithm to reach a
+/// fixpoint. For the precise definition of a WTO and its properties, see
+/// [Bourdoncle1993].
+struct WeakTopologicalOrdering {
+  // `Elements` represents an ordered sequence and nested WTOs induce a
+  // hierarchy in the ordering. When built from a limit flow graph, the first
+  // element of a WTO corresponds to an interval header, which is usually a loop
+  // head (except for the CFG entry block).
+  std::vector<std::variant<const CFGBlock *, WeakTopologicalOrdering>> Elements;
+};
+
+/// Builds a WTO of the nodes in `I`, based on the structure of the intervals.
+WeakTopologicalOrdering getWTO(const CFGInterval &I);
+
+/// Builds a WTO from the limit flow graph of `Cfg` (derived from iteratively
+/// partitioning it into intervals) if and only if it is reducible (its limit
+/// flow graph has one node). Returns `nullop` when `Cfg` is not reducible.
+///
+/// This WTO construction is described in Section 4.2 of [Bourdoncle1993].
+std::optional<WeakTopologicalOrdering> getIntervalWTO(const CFG &Cfg);
+
+/// Formats `WTO` as a human-readable string.
+std::string formatWTO(const WeakTopologicalOrdering &WTO);
+
+struct WTOCompare {
+  WTOCompare(const WeakTopologicalOrdering &WTO);
+
+  bool operator()(const CFGBlock *B1, const CFGBlock *B2) const {
+    auto It1 = BlockOrder.find(B1);
+    auto It2 = BlockOrder.find(B2);
+
+    unsigned V1 = (It1 == BlockOrder.end()) ? 0 : It1->second;
+    unsigned V2 = (It2 == BlockOrder.end()) ? 0 : It2->second;
+    return V1 > V2;
+  }
+
+  using BlockOrderTy = llvm::DenseMap<const CFGBlock *, unsigned>;
+  BlockOrderTy BlockOrder;
+};
 
 } // namespace clang
 
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to