Coverage Report

Created: 2026-07-26 07:11

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/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
1
static void InitArenaIfNecessary() {
61
1
  arena_mu.Lock();
62
1
  if (arena == nullptr) {
63
1
    arena = base_internal::LowLevelAlloc::NewArena(0);
64
1
  }
65
1
  arena_mu.Unlock();
66
1
}
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
23
  Vec() { Init(); }
graphcycles.cc:absl::lts_20240116::synchronization_internal::(anonymous namespace)::Vec<absl::lts_20240116::synchronization_internal::(anonymous namespace)::Node*>::Vec()
Line
Count
Source
78
1
  Vec() { Init(); }
graphcycles.cc:absl::lts_20240116::synchronization_internal::(anonymous namespace)::Vec<int>::Vec()
Line
Count
Source
78
22
  Vec() { Init(); }
79
0
  ~Vec() { Discard(); }
Unexecuted instantiation: graphcycles.cc:absl::lts_20240116::synchronization_internal::(anonymous namespace)::Vec<int>::~Vec()
Unexecuted instantiation: graphcycles.cc:absl::lts_20240116::synchronization_internal::(anonymous namespace)::Vec<absl::lts_20240116::synchronization_internal::(anonymous namespace)::Node*>::~Vec()
80
81
63.2k
  void clear() {
82
63.2k
    Discard();
83
63.2k
    Init();
84
63.2k
  }
85
86
40.8k
  bool empty() const { return size_ == 0; }
87
875k
  uint32_t size() const { return size_; }
graphcycles.cc:absl::lts_20240116::synchronization_internal::(anonymous namespace)::Vec<int>::size() const
Line
Count
Source
87
875k
  uint32_t size() const { return size_; }
graphcycles.cc:absl::lts_20240116::synchronization_internal::(anonymous namespace)::Vec<absl::lts_20240116::synchronization_internal::(anonymous namespace)::Node*>::size() const
Line
Count
Source
87
8
  uint32_t size() const { return size_; }
88
42.8k
  T* begin() { return ptr_; }
graphcycles.cc:absl::lts_20240116::synchronization_internal::(anonymous namespace)::Vec<int>::begin()
Line
Count
Source
88
42.8k
  T* begin() { return ptr_; }
Unexecuted instantiation: graphcycles.cc:absl::lts_20240116::synchronization_internal::(anonymous namespace)::Vec<absl::lts_20240116::synchronization_internal::(anonymous namespace)::Node*>::begin()
89
36.7k
  T* end() { return ptr_ + size_; }
graphcycles.cc:absl::lts_20240116::synchronization_internal::(anonymous namespace)::Vec<int>::end()
Line
Count
Source
89
36.7k
  T* end() { return ptr_ + size_; }
Unexecuted instantiation: graphcycles.cc:absl::lts_20240116::synchronization_internal::(anonymous namespace)::Vec<absl::lts_20240116::synchronization_internal::(anonymous namespace)::Node*>::end()
90
285k
  const T& operator[](uint32_t i) const { return ptr_[i]; }
graphcycles.cc:absl::lts_20240116::synchronization_internal::(anonymous namespace)::Vec<absl::lts_20240116::synchronization_internal::(anonymous namespace)::Node*>::operator[](unsigned int) const
Line
Count
Source
90
211k
  const T& operator[](uint32_t i) const { return ptr_[i]; }
graphcycles.cc:absl::lts_20240116::synchronization_internal::(anonymous namespace)::Vec<int>::operator[](unsigned int) const
Line
Count
Source
90
73.4k
  const T& operator[](uint32_t i) const { return ptr_[i]; }
91
972k
  T& operator[](uint32_t i) { return ptr_[i]; }
graphcycles.cc:absl::lts_20240116::synchronization_internal::(anonymous namespace)::Vec<int>::operator[](unsigned int)
Line
Count
Source
91
549k
  T& operator[](uint32_t i) { return ptr_[i]; }
