Coverage Report

Created: 2026-09-14 06:45

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