Jlm
traverser.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2010 2011 2012 2014 2015 Helge Bahmann <hcb@chaoticmind.net>
3  * Copyright 2013 2014 2015 Nico Reißmann <nico.reissmann@gmail.com>
4  * See COPYING for terms of redistribution.
5  */
6 
7 #include <jlm/rvsdg/graph.hpp>
9 
10 /*
11  How traversers operate:
12 
13  Each node needs a number of "activations" depending on edges
14  connected to the node (is inputs or outputs, dependent on direction
15  of traversal).
16 
17  Each edge coming from the region boundaries will provide one activation,
18  and each edge outgoing from a node that has been visited already will
19  provide one activation. So effectively, both bottom up and top down
20  traversers are just a little bit of beancounting edges and activations.
21  This is done in such a way that each node and each edge is visited exactly
22  once. Thus, the overall time complexity of either traversal is O(#N + #E).
23 */
24 namespace jlm::rvsdg
25 {
26 
27 namespace detail
28 {
29 
30 template<typename Traverser>
32 
33 template<typename Traverser>
34 ForwardingObserver<Traverser>::ForwardingObserver(const Region & region, Traverser & traverser)
35  : RegionObserver(region),
36  traverser_(traverser)
37 {}
38 
39 template<typename Traverser>
40 void
42 {
43  traverser_.onNodeCreate(node);
44 }
45 
46 template<typename Traverser>
47 void
49 {
50  traverser_.onNodeDestroy(node);
51 }
52 
53 template<typename Traverser>
54 void
56 {
57  traverser_.onInputCreate(input);
58 }
59 
60 template<typename Traverser>
61 void
63 {
64  traverser_.onInputChange(input, oldOrigin, newOrigin);
65 }
66 
67 template<typename Traverser>
68 void
70 {
71  traverser_.onInputDestroy(input);
72 }
73 
74 template<bool IsConst>
76 
77 template<bool IsConst>
79  : observer_(*region, *this),
80  nodeIdCutoff_(region->getNextNodeId())
81 {
82  for (auto & node : region->TopNodes())
83  {
84  tracker_.checkNodeActivation(&node, node.ninputs());
85  }
86 
87  for (auto argument : region->Arguments())
88  {
89  for (const auto & user : argument->Users())
90  {
91  if (auto node = TryGetOwnerNode<Node>(user))
92  {
93  tracker_.incActivationCount(node, node->ninputs());
94  }
95  }
96  }
97 }
98 
99 template<bool IsConst>
100 bool
102 {
103  if (auto pred = TryGetOwnerNode<Node>(output))
104  {
105  return tracker_.isNodeVisited(pred);
106  }
107 
108  // The output is a region argument, always considered activated
109  return true;
110 }
111 
112 template<bool IsConst>
113 void
115 {
116  tracker_.checkMarkNodeVisitedIfFrontier(&node);
117  for (const auto & output : node.Outputs())
118  {
119  for (const auto & user : output.Users())
120  {
121  if (auto next = TryGetOwnerNode<Node>(user))
122  {
123  tracker_.incActivationCount(next, next->ninputs());
124  }
125  }
126  }
127 }
128 
129 template<bool IsConst>
132 {
133  while (true)
134  {
135  const auto node = tracker_.peek();
136  if (!node)
137  return nullptr;
138 
139  markAsVisited(*node);
140 
141  // Skip nodes that were created after traversal started
142  if (node->GetNodeId() >= nodeIdCutoff_)
143  continue;
144 
145  return node;
146  }
147 }
148 
149 template<bool IsConst>
150 void
152 {
153  if (node->ninputs() == 0)
154  {
155  tracker_.checkNodeActivation(node, node->ninputs());
156  }
157  else
158  {
159  for (const auto & input : node->Inputs())
160  {
161  if (isOutputActivated(*input.origin()))
162  {
163  tracker_.incActivationCount(node, node->ninputs());
164  }
165  }
166  }
167 
168  // If node would end up on frontier (because all predecessors
169  // have been visited), mark it as visited instead (we do not
170  // want to revisit newly created nodes during topdown traversal).
171  tracker_.checkMarkNodeVisitedIfFrontier(node);
172 }
173 
174 template<bool IsConst>
175 void
177 {
178  // The node is already dead, so removing it can never add anything to the frontier
179  tracker_.removeNode(node);
180 }
181 
182 template<bool IsConst>
183 void
185 {
186  const auto node = TryGetOwnerNode<Node>(*input);
187  if (!node)
188  return;
189 
190  if (isOutputActivated(*input->origin()))
191  {
192  tracker_.incActivationCount(node, node->ninputs());
193  }
194  else
195  {
196  tracker_.checkNodeDeactivation(node, node->ninputs());
197  }
198 }
199 
200 template<bool IsConst>
201 void
203  Input * input,
204  Output * oldOrigin,
205  Output * newOrigin)
206 {
207  const auto node = TryGetOwnerNode<Node>(*input);
208  if (!node)
209  return;
210 
211  int change = 0;
212  if (isOutputActivated(*newOrigin))
213  {
214  change += 1;
215  }
216  if (isOutputActivated(*oldOrigin))
217  {
218  change -= 1;
219  }
220 
221  if (change == 1)
222  {
223  tracker_.incActivationCount(node, node->ninputs());
224  }
225  else if (change == -1)
226  {
227  tracker_.decActivationCount(node, node->ninputs());
228  }
229 }
230 
231 template<bool IsConst>
232 void
234 {
235  const auto node = TryGetOwnerNode<Node>(*input);
236  if (!node)
237  return;
238 
239  if (isOutputActivated(*input->origin()))
240  {
241  // Removing an activated input can never cause de-activation of the node,
242  // so we can use 0 as the deactivation threshold
243  tracker_.decActivationCount(node, 0);
244  }
245  else
246  {
247  // The callback is called before the input is actually removed, so use the new threshold
248  tracker_.checkNodeActivation(node, node->ninputs() - 1);
249  }
250 }
251 
252 template<bool IsConst>
254 
255 template<bool IsConst>
257  : observer_(*region, *this)
258 {
259  for (auto & node : region->BottomNodes())
260  {
261  tracker_.checkNodeActivation(&node, node.numSuccessors());
262  }
263 
264  for (auto result : region->Results())
265  {
266  if (auto node = TryGetOwnerNode<Node>(*result->origin()))
267  {
268  tracker_.incActivationCount(node, node->numSuccessors());
269  }
270  }
271 }
272 
273 template<bool IsConst>
274 bool
276 {
277  if (auto node = TryGetOwnerNode<Node>(input))
278  {
279  return tracker_.isNodeVisited(node);
280  }
281 
282  // The output is a region result, always considered activated
283  return true;
284 }
285 
286 template<bool IsConst>
287 void
289 {
290  tracker_.checkMarkNodeVisitedIfFrontier(&node);
291  for (const auto & input : node.Inputs())
292  {
293  if (auto predecessor = TryGetOwnerNode<Node>(*input.origin()))
294  {
295  tracker_.incActivationCount(predecessor, predecessor->numSuccessors());
296  }
297  }
298 }
299 
300 template<bool IsConst>
303 {
304  auto node = tracker_.peek();
305  if (!node)
306  return nullptr;
307 
308  markAsVisited(*node);
309  return node;
310 }
311 
312 template<bool IsConst>
313 void
315 {
316  // The new node should never be visited, so activate all its predecessors immediately
317  markAsVisited(*node);
318 }
319 
320 template<bool IsConst>
321 void
323 {
324  // In case this is the last node a predecessor is waiting for,
325  // make sure it gets activated before removal.
326  if (!tracker_.isNodeVisited(node))
327  markAsVisited(*node);
328 
329  for (const auto & input : node->Inputs())
330  {
331  if (auto pred = TryGetOwnerNode<Node>(*input.origin()))
332  {
333  // Set the threshold to 0 here: The predecessor node is
334  // still connected, so its successor count is not correct.
335  // However, if the predecessor is on the frontier, it will
336  // remain on the frontier after this removal.
337  tracker_.decActivationCount(pred, 0);
338  }
339  }
340 }
341 
342 template<bool IsConst>
343 void
345 {
346  const auto node = TryGetOwnerNode<Node>(*input->origin());
347  if (!node)
348  return;
349 
350  if (isInputActivated(*input))
351  {
352  tracker_.incActivationCount(node, node->numSuccessors());
353  }
354  else
355  {
356  tracker_.checkNodeDeactivation(node, node->numSuccessors());
357  }
358 }
359 
360 template<bool IsConst>
361 void
363  Input * input,
364  Output * oldOrigin,
365  Output * newOrigin)
366 {
367  const auto inputActive = isInputActivated(*input);
368 
369  if (auto oldNode = TryGetOwnerNode<Node>(*oldOrigin))
370  {
371  if (inputActive)
372  {
373  tracker_.decActivationCount(oldNode, oldNode->numSuccessors());
374  }
375  else
376  {
377  // The oldNode might have just lost its final non-active user
378  tracker_.checkNodeActivation(oldNode, oldNode->numSuccessors());
379  }
380  }
381 
382  if (auto newNode = TryGetOwnerNode<Node>(*newOrigin))
383  {
384  if (inputActive)
385  {
386  tracker_.incActivationCount(newNode, newNode->numSuccessors());
387  }
388  else
389  {
390  tracker_.checkNodeDeactivation(newNode, newNode->numSuccessors());
391  }
392  }
393 }
394 
395 template<bool IsConst>
396 void
398 {
399  const auto pred = TryGetOwnerNode<Node>(*input->origin());
400  if (!pred)
401  return;
402 
403  if (isInputActivated(*input))
404  {
405  // Removing an activated user can never cause de-activation of pred.
406  // The threshold for de-activation can therefore be set to 0.
407  tracker_.decActivationCount(pred, 0);
408  }
409  else
410  {
411  tracker_.checkNodeActivation(pred, pred->numSuccessors() - 1);
412  }
413 }
414 
415 template<typename NodeType>
416 bool
418 {
419  auto i = states_.find(node);
420  return i == states_.end() ? false : i->second.state == TraversalNodeState::behind;
421 }
422 
423 template<typename NodeType>
424 void
425 TraversalTracker<NodeType>::checkNodeActivation(NodeType * node, std::size_t threshold)
426 {
427  auto i = states_.emplace(node, State{ TraversalNodeState::ahead }).first;
428  if (i->second.activationCount >= threshold && i->second.state == TraversalNodeState::ahead)
429  {
430  frontier_.push_back(node);
431  i->second.pos = std::prev(frontier_.end());
432  i->second.state = TraversalNodeState::frontier;
433  }
434 }
435 
436 template<typename NodeType>
437 void
438 TraversalTracker<NodeType>::checkNodeDeactivation(NodeType * node, std::size_t threshold)
439 {
440  auto i = states_.emplace(node, State{ TraversalNodeState::ahead }).first;
441  if (i->second.activationCount < threshold && i->second.state == TraversalNodeState::frontier)
442  {
443  frontier_.erase(i->second.pos);
444  i->second.pos = frontier_.end();
445  i->second.state = TraversalNodeState::ahead;
446  }
447 }
448 
449 template<typename NodeType>
450 void
452 {
453  auto i = states_.emplace(node, State{ TraversalNodeState::ahead }).first;
454  if (i->second.state == TraversalNodeState::frontier)
455  {
456  frontier_.erase(i->second.pos);
457  i->second.pos = frontier_.end();
458  i->second.state = TraversalNodeState::behind;
459  }
460 }
461 
462 template<typename NodeType>
463 void
464 TraversalTracker<NodeType>::incActivationCount(NodeType * node, std::size_t threshold)
465 {
466  auto i = states_.emplace(node, State{ TraversalNodeState::ahead }).first;
467  i->second.activationCount += 1;
468  checkNodeActivation(node, threshold);
469 }
470 
471 template<typename NodeType>
472 void
473 TraversalTracker<NodeType>::decActivationCount(NodeType * node, std::size_t threshold)
474 {
475  auto i = states_.emplace(node, State{ TraversalNodeState::ahead }).first;
476  i->second.activationCount -= 1;
477  checkNodeDeactivation(node, threshold);
478 }
479 
480 template<typename NodeType>
481 void
483 {
484  if (const auto it = states_.find(node); it != states_.end())
485  {
486  if (it->second.state == TraversalNodeState::frontier)
487  frontier_.erase(it->second.pos);
488  states_.erase(it);
489  }
490 }
491 
492 template<typename NodeType>
493 NodeType *
495 {
496  return frontier_.empty() ? nullptr : frontier_.front();
497 }
498 
499 // Explicit instantiation of all versions
500 template class TopDownTraverserGeneric<false>;
501 template class TopDownTraverserGeneric<true>;
502 template class BottomUpTraverserGeneric<false>;
503 template class BottomUpTraverserGeneric<true>;
504 
505 }
506 
507 }
Output * origin() const noexcept
Definition: node.hpp:58
Proxy object to observe changes to a region.
Definition: region.hpp:783
Represent acyclic RVSDG subgraphs.
Definition: region.hpp:213
std::conditional_t< IsConst, const Node, Node > NodeType
Definition: traverser.hpp:250
bool isInputActivated(const Input &input) const
Definition: traverser.cpp:275
std::conditional_t< IsConst, const Region, Region > RegionType
Definition: traverser.hpp:251
void onInputChange(Input *input, Output *oldOrigin, Output *newOrigin)
Definition: traverser.cpp:362
void onNodeDestroy(Node *node) override
Definition: traverser.cpp:48
ForwardingObserver(const Region &region, Traverser &traverser)
Definition: traverser.cpp:34
void onInputCreate(Input *input) override
Definition: traverser.cpp:55
void onInputChange(Input *input, Output *oldOrigin, Output *newOrigin) override
Definition: traverser.cpp:62
void onNodeCreate(Node *node) override
Definition: traverser.cpp:41
void onInputDestroy(Input *input) override
Definition: traverser.cpp:69
bool isOutputActivated(const Output &output) const
Definition: traverser.cpp:101
void onInputChange(Input *in, Output *old_origin, Output *new_origin)
Definition: traverser.cpp:202
std::conditional_t< IsConst, const Node, Node > NodeType
Definition: traverser.hpp:184
std::conditional_t< IsConst, const Region, Region > RegionType
Definition: traverser.hpp:185
void checkMarkNodeVisitedIfFrontier(NodeType *node)
Marks a node visited if it is currently ready for visiting.
Definition: traverser.cpp:451
bool isNodeVisited(NodeType *node) const
Determines whether node has been visited already.
Definition: traverser.cpp:417
void decActivationCount(NodeType *node, std::size_t threshold)
Decrements activation count; removes from frontier if threshold is no longer met.
Definition: traverser.cpp:473
void incActivationCount(NodeType *node, std::size_t threshold)
Increments activation count; adds to frontier if threshold is met.
Definition: traverser.cpp:464
void checkNodeActivation(NodeType *node, std::size_t threshold)
Checks activation count whether node is ready for visiting.
Definition: traverser.cpp:425
void checkNodeDeactivation(NodeType *node, std::size_t threshold)
Checks activation count whether node is no longer ready for visiting.
Definition: traverser.cpp:438
void removeNode(NodeType *node)
Removes any state associated with the given node.
Definition: traverser.cpp:482