graphcycles.cc:absl::lts_20240116::synchronization_internal::(anonymous namespace)::Vec<absl::lts_20240116::synchronization_internal::(anonymous namespace)::Node*>::operator[](unsigned int)
Line
Count
Source
91
423k
  T& operator[](uint32_t i) { return ptr_[i]; }
92
28.5k
  const T& back() const { return ptr_[size_-1]; }
93
28.5k
  void pop_back() { size_--; }
94
95
53.0k
  void push_back(const T& v) {
96
53.0k
    if (size_ == capacity_) Grow(size_ + 1);
97
53.0k
    ptr_[size_] = v;
98
53.0k
    size_++;
99
53.0k
  }
graphcycles.cc:absl::lts_20240116::synchronization_internal::(anonymous namespace)::Vec<absl::lts_20240116::synchronization_internal::(anonymous namespace)::Node*>::push_back(absl::lts_20240116::synchronization_internal::(anonymous namespace)::Node* const&)
Line
Count
Source
95
8
  void push_back(const T& v) {
96
8
    if (size_ == capacity_) Grow(size_ + 1);
97
8
    ptr_[size_] = v;
98
8
    size_++;
99
8
  }
graphcycles.cc:absl::lts_20240116::synchronization_internal::(anonymous namespace)::Vec<int>::push_back(int const&)
Line
Count
Source
95
53.0k
  void push_back(const T& v) {
96
53.0k
    if (size_ == capacity_) Grow(size_ + 1);
97
53.0k
    ptr_[size_] = v;
98
53.0k
    size_++;
99
53.0k
  }
100
101
38.7k
  void resize(uint32_t n) {
102
38.7k
    if (n > capacity_) Grow(n);
103
38.7k
    size_ = n;
104
38.7k
  }
105
106
32.6k
  void fill(const T& val) {
107
294k
    for (uint32_t i = 0; i < size(); i++) {
108
261k
      ptr_[i] = val;
109
261k
    }
110
32.6k
  }
111
112
  // Guarantees src is empty at end.
113
  // Provided for the hash table resizing code below.
114
0
  void MoveFrom(Vec<T>* src) {
115
0
    if (src->ptr_ == src->space_) {
116
      // Need to actually copy
117
0
      resize(src->size_);
118
0
      std::copy_n(src->ptr_, src->size_, ptr_);
119
0
      src->size_ = 0;
120
0
    } else {
121
0
      Discard();
122
0
      ptr_ = src->ptr_;
123
0
      size_ = src->size_;
124
0
      capacity_ = src->capacity_;
125
0
      src->Init();
126
0
    }
127
0
  }
128
129
 private:
130
  T* ptr_;
131
  T space_[kInline];
132
  uint32_t size_;
133
  uint32_t capacity_;
134
135
63.3k
  void Init() {
136
63.3k
    ptr_ = space_;
137
63.3k
    size_ = 0;
138
63.3k
    capacity_ = kInline;
139
63.3k
  }
graphcycles.cc:absl::lts_20240116::synchronization_internal::(anonymous namespace)::Vec<absl::lts_20240116::synchronization_internal::(anonymous namespace)::Node*>::Init()
Line
Count
Source
135
1
  void Init() {
136
1
    ptr_ = space_;
137
1
    size_ = 0;
138
1
    capacity_ = kInline;
139
1
  }
graphcycles.cc:absl::lts_20240116::synchronization_internal::(anonymous namespace)::Vec<int>::Init()
Line
Count
Source
135
63.3k
  void Init() {
136
63.3k
    ptr_ = space_;
137
63.3k
    size_ = 0;
138
63.3k
    capacity_ = kInline;
139
63.3k
  }
140
141
63.2k
  void Discard() {
142
63.2k
    if (ptr_ != space_) base_internal::LowLevelAlloc::Free(ptr_);
143
63.2k
  }
graphcycles.cc:absl::lts_20240116::synchronization_internal::(anonymous namespace)::Vec<int>::Discard()
Line
Count
Source
141
63.2k
  void Discard() {
142
63.2k
    if (ptr_ != space_) base_internal::LowLevelAlloc::Free(ptr_);
143
63.2k
  }
