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