Jlm
Loading...
Searching...
No Matches
OnlineCycleDetection.hpp
Go to the documentation of this file.
1/*
2 * Copyright 2024 HÃ¥vard Krogstie <krogstie.havard@gmail.com>
3 * See COPYING for terms of redistribution.
4 */
5
6#ifndef JLM_LLVM_OPT_ALIAS_ANALYSES_ONLINECYCLEDETECTION_HPP
7#define JLM_LLVM_OPT_ALIAS_ANALYSES_ONLINECYCLEDETECTION_HPP
8
11
12#include <stack>
13#include <vector>
14
15namespace jlm::llvm::aa
16{
17
26template<typename GetSuccessorsFunctor, typename UnifyPointerObjectsFunctor>
28{
29
30public:
32 PointerObjectSet & set,
33 const GetSuccessorsFunctor & GetSuccessors,
34 const UnifyPointerObjectsFunctor & UnifyPointerObjects)
35 : Set_(set),
36 GetSuccessors_(GetSuccessors),
37 UnifyPointerObjects_(UnifyPointerObjects)
38 {}
39
45 void
47 {
48 // Ensure no pointer object has the sentinel value as its index
50
51 // Initialize Online Cycle Detection using a full topological sort of the nodes
53
54 // At this point, all subsetEdges are between unification roots only
55 // Use TarjanSCC to find our starting topological ordering
56 std::vector<PointerObjectIndex> sccIndex;
57 std::vector<PointerObjectIndex> reverseTopologicalOrder;
58
59 // Used by Tarjan to avoid traversing non-roots
60 const auto GetUnificationRoot = [&](PointerObjectIndex node)
61 {
62 return Set_.GetUnificationRoot(node);
63 };
64
65 util::FindStronglyConnectedComponents<PointerObjectIndex>(
67 GetUnificationRoot,
69 sccIndex,
70 reverseTopologicalOrder);
71
72 // Go through the topological ordering and add all unification roots to OCD_ObjectToTopoOrder
73 // Also, if we find any new SCCs while doing this, perform unification right now
74 for (auto it = reverseTopologicalOrder.rbegin(); it != reverseTopologicalOrder.rend(); ++it)
75 {
77
78 // Check if we can unify node with the next node in the topological order
79 if (const auto nextIt = it + 1;
80 nextIt != reverseTopologicalOrder.rend() && sccIndex[*it] == sccIndex[*nextIt])
81 {
82 // We know that the SCC consists of only roots
84 // Make the next object the root, to keep the unification going
85 *nextIt = UnifyPointerObjects_(*it, *nextIt);
86 }
87 else
88 {
89 // Add this root to the next index in the topological order
91 TopoOrderToObject_.push_back(*it);
92 }
93 }
94
96 }
97
106 std::optional<PointerObjectIndex>
108 {
110 // All unification roots should have positions in the topological order
112
113 // If adding the simple edge subset -> superset does not break the invariant, return
114 if (ObjectToTopoOrder_[subset] <= ObjectToTopoOrder_[superset])
115 return std::nullopt;
116
117 // Otherwise, we need to do reordering in this range to fix the invariant
118 const auto lowerTopo = ObjectToTopoOrder_[superset];
119 const auto upperTopo = ObjectToTopoOrder_[subset];
120
121 // Perform DFS to find all roots reachable from superset
122 // We only care about nodes where with its topo index <= upperTopo
123 // These nodes will all need to be moved after subset in the new topological order
125
126 // Start DFS from the superset. false means first visit
127 DfsStack_.emplace(superset, false);
128
129 while (!DfsStack_.empty())
130 {
131 const auto node = DfsStack_.top().first;
132 const bool isFirstVisit = !DfsStack_.top().second;
133 DfsStack_.pop();
135
136 if (isFirstVisit)
137 {
138 // If a "first visit" of this node has already occurred, skip it.
139 // This can happen if the same node is pushed multiple times before being popped.
140 if (!DfsNodesVisited_.insert(node))
141 continue;
142
143 // Push node again to visit it a second time on the way back
144 DfsStack_.emplace(node, true);
145
146 // Push all non-pushed successors, as long as their position in the topological order is
147 // between [lowerTopo, upperTopo]. Nodes outside this range can stay where they are in the
148 // topological order, and will never be part of a path from superset to subset.
149 for (auto successor : GetSuccessors_(node))
150 {
151 auto successorParent = Set_.GetUnificationRoot(successor);
152
153 // Only push nodes that have yet to be visited
154 if (DfsNodesVisited_.Contains(successorParent))
155 continue;
156
157 // Ensure that the topological order invariants are satisfied
158 JLM_ASSERT(ComesAfter(successorParent, node));
159
160 // Only care about nodes within the topological range currently being rearranged
161 const auto topo = ObjectToTopoOrder_[successorParent];
162 if (topo < lowerTopo || topo > upperTopo)
163 continue;
164
165 // Visit the successor
166 DfsStack_.emplace(successorParent, false);
167 }
168 }
169 else
170 {
171 // Node is being visited for the second time, on the way back in the dfs
172 // If this node is the subset, then there is a cycle
173 if (node == subset)
174 {
176 continue;
177 }
178
179 // Check if any of node's successors reach subset
180 for (auto successor : GetSuccessors_(node))
181 {
182 auto successorParent = Set_.GetUnificationRoot(successor);
183 if (DfsCycleNodes_.Contains(successorParent))
184 {
186 break;
187 }
188 }
189 }
190 }
191
192 // The root of the unified cycle, if any
193 std::optional<PointerObjectIndex> optUnificationRoot = std::nullopt;
194 if (!DfsCycleNodes_.IsEmpty())
195 {
196 // If there is a cycle, both the subset and superset must be included
199
200 // Track this cycle in the statistics
203
204 // Merge all entries on the merge list
205 for (const auto node : DfsCycleNodes_.Items())
206 {
207 // Remove node from the topological order, only the final merge result belongs there
209 optUnificationRoot =
210 optUnificationRoot ? UnifyPointerObjects_(*optUnificationRoot, node) : node;
211 }
212 // After the nodes not found during the dfs, the merged node should be next, topologically
213 DfsSupersetAndBeyond_.push_back(*optUnificationRoot);
214 }
215
216 // Now go through the topological order from lowerTopo to upperTopo.
217 // All nodes not visited by the DFS are compacted to the left,
218 // All other nodes are added in order in the supersetAndBeyond list
219 // Nodes that are part of the cycle, if any, are ignored
220 auto nextTopologicalIndex = lowerTopo;
221 for (auto i = lowerTopo; i <= upperTopo; i++)
222 {
223 // Skip all topological indices that do not contain a node
225 continue;
226
228 if (DfsCycleNodes_.Contains(node))
229 continue;
230
231 JLM_ASSERT(ObjectToTopoOrder_[node] == i);
232
233 if (DfsNodesVisited_.Contains(node))
234 {
235 DfsSupersetAndBeyond_.push_back(node);
236 continue;
237 }
238
239 // Add node to its new position
240 ObjectToTopoOrder_[node] = nextTopologicalIndex;
241 TopoOrderToObject_[nextTopologicalIndex] = node;
242 nextTopologicalIndex++;
243 }
244
245 // Next, add all elements from OCD_supersetAndBeyond
246 for (auto node : DfsSupersetAndBeyond_)
247 {
249
250 ObjectToTopoOrder_[node] = nextTopologicalIndex;
251 TopoOrderToObject_[nextTopologicalIndex] = node;
252 nextTopologicalIndex++;
253 }
254
255 // Any leftover positions in the topological order should now become unoccupied
256 while (nextTopologicalIndex <= upperTopo)
257 {
258 TopoOrderToObject_[nextTopologicalIndex] = EmptyTopologicalSlot;
259 nextTopologicalIndex++;
260 }
261
263
264 // return the root of the unification if a cycle was detected, otherwise nullopt
265 return optUnificationRoot;
266 };
267
271 [[nodiscard]] size_t
272 NumOnlineCyclesDetected() const noexcept
273 {
275 }
276
280 [[nodiscard]] size_t
282 {
284 }
285
286private:
294 [[nodiscard]] bool
296 {
297 for (PointerObjectIndex node = 0; node < Set_.NumPointerObjects(); node++)
298 {
299 // Non-unification roots should not have a place in the topological order
300 if (!Set_.IsUnificationRoot(node))
301 {
303 return false;
304 continue;
305 }
306
307 // Ensure we have a position in the topological order
309 return false;
310 const auto topo = ObjectToTopoOrder_[node];
311 if (TopoOrderToObject_[topo] != node)
312 return false;
313
314 // Ensure all outgoing edges go to unifications with higher topological index
315 for (auto successor : GetSuccessors_(node))
316 {
317 const auto successorRoot = Set_.GetUnificationRoot(successor);
318 if (successorRoot == node) // Ignore self-edges
319 continue;
320
321 if (!ComesAfter(successorRoot, node))
322 return false;
323 }
324 }
325
326 // Ensure TopoOrderToObject is a perfect inverse of ObjectToTopoOrder
327 for (size_t i = 0; i < TopoOrderToObject_.size(); i++)
328 {
330 continue;
331 size_t backReference = ObjectToTopoOrder_[TopoOrderToObject_[i]];
332 if (backReference != i)
333 return false;
334 }
335
336 return true;
337 };
338
343 [[nodiscard]] bool
348
353 [[nodiscard]] bool
355 {
356 return TopoOrderToObject_[topologicalIndex] != EmptyTopologicalSlot;
357 }
358
363 [[nodiscard]] bool
365 {
367 return false;
368 return ObjectToTopoOrder_[after] > ObjectToTopoOrder_[before];
369 }
370
374 void
376 {
377 // As we are not currently in the middle of a DFS, the DFS stack should already be empty
378 JLM_ASSERT(DfsStack_.empty());
381 DfsSupersetAndBeyond_.clear();
382 }
383
384 // The PointerObjectSet being operated on
386
387 // Functions used to operate on the subset graph while the worklist solver is running
388 const GetSuccessorsFunctor & GetSuccessors_;
389 const UnifyPointerObjectsFunctor & UnifyPointerObjects_;
390
391 // This sentinel value is used to indicate that
392 // - An object doesn't have a position in the topological order
393 // - A slot in the topological order is not occupied by an object
395 std::numeric_limits<PointerObjectIndex>::max();
396
397 // Invariant:
398 // ObjectToTopoOrder_[PO] == EmptyTopologicalSlot iff PO is not a unification root.
399 // ObjectToTopoOrder_[PO] is smaller than all successors of PO's roots.
400 std::vector<PointerObjectIndex> ObjectToTopoOrder_;
401
402 // TopoOrderToObject_[topo] == EmptyTopologicalSlot iff no PointerObject has the position.
403 // TopoOrderToObject_ is the inverse to ObjectToTopoOrder_
404 std::vector<PointerObjectIndex> TopoOrderToObject_;
405
406 // Data structures used to store state during DFS passes are kept here,
407 // to avoid re-allocating their backing storage every time a new DFS is performed.
408
409 // Stack used for dfs. The boolean is true iff the node is being visited on the way back.
410 // Note: a node can be added to this stack many times, but only once with second=true.
411 std::stack<std::pair<PointerObjectIndex, bool>> DfsStack_;
412
413 // Nodes are added to this set when they are actually visited.
415
416 // Nodes that are part of the discovered cycle (reachable from superset, can reach subset)
417 // If a cycle is detected, subset and superset will both be in this list
419
420 // List used to store the nodes that should come after the subset in the new topological order
421 std::vector<PointerObjectIndex> DfsSupersetAndBeyond_;
422
423 // Statistics measurements
426};
427
428}
429
430#endif // JLM_LLVM_OPT_ALIAS_ANALYSES_ONLINECYCLEDETECTION_HPP
std::vector< PointerObjectIndex > ObjectToTopoOrder_
static constexpr PointerObjectIndex EmptyTopologicalSlot
const GetSuccessorsFunctor & GetSuccessors_
size_t NumOnlineCyclesDetected() const noexcept
bool ComesAfter(PointerObjectIndex after, PointerObjectIndex before) const
std::stack< std::pair< PointerObjectIndex, bool > > DfsStack_
util::HashSet< PointerObjectIndex > DfsCycleNodes_
OnlineCycleDetector(PointerObjectSet &set, const GetSuccessorsFunctor &GetSuccessors, const UnifyPointerObjectsFunctor &UnifyPointerObjects)
std::optional< PointerObjectIndex > MaintainTopologicalOrder(PointerObjectIndex subset, PointerObjectIndex superset)
std::vector< PointerObjectIndex > TopoOrderToObject_
bool IsTopologicalOrderIndexFilled(PointerObjectIndex topologicalIndex) const
const UnifyPointerObjectsFunctor & UnifyPointerObjects_
util::HashSet< PointerObjectIndex > DfsNodesVisited_
std::vector< PointerObjectIndex > DfsSupersetAndBeyond_
size_t NumOnlineCycleUnifications() const noexcept
bool ObjectIsInTopologicalOrder(PointerObjectIndex object) const
PointerObjectIndex GetUnificationRoot(PointerObjectIndex index) const noexcept
bool IsUnificationRoot(PointerObjectIndex index) const noexcept
size_t NumPointerObjects() const noexcept
void Clear() noexcept
Definition HashSet.hpp:138
bool insert(ItemType item)
Definition HashSet.hpp:210
std::size_t Size() const noexcept
Definition HashSet.hpp:187
IteratorRange< ItemConstIterator > Items() const noexcept
Definition HashSet.hpp:223
bool Contains(const ItemType &item) const noexcept
Definition HashSet.hpp:150
bool IsEmpty() const noexcept
Definition HashSet.hpp:198
#define JLM_ASSERT(x)
Definition common.hpp:16
uint32_t PointerObjectIndex