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