1 //===- IntervalPartition.cpp - CFG Partitioning into Intervals --*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 //  This file defines functionality for partitioning a CFG into intervals.
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #include "clang/Analysis/Analyses/IntervalPartition.h"
14 #include "clang/Analysis/CFG.h"
15 #include "llvm/ADT/BitVector.h"
16 #include "llvm/ADT/STLExtras.h"
17 #include <optional>
18 #include <queue>
19 #include <vector>
20 
21 namespace clang {
22 
23 // Intermediate data used in constructing a CFGIntervalNode.
24 template <typename Node> struct BuildResult {
25   // Use a vector to maintain the insertion order. Given the expected small
26   // number of nodes, vector should be sufficiently efficient. Elements must not
27   // be null.
28   std::vector<const Node *> Nodes;
29   // Elements must not be null.
30   llvm::SmallDenseSet<const Node *> Successors;
31 };
32 
33 namespace internal {
getID(const CFGBlock & B)34 static unsigned getID(const CFGBlock &B) { return B.getBlockID(); }
getID(const CFGIntervalNode & I)35 static unsigned getID(const CFGIntervalNode &I) { return I.ID; }
36 
37 // `Node` must be one of `CFGBlock` or `CFGIntervalNode`.
38 template <typename Node>
buildInterval(llvm::BitVector & Partitioned,const Node * Header)39 BuildResult<Node> buildInterval(llvm::BitVector &Partitioned,
40                                 const Node *Header) {
41   assert(Header != nullptr);
42   BuildResult<Node> Interval;
43   Interval.Nodes.push_back(Header);
44   Partitioned.set(getID(*Header));
45 
46   // FIXME: Compare performance against using RPO to consider nodes, rather than
47   // following successors.
48   //
49   // Elements must not be null. Duplicates are prevented using `Workset`, below.
50   std::queue<const Node *> Worklist;
51   llvm::BitVector Workset(Partitioned.size(), false);
52   for (const Node *S : Header->succs())
53     if (S != nullptr)
54       if (auto SID = getID(*S); !Partitioned.test(SID)) {
55         // Successors are unique, so we don't test against `Workset` before
56         // adding to `Worklist`.
57         Worklist.push(S);
58         Workset.set(SID);
59       }
60 
61   // Contains successors of blocks in the interval that couldn't be added to the
62   // interval on their first encounter. This occurs when they have a predecessor
63   // that is either definitively outside the interval or hasn't been considered
64   // yet. In the latter case, we'll revisit the block through some other path
65   // from the interval. At the end of processing the worklist, we filter out any
66   // that ended up in the interval to produce the output set of interval
67   // successors. Elements are never null.
68   std::vector<const Node *> MaybeSuccessors;
69 
70   while (!Worklist.empty()) {
71     const auto *B = Worklist.front();
72     auto ID = getID(*B);
73     Worklist.pop();
74     Workset.reset(ID);
75 
76     // Check whether all predecessors are in the interval, in which case `B`
77     // is included as well.
78     bool AllInInterval = llvm::all_of(B->preds(), [&](const Node *P) {
79       return llvm::is_contained(Interval.Nodes, P);
80     });
81     if (AllInInterval) {
82       Interval.Nodes.push_back(B);
83       Partitioned.set(ID);
84       for (const Node *S : B->succs())
85         if (S != nullptr)
86           if (auto SID = getID(*S);
87               !Partitioned.test(SID) && !Workset.test(SID)) {
88             Worklist.push(S);
89             Workset.set(SID);
90           }
91     } else {
92       MaybeSuccessors.push_back(B);
93     }
94   }
95 
96   // Any block successors not in the current interval are interval successors.
97   for (const Node *B : MaybeSuccessors)
98     if (!llvm::is_contained(Interval.Nodes, B))
99       Interval.Successors.insert(B);
100 
101   return Interval;
102 }
103 
104 template <typename Node>
fillIntervalNode(CFGIntervalGraph & Graph,std::vector<CFGIntervalNode * > & Index,std::queue<const Node * > & Successors,llvm::BitVector & Partitioned,const Node * Header)105 void fillIntervalNode(CFGIntervalGraph &Graph,
106                       std::vector<CFGIntervalNode *> &Index,
107                       std::queue<const Node *> &Successors,
108                       llvm::BitVector &Partitioned, const Node *Header) {
109   BuildResult<Node> Result = buildInterval(Partitioned, Header);
110   for (const auto *S : Result.Successors)
111     Successors.push(S);
112 
113   CFGIntervalNode &Interval = Graph.emplace_back(Graph.size());
114 
115   // Index the nodes of the new interval. The index maps nodes from the input
116   // graph (specifically, `Result.Nodes`) to identifiers of nodes in the output
117   // graph. In this case, the new interval has identifier `ID` so all of its
118   // nodes (`Result.Nodes`) map to `ID`.
119   for (const auto *N : Result.Nodes) {
120     assert(N != nullptr);
121     assert(getID(*N) < Index.size());
122     Index[getID(*N)] = &Interval;
123   }
124 
125   if constexpr (std::is_same_v<std::decay_t<Node>, CFGBlock>)
126     Interval.Nodes = std::move(Result.Nodes);
127   else {
128     std::vector<const CFGBlock *> Nodes;
129     // Flatten the sub vectors into a single list.
130     size_t Count = 0;
131     for (auto &N : Result.Nodes)
132       Count += N->Nodes.size();
133     Nodes.reserve(Count);
134     for (auto &N : Result.Nodes)
135       Nodes.insert(Nodes.end(), N->Nodes.begin(), N->Nodes.end());
136     Interval.Nodes = std::move(Nodes);
137   }
138 }
139 
140 template <typename Node>
partitionIntoIntervalsImpl(unsigned NumBlockIDs,const Node * EntryBlock)141 CFGIntervalGraph partitionIntoIntervalsImpl(unsigned NumBlockIDs,
142                                             const Node *EntryBlock) {
143   assert(EntryBlock != nullptr);
144   CFGIntervalGraph Graph;
145   // `Index` maps all of the nodes of the input graph to the interval to which
146   // they are assigned in the output graph. The values (interval pointers) are
147   // never null.
148   std::vector<CFGIntervalNode *> Index(NumBlockIDs, nullptr);
149 
150   // Lists header nodes (from the input graph) and their associated
151   // interval. Since header nodes can vary in type and are only needed within
152   // this function, we record them separately from `CFGIntervalNode`. This
153   // choice enables to express `CFGIntervalNode` without using a variant.
154   std::vector<std::pair<const Node *, CFGIntervalNode *>> Intervals;
155   llvm::BitVector Partitioned(NumBlockIDs, false);
156   std::queue<const Node *> Successors;
157 
158   fillIntervalNode(Graph, Index, Successors, Partitioned, EntryBlock);
159   Intervals.emplace_back(EntryBlock, &Graph.back());
160 
161   while (!Successors.empty()) {
162     const auto *B = Successors.front();
163     Successors.pop();
164     assert(B != nullptr);
165     if (Partitioned.test(getID(*B)))
166       continue;
167 
168     // B has not been partitioned, but it has a predecessor that has. Create a
169     // new interval from `B`.
170     fillIntervalNode(Graph, Index, Successors, Partitioned, B);
171     Intervals.emplace_back(B, &Graph.back());
172   }
173 
174   // Go back and patch up all the Intervals -- the successors and predecessors.
175   for (auto [H, N] : Intervals) {
176     // Map input-graph predecessors to output-graph nodes and mark those as
177     // predecessors of `N`. Then, mark `N` as a successor of said predecessor.
178     for (const Node *P : H->preds()) {
179       if (P == nullptr)
180         continue;
181 
182       assert(getID(*P) < NumBlockIDs);
183       CFGIntervalNode *Pred = Index[getID(*P)];
184       if (Pred == nullptr)
185         // Unreachable node.
186         continue;
187       if (Pred != N // Not a backedge.
188           && N->Predecessors.insert(Pred).second)
189         // Note: given the guard above, which guarantees we only ever insert
190         // unique elements, we could use a simple list (like `vector`) for
191         // `Successors`, rather than a set.
192         Pred->Successors.insert(N);
193     }
194   }
195 
196   return Graph;
197 }
198 
buildInterval(const CFGBlock * Header)199 std::vector<const CFGBlock *> buildInterval(const CFGBlock *Header) {
200   llvm::BitVector Partitioned(Header->getParent()->getNumBlockIDs(), false);
201   return buildInterval(Partitioned, Header).Nodes;
202 }
203 
partitionIntoIntervals(const CFG & Cfg)204 CFGIntervalGraph partitionIntoIntervals(const CFG &Cfg) {
205   return partitionIntoIntervalsImpl(Cfg.getNumBlockIDs(), &Cfg.getEntry());
206 }
207 
partitionIntoIntervals(const CFGIntervalGraph & Graph)208 CFGIntervalGraph partitionIntoIntervals(const CFGIntervalGraph &Graph) {
209   return partitionIntoIntervalsImpl(Graph.size(), &Graph[0]);
210 }
211 } // namespace internal
212 
getIntervalWTO(const CFG & Cfg)213 std::optional<std::vector<const CFGBlock *>> getIntervalWTO(const CFG &Cfg) {
214   // Backing storage for the allocated nodes in each graph.
215   unsigned PrevSize = Cfg.size();
216   if (PrevSize == 0)
217     return {};
218   internal::CFGIntervalGraph Graph = internal::partitionIntoIntervals(Cfg);
219   unsigned Size = Graph.size();
220   while (Size > 1 && Size < PrevSize) {
221     PrevSize = Graph.size();
222     Graph = internal::partitionIntoIntervals(Graph);
223     Size = Graph.size();
224   }
225   if (Size > 1)
226     // Not reducible.
227     return std::nullopt;
228 
229   assert(Size != 0);
230   return std::move(Graph[0].Nodes);
231 }
232 
WTOCompare(const WeakTopologicalOrdering & WTO)233 WTOCompare::WTOCompare(const WeakTopologicalOrdering &WTO) {
234   if (WTO.empty())
235     return;
236   auto N = WTO[0]->getParent()->getNumBlockIDs();
237   BlockOrder.resize(N, 0);
238   for (unsigned I = 0, S = WTO.size(); I < S; ++I)
239     BlockOrder[WTO[I]->getBlockID()] = I + 1;
240 }
241 } // namespace clang
242