Unexecuted instantiation: graphcycles.cc:absl::lts_20240116::synchronization_internal::(anonymous namespace)::Vec<absl::lts_20240116::synchronization_internal::(anonymous namespace)::Node*>::Discard()
144
145
0
  void Grow(uint32_t n) {
146
0
    while (capacity_ < n) {
147
0
      capacity_ *= 2;
148
0
    }
149
0
    size_t request = static_cast<size_t>(capacity_) * sizeof(T);
150
0
    T* copy = static_cast<T*>(
151
0
        base_internal::LowLevelAlloc::AllocWithArena(request, arena));
152
0
    std::copy_n(ptr_, size_, copy);
153
0
    Discard();
154
0
    ptr_ = copy;
155
0
  }
Unexecuted instantiation: graphcycles.cc:absl::lts_20240116::synchronization_internal::(anonymous namespace)::Vec<int>::Grow(unsigned int)
Unexecuted instantiation: graphcycles.cc:absl::lts_20240116::synchronization_internal::(anonymous namespace)::Vec<absl::lts_20240116::synchronization_internal::(anonymous namespace)::Node*>::Grow(unsigned int)
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
16
  NodeSet() { Init(); }
165
166
32.6k
  void clear() { Init(); }
167
0
  bool contains(int32_t v) const { return table_[FindIndex(v)] == v; }
168
169
55.1k
  bool insert(int32_t v) {
170
55.1k
    uint32_t i = FindIndex(v);
171
55.1k
    if (table_[i] == v) {
172
18.3k
      return false;
173
18.3k
    }
174
36.7k
    if (table_[i] == kEmpty) {
175
      // Only inserting over an empty cell increases the number of occupied
176
      // slots.
177
36.7k
      occupied_++;
178
36.7k
    }
179
36.7k
    table_[i] = v;
180
    // Double when 75% full.
181
36.7k
    if (occupied_ >= table_.size() - table_.size()/4) Grow();
182
36.7k
    return true;
183
55.1k
  }
184
185
18.3k
  void erase(int32_t v) {
186
18.3k
    uint32_t i = FindIndex(v);
187
18.3k
    if (table_[i] == v) {
188
18.3k
      table_[i] = kDel;
189
18.3k
    }
190
18.3k
  }
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
65.3k
  for (int32_t elem, _cursor = 0; (eset).Next(&_cursor, &elem); )
197
65.3k
  bool Next(int32_t* cursor, int32_t* elem) {
198
404k
    while (static_cast<uint32_t>(*cursor) < table_.size()) {
199
359k
      int32_t v = table_[static_cast<uint32_t>(*cursor)];
200
359k
      (*cursor)++;
201
359k
      if (v >= 0) {
202
20.4k
        *elem = v;
203
20.4k
        return true;
204
20.4k
      }
205
359k
    }
206
44.9k
    return false;
207
65.3k
  }
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
73.4k
  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
73.4k
  uint32_t FindIndex(int32_t v) const {
218
    // Search starting at hash index.
219
73.4k
    const uint32_t mask = table_.size() - 1;
220
73.4k
    uint32_t i = Hash(v) & mask;
221
73.4k
    uint32_t deleted_index = 0;  // index of first deleted element we see
222
73.4k
    bool seen_deleted_element = false;
223
73.4k
    while (true) {
224
73.4k
      int32_t e = table_[i];
225
73.4k
      if (v == e) {
226
36.7k
        return i;
227
36.7k
      } else if (e == kEmpty) {
228
        // Return any previously encountered deleted slot.
229
36.7k
        return seen_deleted_element ? deleted_index : i;
230
36.7k
      } else if (e == kDel && !seen_deleted_element) {
231
        // Keep searching since v might be present later.
232
0
        deleted_index = i;
233
0
        seen_deleted_element = true;
234
0
      }
235
0
      i = (i + 1) & mask;  // Linear probing; quadratic is slightly slower.
236
0
    }
237
73.4k
  }
238
239
32.6k
  void Init() {
240
32.6k
    table_.clear();
241
32.6k
    table_.resize(kInline);
242
32.6k
    table_.fill(kEmpty);
243
32.6k
    occupied_ = 0;
244
32.6k
  }
