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