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