... notably, this includes Abseil's own StatusOr type, which conflicted with our implementation (that was taken from TensorFlow). Change-Id: Ie7d6764b64055caaeb8dc7b6b9d066291e6b538f
		
			
				
	
	
		
			698 lines
		
	
	
	
		
			20 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			698 lines
		
	
	
	
		
			20 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // Copyright 2017 The Abseil Authors.
 | |
| //
 | |
| // Licensed under the Apache License, Version 2.0 (the "License");
 | |
| // you may not use this file except in compliance with the License.
 | |
| // You may obtain a copy of the License at
 | |
| //
 | |
| //      https://www.apache.org/licenses/LICENSE-2.0
 | |
| //
 | |
| // Unless required by applicable law or agreed to in writing, software
 | |
| // distributed under the License is distributed on an "AS IS" BASIS,
 | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| // See the License for the specific language governing permissions and
 | |
| // limitations under the License.
 | |
| 
 | |
| // GraphCycles provides incremental cycle detection on a dynamic
 | |
| // graph using the following algorithm:
 | |
| //
 | |
| // A dynamic topological sort algorithm for directed acyclic graphs
 | |
| // David J. Pearce, Paul H. J. Kelly
 | |
| // Journal of Experimental Algorithmics (JEA) JEA Homepage archive
 | |
| // Volume 11, 2006, Article No. 1.7
 | |
| //
 | |
| // Brief summary of the algorithm:
 | |
| //
 | |
| // (1) Maintain a rank for each node that is consistent
 | |
| //     with the topological sort of the graph. I.e., path from x to y
 | |
| //     implies rank[x] < rank[y].
 | |
| // (2) When a new edge (x->y) is inserted, do nothing if rank[x] < rank[y].
 | |
| // (3) Otherwise: adjust ranks in the neighborhood of x and y.
 | |
| 
 | |
| #include "absl/base/attributes.h"
 | |
| // This file is a no-op if the required LowLevelAlloc support is missing.
 | |
| #include "absl/base/internal/low_level_alloc.h"
 | |
| #ifndef ABSL_LOW_LEVEL_ALLOC_MISSING
 | |
| 
 | |
| #include "absl/synchronization/internal/graphcycles.h"
 | |
| 
 | |
| #include <algorithm>
 | |
| #include <array>
 | |
| #include <limits>
 | |
| #include "absl/base/internal/hide_ptr.h"
 | |
| #include "absl/base/internal/raw_logging.h"
 | |
| #include "absl/base/internal/spinlock.h"
 | |
| 
 | |
| // Do not use STL.   This module does not use standard memory allocation.
 | |
| 
 | |