245
246
0
  void Grow() {
247
0
    Vec<int32_t> copy;
248
0
    copy.MoveFrom(&table_);
249
0
    occupied_ = 0;
250
0
    table_.resize(copy.size() * 2);
251
0
    table_.fill(kEmpty);
252
253
0
    for (const auto& e : copy) {
254
0
      if (e >= 0) insert(e);
255
0
    }
256
0
  }
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
195k
inline GraphId MakeId(int32_t index, uint32_t version) {
267
195k
  GraphId g;
268
195k
  g.handle =
269
195k
      (static_cast<uint64_t>(version) << 32) | static_cast<uint32_t>(index);
270
195k
  return g;
271
195k
}
272
273
216k
inline int32_t NodeIndex(GraphId id) {
274
216k
  return static_cast<int32_t>(id.handle);
275
216k
}
276
277
142k
inline uint32_t NodeVersion(GraphId id) {
278
142k
  return static_cast<uint32_t>(id.handle >> 32);
279
142k
}
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
1
  explicit PointerMap(const Vec<Node*>* nodes) : nodes_(nodes) {
298
1
    table_.fill(-1);
299
1
  }
300
301
195k
  int32_t Find(void* ptr) {
302
195k
    auto masked = base_internal::HidePtr(ptr);
303
195k
    for (int32_t i = table_[Hash(ptr)]; i != -1;) {
304
178k
      Node* n = (*nodes_)[static_cast<uint32_t>(i)];
305
178k
      if (n->masked_ptr == masked) return i;
306
0
      i = n->next_hash;
307
0
    }
308
16.3k
    return -1;
309
195k
  }
310
311
16.3k
  void Add(void* ptr, int32_t i) {
312
16.3k
    int32_t* head = &table_[Hash(ptr)];
313
16.3k
    (*nodes_)[static_cast<uint32_t>(i)]->next_hash = *head;
314
16.3k
    *head = i;
315
16.3k
  }
316
317
18.3k
  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
18.3k
    auto masked = base_internal::HidePtr(ptr);
321
18.3k
    for (int32_t* slot = &table_[Hash(ptr)]; *slot != -1; ) {
322
16.3k
      int32_t index = *slot;
323
16.3k
      Node* n = (*nodes_)[static_cast<uint32_t>(index)];
324
16.3k
      if (n->masked_ptr == masked) {
325
16.3k
        *slot = n->next_hash;  // Remove n from linked list
326
16.3k
        n->next_hash = -1;
327
16.3k
        return index;
328
16.3k
      }
329
0
      slot = &n->next_hash;
330
0
    }
331
2.04k
    return -1;
332
18.3k
  }
333
334
 private:
335
  // Number of buckets in hash table for pointer lookups.
336
  static constexpr uint32_t kHashTableSize = 8171;  // should be prime
337
338
  const Vec<Node*>* nodes_;
339
  std::array<int32_t, kHashTableSize> table_;
340
341
229k
  static uint32_t Hash(void* ptr) {
342
229k
    return reinterpret_cast<uintptr_t>(ptr) % kHashTableSize;
343
229k
  }
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
1
  Rep() : ptrmap_(&nodes_) {}
