Jlm
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 
10 #include <jlm/util/TarjanScc.hpp>
11 
12 #include <stack>
13 #include <vector>
14 
15 namespace jlm::llvm::aa
16 {
17 
26 template<typename GetSuccessorsFunctor, typename UnifyPointerObjectsFunctor>
28 {
29 
30 public:
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
124  ClearDfsState();
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  {
175  DfsCycleNodes_.insert(node);
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  {
185  DfsCycleNodes_.insert(node);
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
281  NumOnlineCycleUnifications() const noexcept
282  {
284  }
285 
286 private:
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  {
302  if (ObjectIsInTopologicalOrder(node))
303  return false;
304  continue;
305  }
306 
307  // Ensure we have a position in the topological order
308  if (!ObjectIsInTopologicalOrder(node))
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
345  {
346  return ObjectToTopoOrder_[object] != EmptyTopologicalSlot;
347  }
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
bool Contains(const ItemType &item) const noexcept
Definition: HashSet.hpp:150
IteratorRange< ItemConstIterator > Items() const noexcept
Definition: HashSet.hpp:223
bool IsEmpty() const noexcept
Definition: HashSet.hpp:198
#define JLM_ASSERT(x)
Definition: common.hpp:16
uint32_t PointerObjectIndex