| namespace absl {
 | |
| ABSL_NAMESPACE_BEGIN
 | |
| namespace synchronization_internal {
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| // Avoid LowLevelAlloc's default arena since it calls malloc hooks in
 | |
| // which people are doing things like acquiring Mutexes.
 | |
| ABSL_CONST_INIT static absl::base_internal::SpinLock arena_mu(
 | |
|     absl::kConstInit, base_internal::SCHEDULE_KERNEL_ONLY);
 | |
| ABSL_CONST_INIT static base_internal::LowLevelAlloc::Arena* arena;
 | |
| 
 | |
| static void InitArenaIfNecessary() {
 | |
|   arena_mu.Lock();
 | |
|   if (arena == nullptr) {
 | |
|     arena = base_internal::LowLevelAlloc::NewArena(0);
 | |
|   }
 | |
|   arena_mu.Unlock();
 | |
| }
 | |
| 
 | |
| // Number of inlined elements in Vec.  Hash table implementation
 | |
| // relies on this being a power of two.
 | |
| static const uint32_t kInline = 8;
 | |
| 
 | |
| // A simple LowLevelAlloc based resizable vector with inlined storage
 | |
| // for a few elements.  T must be a plain type since constructor
 | |
| // and destructor are not run on elements of type T managed by Vec.
 | |
| template <typename T>
 | |
| class Vec {
 | |
|  public:
 | |
|   Vec() { Init(); }
 | |
|   ~Vec() { Discard(); }
 | |
| 
 | |
|   void clear() {
 | |
|     Discard();
 | |
|     Init();
 | |
|   }
 | |
| 
 | |
|   bool empty() const { return size_ == 0; }
 | |
|   uint32_t size() const { return size_; }
 | |
|   T* begin() { return ptr_; }
 | |
|   T* end() { return ptr_ + size_; }
 | |
|   const T& operator[](uint32_t i) const { return ptr_[i]; }
 | |
|   T& operator[](uint32_t i) { return ptr_[i]; }
 | |
|   const T& back() const { return ptr_[size_-1]; }
 | |
|   void pop_back() { size_--; }
 | |
| 
 | |
|   void push_back(const T& v) {
 | |
|     if (size_ == capacity_) Grow(size_ + 1);
 | |
|     ptr_[size_] = v;
 | |
|     size_++;
 | |
|   }
 | |
| 
 | |
|   void resize(uint32_t n) {
 | |
|     if (n > capacity_) Grow(n);
 | |
|     size_ = n;
 | |
|   }
 | |
| 
 | |
|   void fill(const T& val) {
 | |
|     for (uint32_t i = 0; i < size(); i++) {
 | |
|       ptr_[i] = val;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Guarantees src is empty at end.
 | |
|   // Provided for the hash table resizing code below.
 | |
|   void MoveFrom(Vec<T>* src) {
 | |
|     if (src->ptr_ == src->space_) {
 | |
|       // Need to actually copy
 | |
|       resize(src->size_);
 | |
|       std::copy(src->ptr_, src->ptr_ + src->size_, ptr_);
 | |
|       src->size_ = 0;
 | |
|     } else {
 | |
|       Discard();
 | |
|       ptr_ = src->ptr_;
 | |
|       size_ = src->size_;
 | |
|       capacity_ = src->capacity_;
 | |
|       src->Init();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   T* ptr_;
 | |
|   T space_[kInline];
 | |
|   uint32_t size_;
 | |
|   uint32_t capacity_;
 | |
| 
 | |
|   void Init() {
 | |
|     ptr_ = space_;
 | |
|     size_ = 0;
 | |
|     capacity_ = kInline;
 | |
|   }
 | |
| 
 | |
|   void Discard() {
 | |
|     if (ptr_ != space_) base_internal::LowLevelAlloc::Free(ptr_);
 | |
|   }
 | |
| 
 | |
|   void Grow(uint32_t n) {
 | |
|     while (capacity_ < n) {
 | |
|       capacity_ *= 2;
 | |
|     }
 | |
|     size_t request = static_cast<size_t>(capacity_) * sizeof(T);
 | |
|     T* copy = static_cast<T*>(
 | |
|         base_internal::LowLevelAlloc::AllocWithArena(request, arena));
 | |
|     std::copy(ptr_, ptr_ + size_, copy);
 | |
|     Discard();
 | |
|     ptr_ = copy;
 | |
|   }
 | |
| 
 | |
|   Vec(const Vec&) = delete;
 | |
|   Vec& operator=(const Vec&) = delete;
 | |
| };
 | |
| 
 | |
| // A hash set of non-negative int32_t that uses Vec for its underlying storage.
 | |
| class NodeSet {
 | |
|  public:
 | |
|   NodeSet() { Init(); }
 | |
| 
 | |
|   void clear() { Init(); }
 | |
|   bool contains(int32_t v) const { return table_[FindIndex(v)] == v; }
 | |
| 
 | |
|   bool insert(int32_t v) {
 | |
|     uint32_t i = FindIndex(v);
 | |
|     if (table_[i] == v) {
 | |
|       return false;
 | |
|     }
 | |
|     if (table_[i] == kEmpty) {
 | |
|       // Only inserting over an empty cell increases the number of occupied
 | |
|       // slots.
 | |
|       occupied_++;
 | |
|     }
 | |
|     table_[i] = v;
 | |
|     // Double when 75% full.
 | |
|     if (occupied_ >= table_.size() - table_.size()/4) Grow();
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   void erase(uint32_t v) {
 | |
|     uint32_t i = FindIndex(v);
 | |
|     if (static_cast<uint32_t>(table_[i]) == v) {
 | |
|       table_[i] = kDel;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Iteration: is done via HASH_FOR_EACH
 | |
|   // Example:
 | |
|   //    HASH_FOR_EACH(elem, node->out) { ... }
 | |
| #define HASH_FOR_EACH(elem, eset) \
 | |
|   for (int32_t elem, _cursor = 0; (eset).Next(&_cursor, &elem); )
 | |
|   bool Next(int32_t* cursor, int32_t* elem) {
 | |
|     while (static_cast<uint32_t>(*cursor) < table_.size()) {
 | |
|       int32_t v = table_[*cursor];
 | |
|       (*cursor)++;
 | |
|       if (v >= 0) {
 | |
|         *elem = v;
 | |
|         return true;
 | |
|       }
 | |
|     }
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   enum : int32_t { kEmpty = -1, kDel = -2 };
 | |
|   Vec<int32_t> table_;
 | |
|   uint32_t occupied_;     // Count of non-empty slots (includes deleted slots)
 | |
| 
 | |
|   static uint32_t Hash(uint32_t a) { return a * 41; }
 | |
| 
 | |
|   // Return index for storing v.  May return an empty index or deleted index
 | |
|   int FindIndex(int32_t v) const {
 | |
|     // Search starting at hash index.
 | |
|     const uint32_t mask = table_.size() - 1;
 | |
|     uint32_t i = Hash(v) & mask;
 | |
|     int deleted_index = -1;  // If >= 0, index of first deleted element we see
 | |
|     while (true) {
 | |
|       int32_t e = table_[i];
 | |
|       if (v == e) {
 | |
|         return i;
 | |
|       } else if (e == kEmpty) {
 | |
|         // Return any previously encountered deleted slot.
 | |
|         return (deleted_index >= 0) ? deleted_index : i;
 | |
|       } else if (e == kDel && deleted_index < 0) {
 | |
|         // Keep searching since v might be present later.
 | |
|         deleted_index = i;
 | |
|       }
 | |
|       i = (i + 1) & mask;  // Linear probing; quadratic is slightly slower.
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   void Init() {
 | |
|     table_.clear();
 | |
|     table_.resize(kInline);
 | |
|     table_.fill(kEmpty);
 | |
|     occupied_ = 0;
 | |
|   }
 | |
| 
 | |
|   void Grow() {
 | |
|     Vec<int32_t> copy;
 | |
|     copy.MoveFrom(&table_);
 | |
|     occupied_ = 0;
 | |
|     table_.resize(copy.size() * 2);
 | |
|     table_.fill(kEmpty);
 | |
| 
 | |
|     for (const auto& e : copy) {
 | |
|       if (e >= 0) insert(e);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   NodeSet(const NodeSet&) = delete;
 | |
|   NodeSet& operator=(const NodeSet&) = delete;
 | |
| };
 | |
| 
 | |
| // We encode a node index and a node version in GraphId.  The version
 | |
| // number is incremented when the GraphId is freed which automatically
 | |
| // invalidates all copies of the GraphId.
 | |
| 
 | |
| inline GraphId MakeId(int32_t index, uint32_t version) {
 | |
|   GraphId g;
 | |
|   g.handle =
 | |
|       (static_cast<uint64_t>(version) << 32) | static_cast<uint32_t>(index);
 | |
|   return g;
 | |
| }
 | |
| 
 | |
| inline int32_t NodeIndex(GraphId id) {
 | |
|   return static_cast<uint32_t>(id.handle & 0xfffffffful);
 | |
| }
 | |
| 
 | |
| inline uint32_t NodeVersion(GraphId id) {
 | |
|   return static_cast<uint32_t>(id.handle >> 32);
 | |
| }
 | |
| 
 | |
| struct Node {
 | |
|   int32_t rank;               // rank number assigned by Pearce-Kelly algorithm
 | |
|   uint32_t version;           // Current version number
 | |
|   int32_t next_hash;          // Next entry in hash table
 | |
|   bool visited;               // Temporary marker used by depth-first-search
 | |
|   uintptr_t masked_ptr;       // User-supplied pointer
 | |
|   NodeSet in;                 // List of immediate predecessor nodes in graph
 | |
|   NodeSet out;                // List of immediate successor nodes in graph
 | |
|   int priority;               // Priority of recorded stack trace.
 | |
|   int nstack;                 // Depth of recorded stack trace.
 | |
|   void* stack[40];            // stack[0,nstack-1] holds stack trace for node.
 | |
| };
 | |
| 
 | |
| // Hash table for pointer to node index lookups.
 | |
| class PointerMap {
 | |
|  public:
 | |
|   explicit PointerMap(const Vec<Node*>* nodes) : nodes_(nodes) {
 | |
|     table_.fill(-1);
 | |
|   }
 | |
| 
 | |
|   int32_t Find(void* ptr) {
 | |
|     auto masked = base_internal::HidePtr(ptr);
 | |
|     for (int32_t i = table_[Hash(ptr)]; i != -1;) {
 | |
|       Node* n = (*nodes_)[i];
 | |
|       if (n->masked_ptr == masked) return i;
 | |
|       i = n->next_hash;
 | |
|     }
 | |
|     return -1;
 | |
|   }
 | |
| 
 | |
|   void Add(void* ptr, int32_t i) {
 | |
|     int32_t* head = &table_[Hash(ptr)];
 | |
|     (*nodes_)[i]->next_hash = *head;
 | |
|     *head = i;
 | |
|   }
 | |
| 
 | |
|   int32_t Remove(void* ptr) {
 | |
|     // Advance through linked list while keeping track of the
 | |
|     // predecessor slot that points to the current entry.
 | |
|     auto masked = base_internal::HidePtr(ptr);
 | |
|     for (int32_t* slot = &table_[Hash(ptr)]; *slot != -1; ) {
 | |
|       int32_t index = *slot;
 | |
|       Node* n = (*nodes_)[index];
 | |
|       if (n->masked_ptr == masked) {
 | |
|         *slot = n->next_hash;  // Remove n from linked list
 | |
|         n->next_hash = -1;
 | |
|         return index;
 | |
|       }
 | |
|       slot = &n->next_hash;
 | |
|     }
 | |
|     return -1;
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   // Number of buckets in hash table for pointer lookups.
 | |
|   static constexpr uint32_t kHashTableSize = 8171;  // should be prime
 | |
| 
 | |
|   const Vec<Node*>* nodes_;
 | |
|   std::array<int32_t, kHashTableSize> table_;
 | |
| 
 | |
|   static uint32_t Hash(void* ptr) {
 | |
|     return reinterpret_cast<uintptr_t>(ptr) % kHashTableSize;
 | |
|   }
 | |
| };
 | |
| 
 | |
| }  // namespace
 | |
| 
 | |
| struct GraphCycles::Rep {
 | |
|   Vec<Node*> nodes_;
 | |
|   Vec<int32_t> free_nodes_;  // Indices for unused entries in nodes_
 | |
|   PointerMap ptrmap_;
 | |
| 
 | |
|   // Temporary state.
 | |
|   Vec<int32_t> deltaf_;  // Results of forward DFS
 | |
|   Vec<int32_t> deltab_;  // Results of backward DFS
 | |
|   Vec<int32_t> list_;    // All nodes to reprocess
 | |
|   Vec<int32_t> merged_;  // Rank values to assign to list_ entries
 | |
|   Vec<int32_t> stack_;   // Emulates recursion stack for depth-first searches
 | |
| 
 | |
|   Rep() : ptrmap_(&nodes_) {}
 | |
| };
 | |
| 
 | |
| static Node* FindNode(GraphCycles::Rep* rep, GraphId id) {
 | |
|   Node* n = rep->nodes_[NodeIndex(id)];
 | |
|   return (n->version == NodeVersion(id)) ? n : nullptr;
 | |
| }
 | |
| 
 | |
| GraphCycles::GraphCycles() {
 | |
|   InitArenaIfNecessary();
 | |
|   rep_ = new (base_internal::LowLevelAlloc::AllocWithArena(sizeof(Rep), arena))
 | |
|       Rep;
 | |
| }
 | |
| 
 | |
| GraphCycles::~GraphCycles() {
 | |
|   for (auto* node : rep_->nodes_) {
 | |
|     node->Node::~Node();
 | |
|     base_internal::LowLevelAlloc::Free(node);
 | |
|   }
 | |
|   rep_->Rep::~Rep();
 | |
|   base_internal::LowLevelAlloc::Free(rep_);
 | |
| }
 | |
| 
 | |
| bool GraphCycles::CheckInvariants() const {
 | |
|   Rep* r = rep_;
 | |
|   NodeSet ranks;  // Set of ranks seen so far.
 | |
|   for (uint32_t x = 0; x < r->nodes_.size(); x++) {
 | |
|     Node* nx = r->nodes_[x];
 | |
|     void* ptr = base_internal::UnhidePtr<void>(nx->masked_ptr);
 | |
|     if (ptr != nullptr && static_cast<uint32_t>(r->ptrmap_.Find(ptr)) != x) {
 | |
|       ABSL_RAW_LOG(FATAL, "Did not find live node in hash table %u %p", x, ptr);
 | |
|     }
 | |
|     if (nx->visited) {
 | |
|       ABSL_RAW_LOG(FATAL, "Did not clear visited marker on node %u", x);
 | |
|     }
 | |
|     if (!ranks.insert(nx->rank)) {
 | |
|       ABSL_RAW_LOG(FATAL, "Duplicate occurrence of rank %d", nx->rank);
 | |
|     }
 | |
|     HASH_FOR_EACH(y, nx->out) {
 | |
|       Node* ny = r->nodes_[y];
 | |
|       if (nx->rank >= ny->rank) {
 | |
|         ABSL_RAW_LOG(FATAL, "Edge %u->%d has bad rank assignment %d->%d", x, y,
 | |
|                      nx->rank, ny->rank);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| GraphId GraphCycles::GetId(void* ptr) {
 | |
|   int32_t i = rep_->ptrmap_.Find(ptr);
 | |
|   if (i != -1) {
 | |
|     return MakeId(i, rep_->nodes_[i]->version);
 | |
|   } else if (rep_->free_nodes_.empty()) {
 | |
|     Node* n =
 | |
|         new (base_internal::LowLevelAlloc::AllocWithArena(sizeof(Node), arena))
 | |
|             Node;
 | |
|     n->version = 1;  // Avoid 0 since it is used by InvalidGraphId()
 | |
|     n->visited = false;
 | |
|     n->rank = rep_->nodes_.size();
 | |
|     n->masked_ptr = base_internal::HidePtr(ptr);
 | |
|     n->nstack = 0;
 | |
|     n->priority = 0;
 | |
|     rep_->nodes_.push_back(n);
 | |
|     rep_->ptrmap_.Add(ptr, n->rank);
 | |
|     return MakeId(n->rank, n->version);
 | |
|   } else {
 | |
|     // Preserve preceding rank since the set of ranks in use must be
 | |
|     // a permutation of [0,rep_->nodes_.size()-1].
 | |
|     int32_t r = rep_->free_nodes_.back();
 | |
|     rep_->free_nodes_.pop_back();
 | |
|     Node* n = rep_->nodes_[r];
 | |
|     n->masked_ptr = base_internal::HidePtr(ptr);
 | |
|     n->nstack = 0;
 | |
|     n->priority = 0;
 | |
|     rep_->ptrmap_.Add(ptr, r);
 | |
|     return MakeId(r, n->version);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void GraphCycles::RemoveNode(void* ptr) {
 | |
|   int32_t i = rep_->ptrmap_.Remove(ptr);
 | |
|   if (i == -1) {
 | |
|     return;
 | |
|   }
 | |
|   Node* x = rep_->nodes_[i];
 | |
|   HASH_FOR_EACH(y, x->out) {
 | |
|     rep_->nodes_[y]->in.erase(i);
 | |
|   }
 | |
|   HASH_FOR_EACH(y, x->in) {
 | |
|     rep_->nodes_[y]->out.erase(i);
 | |
|   }
 | |
|   x->in.clear();
 | |
|   x->out.clear();
 | |
|   x->masked_ptr = base_internal::HidePtr<void>(nullptr);
 | |
|   if (x->version == std::numeric_limits<uint32_t>::max()) {
 | |
|     // Cannot use x any more
 | |
|   } else {
 | |
|     x->version++;  // Invalidates all copies of node.
 | |
|     rep_->free_nodes_.push_back(i);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void* GraphCycles::Ptr(GraphId id) {
 | |
|   Node* n = FindNode(rep_, id);
 | |
|   return n == nullptr ? nullptr
 | |
|                       : base_internal::UnhidePtr<void>(n->masked_ptr);
 | |
| }
 | |
| 
 | |
| bool GraphCycles::HasNode(GraphId node) {
 | |
|   return FindNode(rep_, node) != nullptr;
 | |
| }
 | |
| 
 | |
| bool GraphCycles::HasEdge(GraphId x, GraphId y) const {
 | |
|   Node* xn = FindNode(rep_, x);
 | |
|   return xn && FindNode(rep_, y) && xn->out.contains(NodeIndex(y));
 | |
| }
 | |
| 
 | |
| void GraphCycles::RemoveEdge(GraphId x, GraphId y) {
 | |
|   Node* xn = FindNode(rep_, x);
 | |
|   Node* yn = FindNode(rep_, y);
 | |
|   if (xn && yn) {
 | |
|     xn->out.erase(NodeIndex(y));
 | |
|     yn->in.erase(NodeIndex(x));
 | |
|     // No need to update the rank assignment since a previous valid
 | |
|     // rank assignment remains valid after an edge deletion.
 | |
|   }
 | |
| }
 | |
| 
 | |
| static bool ForwardDFS(GraphCycles::Rep* r, int32_t n, int32_t upper_bound);
 | |
| static void BackwardDFS(GraphCycles::Rep* r, int32_t n, int32_t lower_bound);
 | |
| static void Reorder(GraphCycles::Rep* r);
 | |
| static void Sort(const Vec<Node*>&, Vec<int32_t>* delta);
 | |
| static void MoveToList(
 | |
|     GraphCycles::Rep* r, Vec<int32_t>* src, Vec<int32_t>* dst);
 | |
| 
 | |
| bool GraphCycles::InsertEdge(GraphId idx, GraphId idy) {
 | |
|   Rep* r = rep_;
 | |
|   const int32_t x = NodeIndex(idx);
 | |
|   const int32_t y = NodeIndex(idy);
 | |
|   Node* nx = FindNode(r, idx);
 | |
|   Node* ny = FindNode(r, idy);
 | |
|   if (nx == nullptr || ny == nullptr) return true;  // Expired ids
 | |
| 
 | |
|   if (nx == ny) return false;  // Self edge
 | |
|   if (!nx->out.insert(y)) {
 | |
|     // Edge already exists.
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   ny->in.insert(x);
 | |
| 
 | |
|   if (nx->rank <= ny->rank) {
 | |
|     // New edge is consistent with existing rank assignment.
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // Current rank assignments are incompatible with the new edge.  Recompute.
 | |
|   // We only need to consider nodes that fall in the range [ny->rank,nx->rank].
 | |
|   if (!ForwardDFS(r, y, nx->rank)) {
 | |
|     // Found a cycle.  Undo the insertion and tell caller.
 | |
|     nx->out.erase(y);
 | |
|     ny->in.erase(x);
 | |
|     // Since we do not call Reorder() on this path, clear any visited
 | |
|     // markers left by ForwardDFS.
 | |
|     for (const auto& d : r->deltaf_) {
 | |
|       r->nodes_[d]->visited = false;
 | |
|     }
 | |
|     return false;
 | |
|   }
 | |
|   BackwardDFS(r, x, ny->rank);
 | |
|   Reorder(r);
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| static bool ForwardDFS(GraphCycles::Rep* r, int32_t n, int32_t upper_bound) {
 | |
|   // Avoid recursion since stack space might be limited.
 | |
|   // We instead keep a stack of nodes to visit.
 | |
|   r->deltaf_.clear();
 | |
|   r->stack_.clear();
 | |
|   r->stack_.push_back(n);
 | |
|   while (!r->stack_.empty()) {
 | |
|     n = r->stack_.back();
 | |
|     r->stack_.pop_back();
 | |
|     Node* nn = r->nodes_[n];
 | |
|     if (nn->visited) continue;
 | |
| 
 | |
|     nn->visited = true;
 | |
|     r->deltaf_.push_back(n);
 | |
| 
 | |
|     HASH_FOR_EACH(w, nn->out) {
 | |
|       Node* nw = r->nodes_[w];
 | |
|       if (nw->rank == upper_bound) {
 | |
|         return false;  // Cycle
 | |
|       }
 | |
|       if (!nw->visited && nw->rank < upper_bound) {
 | |
|         r->stack_.push_back(w);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| static void BackwardDFS(GraphCycles::Rep* r, int32_t n, int32_t lower_bound) {
 | |
|   r->deltab_.clear();
 | |
|   r->stack_.clear();
 | |
|   r->stack_.push_back(n);
 | |
|   while (!r->stack_.empty()) {
 | |
|     n = r->stack_.back();
 | |
|     r->stack_.pop_back();
 | |
|     Node* nn = r->nodes_[n];
 | |
|     if (nn->visited) continue;
 | |
| 
 | |
|     nn->visited = true;
 | |
|     r->deltab_.push_back(n);
 | |
| 
 | |
|     HASH_FOR_EACH(w, nn->in) {
 | |
|       Node* nw = r->nodes_[w];
 | |
|       if (!nw->visited && lower_bound < nw->rank) {
 | |
|         r->stack_.push_back(w);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void Reorder(GraphCycles::Rep* r) {
 | |
|   Sort(r->nodes_, &r->deltab_);
 | |
|   Sort(r->nodes_, &r->deltaf_);
 | |
| 
 | |
|   // Adds contents of delta lists to list_ (backwards deltas first).
 | |
|   r->list_.clear();
 | |
|   MoveToList(r, &r->deltab_, &r->list_);
 | |
|   MoveToList(r, &r->deltaf_, &r->list_);
 | |
| 
 | |
|   // Produce sorted list of all ranks that will be reassigned.
 | |
|   r->merged_.resize(r->deltab_.size() + r->deltaf_.size());
 | |
|   std::merge(r->deltab_.begin(), r->deltab_.end(),
 | |
|              r->deltaf_.begin(), r->deltaf_.end(),
 | |
|              r->merged_.begin());
 | |
| 
 | |
|   // Assign the ranks in order to the collected list.
 | |
|   for (uint32_t i = 0; i < r->list_.size(); i++) {
 | |
|     r->nodes_[r->list_[i]]->rank = r->merged_[i];
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void Sort(const Vec<Node*>& nodes, Vec<int32_t>* delta) {
 | |
|   struct ByRank {
 | |
|     const Vec<Node*>* nodes;
 | |
|     bool operator()(int32_t a, int32_t b) const {
 | |
|       return (*nodes)[a]->rank < (*nodes)[b]->rank;
 | |
|     }
 | |
|   };
 | |
|   ByRank cmp;
 | |
|   cmp.nodes = &nodes;
 | |
|   std::sort(delta->begin(), delta->end(), cmp);
 | |
| }
 | |
| 
 | |
| static void MoveToList(
 | |
|     GraphCycles::Rep* r, Vec<int32_t>* src, Vec<int32_t>* dst) {
 | |
|   for (auto& v : *src) {
 | |
|     int32_t w = v;
 | |
|     v = r->nodes_[w]->rank;         // Replace v entry with its rank
 | |
|     r->nodes_[w]->visited = false;  // Prepare for future DFS calls
 | |
|     dst->push_back(w);
 | |
|   }
 | |
| }
 | |
| 
 | |
| int GraphCycles::FindPath(GraphId idx, GraphId idy, int max_path_len,
 | |
|                           GraphId path[]) const {
 | |
|   Rep* r = rep_;
 | |
|   if (FindNode(r, idx) == nullptr || FindNode(r, idy) == nullptr) return 0;
 | |
|   const int32_t x = NodeIndex(idx);
 | |
|   const int32_t y = NodeIndex(idy);
 | |
| 
 | |
|   // Forward depth first search starting at x until we hit y.
 | |
|   // As we descend into a node, we push it onto the path.
 | |
|   // As we leave a node, we remove it from the path.
 | |
|   int path_len = 0;
 | |
| 
 | |
|   NodeSet seen;
 | |
|   r->stack_.clear();
 | |
|   r->stack_.push_back(x);
 | |
|   while (!r->stack_.empty()) {
 | |
|     int32_t n = r->stack_.back();
 | |
|     r->stack_.pop_back();
 | |
|     if (n < 0) {
 | |
|       // Marker to indicate that we are leaving a node
 | |
|       path_len--;
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     if (path_len < max_path_len) {
 | |
|       path[path_len] = MakeId(n, rep_->nodes_[n]->version);
 | |
|     }
 | |
|     path_len++;
 | |
|     r->stack_.push_back(-1);  // Will remove tentative path entry
 | |
| 
 | |
|     if (n == y) {
 | |
|       return path_len;
 | |
|     }
 | |
| 
 | |
|     HASH_FOR_EACH(w, r->nodes_[n]->out) {
 | |
|       if (seen.insert(w)) {
 | |
|         r->stack_.push_back(w);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| bool GraphCycles::IsReachable(GraphId x, GraphId y) const {
 | |
|   return FindPath(x, y, 0, nullptr) > 0;
 | |
| }
 | |
| 
 | |
| void GraphCycles::UpdateStackTrace(GraphId id, int priority,
 | |
|                                    int (*get_stack_trace)(void** stack, int)) {
 | |
|   Node* n = FindNode(rep_, id);
 | |
|   if (n == nullptr || n->priority >= priority) {
 | |
|     return;
 | |
|   }
 | |
|   n->nstack = (*get_stack_trace)(n->stack, ABSL_ARRAYSIZE(n->stack));
 | |
|   n->priority = priority;
 | |
| }
 | |
| 
 | |
| int GraphCycles::GetStackTrace(GraphId id, void*** ptr) {
 | |
|   Node* n = FindNode(rep_, id);
 | |
|   if (n == nullptr) {
 | |
|     *ptr = nullptr;
 | |
|     return 0;
 | |
|   } else {
 | |
|     *ptr = n->stack;
 | |
|     return n->nstack;
 | |
|   }
 | |
| }
 | |
| 
 | |
| }  // namespace synchronization_internal
 | |
| ABSL_NAMESPACE_END
 | |
| }  // namespace absl
 | |
| 
 | |
| #endif  // ABSL_LOW_LEVEL_ALLOC_MISSING
 |