361
};
362
363
142k
static Node* FindNode(GraphCycles::Rep* rep, GraphId id) {
364
142k
  Node* n = rep->nodes_[static_cast<uint32_t>(NodeIndex(id))];
365
142k
  return (n->version == NodeVersion(id)) ? n : nullptr;
366
142k
}
367
368
1
GraphCycles::GraphCycles() {
369
1
  InitArenaIfNecessary();
370
1
  rep_ = new (base_internal::LowLevelAlloc::AllocWithArena(sizeof(Rep), arena))
371
1
      Rep;
372
1
}
373
374
0
GraphCycles::~GraphCycles() {
375
0
  for (auto* node : rep_->nodes_) {
376
0
    node->Node::~Node();
377
0
    base_internal::LowLevelAlloc::Free(node);
378
0
  }
379
0
  rep_->Rep::~Rep();
380
0
  base_internal::LowLevelAlloc::Free(rep_);
381
0
}
382
383
0
bool GraphCycles::CheckInvariants() const {
384
0
  Rep* r = rep_;
385
0
  NodeSet ranks;  // Set of ranks seen so far.
386
0
  for (uint32_t x = 0; x < r->nodes_.size(); x++) {
387
0
    Node* nx = r->nodes_[x];
388
0
    void* ptr = base_internal::UnhidePtr<void>(nx->masked_ptr);
389
0
    if (ptr != nullptr && static_cast<uint32_t>(r->ptrmap_.Find(ptr)) != x) {
390
0
      ABSL_RAW_LOG(FATAL, "Did not find live node in hash table %" PRIu32 " %p",
391
0
                   x, ptr);
392
0
    }
393
0
    if (nx->visited) {
394
0
      ABSL_RAW_LOG(FATAL, "Did not clear visited marker on node %" PRIu32, x);
395
0
    }
396
0
    if (!ranks.insert(nx->rank)) {
397
0
      ABSL_RAW_LOG(FATAL, "Duplicate occurrence of rank %" PRId32, nx->rank);
398
0
    }
399
0
    HASH_FOR_EACH(y, nx->out) {
400
0
      Node* ny = r->nodes_[static_cast<uint32_t>(y)];
401
0
      if (nx->rank >= ny->rank) {
402
0
        ABSL_RAW_LOG(FATAL,
403
0
                     "Edge %" PRIu32 " ->%" PRId32
404
0
                     " has bad rank assignment %" PRId32 "->%" PRId32,
405
0
                     x, y, nx->rank, ny->rank);
406
0
      }
407
0
    }
408
0
  }
409
0
  return true;
410
0
}
411
412
195k
GraphId GraphCycles::GetId(void* ptr) {
413
195k
  int32_t i = rep_->ptrmap_.Find(ptr);
414
195k
  if (i != -1) {
415
178k
    return MakeId(i, rep_->nodes_[static_cast<uint32_t>(i)]->version);
416
178k
  } else if (rep_->free_nodes_.empty()) {
417
8
    Node* n =
418
8
        new (base_internal::LowLevelAlloc::AllocWithArena(sizeof(Node), arena))
419
8
            Node;
420
8
    n->version = 1;  // Avoid 0 since it is used by InvalidGraphId()
421
8
    n->visited = false;
422
8
    n->rank = static_cast<int32_t>(rep_->nodes_.size());
423
8
    n->masked_ptr = base_internal::HidePtr(ptr);
424
8
    n->nstack = 0;
425
8
    n->priority = 0;
426
8
    rep_->nodes_.push_back(n);
427
8
    rep_->ptrmap_.Add(ptr, n->rank);
428
8
    return MakeId(n->rank, n->version);
429
16.3k
  } else {
430
    // Preserve preceding rank since the set of ranks in use must be
431
    // a permutation of [0,rep_->nodes_.size()-1].
432
16.3k
    int32_t r = rep_->free_nodes_.back();
433
16.3k
    rep_->free_nodes_.pop_back();
434
16.3k
    Node* n = rep_->nodes_[static_cast<uint32_t>(r)];
435
16.3k
    n->masked_ptr = base_internal::HidePtr(ptr);
436
16.3k
    n->nstack = 0;
437
16.3k
    n->priority = 0;
438
16.3k
    rep_->ptrmap_.Add(ptr, r);
439
16.3k
    return MakeId(r, n->version);
440
16.3k
  }
441
195k
}
442
443
18.3k
void GraphCycles::RemoveNode(void* ptr) {
444
18.3k
  int32_t i = rep_->ptrmap_.Remove(ptr);
445
18.3k
  if (i == -1) {
446
2.04k
    return;
447
2.04k
  }
448
16.3k
  Node* x = rep_->nodes_[static_cast<uint32_t>(i)];
449
16.3k
  HASH_FOR_EACH(y, x->out) {
450
6.12k
    rep_->nodes_[static_cast<uint32_t>(y)]->in.erase(i);
451
6.12k
  }
452
16.3k
  HASH_FOR_EACH(y, x->in) {
453
12.2k
    rep_->nodes_[static_cast<uint32_t>(y)]->out.erase(i);
454
12.2k
  }
455
16.3k
  x->in.clear();
456
16.3k
  x->out.clear();
457
16.3k
  x->masked_ptr = base_internal::HidePtr<void>(nullptr);
458
16.3k
  if (x->version == std::numeric_limits<uint32_t>::max()) {
459
    // Cannot use x any more
460
16.3k
  } else {
461
16.3k
    x->version++;  // Invalidates all copies of node.
462
16.3k
    rep_->free_nodes_.push_back(i);
463
16.3k
  }
464
16.3k
}
465
466
36.7k
void* GraphCycles::Ptr(GraphId id) {
467
36.7k
  Node* n = FindNode(rep_, id);
468
36.7k
  return n == nullptr ? nullptr
469
36.7k
                      : base_internal::UnhidePtr<void>(n->masked_ptr);
470
36.7k
}
471
472
0
bool GraphCycles::HasNode(GraphId node) {
473
0
  return FindNode(rep_, node) != nullptr;
474
0
}
475
476
0
bool GraphCycles::HasEdge(GraphId x, GraphId y) const {
477
0
  Node* xn = FindNode(rep_, x);
478
0
  return xn && FindNode(rep_, y) && xn->out.contains(NodeIndex(y));
479
0
}
480
481
0
void GraphCycles::RemoveEdge(GraphId x, GraphId y) {
482
0
  Node* xn = FindNode(rep_, x);
483
0
  Node* yn = FindNode(rep_, y);
484
0
  if (xn && yn) {
485
0
    xn->out.erase(NodeIndex(y));
486
0
    yn->in.erase(NodeIndex(x));
487
    // No need to update the rank assignment since a previous valid
488
    // rank assignment remains valid after an edge deletion.
489
0
  }
490
0
}
491
492
static bool ForwardDFS(GraphCycles::Rep* r, int32_t n, int32_t upper_bound);
493
static void BackwardDFS(GraphCycles::Rep* r, int32_t n, int32_t lower_bound);
494
static void Reorder(GraphCycles::Rep* r);
495
static void Sort(const Vec<Node*>&, Vec<int32_t>* delta);
496
static void MoveToList(
497
    GraphCycles::Rep* r, Vec<int32_t>* src, Vec<int32_t>* dst);
