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