• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2017 The Abseil Authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 // GraphCycles provides incremental cycle detection on a dynamic
16 // graph using the following algorithm:
17 //
18 // A dynamic topological sort algorithm for directed acyclic graphs
19 // David J. Pearce, Paul H. J. Kelly
20 // Journal of Experimental Algorithmics (JEA) JEA Homepage archive
21 // Volume 11, 2006, Article No. 1.7
22 //
23 // Brief summary of the algorithm:
24 //
25 // (1) Maintain a rank for each node that is consistent
26 //     with the topological sort of the graph. I.e., path from x to y
27 //     implies rank[x] < rank[y].
28 // (2) When a new edge (x->y) is inserted, do nothing if rank[x] < rank[y].
29 // (3) Otherwise: adjust ranks in the neighborhood of x and y.
30 
31 #include "absl/base/attributes.h"
32 // This file is a no-op if the required LowLevelAlloc support is missing.
33 #include "absl/base/internal/low_level_alloc.h"
34 #ifndef ABSL_LOW_LEVEL_ALLOC_MISSING
35 
36 #include "absl/synchronization/internal/graphcycles.h"
37 
38 #include <algorithm>
39 #include <array>
40 #include "absl/base/internal/hide_ptr.h"
41 #include "absl/base/internal/raw_logging.h"
42 #include "absl/base/internal/spinlock.h"
43 
44 // Do not use STL.   This module does not use standard memory allocation.
45 
46 namespace absl {
47 ABSL_NAMESPACE_BEGIN
48 namespace synchronization_internal {
49 
50 namespace {
51 
52 // Avoid LowLevelAlloc's default arena since it calls malloc hooks in
53 // which people are doing things like acquiring Mutexes.
54 static absl::base_internal::SpinLock arena_mu(
55     absl::base_internal::kLinkerInitialized);
56 static base_internal::LowLevelAlloc::Arena* arena;
57 
InitArenaIfNecessary()58 static void InitArenaIfNecessary() {
59   arena_mu.Lock();
60   if (arena == nullptr) {
61     arena = base_internal::LowLevelAlloc::NewArena(0);
62   }
63   arena_mu.Unlock();
64 }
65 
66 // Number of inlined elements in Vec.  Hash table implementation
67 // relies on this being a power of two.
68 static const uint32_t kInline = 8;
69 
70 // A simple LowLevelAlloc based resizable vector with inlined storage
71 // for a few elements.  T must be a plain type since constructor
72 // and destructor are not run on elements of type T managed by Vec.
73 template <typename T>
74 class Vec {
75  public:
Vec()76   Vec() { Init(); }
~Vec()77   ~Vec() { Discard(); }
78 
clear()79   void clear() {
80     Discard();
81     Init();
82   }
83 
empty() const84   bool empty() const { return size_ == 0; }
size() const85   uint32_t size() const { return size_; }
begin()86   T* begin() { return ptr_; }
end()87   T* end() { return ptr_ + size_; }
operator [](uint32_t i) const88   const T& operator[](uint32_t i) const { return ptr_[i]; }
operator [](uint32_t i)89   T& operator[](uint32_t i) { return ptr_[i]; }
back() const90   const T& back() const { return ptr_[size_-1]; }
pop_back()91   void pop_back() { size_--; }
92 
push_back(const T & v)93   void push_back(const T& v) {
94     if (size_ == capacity_) Grow(size_ + 1);
95     ptr_[size_] = v;
96     size_++;
97   }
98 
resize(uint32_t n)99   void resize(uint32_t n) {
100     if (n > capacity_) Grow(n);
101     size_ = n;
102   }
103 
fill(const T & val)104   void fill(const T& val) {
105     for (uint32_t i = 0; i < size(); i++) {
106       ptr_[i] = val;
107     }
108   }
109 
110   // Guarantees src is empty at end.
111   // Provided for the hash table resizing code below.
MoveFrom(Vec<T> * src)112   void MoveFrom(Vec<T>* src) {
113     if (src->ptr_ == src->space_) {
114       // Need to actually copy
115       resize(src->size_);
116       std::copy(src->ptr_, src->ptr_ + src->size_, ptr_);
117       src->size_ = 0;
118     } else {
119       Discard();
120       ptr_ = src->ptr_;
121       size_ = src->size_;
122       capacity_ = src->capacity_;
123       src->Init();
124     }
125   }
126 
127  private:
128   T* ptr_;
129   T space_[kInline];
130   uint32_t size_;
131   uint32_t capacity_;
132 
Init()133   void Init() {
134     ptr_ = space_;
135     size_ = 0;
136     capacity_ = kInline;
137   }
138 
Discard()139   void Discard() {
140     if (ptr_ != space_) base_internal::LowLevelAlloc::Free(ptr_);
141   }
142 
Grow(uint32_t n)143   void Grow(uint32_t n) {
144     while (capacity_ < n) {
145       capacity_ *= 2;
146     }
147     size_t request = static_cast<size_t>(capacity_) * sizeof(T);
148     T* copy = static_cast<T*>(
149         base_internal::LowLevelAlloc::AllocWithArena(request, arena));
150     std::copy(ptr_, ptr_ + size_, copy);
151     Discard();
152     ptr_ = copy;
153   }
154 
155   Vec(const Vec&) = delete;
156   Vec& operator=(const Vec&) = delete;
157 };
158 
159 // A hash set of non-negative int32_t that uses Vec for its underlying storage.
160 class NodeSet {
161  public:
NodeSet()162   NodeSet() { Init(); }
163 
clear()164   void clear() { Init(); }
contains(int32_t v) const165   bool contains(int32_t v) const { return table_[FindIndex(v)] == v; }
166 
insert(int32_t v)167   bool insert(int32_t v) {
168     uint32_t i = FindIndex(v);
169     if (table_[i] == v) {
170       return false;
171     }
172     if (table_[i] == kEmpty) {
173       // Only inserting over an empty cell increases the number of occupied
174       // slots.
175       occupied_++;
176     }
177     table_[i] = v;
178     // Double when 75% full.
179     if (occupied_ >= table_.size() - table_.size()/4) Grow();
180     return true;
181   }
182 
erase(uint32_t v)183   void erase(uint32_t v) {
184     uint32_t i = FindIndex(v);
185     if (static_cast<uint32_t>(table_[i]) == v) {
186       table_[i] = kDel;
187     }
188   }
189 
190   // Iteration: is done via HASH_FOR_EACH
191   // Example:
192   //    HASH_FOR_EACH(elem, node->out) { ... }
193 #define HASH_FOR_EACH(elem, eset) \
194   for (int32_t elem, _cursor = 0; (eset).Next(&_cursor, &elem); )
Next(int32_t * cursor,int32_t * elem)195   bool Next(int32_t* cursor, int32_t* elem) {
196     while (static_cast<uint32_t>(*cursor) < table_.size()) {
197       int32_t v = table_[*cursor];
198       (*cursor)++;
199       if (v >= 0) {
200         *elem = v;
201         return true;
202       }
203     }
204     return false;
205   }
206 
207  private:
208   enum : int32_t { kEmpty = -1, kDel = -2 };
209   Vec<int32_t> table_;
210   uint32_t occupied_;     // Count of non-empty slots (includes deleted slots)
211 
Hash(uint32_t a)212   static uint32_t Hash(uint32_t a) { return a * 41; }
213 
214   // Return index for storing v.  May return an empty index or deleted index
FindIndex(int32_t v) const215   int FindIndex(int32_t v) const {
216     // Search starting at hash index.
217     const uint32_t mask = table_.size() - 1;
218     uint32_t i = Hash(v) & mask;
219     int deleted_index = -1;  // If >= 0, index of first deleted element we see
220     while (true) {
221       int32_t e = table_[i];
222       if (v == e) {
223         return i;
224       } else if (e == kEmpty) {
225         // Return any previously encountered deleted slot.
226         return (deleted_index >= 0) ? deleted_index : i;
227       } else if (e == kDel && deleted_index < 0) {
228         // Keep searching since v might be present later.
229         deleted_index = i;
230       }
231       i = (i + 1) & mask;  // Linear probing; quadratic is slightly slower.
232     }
233   }
234 
Init()235   void Init() {
236     table_.clear();
237     table_.resize(kInline);
238     table_.fill(kEmpty);
239     occupied_ = 0;
240   }
241 
Grow()242   void Grow() {
243     Vec<int32_t> copy;
244     copy.MoveFrom(&table_);
245     occupied_ = 0;
246     table_.resize(copy.size() * 2);
247     table_.fill(kEmpty);
248 
249     for (const auto& e : copy) {
250       if (e >= 0) insert(e);
251     }
252   }
253 
254   NodeSet(const NodeSet&) = delete;
255   NodeSet& operator=(const NodeSet&) = delete;
256 };
257 
258 // We encode a node index and a node version in GraphId.  The version
259 // number is incremented when the GraphId is freed which automatically
260 // invalidates all copies of the GraphId.
261 
MakeId(int32_t index,uint32_t version)262 inline GraphId MakeId(int32_t index, uint32_t version) {
263   GraphId g;
264   g.handle =
265       (static_cast<uint64_t>(version) << 32) | static_cast<uint32_t>(index);
266   return g;
267 }
268 
NodeIndex(GraphId id)269 inline int32_t NodeIndex(GraphId id) {
270   return static_cast<uint32_t>(id.handle & 0xfffffffful);
271 }
272 
NodeVersion(GraphId id)273 inline uint32_t NodeVersion(GraphId id) {
274   return static_cast<uint32_t>(id.handle >> 32);
275 }
276 
277 struct Node {
278   int32_t rank;               // rank number assigned by Pearce-Kelly algorithm
279   uint32_t version;           // Current version number
280   int32_t next_hash;          // Next entry in hash table
281   bool visited;               // Temporary marker used by depth-first-search
282   uintptr_t masked_ptr;       // User-supplied pointer
283   NodeSet in;                 // List of immediate predecessor nodes in graph
284   NodeSet out;                // List of immediate successor nodes in graph
285   int priority;               // Priority of recorded stack trace.
286   int nstack;                 // Depth of recorded stack trace.
287   void* stack[40];            // stack[0,nstack-1] holds stack trace for node.
288 };
289 
290 // Hash table for pointer to node index lookups.
291 class PointerMap {
292  public:
PointerMap(const Vec<Node * > * nodes)293   explicit PointerMap(const Vec<Node*>* nodes) : nodes_(nodes) {
294     table_.fill(-1);
295   }
296 
Find(void * ptr)297   int32_t Find(void* ptr) {
298     auto masked = base_internal::HidePtr(ptr);
299     for (int32_t i = table_[Hash(ptr)]; i != -1;) {
300       Node* n = (*nodes_)[i];
301       if (n->masked_ptr == masked) return i;
302       i = n->next_hash;
303     }
304     return -1;
305   }
306 
Add(void * ptr,int32_t i)307   void Add(void* ptr, int32_t i) {
308     int32_t* head = &table_[Hash(ptr)];
309     (*nodes_)[i]->next_hash = *head;
310     *head = i;
311   }
312 
Remove(void * ptr)313   int32_t Remove(void* ptr) {
314     // Advance through linked list while keeping track of the
315     // predecessor slot that points to the current entry.
316     auto masked = base_internal::HidePtr(ptr);
317     for (int32_t* slot = &table_[Hash(ptr)]; *slot != -1; ) {
318       int32_t index = *slot;
319       Node* n = (*nodes_)[index];
320       if (n->masked_ptr == masked) {
321         *slot = n->next_hash;  // Remove n from linked list
322         n->next_hash = -1;
323         return index;
324       }
325       slot = &n->next_hash;
326     }
327     return -1;
328   }
329 
330  private:
331   // Number of buckets in hash table for pointer lookups.
332   static constexpr uint32_t kHashTableSize = 8171;  // should be prime
333 
334   const Vec<Node*>* nodes_;
335   std::array<int32_t, kHashTableSize> table_;
336 
Hash(void * ptr)337   static uint32_t Hash(void* ptr) {
338     return reinterpret_cast<uintptr_t>(ptr) % kHashTableSize;
339   }
340 };
341 
342 }  // namespace
343 
344 struct GraphCycles::Rep {
345   Vec<Node*> nodes_;
346   Vec<int32_t> free_nodes_;  // Indices for unused entries in nodes_
347   PointerMap ptrmap_;
348 
349   // Temporary state.
350   Vec<int32_t> deltaf_;  // Results of forward DFS
351   Vec<int32_t> deltab_;  // Results of backward DFS
352   Vec<int32_t> list_;    // All nodes to reprocess
353   Vec<int32_t> merged_;  // Rank values to assign to list_ entries
354   Vec<int32_t> stack_;   // Emulates recursion stack for depth-first searches
355 
Repabsl::synchronization_internal::GraphCycles::Rep356   Rep() : ptrmap_(&nodes_) {}
357 };
358 
FindNode(GraphCycles::Rep * rep,GraphId id)359 static Node* FindNode(GraphCycles::Rep* rep, GraphId id) {
360   Node* n = rep->nodes_[NodeIndex(id)];
361   return (n->version == NodeVersion(id)) ? n : nullptr;
362 }
363 
GraphCycles()364 GraphCycles::GraphCycles() {
365   InitArenaIfNecessary();
366   rep_ = new (base_internal::LowLevelAlloc::AllocWithArena(sizeof(Rep), arena))
367       Rep;
368 }
369 
~GraphCycles()370 GraphCycles::~GraphCycles() {
371   for (auto* node : rep_->nodes_) {
372     node->Node::~Node();
373     base_internal::LowLevelAlloc::Free(node);
374   }
375   rep_->Rep::~Rep();
376   base_internal::LowLevelAlloc::Free(rep_);
377 }
378 
CheckInvariants() const379 bool GraphCycles::CheckInvariants() const {
380   Rep* r = rep_;
381   NodeSet ranks;  // Set of ranks seen so far.
382   for (uint32_t x = 0; x < r->nodes_.size(); x++) {
383     Node* nx = r->nodes_[x];
384     void* ptr = base_internal::UnhidePtr<void>(nx->masked_ptr);
385     if (ptr != nullptr && static_cast<uint32_t>(r->ptrmap_.Find(ptr)) != x) {
386       ABSL_RAW_LOG(FATAL, "Did not find live node in hash table %u %p", x, ptr);
387     }
388     if (nx->visited) {
389       ABSL_RAW_LOG(FATAL, "Did not clear visited marker on node %u", x);
390     }
391     if (!ranks.insert(nx->rank)) {
392       ABSL_RAW_LOG(FATAL, "Duplicate occurrence of rank %d", nx->rank);
393     }
394     HASH_FOR_EACH(y, nx->out) {
395       Node* ny = r->nodes_[y];
396       if (nx->rank >= ny->rank) {
397         ABSL_RAW_LOG(FATAL, "Edge %u->%d has bad rank assignment %d->%d", x, y,
398                      nx->rank, ny->rank);
399       }
400     }
401   }
402   return true;
403 }
404 
GetId(void * ptr)405 GraphId GraphCycles::GetId(void* ptr) {
406   int32_t i = rep_->ptrmap_.Find(ptr);
407   if (i != -1) {
408     return MakeId(i, rep_->nodes_[i]->version);
409   } else if (rep_->free_nodes_.empty()) {
410     Node* n =
411         new (base_internal::LowLevelAlloc::AllocWithArena(sizeof(Node), arena))
412             Node;
413     n->version = 1;  // Avoid 0 since it is used by InvalidGraphId()
414     n->visited = false;
415     n->rank = rep_->nodes_.size();
416     n->masked_ptr = base_internal::HidePtr(ptr);
417     n->nstack = 0;
418     n->priority = 0;
419     rep_->nodes_.push_back(n);
420     rep_->ptrmap_.Add(ptr, n->rank);
421     return MakeId(n->rank, n->version);
422   } else {
423     // Preserve preceding rank since the set of ranks in use must be
424     // a permutation of [0,rep_->nodes_.size()-1].
425     int32_t r = rep_->free_nodes_.back();
426     rep_->free_nodes_.pop_back();
427     Node* n = rep_->nodes_[r];
428     n->masked_ptr = base_internal::HidePtr(ptr);
429     n->nstack = 0;
430     n->priority = 0;
431     rep_->ptrmap_.Add(ptr, r);
432     return MakeId(r, n->version);
433   }
434 }
435 
RemoveNode(void * ptr)436 void GraphCycles::RemoveNode(void* ptr) {
437   int32_t i = rep_->ptrmap_.Remove(ptr);
438   if (i == -1) {
439     return;
440   }
441   Node* x = rep_->nodes_[i];
442   HASH_FOR_EACH(y, x->out) {
443     rep_->nodes_[y]->in.erase(i);
444   }
445   HASH_FOR_EACH(y, x->in) {
446     rep_->nodes_[y]->out.erase(i);
447   }
448   x->in.clear();
449   x->out.clear();
450   x->masked_ptr = base_internal::HidePtr<void>(nullptr);
451   if (x->version == std::numeric_limits<uint32_t>::max()) {
452     // Cannot use x any more
453   } else {
454     x->version++;  // Invalidates all copies of node.
455     rep_->free_nodes_.push_back(i);
456   }
457 }
458 
Ptr(GraphId id)459 void* GraphCycles::Ptr(GraphId id) {
460   Node* n = FindNode(rep_, id);
461   return n == nullptr ? nullptr
462                       : base_internal::UnhidePtr<void>(n->masked_ptr);
463 }
464 
HasNode(GraphId node)465 bool GraphCycles::HasNode(GraphId node) {
466   return FindNode(rep_, node) != nullptr;
467 }
468 
HasEdge(GraphId x,GraphId y) const469 bool GraphCycles::HasEdge(GraphId x, GraphId y) const {
470   Node* xn = FindNode(rep_, x);
471   return xn && FindNode(rep_, y) && xn->out.contains(NodeIndex(y));
472 }
473 
RemoveEdge(GraphId x,GraphId y)474 void GraphCycles::RemoveEdge(GraphId x, GraphId y) {
475   Node* xn = FindNode(rep_, x);
476   Node* yn = FindNode(rep_, y);
477   if (xn && yn) {
478     xn->out.erase(NodeIndex(y));
479     yn->in.erase(NodeIndex(x));
480     // No need to update the rank assignment since a previous valid
481     // rank assignment remains valid after an edge deletion.
482   }
483 }
484 
485 static bool ForwardDFS(GraphCycles::Rep* r, int32_t n, int32_t upper_bound);
486 static void BackwardDFS(GraphCycles::Rep* r, int32_t n, int32_t lower_bound);
487 static void Reorder(GraphCycles::Rep* r);
488 static void Sort(const Vec<Node*>&, Vec<int32_t>* delta);
489 static void MoveToList(
490     GraphCycles::Rep* r, Vec<int32_t>* src, Vec<int32_t>* dst);
491 
InsertEdge(GraphId idx,GraphId idy)492 bool GraphCycles::InsertEdge(GraphId idx, GraphId idy) {
493   Rep* r = rep_;
494   const int32_t x = NodeIndex(idx);
495   const int32_t y = NodeIndex(idy);
496   Node* nx = FindNode(r, idx);
497   Node* ny = FindNode(r, idy);
498   if (nx == nullptr || ny == nullptr) return true;  // Expired ids
499 
500   if (nx == ny) return false;  // Self edge
501   if (!nx->out.insert(y)) {
502     // Edge already exists.
503     return true;
504   }
505 
506   ny->in.insert(x);
507 
508   if (nx->rank <= ny->rank) {
509     // New edge is consistent with existing rank assignment.
510     return true;
511   }
512 
513   // Current rank assignments are incompatible with the new edge.  Recompute.
514   // We only need to consider nodes that fall in the range [ny->rank,nx->rank].
515   if (!ForwardDFS(r, y, nx->rank)) {
516     // Found a cycle.  Undo the insertion and tell caller.
517     nx->out.erase(y);
518     ny->in.erase(x);
519     // Since we do not call Reorder() on this path, clear any visited
520     // markers left by ForwardDFS.
521     for (const auto& d : r->deltaf_) {
522       r->nodes_[d]->visited = false;
523     }
524     return false;
525   }
526   BackwardDFS(r, x, ny->rank);
527   Reorder(r);
528   return true;
529 }
530 
ForwardDFS(GraphCycles::Rep * r,int32_t n,int32_t upper_bound)531 static bool ForwardDFS(GraphCycles::Rep* r, int32_t n, int32_t upper_bound) {
532   // Avoid recursion since stack space might be limited.
533   // We instead keep a stack of nodes to visit.
534   r->deltaf_.clear();
535   r->stack_.clear();
536   r->stack_.push_back(n);
537   while (!r->stack_.empty()) {
538     n = r->stack_.back();
539     r->stack_.pop_back();
540     Node* nn = r->nodes_[n];
541     if (nn->visited) continue;
542 
543     nn->visited = true;
544     r->deltaf_.push_back(n);
545 
546     HASH_FOR_EACH(w, nn->out) {
547       Node* nw = r->nodes_[w];
548       if (nw->rank == upper_bound) {
549         return false;  // Cycle
550       }
551       if (!nw->visited && nw->rank < upper_bound) {
552         r->stack_.push_back(w);
553       }
554     }
555   }
556   return true;
557 }
558 
BackwardDFS(GraphCycles::Rep * r,int32_t n,int32_t lower_bound)559 static void BackwardDFS(GraphCycles::Rep* r, int32_t n, int32_t lower_bound) {
560   r->deltab_.clear();
561   r->stack_.clear();
562   r->stack_.push_back(n);
563   while (!r->stack_.empty()) {
564     n = r->stack_.back();
565     r->stack_.pop_back();
566     Node* nn = r->nodes_[n];
567     if (nn->visited) continue;
568 
569     nn->visited = true;
570     r->deltab_.push_back(n);
571 
572     HASH_FOR_EACH(w, nn->in) {
573       Node* nw = r->nodes_[w];
574       if (!nw->visited && lower_bound < nw->rank) {
575         r->stack_.push_back(w);
576       }
577     }
578   }
579 }
580 
Reorder(GraphCycles::Rep * r)581 static void Reorder(GraphCycles::Rep* r) {
582   Sort(r->nodes_, &r->deltab_);
583   Sort(r->nodes_, &r->deltaf_);
584 
585   // Adds contents of delta lists to list_ (backwards deltas first).
586   r->list_.clear();
587   MoveToList(r, &r->deltab_, &r->list_);
588   MoveToList(r, &r->deltaf_, &r->list_);
589 
590   // Produce sorted list of all ranks that will be reassigned.
591   r->merged_.resize(r->deltab_.size() + r->deltaf_.size());
592   std::merge(r->deltab_.begin(), r->deltab_.end(),
593              r->deltaf_.begin(), r->deltaf_.end(),
594              r->merged_.begin());
595 
596   // Assign the ranks in order to the collected list.
597   for (uint32_t i = 0; i < r->list_.size(); i++) {
598     r->nodes_[r->list_[i]]->rank = r->merged_[i];
599   }
600 }
601 
Sort(const Vec<Node * > & nodes,Vec<int32_t> * delta)602 static void Sort(const Vec<Node*>& nodes, Vec<int32_t>* delta) {
603   struct ByRank {
604     const Vec<Node*>* nodes;
605     bool operator()(int32_t a, int32_t b) const {
606       return (*nodes)[a]->rank < (*nodes)[b]->rank;
607     }
608   };
609   ByRank cmp;
610   cmp.nodes = &nodes;
611   std::sort(delta->begin(), delta->end(), cmp);
612 }
613 
MoveToList(GraphCycles::Rep * r,Vec<int32_t> * src,Vec<int32_t> * dst)614 static void MoveToList(
615     GraphCycles::Rep* r, Vec<int32_t>* src, Vec<int32_t>* dst) {
616   for (auto& v : *src) {
617     int32_t w = v;
618     v = r->nodes_[w]->rank;         // Replace v entry with its rank
619     r->nodes_[w]->visited = false;  // Prepare for future DFS calls
620     dst->push_back(w);
621   }
622 }
623 
FindPath(GraphId idx,GraphId idy,int max_path_len,GraphId path[]) const624 int GraphCycles::FindPath(GraphId idx, GraphId idy, int max_path_len,
625                           GraphId path[]) const {
626   Rep* r = rep_;
627   if (FindNode(r, idx) == nullptr || FindNode(r, idy) == nullptr) return 0;
628   const int32_t x = NodeIndex(idx);
629   const int32_t y = NodeIndex(idy);
630 
631   // Forward depth first search starting at x until we hit y.
632   // As we descend into a node, we push it onto the path.
633   // As we leave a node, we remove it from the path.
634   int path_len = 0;
635 
636   NodeSet seen;
637   r->stack_.clear();
638   r->stack_.push_back(x);
639   while (!r->stack_.empty()) {
640     int32_t n = r->stack_.back();
641     r->stack_.pop_back();
642     if (n < 0) {
643       // Marker to indicate that we are leaving a node
644       path_len--;
645       continue;
646     }
647 
648     if (path_len < max_path_len) {
649       path[path_len] = MakeId(n, rep_->nodes_[n]->version);
650     }
651     path_len++;
652     r->stack_.push_back(-1);  // Will remove tentative path entry
653 
654     if (n == y) {
655       return path_len;
656     }
657 
658     HASH_FOR_EACH(w, r->nodes_[n]->out) {
659       if (seen.insert(w)) {
660         r->stack_.push_back(w);
661       }
662     }
663   }
664 
665   return 0;
666 }
667 
IsReachable(GraphId x,GraphId y) const668 bool GraphCycles::IsReachable(GraphId x, GraphId y) const {
669   return FindPath(x, y, 0, nullptr) > 0;
670 }
671 
UpdateStackTrace(GraphId id,int priority,int (* get_stack_trace)(void ** stack,int))672 void GraphCycles::UpdateStackTrace(GraphId id, int priority,
673                                    int (*get_stack_trace)(void** stack, int)) {
674   Node* n = FindNode(rep_, id);
675   if (n == nullptr || n->priority >= priority) {
676     return;
677   }
678   n->nstack = (*get_stack_trace)(n->stack, ABSL_ARRAYSIZE(n->stack));
679   n->priority = priority;
680 }
681 
GetStackTrace(GraphId id,void *** ptr)682 int GraphCycles::GetStackTrace(GraphId id, void*** ptr) {
683   Node* n = FindNode(rep_, id);
684   if (n == nullptr) {
685     *ptr = nullptr;
686     return 0;
687   } else {
688     *ptr = n->stack;
689     return n->nstack;
690   }
691 }
692 
693 }  // namespace synchronization_internal
694 ABSL_NAMESPACE_END
695 }  // namespace absl
696 
697 #endif  // ABSL_LOW_LEVEL_ALLOC_MISSING
698