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 { |
34 | static unsigned getID(const CFGBlock &B) { return B.getBlockID(); } |
35 | static unsigned getID(const CFGIntervalNode &I) { return I.ID; } |
36 | |
37 | // `Node` must be one of `CFGBlock` or `CFGIntervalNode`. |
38 | template <typename Node> |
39 | BuildResult<Node> buildInterval(llvm::BitVector &Partitioned, |
40 | const Node *) { |
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> |
105 | void fillIntervalNode(CFGIntervalGraph &Graph, |
106 | std::vector<CFGIntervalNode *> &Index, |
107 | std::queue<const Node *> &Successors, |
108 | llvm::BitVector &Partitioned, const Node *) { |
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(args: 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(n: 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> |
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 | |
199 | std::vector<const CFGBlock *> buildInterval(const CFGBlock *) { |
200 | llvm::BitVector Partitioned(Header->getParent()->getNumBlockIDs(), false); |
201 | return buildInterval(Partitioned, Header).Nodes; |
202 | } |
203 | |
204 | CFGIntervalGraph partitionIntoIntervals(const CFG &Cfg) { |
205 | return partitionIntoIntervalsImpl(NumBlockIDs: Cfg.getNumBlockIDs(), EntryBlock: &Cfg.getEntry()); |
206 | } |
207 | |
208 | CFGIntervalGraph partitionIntoIntervals(const CFGIntervalGraph &Graph) { |
209 | return partitionIntoIntervalsImpl(NumBlockIDs: Graph.size(), EntryBlock: &Graph[0]); |
210 | } |
211 | } // namespace internal |
212 | |
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 | |
233 | WTOCompare::WTOCompare(const WeakTopologicalOrdering &WTO) { |
234 | if (WTO.empty()) |
235 | return; |
236 | auto N = WTO[0]->getParent()->getNumBlockIDs(); |
237 | BlockOrder.resize(new_size: N, x: 0); |
238 | for (unsigned I = 0, S = WTO.size(); I < S; ++I) |
239 | BlockOrder[WTO[I]->getBlockID()] = I + 1; |
240 | } |
241 | } // namespace clang |
242 | |