Coverage Report

Created: 2026-10-28 06:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/abseil-cpp/absl/synchronization/internal/graphcycles.cc
Line
Count
Source
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
// Unlike in the paper, ranks are spaced apart, which often lets step (3) move
32
// just x or just y (see "Rank allocation" below).
33
34
#include "absl/base/internal/low_level_alloc.h"  // IWYU pragma: keep
35
// This file is a no-op if the required LowLevelAlloc support is missing.
36
#ifndef ABSL_LOW_LEVEL_ALLOC_MISSING
37
38
#include <algorithm>
39
#include <array>
40
#include <cinttypes>
41
#include <cstddef>
42
#include <cstdint>
43
#include <limits>
44
45
#include "absl/algorithm/container.h"
46
#include "absl/base/attributes.h"
47
#include "absl/base/config.h"
48
#include "absl/base/internal/hide_ptr.h"
49
#include "absl/base/internal/raw_logging.h"
50
#include "absl/base/internal/scheduling_mode.h"
51
#include "absl/base/internal/spinlock.h"
52
#include "absl/base/macros.h"
53
#include "absl/synchronization/internal/graphcycles.h"
54
55
// Do not use STL.   This module does not use standard memory allocation.
56
57
namespace absl {
58
ABSL_NAMESPACE_BEGIN
59
namespace synchronization_internal {
60
61
namespace {
62
63
// Avoid LowLevelAlloc's default arena since it calls malloc hooks in
64
// which people are doing things like acquiring Mutexes.
65
ABSL_CONST_INIT static absl::base_internal::SpinLock arena_mu(
66
    base_internal::SCHEDULE_KERNEL_ONLY);
67
ABSL_CONST_INIT static base_internal::LowLevelAlloc::Arena* arena;
68
69
1
static void InitArenaIfNecessary() {
70
1
  base_internal::SpinLockHolder l(arena_mu);
71
1
  if (arena == nullptr) {
72
1
    arena = base_internal::LowLevelAlloc::NewArena(0);
73
1
  }
74
1
}
75
76
// Number of inlined elements in Vec.  Hash table implementation
77
// relies on this being a power of two.
78
static const uint32_t kInline = 8;
79
80
// A simple LowLevelAlloc based resizable vector with inlined storage
81
// for a few elements.  T must be a plain type since constructor
82
// and destructor are not run on elements of type T managed by Vec.
83
template <typename T>
84
class Vec {
85
 public:
86
5.15k
  Vec() { Init(); }
graphcycles.cc:absl::synchronization_internal::(anonymous namespace)::Vec<absl::synchronization_internal::(anonymous namespace)::Node*>::Vec()
Line
Count
Source
86
1
  Vec() { Init(); }
graphcycles.cc:absl::synchronization_internal::(anonymous namespace)::Vec<int>::Vec()
Line
Count
Source
86
5.15k
  Vec() { Init(); }
graphcycles.cc:absl::synchronization_internal::(anonymous namespace)::Vec<long>::Vec()
Line
Count
Source
86
1
  Vec() { Init(); }
87
0
  ~Vec() { Discard(); }
Unexecuted instantiation: graphcycles.cc:absl::synchronization_internal::(anonymous namespace)::Vec<int>::~Vec()
Unexecuted instantiation: graphcycles.cc:absl::synchronization_internal::(anonymous namespace)::Vec<long>::~Vec()
Unexecuted instantiation: graphcycles.cc:absl::synchronization_internal::(anonymous namespace)::Vec<absl::synchronization_internal::(anonymous namespace)::Node*>::~Vec()
88
89
5.14k
  void clear() {
90
5.14k
    Discard();
91
5.14k
    Init();
92
5.14k
  }
93
94
2.57k
  bool empty() const { return size_ == 0; }
95
363k
  uint32_t size() const { return size_; }
graphcycles.cc:absl::synchronization_internal::(anonymous namespace)::Vec<int>::size() const
Line
Count
Source
95
361k
  uint32_t size() const { return size_; }
graphcycles.cc:absl::synchronization_internal::(anonymous namespace)::Vec<absl::synchronization_internal::(anonymous namespace)::Node*>::size() const
Line
Count
Source
95
2.57k
  uint32_t size() const { return size_; }
96
0
  T* begin() { return ptr_; }
Unexecuted instantiation: graphcycles.cc:absl::synchronization_internal::(anonymous namespace)::Vec<int>::begin()
Unexecuted instantiation: graphcycles.cc:absl::synchronization_internal::(anonymous namespace)::Vec<absl::synchronization_internal::(anonymous namespace)::Node*>::begin()
97
0
  T* end() { return ptr_ + size_; }
Unexecuted instantiation: graphcycles.cc:absl::synchronization_internal::(anonymous namespace)::Vec<int>::end()
Unexecuted instantiation: graphcycles.cc:absl::synchronization_internal::(anonymous namespace)::Vec<absl::synchronization_internal::(anonymous namespace)::Node*>::end()
98
677k
  const T& operator[](uint32_t i) const { return ptr_[i]; }
graphcycles.cc:absl::synchronization_internal::(anonymous namespace)::Vec<absl::synchronization_internal::(anonymous namespace)::Node*>::operator[](unsigned int) const
Line
Count
Source
98
367k
  const T& operator[](uint32_t i) const { return ptr_[i]; }
graphcycles.cc:absl::synchronization_internal::(anonymous namespace)::Vec<int>::operator[](unsigned int) const
Line
Count
Source
98
309k
  const T& operator[](uint32_t i) const { return ptr_[i]; }
99
2.17M
  T& operator[](uint32_t i) { return ptr_[i]; }
graphcycles.cc:absl::synchronization_internal::(anonymous namespace)::Vec<int>::operator[](unsigned int)
Line
Count
Source
99
314k
  T& operator[](uint32_t i) { return ptr_[i]; }
Unexecuted instantiation: graphcycles.cc:absl::synchronization_internal::(anonymous namespace)::Vec<long>::operator[](unsigned int)
graphcycles.cc:absl::synchronization_internal::(anonymous namespace)::Vec<absl::synchronization_internal::(anonymous namespace)::Node*>::operator[](unsigned int)
Line
Count
Source
99
1.86M
  T& operator[](uint32_t i) { return ptr_[i]; }
100
0
  const T& back() const { return ptr_[size_ - 1]; }
101
0
  void pop_back() { size_--; }
102
103
2.57k
  void push_back(const T& v) {
104
2.57k
    if (size_ == capacity_) Grow(size_ + 1);
105
2.57k
    ptr_[size_] = v;
106
2.57k
    size_++;
107
2.57k
  }
graphcycles.cc:absl::synchronization_internal::(anonymous namespace)::Vec<absl::synchronization_internal::(anonymous namespace)::Node*>::push_back(absl::synchronization_internal::(anonymous namespace)::Node* const&)
Line
Count
Source
103
2.57k
  void push_back(const T& v) {
104
2.57k
    if (size_ == capacity_) Grow(size_ + 1);
105
2.57k
    ptr_[size_] = v;
106
2.57k
    size_++;
107
2.57k
  }
Unexecuted instantiation: graphcycles.cc:absl::synchronization_internal::(anonymous namespace)::Vec<int>::push_back(int const&)
108
109
5.14k
  void resize(uint32_t n) {
110
5.14k
    if (n > capacity_) Grow(n);
111
5.14k
    size_ = n;
112
5.14k
  }
graphcycles.cc:absl::synchronization_internal::(anonymous namespace)::Vec<int>::resize(unsigned int)
Line
Count
Source
109
5.14k
  void resize(uint32_t n) {
110
5.14k
    if (n > capacity_) Grow(n);
111
5.14k
    size_ = n;
112
5.14k
  }
Unexecuted instantiation: graphcycles.cc:absl::synchronization_internal::(anonymous namespace)::Vec<long>::resize(unsigned int)
Unexecuted instantiation: graphcycles.cc:absl::synchronization_internal::(anonymous namespace)::Vec<absl::synchronization_internal::(anonymous namespace)::Node*>::resize(unsigned int)
113
114
5.14k
  void fill(const T& val) {
115
46.2k
    for (uint32_t i = 0; i < size(); i++) {
116
41.1k
      ptr_[i] = val;
117
41.1k
    }
118
5.14k
  }
119
120
  // Guarantees src is empty at end.
121
  // Provided for the hash table resizing code below.
122
0
  void MoveFrom(Vec<T>* src) {
123
0
    if (src->ptr_ == src->space_) {
124
      // Need to actually copy
125
0
      resize(src->size_);
126
0
      std::copy_n(src->ptr_, src->size_, ptr_);
127
0
      src->size_ = 0;
128
0
    } else {
129
0
      Discard();
130
0
      ptr_ = src->ptr_;
131
0
      size_ = src->size_;
132
0
      capacity_ = src->capacity_;
133
0
      src->Init();
134
0
    }
135
0
  }
136
137
 private:
138
  T* ptr_;
139
  T space_[kInline];
140
  uint32_t size_;
141
  uint32_t capacity_;
142
143
10.2k
  void Init() {
144
10.2k
    ptr_ = space_;
145
10.2k
    size_ = 0;
146
10.2k
    capacity_ = kInline;
147
10.2k
  }
graphcycles.cc:absl::synchronization_internal::(anonymous namespace)::Vec<absl::synchronization_internal::(anonymous namespace)::Node*>::Init()
Line
Count
Source
143
1
  void Init() {
144
1
    ptr_ = space_;
145
1
    size_ = 0;
146
1
    capacity_ = kInline;
147
1
  }
graphcycles.cc:absl::synchronization_internal::(anonymous namespace)::Vec<int>::Init()
Line
Count
Source
143
10.2k
  void Init() {
144
10.2k
    ptr_ = space_;
145
10.2k
    size_ = 0;
146
10.2k
    capacity_ = kInline;
147
10.2k
  }
graphcycles.cc:absl::synchronization_internal::(anonymous namespace)::Vec<long>::Init()
Line
Count
Source
143
1
  void Init() {
144
1
    ptr_ = space_;
145
1
    size_ = 0;
146
1
    capacity_ = kInline;
147
1
  }
148
149
5.15k
  void Discard() {
150
5.15k
    if (ptr_ != space_) base_internal::LowLevelAlloc::Free(ptr_);
151
5.15k
  }
graphcycles.cc:absl::synchronization_internal::(anonymous namespace)::Vec<int>::Discard()
Line
Count
Source
149
5.14k
  void Discard() {
150
5.14k
    if (ptr_ != space_) base_internal::LowLevelAlloc::Free(ptr_);
151
5.14k
  }
Unexecuted instantiation: graphcycles.cc:absl::synchronization_internal::(anonymous namespace)::Vec<long>::Discard()
graphcycles.cc:absl::synchronization_internal::(anonymous namespace)::Vec<absl::synchronization_internal::(anonymous namespace)::Node*>::Discard()
Line
Count
Source
149
9
  void Discard() {
150
9
    if (ptr_ != space_) base_internal::LowLevelAlloc::Free(ptr_);
151
9
  }
152
153
9
  void Grow(uint32_t n) {
154
18
    while (capacity_ < n) {
155
9
      capacity_ *= 2;
156
9
    }
157
9
    size_t request = static_cast<size_t>(capacity_) * sizeof(T);
158
9
    T* copy = static_cast<T*>(
159
9
        base_internal::LowLevelAlloc::AllocWithArena(request, arena));
160
9
    std::copy_n(ptr_, size_, copy);
161
9
    Discard();
162
9
    ptr_ = copy;
163
9
  }
Unexecuted instantiation: graphcycles.cc:absl::synchronization_internal::(anonymous namespace)::Vec<int>::Grow(unsigned int)
Unexecuted instantiation: graphcycles.cc:absl::synchronization_internal::(anonymous namespace)::Vec<long>::Grow(unsigned int)
graphcycles.cc:absl::synchronization_internal::(anonymous namespace)::Vec<absl::synchronization_internal::(anonymous namespace)::Node*>::Grow(unsigned int)
Line
Count
Source
153
9
  void Grow(uint32_t n) {
154
18
    while (capacity_ < n) {
155
9
      capacity_ *= 2;
156
9
    }
157
9
    size_t request = static_cast<size_t>(capacity_) * sizeof(T);
158
9
    T* copy = static_cast<T*>(
159
9
        base_internal::LowLevelAlloc::AllocWithArena(request, arena));
160
9
    std::copy_n(ptr_, size_, copy);
161
9
    Discard();
162
9
    ptr_ = copy;
163
9
  }
164
165
  Vec(const Vec&) = delete;
166
  Vec& operator=(const Vec&) = delete;
167
};
168
169
// A hash set of non-negative int32_t that uses Vec for its underlying storage.
170
class NodeSet {
171
 public:
172
5.14k
  NodeSet() { Init(); }
173
174
0
  void clear() { Init(); }
175
0
  bool contains(int32_t v) const { return table_[FindIndex(v)] == v; }
176
177
309k
  bool insert(int32_t v) {
178
309k
    uint32_t i = FindIndex(v);
179
309k
    if (table_[i] == v) {
180
307k
      return false;
181
307k
    }
182
2.57k
    if (table_[i] == kEmpty) {
183
      // Only inserting over an empty cell increases the number of occupied
184
      // slots.
185
2.57k
      occupied_++;
186
2.57k
    }
187
2.57k
    table_[i] = v;
188
    // Double when 75% full.
189
2.57k
    if (occupied_ >= table_.size() - table_.size() / 4) Grow();
190
2.57k
    return true;
191
309k
  }
192
193
0
  void erase(int32_t v) {
194
0
    uint32_t i = FindIndex(v);
195
0
    if (table_[i] == v) {
196
0
      table_[i] = kDel;
197
0
    }
198
0
  }
199
200
  // Iteration: is done via HASH_FOR_EACH
201
  // Example:
202
  //    HASH_FOR_EACH(elem, node->out) { ... }
203
#define HASH_FOR_EACH(elem, eset) \
204
0
  for (int32_t elem, _cursor = 0; (eset).Next(&_cursor, &elem);)
205
0
  bool Next(int32_t* cursor, int32_t* elem) {
206
0
    while (static_cast<uint32_t>(*cursor) < table_.size()) {
207
0
      int32_t v = table_[static_cast<uint32_t>(*cursor)];
208
0
      (*cursor)++;
209
0
      if (v >= 0) {
210
0
        *elem = v;
211
0
        return true;
212
0
      }
213
0
    }
214
0
    return false;
215
0
  }
216
217
 private:
218
  enum : int32_t { kEmpty = -1, kDel = -2 };
219
  Vec<int32_t> table_;
220
  uint32_t occupied_;  // Count of non-empty slots (includes deleted slots)
221
222
309k
  static uint32_t Hash(int32_t a) { return static_cast<uint32_t>(a) * 41; }
223
224
  // Return index for storing v.  May return an empty index or deleted index
225
309k
  uint32_t FindIndex(int32_t v) const {
226
    // Search starting at hash index.
227
309k
    const uint32_t mask = table_.size() - 1;
228
309k
    uint32_t i = Hash(v) & mask;
229
309k
    uint32_t deleted_index = 0;  // index of first deleted element we see
230
309k
    bool seen_deleted_element = false;
231
309k
    while (true) {
232
309k
      int32_t e = table_[i];
233
309k
      if (v == e) {
234
307k
        return i;
235
307k
      } else if (e == kEmpty) {
236
        // Return any previously encountered deleted slot.
237
2.57k
        return seen_deleted_element ? deleted_index : i;
238
2.57k
      } else if (e == kDel && !seen_deleted_element) {
239
        // Keep searching since v might be present later.
240
0
        deleted_index = i;
241
0
        seen_deleted_element = true;
242
0
      }
243
0
      i = (i + 1) & mask;  // Linear probing; quadratic is slightly slower.
244
0
    }
245
309k
  }
246
247
5.14k
  void Init() {
248
5.14k
    table_.clear();
249
5.14k
    table_.resize(kInline);
250
5.14k
    table_.fill(kEmpty);
251
5.14k
    occupied_ = 0;
252
5.14k
  }
253
254
0
  void Grow() {
255
0
    Vec<int32_t> copy;
256
0
    copy.MoveFrom(&table_);
257
0
    occupied_ = 0;
258
0
    table_.resize(copy.size() * 2);
259
0
    table_.fill(kEmpty);
260
261
0
    for (const auto& e : copy) {
262
0
      if (e >= 0) insert(e);
263
0
    }
264
0
  }
265
266
  NodeSet(const NodeSet&) = delete;
267
  NodeSet& operator=(const NodeSet&) = delete;
268
};
269
270
// We encode a node index and a node version in GraphId.  The version
271
// number is incremented when the GraphId is freed which automatically
272
// invalidates all copies of the GraphId.
273
274
367k
inline GraphId MakeId(int32_t index, uint32_t version) {
275
367k
  GraphId g;
276
367k
  g.handle =
277
367k
      (static_cast<uint64_t>(version) << 32) | static_cast<uint32_t>(index);
278
367k
  return g;
279
367k
}
280
281
2.09M
inline int32_t NodeIndex(GraphId id) { return static_cast<int32_t>(id.handle); }
282
283
1.48M
inline uint32_t NodeVersion(GraphId id) {
284
1.48M
  return static_cast<uint32_t>(id.handle >> 32);
285
1.48M
}
286
287
struct Node {
288
  int64_t rank;          // Unique rank (see "Rank allocation")
289
  int32_t prev_slot;     // Slot with the next lower rank, or -1 if none
290
  int32_t next_slot;     // Slot with the next higher rank, or -1 if none
291
  uint32_t version;      // Current version number
292
  int32_t next_hash;     // Next entry in hash table
293
  bool visited;          // Temporary marker used by depth-first-search
294
  uintptr_t masked_ptr;  // User-supplied pointer
295
  NodeSet in;            // List of immediate predecessor nodes in graph
296
  NodeSet out;           // List of immediate successor nodes in graph
297
  int priority;          // Priority of recorded stack trace.
298
  int nstack;            // Depth of recorded stack trace.
299
  void* stack[40];       // stack[0,nstack-1] holds stack trace for node.
300
};
301
302
// Hash table for pointer to node index lookups.
303
class PointerMap {
304
 public:
305
1
  explicit PointerMap(const Vec<Node*>* nodes) : nodes_(nodes) {
306
1
    table_.fill(-1);
307
1
  }
308
309
367k
  int32_t Find(void* ptr) {
310
367k
    auto masked = base_internal::HidePtr(ptr);
311
367k
    for (int32_t i = table_[Hash(ptr)]; i != -1;) {
312
365k
      Node* n = (*nodes_)[static_cast<uint32_t>(i)];
313
365k
      if (n->masked_ptr == masked) return i;
314
1
      i = n->next_hash;
315
1
    }
316
2.57k
    return -1;
317
367k
  }
318
319
2.57k
  void Add(void* ptr, int32_t i) {
320
2.57k
    int32_t* head = &table_[Hash(ptr)];
321
2.57k
    (*nodes_)[static_cast<uint32_t>(i)]->next_hash = *head;
322
2.57k
    *head = i;
323
2.57k
  }
324
325
0
  int32_t Remove(void* ptr) {
326
    // Advance through linked list while keeping track of the
327
    // predecessor slot that points to the current entry.
328
0
    auto masked = base_internal::HidePtr(ptr);
329
0
    for (int32_t* slot = &table_[Hash(ptr)]; *slot != -1;) {
330
0
      int32_t index = *slot;
331
0
      Node* n = (*nodes_)[static_cast<uint32_t>(index)];
332
0
      if (n->masked_ptr == masked) {
333
0
        *slot = n->next_hash;  // Remove n from linked list
334
0
        n->next_hash = -1;
335
0
        return index;
336
0
      }
337
0
      slot = &n->next_hash;
338
0
    }
339
0
    return -1;
340
0
  }
341
342
 private:
343
  // Number of buckets in hash table for pointer lookups.
344
  static constexpr uint32_t kHashTableSize = 262139;  // should be prime
345
346
  const Vec<Node*>* nodes_;
347
  std::array<int32_t, kHashTableSize> table_;
348
349
370k
  static uint32_t Hash(void* ptr) {
350
370k
    return reinterpret_cast<uintptr_t>(ptr) % kHashTableSize;
351
370k
  }
352
};
353
354
}  // namespace
355
356
// Rank allocation
357
//
358
// Ranks are unique int64_t values. All slots, including free slots, are kept in
359
// a doubly linked list sorted by rank (Node::prev_slot and Node::next_slot,
360
// from Rep::lowest_slot to Rep::highest_slot). Free slots keep their rank and
361
// their place in the list, which are reused along with the slot. New slots get
362
// ranks kRankStride above the highest rank.
363
//
364
// When a new edge x->y violates the rank order, InsertEdge first tries to move
365
// just x below y, or just y above x (see TryMoveXBelowY()): either to the
366
// midpoint between two ranks that are adjacent in the list, or kRankStride
367
// beyond the lowest or highest rank. No other slot can have such a rank, so
368
// ranks stay unique.
369
//
370
// kRankStride leaves room for about 32 moves, one after another, into the gap
371
// between two adjacent new slots. When a gap has no room left, Respace() first
372
// spreads out the ranks around it. Ranks beyond the lowest and highest ones
373
// stay within [kMinRank, kMaxRank], which leaves room for about 2^30 moves at
374
// each end before all slots are respaced.
375
static constexpr int64_t kRankStride = int64_t{1} << 32;
376
static constexpr int64_t kMinRank = -(int64_t{1} << 62);
377
static constexpr int64_t kMaxRank = int64_t{1} << 62;
378
// Respace() spreads out ranks until they can be at least this far apart.
379
static constexpr uint64_t kMinRespacedGap = uint64_t{1} << 16;
380
// Maximum number of in-neighbors of x (or out-neighbors of y) that
381
// TryMoveXBelowY() (or TryMoveYAboveX()) will examine.
382
static constexpr int kMaxSingleNodeDegree = 16;
383
384
struct GraphCycles::Rep {
385
  Vec<Node*> nodes_;
386
  Vec<int32_t> free_nodes_;  // Indices for unused entries in nodes_
387
  PointerMap ptrmap_;
388
  int32_t lowest_slot = -1;   // Slot with the lowest rank, or -1 if none
389
  int32_t highest_slot = -1;  // Slot with the highest rank, or -1 if none
390
  int64_t rank_stride = kRankStride;  // See TestOnlySetRankStride()
391
392
  // Temporary state.
393
  Vec<int32_t> deltaf_;  // Results of forward DFS
394
  Vec<int32_t> deltab_;  // Results of backward DFS
395
  Vec<int32_t> list_;    // All nodes to reprocess
396
  Vec<int32_t> stack_;   // Emulates recursion stack for depth-first searches
397
  // Used by Reorder(): the slots to reorder, sorted by their current ranks, and
398
  // the rank, prev_slot and next_slot of each.
399
  Vec<int32_t> sorted_slots;
400
  Vec<int64_t> sorted_ranks;
401
  Vec<int32_t> old_prev_slots;
402
  Vec<int32_t> old_next_slots;
403
404
1
  Rep() : ptrmap_(&nodes_) {}
405
};
406
407
1.48M
static Node* FindNode(GraphCycles::Rep* rep, GraphId id) {
408
1.48M
  Node* n = rep->nodes_[static_cast<uint32_t>(NodeIndex(id))];
409
1.48M
  return (n->version == NodeVersion(id)) ? n : nullptr;
410
1.48M
}
411
412
15.4k
static Node* NodeAt(GraphCycles::Rep* r, int32_t slot) {
413
15.4k
  return r->nodes_[static_cast<uint32_t>(slot)];
414
15.4k
}
415
416
// Makes next_slot follow prev_slot in the rank list. prev_slot == -1 makes
417
// next_slot the lowest slot, and next_slot == -1 makes prev_slot the highest.
418
5.14k
static void Link(GraphCycles::Rep* r, int32_t prev_slot, int32_t next_slot) {
419
5.14k
  if (prev_slot == -1) {
420
1
    r->lowest_slot = next_slot;
421
5.14k
  } else {
422
5.14k
    NodeAt(r, prev_slot)->next_slot = next_slot;
423
5.14k
  }
424
5.14k
  if (next_slot == -1) {
425
2.57k
    r->highest_slot = prev_slot;
426
2.57k
  } else {
427
2.57k
    NodeAt(r, next_slot)->prev_slot = prev_slot;
428
2.57k
  }
429
5.14k
}
430
431
// Removes slot from the rank list.
432
0
static void Unlink(GraphCycles::Rep* r, int32_t slot) {
433
0
  Link(r, NodeAt(r, slot)->prev_slot, NodeAt(r, slot)->next_slot);
434
0
}
435
436
// Returns upper - lower, for lower <= upper, without overflow.
437
0
static uint64_t RankDistance(int64_t lower, int64_t upper) {
438
0
  return static_cast<uint64_t>(upper) - static_cast<uint64_t>(lower);
439
0
}
440
441
// Gives the run of adjacent slots from first_slot to last_slot in the rank list
442
// evenly spaced ranks, keeping their order. First grows the run on both sides
443
// until its slots can be at least kMinRespacedGap apart between the ranks just
444
// outside it (kMinRank and kMaxRank beyond the ends of the list), or until it
445
// covers the whole list. The whole list is spaced across
446
// [kMinRank / 2, kMaxRank / 2], which leaves room beyond both of its ends.
447
static void Respace(GraphCycles::Rep* r, int32_t first_slot,
448
0
                    int32_t last_slot) {
449
0
  uint64_t count = 1;
450
0
  for (int32_t slot = first_slot; slot != last_slot;
451
0
       slot = NodeAt(r, slot)->next_slot) {
452
0
    count++;
453
0
  }
454
0
  int64_t lower;
455
0
  int64_t upper;
456
0
  while (true) {
457
0
    const int32_t before_slot = NodeAt(r, first_slot)->prev_slot;
458
0
    const int32_t after_slot = NodeAt(r, last_slot)->next_slot;
459
0
    if (before_slot == -1 && after_slot == -1) {
460
0
      lower = kMinRank / 2;
461
0
      upper = kMaxRank / 2;
462
0
      break;
463
0
    }
464
0
    lower = before_slot == -1 ? kMinRank : NodeAt(r, before_slot)->rank;
465
0
    upper = after_slot == -1 ? kMaxRank : NodeAt(r, after_slot)->rank;
466
0
    if (RankDistance(lower, upper) / (count + 1) >= kMinRespacedGap) break;
467
    // Double the size of the run, as far as the ends of the list allow.
468
0
    for (const uint64_t target = 2 * count; count < target;) {
469
0
      const int32_t prev_slot = NodeAt(r, first_slot)->prev_slot;
470
0
      const int32_t next_slot = NodeAt(r, last_slot)->next_slot;
471
0
      if (prev_slot == -1 && next_slot == -1) break;
472
0
      if (prev_slot != -1) {
473
0
        first_slot = prev_slot;
474
0
        count++;
475
0
      }
476
0
      if (next_slot != -1) {
477
0
        last_slot = next_slot;
478
0
        count++;
479
0
      }
480
0
    }
481
0
  }
482
0
  const uint64_t step = RankDistance(lower, upper) / (count + 1);
483
0
  int64_t rank = lower;
484
0
  for (int32_t slot = first_slot;; slot = NodeAt(r, slot)->next_slot) {
485
0
    rank += static_cast<int64_t>(step);
486
0
    NodeAt(r, slot)->rank = rank;
487
0
    if (slot == last_slot) return;
488
0
  }
489
0
}
490
491
// Links slot, which is not in the rank list, between prev_slot and next_slot,
492
// which are adjacent in it (-1 beyond its ends), with a rank between theirs:
493
// rank_stride (kRankStride by default) beyond prev_slot or next_slot at the
494
// ends of the list, and halfway between them otherwise. If there is no room
495
// for such a rank, respaces ranks first.
496
static void LinkBetween(GraphCycles::Rep* r, int32_t slot, int32_t prev_slot,
497
2.57k
                        int32_t next_slot) {
498
2.57k
  int64_t rank;
499
2.57k
  if (prev_slot == -1 && next_slot == -1) {
500
1
    rank = 0;
501
2.57k
  } else if (prev_slot == -1) {
502
0
    if (NodeAt(r, next_slot)->rank < kMinRank + r->rank_stride) {
503
0
      Respace(r, r->lowest_slot, r->highest_slot);
504
0
    }
505
0
    rank = NodeAt(r, next_slot)->rank - r->rank_stride;
506
2.57k
  } else if (next_slot == -1) {
507
2.57k
    if (NodeAt(r, prev_slot)->rank > kMaxRank - r->rank_stride) {
508
0
      Respace(r, r->lowest_slot, r->highest_slot);
509
0
    }
510
2.57k
    rank = NodeAt(r, prev_slot)->rank + r->rank_stride;
511
2.57k
  } else {
512
0
    const Node* prev_node = NodeAt(r, prev_slot);
513
0
    const Node* next_node = NodeAt(r, next_slot);
514
0
    if (RankDistance(prev_node->rank, next_node->rank) < 2) {
515
0
      Respace(r, prev_slot, next_slot);
516
0
    }
517
0
    const uint64_t gap = RankDistance(prev_node->rank, next_node->rank);
518
0
    rank = prev_node->rank + static_cast<int64_t>(gap / 2);
519
0
  }
520
2.57k
  NodeAt(r, slot)->rank = rank;
521
2.57k
  Link(r, prev_slot, slot);
522
2.57k
  Link(r, slot, next_slot);
523
2.57k
}
524
525
0
void GraphCycles::TestOnlyAddNodes(uint32_t n) {
526
0
  uint32_t old_size = rep_->nodes_.size();
527
0
  rep_->nodes_.resize(n);
528
0
  for (auto i = old_size; i < n; ++i) {
529
0
    rep_->nodes_[i] = nullptr;
530
0
  }
531
0
}
532
533
0
void GraphCycles::TestOnlySetRankStride(int64_t stride) {
534
  // After Respace() of the whole list, there is room for a stride of 2^61
535
  // beyond each end.
536
0
  ABSL_RAW_CHECK(stride >= 1 && stride <= kMaxRank / 2, "Bad rank stride");
537
0
  rep_->rank_stride = stride;
538
0
}
539
540
1
GraphCycles::GraphCycles() {
541
1
  InitArenaIfNecessary();
542
1
  rep_ = new (base_internal::LowLevelAlloc::AllocWithArena(sizeof(Rep), arena))
543
1
      Rep;
544
1
}
545
546
0
GraphCycles::~GraphCycles() {
547
0
  for (auto* node : rep_->nodes_) {
548
0
    if (node == nullptr) {
549
0
      continue;
550
0
    }
551
0
    node->Node::~Node();
552
0
    base_internal::LowLevelAlloc::Free(node);
553
0
  }
554
0
  rep_->Rep::~Rep();
555
0
  base_internal::LowLevelAlloc::Free(rep_);
556
0
}
557
558
0
bool GraphCycles::CheckInvariants() const {
559
0
  Rep* r = rep_;
560
0
  for (uint32_t x = 0; x < r->nodes_.size(); x++) {
561
0
    Node* nx = r->nodes_[x];
562
0
    void* ptr = base_internal::UnhidePtr<void>(nx->masked_ptr);
563
0
    if (ptr != nullptr && static_cast<uint32_t>(r->ptrmap_.Find(ptr)) != x) {
564
0
      ABSL_RAW_LOG(FATAL, "Did not find live node in hash table %" PRIu32 " %p",
565
0
                   x, ptr);
566
0
    }
567
0
    if (nx->visited) {
568
0
      ABSL_RAW_LOG(FATAL, "Did not clear visited marker on node %" PRIu32, x);
569
0
    }
570
0
    HASH_FOR_EACH(y, nx->out) {
571
0
      Node* ny = r->nodes_[static_cast<uint32_t>(y)];
572
0
      if (nx->rank >= ny->rank) {
573
0
        ABSL_RAW_LOG(FATAL,
574
0
                     "Edge %" PRIu32 " ->%" PRId32
575
0
                     " has bad rank assignment %" PRId64 "->%" PRId64,
576
0
                     x, y, nx->rank, ny->rank);
577
0
      }
578
0
    }
579
0
  }
580
  // The rank list holds each slot once, in increasing rank order, so ranks are
581
  // unique.
582
0
  const uint32_t num_slots = r->nodes_.size();
583
0
  uint32_t num_listed = 0;
584
0
  int32_t prev_slot = -1;
585
0
  for (int32_t slot = r->lowest_slot; slot != -1;
586
0
       slot = NodeAt(r, slot)->next_slot) {
587
0
    if (NodeAt(r, slot)->prev_slot != prev_slot) {
588
0
      ABSL_RAW_LOG(FATAL,
589
0
                   "Slot %" PRId32 " has prev_slot %" PRId32
590
0
                   " instead of %" PRId32,
591
0
                   slot, NodeAt(r, slot)->prev_slot, prev_slot);
592
0
    }
593
0
    if (prev_slot != -1 &&
594
0
        NodeAt(r, prev_slot)->rank >= NodeAt(r, slot)->rank) {
595
0
      ABSL_RAW_LOG(
596
0
          FATAL, "Rank of slot %" PRId32 " is not above rank of slot %" PRId32,
597
0
          slot, prev_slot);
598
0
    }
599
0
    if (++num_listed > num_slots) {
600
0
      ABSL_RAW_LOG(FATAL, "Rank list has more entries than there are slots");
601
0
    }
602
0
    prev_slot = slot;
603
0
  }
604
0
  if (prev_slot != r->highest_slot || num_listed != num_slots) {
605
0
    ABSL_RAW_LOG(FATAL, "Rank list has %" PRIu32 " of %" PRIu32 " slots",
606
0
                 num_listed, num_slots);
607
0
  }
608
0
  return true;
609
0
}
610
611
367k
GraphId GraphCycles::GetId(void* ptr) {
612
367k
  int32_t i = rep_->ptrmap_.Find(ptr);
613
367k
  if (i != -1) {
614
365k
    return MakeId(i, rep_->nodes_[static_cast<uint32_t>(i)]->version);
615
365k
  } else if (rep_->free_nodes_.empty()) {
616
2.57k
    const int32_t slot = static_cast<int32_t>(rep_->nodes_.size());
617
2.57k
    Node* n =
618
2.57k
        new (base_internal::LowLevelAlloc::AllocWithArena(sizeof(Node), arena))
619
2.57k
            Node;
620
2.57k
    n->version = 1;  // Avoid 0 since it is used by InvalidGraphId()
621
2.57k
    n->visited = false;
622
2.57k
    n->masked_ptr = base_internal::HidePtr(ptr);
623
2.57k
    n->nstack = 0;
624
2.57k
    n->priority = 0;
625
2.57k
    rep_->nodes_.push_back(n);
626
2.57k
    LinkBetween(rep_, slot, rep_->highest_slot, /*next_slot=*/-1);
627
2.57k
    rep_->ptrmap_.Add(ptr, slot);
628
2.57k
    return MakeId(slot, n->version);
629
2.57k
  } else {
630
    // The slot keeps its rank (see "Rank allocation").
631
0
    int32_t slot = rep_->free_nodes_.back();
632
0
    rep_->free_nodes_.pop_back();
633
0
    Node* n = rep_->nodes_[static_cast<uint32_t>(slot)];
634
0
    n->masked_ptr = base_internal::HidePtr(ptr);
635
0
    n->nstack = 0;
636
0
    n->priority = 0;
637
0
    rep_->ptrmap_.Add(ptr, slot);
638
0
    return MakeId(slot, n->version);
639
0
  }
640
367k
}
641
642
0
void GraphCycles::RemoveNode(void* ptr) {
643
0
  int32_t i = rep_->ptrmap_.Remove(ptr);
644
0
  if (i == -1) {
645
0
    return;
646
0
  }
647
0
  Node* x = rep_->nodes_[static_cast<uint32_t>(i)];
648
0
  HASH_FOR_EACH(y, x->out) {
649
0
    rep_->nodes_[static_cast<uint32_t>(y)]->in.erase(i);
650
0
  }
651
0
  HASH_FOR_EACH(y, x->in) {
652
0
    rep_->nodes_[static_cast<uint32_t>(y)]->out.erase(i);
653
0
  }
654
0
  x->in.clear();
655
0
  x->out.clear();
656
0
  x->masked_ptr = base_internal::HidePtr<void>(nullptr);
657
0
  if (x->version == std::numeric_limits<uint32_t>::max()) {
658
    // Cannot use x any more
659
0
  } else {
660
0
    x->version++;  // Invalidates all copies of node.
661
0
    rep_->free_nodes_.push_back(i);
662
0
  }
663
0
}
664
665
557k
void* GraphCycles::Ptr(GraphId id) {
666
557k
  Node* n = FindNode(rep_, id);
667
557k
  return n == nullptr ? nullptr : base_internal::UnhidePtr<void>(n->masked_ptr);
668
557k
}
669
670
0
bool GraphCycles::HasNode(GraphId node) {
671
0
  return FindNode(rep_, node) != nullptr;
672
0
}
673
674
0
bool GraphCycles::HasEdge(GraphId x, GraphId y) const {
675
0
  Node* xn = FindNode(rep_, x);
676
0
  return xn && FindNode(rep_, y) && xn->out.contains(NodeIndex(y));
677
0
}
678
679
0
void GraphCycles::RemoveEdge(GraphId x, GraphId y) {
680
0
  Node* xn = FindNode(rep_, x);
681
0
  Node* yn = FindNode(rep_, y);
682
0
  if (xn && yn) {
683
0
    xn->out.erase(NodeIndex(y));
684
0
    yn->in.erase(NodeIndex(x));
685
    // No need to update the rank assignment since a previous valid
686
    // rank assignment remains valid after an edge deletion.
687
0
  }
688
0
}
689
690
static bool ForwardDFS(GraphCycles::Rep* r, int32_t n, int64_t upper_bound);
691
static void BackwardDFS(GraphCycles::Rep* r, int32_t n, int64_t lower_bound);
692
static void Reorder(GraphCycles::Rep* r);
693
static void Sort(const Vec<Node*>&, Vec<int32_t>* delta);
694
static void MoveToList(GraphCycles::Rep* r, Vec<int32_t>* src,
695
                       Vec<int32_t>* dst);
696
697
// TryMoveXBelowY() and TryMoveYAboveX() are called by InsertEdge(x, y) when
698
// rank(x) > rank(y). They try to restore the rank order by moving only x below
699
// y, or only y above x, which is much cheaper than the Pearce-Kelly search and
700
// reorder.
701
//
702
// x can move below y if all of x's in-neighbors rank below y. This also shows
703
// that the new edge does not close a cycle: every node reachable from y ranks
704
// at or above y, so a path from y to x would have to enter x from such a node.
705
// Similarly, y can move above x if all of y's out-neighbors rank above x.
706
//
707
// Each returns false, changing nothing, if the move is not possible or if
708
// there are more than kMaxSingleNodeDegree neighbors to check.
709
0
static bool TryMoveXBelowY(GraphCycles::Rep* r, int32_t x, int32_t y) {
710
0
  Node* nx = NodeAt(r, x);
711
0
  Node* ny = NodeAt(r, y);
712
0
  int degree = 0;
713
0
  HASH_FOR_EACH(u, nx->in) {
714
0
    if (++degree > kMaxSingleNodeDegree) return false;
715
0
    if (NodeAt(r, u)->rank >= ny->rank) return false;
716
0
  }
717
0
  Unlink(r, x);
718
0
  if (degree == 0) {
719
    // Move x below all other nodes.
720
0
    LinkBetween(r, x, /*prev_slot=*/-1, r->lowest_slot);
721
0
  } else {
722
    // Move x right below y. Its in-neighbors rank below y, so they rank no
723
    // higher than the node right below y.
724
0
    LinkBetween(r, x, ny->prev_slot, y);
725
0
  }
726
0
  return true;
727
0
}
728
729
0
static bool TryMoveYAboveX(GraphCycles::Rep* r, int32_t x, int32_t y) {
730
0
  Node* nx = NodeAt(r, x);
731
0
  Node* ny = NodeAt(r, y);
732
0
  int degree = 0;
733
0
  HASH_FOR_EACH(w, ny->out) {
734
0
    if (++degree > kMaxSingleNodeDegree) return false;
735
0
    if (NodeAt(r, w)->rank <= nx->rank) return false;
736
0
  }
737
0
  Unlink(r, y);
738
0
  if (degree == 0) {
739
    // Move y above all other nodes.
740
0
    LinkBetween(r, y, r->highest_slot, /*next_slot=*/-1);
741
0
  } else {
742
    // Move y right above x. Its out-neighbors rank above x, so they rank no
743
    // lower than the node right above x.
744
0
    LinkBetween(r, y, x, nx->next_slot);
745
0
  }
746
0
  return true;
747
0
}
748
749
308k
bool GraphCycles::InsertEdge(GraphId idx, GraphId idy) {
750
308k
  Rep* r = rep_;
751
308k
  const int32_t x = NodeIndex(idx);
752
308k
  const int32_t y = NodeIndex(idy);
753
308k
  Node* nx = FindNode(r, idx);
754
308k
  Node* ny = FindNode(r, idy);
755
308k
  if (nx == nullptr || ny == nullptr) return true;  // Expired ids
756
757
308k
  if (nx == ny) return false;  // Self edge
758
308k
  if (!nx->out.insert(y)) {
759
    // Edge already exists.
760
307k
    return true;
761
307k
  }
762
763
1.28k
  ny->in.insert(x);
764
765
1.28k
  if (nx->rank <= ny->rank) {
766
    // New edge is consistent with existing rank assignment.
767
1.28k
    return true;
768
1.28k
  }
769
770
  // Try moving just x or just y.
771
0
  if (TryMoveXBelowY(r, x, y) || TryMoveYAboveX(r, x, y)) return true;
772
773
  // Current rank assignments are incompatible with the new edge.  Recompute.
774
  // We only need to consider nodes that fall in the range [ny->rank,nx->rank].
775
0
  if (!ForwardDFS(r, y, nx->rank)) {
776
    // Found a cycle.  Undo the insertion and tell caller.
777
0
    nx->out.erase(y);
778
0
    ny->in.erase(x);
779
    // Since we do not call Reorder() on this path, clear any visited
780
    // markers left by ForwardDFS.
781
0
    for (const auto& d : r->deltaf_) {
782
0
      r->nodes_[static_cast<uint32_t>(d)]->visited = false;
783
0
    }
784
0
    return false;
785
0
  }
786
0
  BackwardDFS(r, x, ny->rank);
787
0
  Reorder(r);
788
0
  return true;
789
0
}
790
791
0
static bool ForwardDFS(GraphCycles::Rep* r, int32_t n, int64_t upper_bound) {
792
  // Avoid recursion since stack space might be limited.
793
  // We instead keep a stack of nodes to visit.
794
0
  r->deltaf_.clear();
795
0
  r->stack_.clear();
796
0
  r->stack_.push_back(n);
797
0
  while (!r->stack_.empty()) {
798
0
    n = r->stack_.back();
799
0
    r->stack_.pop_back();
800
0
    Node* nn = r->nodes_[static_cast<uint32_t>(n)];
801
0
    if (nn->visited) continue;
802
803
0
    nn->visited = true;
804
0
    r->deltaf_.push_back(n);
805
806
0
    HASH_FOR_EACH(w, nn->out) {
807
0
      Node* nw = r->nodes_[static_cast<uint32_t>(w)];
808
0
      if (nw->rank == upper_bound) {
809
0
        return false;  // Cycle
810
0
      }
811
0
      if (!nw->visited && nw->rank < upper_bound) {
812
0
        r->stack_.push_back(w);
813
0
      }
814
0
    }
815
0
  }
816
0
  return true;
817
0
}
818
819
0
static void BackwardDFS(GraphCycles::Rep* r, int32_t n, int64_t lower_bound) {
820
0
  r->deltab_.clear();
821
0
  r->stack_.clear();
822
0
  r->stack_.push_back(n);
823
0
  while (!r->stack_.empty()) {
824
0
    n = r->stack_.back();
825
0
    r->stack_.pop_back();
826
0
    Node* nn = r->nodes_[static_cast<uint32_t>(n)];
827
0
    if (nn->visited) continue;
828
829
0
    nn->visited = true;
830
0
    r->deltab_.push_back(n);
831
832
0
    HASH_FOR_EACH(w, nn->in) {
833
0
      Node* nw = r->nodes_[static_cast<uint32_t>(w)];
834
0
      if (!nw->visited && lower_bound < nw->rank) {
835
0
        r->stack_.push_back(w);
836
0
      }
837
0
    }
838
0
  }
839
0
}
840
841
0
static void Reorder(GraphCycles::Rep* r) {
842
0
  Sort(r->nodes_, &r->deltab_);
843
0
  Sort(r->nodes_, &r->deltaf_);
844
845
  // The slots to reorder, sorted by their current ranks. In their new order
846
  // (list_), they take over the same ranks and places in the rank list.
847
0
  const uint32_t count = r->deltab_.size() + r->deltaf_.size();
848
0
  r->sorted_slots.resize(count);
849
0
  std::merge(r->deltab_.begin(), r->deltab_.end(), r->deltaf_.begin(),
850
0
             r->deltaf_.end(), r->sorted_slots.begin(),
851
0
             [r](int32_t a, int32_t b) {
852
0
               return NodeAt(r, a)->rank < NodeAt(r, b)->rank;
853
0
             });
854
855
  // Adds contents of delta lists to list_ (backwards deltas first).
856
0
  r->list_.clear();
857
0
  MoveToList(r, &r->deltab_, &r->list_);
858
0
  MoveToList(r, &r->deltaf_, &r->list_);
859
860
  // Record the rank, prev_slot and next_slot of each place before changing
861
  // them.
862
0
  r->sorted_ranks.resize(count);
863
0
  r->old_prev_slots.resize(count);
864
0
  r->old_next_slots.resize(count);
865
0
  for (uint32_t i = 0; i < count; i++) {
866
0
    const Node* p = NodeAt(r, r->sorted_slots[i]);
867
0
    r->sorted_ranks[i] = p->rank;
868
0
    r->old_prev_slots[i] = p->prev_slot;
869
0
    r->old_next_slots[i] = p->next_slot;
870
0
  }
871
872
  // Assign the ranks and places in order to the collected list. A prev_slot
873
  // (or next_slot) that is also being reordered must be the previous (or next)
874
  // place in sorted_slots, so link to the slot that now takes that place
875
  // instead.
876
0
  for (uint32_t i = 0; i < count; i++) {
877
0
    const int32_t slot = r->list_[i];
878
0
    NodeAt(r, slot)->rank = r->sorted_ranks[i];
879
0
    if (i > 0 && r->old_prev_slots[i] == r->sorted_slots[i - 1]) {
880
0
      Link(r, r->list_[i - 1], slot);
881
0
    } else {
882
0
      Link(r, r->old_prev_slots[i], slot);
883
0
    }
884
0
    if (i + 1 == count || r->old_next_slots[i] != r->sorted_slots[i + 1]) {
885
0
      Link(r, slot, r->old_next_slots[i]);
886
0
    }
887
0
  }
888
0
}
889
890
0
static void Sort(const Vec<Node*>& nodes, Vec<int32_t>* delta) {
891
0
  struct ByRank {
892
0
    const Vec<Node*>* nodes;
893
0
    bool operator()(int32_t a, int32_t b) const {
894
0
      return (*nodes)[static_cast<uint32_t>(a)]->rank <
895
0
             (*nodes)[static_cast<uint32_t>(b)]->rank;
896
0
    }
897
0
  };
898
0
  ByRank cmp;
899
0
  cmp.nodes = &nodes;
900
0
  absl::c_sort(*delta, cmp);
901
0
}
902
903
static void MoveToList(GraphCycles::Rep* r, Vec<int32_t>* src,
904
0
                       Vec<int32_t>* dst) {
905
0
  for (int32_t w : *src) {
906
    // Prepare for future DFS calls
907
0
    r->nodes_[static_cast<uint32_t>(w)]->visited = false;
908
0
    dst->push_back(w);
909
0
  }
910
0
}
911
912
int GraphCycles::FindPath(GraphId idx, GraphId idy, int max_path_len,
913
0
                          GraphId path[]) const {
914
0
  Rep* r = rep_;
915
0
  if (FindNode(r, idx) == nullptr || FindNode(r, idy) == nullptr) return 0;
916
0
  const int32_t x = NodeIndex(idx);
917
0
  const int32_t y = NodeIndex(idy);
918
919
  // Forward depth first search starting at x until we hit y.
920
  // As we descend into a node, we push it onto the path.
921
  // As we leave a node, we remove it from the path.
922
0
  int path_len = 0;
923
924
0
  NodeSet seen;
925
0
  r->stack_.clear();
926
0
  r->stack_.push_back(x);
927
0
  while (!r->stack_.empty()) {
928
0
    int32_t n = r->stack_.back();
929
0
    r->stack_.pop_back();
930
0
    if (n < 0) {
931
      // Marker to indicate that we are leaving a node
932
0
      path_len--;
933
0
      continue;
934
0
    }
935
936
0
    if (path_len < max_path_len) {
937
0
      path[path_len] =
938
0
          MakeId(n, rep_->nodes_[static_cast<uint32_t>(n)]->version);
939
0
    }
940
0
    path_len++;
941
0
    r->stack_.push_back(-1);  // Will remove tentative path entry
942
943
0
    if (n == y) {
944
0
      return path_len;
945
0
    }
946
947
0
    HASH_FOR_EACH(w, r->nodes_[static_cast<uint32_t>(n)]->out) {
948
0
      if (seen.insert(w)) {
949
0
        r->stack_.push_back(w);
950
0
      }
951
0
    }
952
0
  }
953
954
0
  return 0;
955
0
}
956
957
0
bool GraphCycles::IsReachable(GraphId x, GraphId y) const {
958
0
  return FindPath(x, y, 0, nullptr) > 0;
959
0
}
960
961
void GraphCycles::UpdateStackTrace(GraphId id, int priority,
962
308k
                                   int (*get_stack_trace)(void** stack, int)) {
963
308k
  Node* n = FindNode(rep_, id);
964
308k
  if (n == nullptr || n->priority >= priority) {
965
307k
    return;
966
307k
  }
967
1.28k
  n->nstack =
968
1.28k
      (*get_stack_trace)(n->stack, static_cast<int>(std::size(n->stack)));
969
1.28k
  n->priority = priority;
970
1.28k
}
971
972
0
int GraphCycles::GetStackTrace(GraphId id, void*** ptr) {
973
0
  Node* n = FindNode(rep_, id);
974
0
  if (n == nullptr) {
975
0
    *ptr = nullptr;
976
0
    return 0;
977
0
  } else {
978
0
    *ptr = n->stack;
979
0
    return n->nstack;
980
0
  }
981
0
}
982
983
}  // namespace synchronization_internal
984
ABSL_NAMESPACE_END
985
}  // namespace absl
986
987
#endif  // ABSL_LOW_LEVEL_ALLOC_MISSING