498
499
36.7k
bool GraphCycles::InsertEdge(GraphId idx, GraphId idy) {
500
36.7k
  Rep* r = rep_;
501
36.7k
  const int32_t x = NodeIndex(idx);
502
36.7k
  const int32_t y = NodeIndex(idy);
503
36.7k
  Node* nx = FindNode(r, idx);
504
36.7k
  Node* ny = FindNode(r, idy);
505
36.7k
  if (nx == nullptr || ny == nullptr) return true;  // Expired ids
506
507
36.7k
  if (nx == ny) return false;  // Self edge
508
36.7k
  if (!nx->out.insert(y)) {
509
    // Edge already exists.
510
18.3k
    return true;
511
18.3k
  }
512
513
18.3k
  ny->in.insert(x);
514
515
18.3k
  if (nx->rank <= ny->rank) {
516
    // New edge is consistent with existing rank assignment.
517
12.2k
    return true;
518
12.2k
  }
519
520
  // Current rank assignments are incompatible with the new edge.  Recompute.
521
  // We only need to consider nodes that fall in the range [ny->rank,nx->rank].
522
6.12k
  if (!ForwardDFS(r, y, nx->rank)) {
523
    // Found a cycle.  Undo the insertion and tell caller.
524
0
    nx->out.erase(y);
525
0
    ny->in.erase(x);
526
    // Since we do not call Reorder() on this path, clear any visited
527
    // markers left by ForwardDFS.
528
0
    for (const auto& d : r->deltaf_) {
529
0
      r->nodes_[static_cast<uint32_t>(d)]->visited = false;
530
0
    }
531
0
    return false;
532
0
  }
533
6.12k
  BackwardDFS(r, x, ny->rank);
534
6.12k
  Reorder(r);
535
6.12k
  return true;
536
6.12k
}
537
538
6.12k
static bool ForwardDFS(GraphCycles::Rep* r, int32_t n, int32_t upper_bound) {
539
  // Avoid recursion since stack space might be limited.
540
  // We instead keep a stack of nodes to visit.
541
6.12k
  r->deltaf_.clear();
542
6.12k
  r->stack_.clear();
543
6.12k
  r->stack_.push_back(n);
544
12.2k
  while (!r->stack_.empty()) {
545
6.12k
    n = r->stack_.back();
546
6.12k
    r->stack_.pop_back();
547
6.12k
    Node* nn = r->nodes_[static_cast<uint32_t>(n)];
548
6.12k
    if (nn->visited) continue;
549
550
6.12k
    nn->visited = true;
551
6.12k
    r->deltaf_.push_back(n);
552
553
6.12k
    HASH_FOR_EACH(w, nn->out) {
554
2
      Node* nw = r->nodes_[static_cast<uint32_t>(w)];
555
2
      if (nw->rank == upper_bound) {
556
0
        return false;  // Cycle
557
0
      }
558
2
      if (!nw->visited && nw->rank < upper_bound) {
559
0
        r->stack_.push_back(w);
560
0
      }
561
2
    }
562
6.12k
  }
563
6.12k
  return true;
564
6.12k
}
565
566
6.12k
static void BackwardDFS(GraphCycles::Rep* r, int32_t n, int32_t lower_bound) {
567
6.12k
  r->deltab_.clear();
568
6.12k
  r->stack_.clear();
569
6.12k
  r->stack_.push_back(n);
570
12.2k
  while (!r->stack_.empty()) {
571
6.12k
    n = r->stack_.back();
572
6.12k
    r->stack_.pop_back();
573
6.12k
    Node* nn = r->nodes_[static_cast<uint32_t>(n)];
574
6.12k
    if (nn->visited) continue;
575
576
6.12k
    nn->visited = true;
577
6.12k
    r->deltab_.push_back(n);
578
579
6.12k
    HASH_FOR_EACH(w, nn->in) {
580
2.04k
      Node* nw = r->nodes_[static_cast<uint32_t>(w)];
581
2.04k
      if (!nw->visited && lower_bound < nw->rank) {
582
0
        r->stack_.push_back(w);
583
0
      }
584
2.04k
    }
585
6.12k
  }
586
6.12k
}
587
588
6.12k
static void Reorder(GraphCycles::Rep* r) {
589
6.12k
  Sort(r->nodes_, &r->deltab_);
590
6.12k
  Sort(r->nodes_, &r->deltaf_);
591
592
  // Adds contents of delta lists to list_ (backwards deltas first).
593
6.12k
  r->list_.clear();
594
6.12k
  MoveToList(r, &r->deltab_, &r->list_);
595
6.12k
  MoveToList(r, &r->deltaf_, &r->list_);
596
597
  // Produce sorted list of all ranks that will be reassigned.
598
6.12k
  r->merged_.resize(r->deltab_.size() + r->deltaf_.size());
599
6.12k
  std::merge(r->deltab_.begin(), r->deltab_.end(),
600
6.12k
             r->deltaf_.begin(), r->deltaf_.end(),
601
6.12k
             r->merged_.begin());
602
603
  // Assign the ranks in order to the collected list.
604
18.3k
  for (uint32_t i = 0; i < r->list_.size(); i++) {
605
12.2k
    r->nodes_[static_cast<uint32_t>(r->list_[i])]->rank = r->merged_[i];
606
12.2k
  }
607
6.12k
}
608
609
12.2k
static void Sort(const Vec<Node*>& nodes, Vec<int32_t>* delta) {
610
12.2k
  struct ByRank {
611
12.2k
    const Vec<Node*>* nodes;
612
12.2k
    bool operator()(int32_t a, int32_t b) const {
613
0
      return (*nodes)[static_cast<uint32_t>(a)]->rank <
614
0
             (*nodes)[static_cast<uint32_t>(b)]->rank;
615
0
    }
616
12.2k
  };
617
12.2k
  ByRank cmp;
618
12.2k
  cmp.nodes = &nodes;
619
12.2k
  std::sort(delta->begin(), delta->end(), cmp);
620
12.2k
}
621
622
static void MoveToList(
623
12.2k
    GraphCycles::Rep* r, Vec<int32_t>* src, Vec<int32_t>* dst) {
624
12.2k
  for (auto& v : *src) {
625
12.2k
    int32_t w = v;
626
    // Replace v entry with its rank
627
12.2k
    v = r->nodes_[static_cast<uint32_t>(w)]->rank;
628
    // Prepare for future DFS calls
629
12.2k
    r->nodes_[static_cast<uint32_t>(w)]->visited = false;
630
12.2k
    dst->push_back(w);
631
12.2k
  }
632
12.2k
}
633
634
int GraphCycles::FindPath(GraphId idx, GraphId idy, int max_path_len,
635
0
                          GraphId path[]) const {
636
0
  Rep* r = rep_;
637
0
  if (FindNode(r, idx) == nullptr || FindNode(r, idy) == nullptr) return 0;
638
0
  const int32_t x = NodeIndex(idx);
639
0
  const int32_t y = NodeIndex(idy);
640
641
  // Forward depth first search starting at x until we hit y.
642
  // As we descend into a node, we push it onto the path.
643
  // As we leave a node, we remove it from the path.
644
0
  int path_len = 0;
645
646
0
  NodeSet seen;
647
0
  r->stack_.clear();
648
0
  r->stack_.push_back(x);
649
0
  while (!r->stack_.empty()) {
650
0
    int32_t n = r->stack_.back();
651
0
    r->stack_.pop_back();
652
0
    if (n < 0) {
653
      // Marker to indicate that we are leaving a node
654
0
      path_len--;
655
0
      continue;
656
0
    }
657
658
0
    if (path_len < max_path_len) {
659
0
      path[path_len] =
660
0
          MakeId(n, rep_->nodes_[static_cast<uint32_t>(n)]->version);
661
0
    }
662
0
    path_len++;
663
0
    r->stack_.push_back(-1);  // Will remove tentative path entry
664
665
0
    if (n == y) {
666
0
      return path_len;
667
0
    }
668
669
0
    HASH_FOR_EACH(w, r->nodes_[static_cast<uint32_t>(n)]->out) {
670
0
      if (seen.insert(w)) {
671
0
        r->stack_.push_back(w);
672
0
      }
673
0
    }
674
0
  }
675
676
0
  return 0;
677
0
}
678
679
0
bool GraphCycles::IsReachable(GraphId x, GraphId y) const {
680
0
  return FindPath(x, y, 0, nullptr) > 0;
681
0
}
682
683
void GraphCycles::UpdateStackTrace(GraphId id, int priority,
684
32.6k
                                   int (*get_stack_trace)(void** stack, int)) {
685
32.6k
  Node* n = FindNode(rep_, id);
686
32.6k
  if (n == nullptr || n->priority >= priority) {
687
20.4k
    return;
688
20.4k
  }
689
12.2k
  n->nstack = (*get_stack_trace)(n->stack, ABSL_ARRAYSIZE(n->stack));
690
12.2k
  n->priority = priority;
691
12.2k
}
692
693
0
int GraphCycles::GetStackTrace(GraphId id, void*** ptr) {
694
0
  Node* n = FindNode(rep_, id);
695
0
  if (n == nullptr) {
696
0
    *ptr = nullptr;
697
0
    return 0;
698
0
  } else {
699
0
    *ptr = n->stack;
700
0
    return n->nstack;
701
0
  }
702
0
}
703
704
}  // namespace synchronization_internal
705
ABSL_NAMESPACE_END
706
}  // namespace absl
707
708
#endif  // ABSL_LOW_LEVEL_ALLOC_MISSING