tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

graphcycles.cc (20966B)


      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 
     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:
     78  Vec() { Init(); }
     79  ~Vec() { Discard(); }
     80 
     81  void clear() {
     82    Discard();
     83    Init();
     84  }
     85 
     86  bool empty() const { return size_ == 0; }
     87  uint32_t size() const { return size_; }
     88  T* begin() { return ptr_; }
     89  T* end() { return ptr_ + size_; }
     90  const T& operator[](uint32_t i) const { return ptr_[i]; }
     91  T& operator[](uint32_t i) { return ptr_[i]; }
     92  const T& back() const { return ptr_[size_-1]; }
     93  void pop_back() { size_--; }
     94 
     95  void push_back(const T& v) {
     96    if (size_ == capacity_) Grow(size_ + 1);
     97    ptr_[size_] = v;
     98    size_++;
     99  }
    100 
    101  void resize(uint32_t n) {
    102    if (n > capacity_) Grow(n);
    103    size_ = n;
    104  }
    105 
    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.
    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 
    135  void Init() {
    136    ptr_ = space_;
    137    size_ = 0;
    138    capacity_ = kInline;
    139  }
    140 
    141  void Discard() {
    142    if (ptr_ != space_) base_internal::LowLevelAlloc::Free(ptr_);
    143  }
    144 
    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:
    164  NodeSet() { Init(); }
    165 
    166  void clear() { Init(); }
    167  bool contains(int32_t v) const { return table_[FindIndex(v)] == v; }
    168 
    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 
    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); )
    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 
    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
    217  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 
    239  void Init() {
    240    table_.clear();
    241    table_.resize(kInline);
    242    table_.fill(kEmpty);
    243    occupied_ = 0;
    244  }
    245 
    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 
    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 
    273 inline int32_t NodeIndex(GraphId id) {
    274  return static_cast<int32_t>(id.handle);
    275 }
    276 
    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:
    297  explicit PointerMap(const Vec<Node*>* nodes) : nodes_(nodes) {
    298    table_.fill(-1);
    299  }
    300 
    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 
    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 
    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 
    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 
    360  Rep() : ptrmap_(&nodes_) {}
    361 };
    362 
    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 
    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 
    376 GraphCycles::GraphCycles() {
    377  InitArenaIfNecessary();
    378  rep_ = new (base_internal::LowLevelAlloc::AllocWithArena(sizeof(Rep), arena))
    379      Rep;
    380 }
    381 
    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 
    392 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 
    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 
    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 
    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 
    481 bool GraphCycles::HasNode(GraphId node) {
    482  return FindNode(rep_, node) != nullptr;
    483 }
    484 
    485 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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    643 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 
    688 bool GraphCycles::IsReachable(GraphId x, GraphId y) const {
    689  return FindPath(x, y, 0, nullptr) > 0;
    690 }
    691 
    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 
    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