/work/obj-fuzz/dist/include/js/UbiNodeDominatorTree.h
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
2 | | * vim: set ts=8 sts=4 et sw=4 tw=99: |
3 | | * This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #ifndef js_UbiNodeDominatorTree_h |
8 | | #define js_UbiNodeDominatorTree_h |
9 | | |
10 | | #include "mozilla/Attributes.h" |
11 | | #include "mozilla/DebugOnly.h" |
12 | | #include "mozilla/Maybe.h" |
13 | | #include "mozilla/Move.h" |
14 | | #include "mozilla/UniquePtr.h" |
15 | | |
16 | | #include "js/AllocPolicy.h" |
17 | | #include "js/UbiNode.h" |
18 | | #include "js/UbiNodePostOrder.h" |
19 | | #include "js/Utility.h" |
20 | | #include "js/Vector.h" |
21 | | |
22 | | namespace JS { |
23 | | namespace ubi { |
24 | | |
25 | | /** |
26 | | * In a directed graph with a root node `R`, a node `A` is said to "dominate" a |
27 | | * node `B` iff every path from `R` to `B` contains `A`. A node `A` is said to |
28 | | * be the "immediate dominator" of a node `B` iff it dominates `B`, is not `B` |
29 | | * itself, and does not dominate any other nodes which also dominate `B` in |
30 | | * turn. |
31 | | * |
32 | | * If we take every node from a graph `G` and create a new graph `T` with edges |
33 | | * to each node from its immediate dominator, then `T` is a tree (each node has |
34 | | * only one immediate dominator, or none if it is the root). This tree is called |
35 | | * a "dominator tree". |
36 | | * |
37 | | * This class represents a dominator tree constructed from a `JS::ubi::Node` |
38 | | * heap graph. The domination relationship and dominator trees are useful tools |
39 | | * for analyzing heap graphs because they tell you: |
40 | | * |
41 | | * - Exactly what could be reclaimed by the GC if some node `A` became |
42 | | * unreachable: those nodes which are dominated by `A`, |
43 | | * |
44 | | * - The "retained size" of a node in the heap graph, in contrast to its |
45 | | * "shallow size". The "shallow size" is the space taken by a node itself, |
46 | | * not counting anything it references. The "retained size" of a node is its |
47 | | * shallow size plus the size of all the things that would be collected if |
48 | | * the original node wasn't (directly or indirectly) referencing them. In |
49 | | * other words, the retained size is the shallow size of a node plus the |
50 | | * shallow sizes of every other node it dominates. For example, the root |
51 | | * node in a binary tree might have a small shallow size that does not take |
52 | | * up much space itself, but it dominates the rest of the binary tree and |
53 | | * its retained size is therefore significant (assuming no external |
54 | | * references into the tree). |
55 | | * |
56 | | * The simple, engineered algorithm presented in "A Simple, Fast Dominance |
57 | | * Algorithm" by Cooper el al[0] is used to find dominators and construct the |
58 | | * dominator tree. This algorithm runs in O(n^2) time, but is faster in practice |
59 | | * than alternative algorithms with better theoretical running times, such as |
60 | | * Lengauer-Tarjan which runs in O(e * log(n)). The big caveat to that statement |
61 | | * is that Cooper et al found it is faster in practice *on control flow graphs* |
62 | | * and I'm not convinced that this property also holds on *heap* graphs. That |
63 | | * said, the implementation of this algorithm is *much* simpler than |
64 | | * Lengauer-Tarjan and has been found to be fast enough at least for the time |
65 | | * being. |
66 | | * |
67 | | * [0]: http://www.cs.rice.edu/~keith/EMBED/dom.pdf |
68 | | */ |
69 | | class JS_PUBLIC_API(DominatorTree) |
70 | | { |
71 | | private: |
72 | | // Types. |
73 | | |
74 | | using PredecessorSets = js::HashMap<Node, NodeSetPtr, js::DefaultHasher<Node>, |
75 | | js::SystemAllocPolicy>; |
76 | | using NodeToIndexMap = js::HashMap<Node, uint32_t, js::DefaultHasher<Node>, |
77 | | js::SystemAllocPolicy>; |
78 | | class DominatedSets; |
79 | | |
80 | | public: |
81 | | class DominatedSetRange; |
82 | | |
83 | | /** |
84 | | * A pointer to an immediately dominated node. |
85 | | * |
86 | | * Don't use this type directly; it is no safer than regular pointers. This |
87 | | * is only for use indirectly with range-based for loops and |
88 | | * `DominatedSetRange`. |
89 | | * |
90 | | * @see JS::ubi::DominatorTree::getDominatedSet |
91 | | */ |
92 | | class DominatedNodePtr |
93 | | { |
94 | | friend class DominatedSetRange; |
95 | | |
96 | | const JS::ubi::Vector<Node>& postOrder; |
97 | | const uint32_t* ptr; |
98 | | |
99 | | DominatedNodePtr(const JS::ubi::Vector<Node>& postOrder, const uint32_t* ptr) |
100 | | : postOrder(postOrder) |
101 | | , ptr(ptr) |
102 | 0 | { } |
103 | | |
104 | | public: |
105 | 0 | bool operator!=(const DominatedNodePtr& rhs) const { return ptr != rhs.ptr; } |
106 | 0 | void operator++() { ptr++; } |
107 | 0 | const Node& operator*() const { return postOrder[*ptr]; } |
108 | | }; |
109 | | |
110 | | /** |
111 | | * A range of immediately dominated `JS::ubi::Node`s for use with |
112 | | * range-based for loops. |
113 | | * |
114 | | * @see JS::ubi::DominatorTree::getDominatedSet |
115 | | */ |
116 | | class DominatedSetRange |
117 | | { |
118 | | friend class DominatedSets; |
119 | | |
120 | | const JS::ubi::Vector<Node>& postOrder; |
121 | | const uint32_t* beginPtr; |
122 | | const uint32_t* endPtr; |
123 | | |
124 | | DominatedSetRange(JS::ubi::Vector<Node>& postOrder, const uint32_t* begin, const uint32_t* end) |
125 | | : postOrder(postOrder) |
126 | | , beginPtr(begin) |
127 | | , endPtr(end) |
128 | 0 | { |
129 | 0 | MOZ_ASSERT(begin <= end); |
130 | 0 | } |
131 | | |
132 | | public: |
133 | 0 | DominatedNodePtr begin() const { |
134 | 0 | MOZ_ASSERT(beginPtr <= endPtr); |
135 | 0 | return DominatedNodePtr(postOrder, beginPtr); |
136 | 0 | } |
137 | | |
138 | 0 | DominatedNodePtr end() const { |
139 | 0 | return DominatedNodePtr(postOrder, endPtr); |
140 | 0 | } |
141 | | |
142 | 0 | size_t length() const { |
143 | 0 | MOZ_ASSERT(beginPtr <= endPtr); |
144 | 0 | return endPtr - beginPtr; |
145 | 0 | } |
146 | | |
147 | | /** |
148 | | * Safely skip ahead `n` dominators in the range, in O(1) time. |
149 | | * |
150 | | * Example usage: |
151 | | * |
152 | | * mozilla::Maybe<DominatedSetRange> range = myDominatorTree.getDominatedSet(myNode); |
153 | | * if (range.isNothing()) { |
154 | | * // Handle unknown nodes however you see fit... |
155 | | * return false; |
156 | | * } |
157 | | * |
158 | | * // Don't care about the first ten, for whatever reason. |
159 | | * range->skip(10); |
160 | | * for (const JS::ubi::Node& dominatedNode : *range) { |
161 | | * // ... |
162 | | * } |
163 | | */ |
164 | 0 | void skip(size_t n) { |
165 | 0 | beginPtr += n; |
166 | 0 | if (beginPtr > endPtr) { |
167 | 0 | beginPtr = endPtr; |
168 | 0 | } |
169 | 0 | } |
170 | | }; |
171 | | |
172 | | private: |
173 | | /** |
174 | | * The set of all dominated sets in a dominator tree. |
175 | | * |
176 | | * Internally stores the sets in a contiguous array, with a side table of |
177 | | * indices into that contiguous array to denote the start index of each |
178 | | * individual set. |
179 | | */ |
180 | | class DominatedSets |
181 | | { |
182 | | JS::ubi::Vector<uint32_t> dominated; |
183 | | JS::ubi::Vector<uint32_t> indices; |
184 | | |
185 | | DominatedSets(JS::ubi::Vector<uint32_t>&& dominated, JS::ubi::Vector<uint32_t>&& indices) |
186 | | : dominated(std::move(dominated)) |
187 | | , indices(std::move(indices)) |
188 | 0 | { } |
189 | | |
190 | | public: |
191 | | // DominatedSets is not copy-able. |
192 | | DominatedSets(const DominatedSets& rhs) = delete; |
193 | | DominatedSets& operator=(const DominatedSets& rhs) = delete; |
194 | | |
195 | | // DominatedSets is move-able. |
196 | | DominatedSets(DominatedSets&& rhs) |
197 | | : dominated(std::move(rhs.dominated)) |
198 | | , indices(std::move(rhs.indices)) |
199 | 0 | { |
200 | 0 | MOZ_ASSERT(this != &rhs, "self-move not allowed"); |
201 | 0 | } |
202 | 0 | DominatedSets& operator=(DominatedSets&& rhs) { |
203 | 0 | this->~DominatedSets(); |
204 | 0 | new (this) DominatedSets(std::move(rhs)); |
205 | 0 | return *this; |
206 | 0 | } |
207 | | |
208 | | /** |
209 | | * Create the DominatedSets given the mapping of a node index to its |
210 | | * immediate dominator. Returns `Some` on success, `Nothing` on OOM |
211 | | * failure. |
212 | | */ |
213 | 0 | static mozilla::Maybe<DominatedSets> Create(const JS::ubi::Vector<uint32_t>& doms) { |
214 | 0 | auto length = doms.length(); |
215 | 0 | MOZ_ASSERT(length < UINT32_MAX); |
216 | 0 |
|
217 | 0 | // Create a vector `dominated` holding a flattened set of buckets of |
218 | 0 | // immediately dominated children nodes, with a lookup table |
219 | 0 | // `indices` mapping from each node to the beginning of its bucket. |
220 | 0 | // |
221 | 0 | // This has three phases: |
222 | 0 | // |
223 | 0 | // 1. Iterate over the full set of nodes and count up the size of |
224 | 0 | // each bucket. These bucket sizes are temporarily stored in the |
225 | 0 | // `indices` vector. |
226 | 0 | // |
227 | 0 | // 2. Convert the `indices` vector to store the cumulative sum of |
228 | 0 | // the sizes of all buckets before each index, resulting in a |
229 | 0 | // mapping from node index to one past the end of that node's |
230 | 0 | // bucket. |
231 | 0 | // |
232 | 0 | // 3. Iterate over the full set of nodes again, filling in bucket |
233 | 0 | // entries from the end of the bucket's range to its |
234 | 0 | // beginning. This decrements each index as a bucket entry is |
235 | 0 | // filled in. After having filled in all of a bucket's entries, |
236 | 0 | // the index points to the start of the bucket. |
237 | 0 |
|
238 | 0 | JS::ubi::Vector<uint32_t> dominated; |
239 | 0 | JS::ubi::Vector<uint32_t> indices; |
240 | 0 | if (!dominated.growBy(length) || !indices.growBy(length)) { |
241 | 0 | return mozilla::Nothing(); |
242 | 0 | } |
243 | 0 | |
244 | 0 | // 1 |
245 | 0 | memset(indices.begin(), 0, length * sizeof(uint32_t)); |
246 | 0 | for (uint32_t i = 0; i < length; i++) { |
247 | 0 | indices[doms[i]]++; |
248 | 0 | } |
249 | 0 |
|
250 | 0 | // 2 |
251 | 0 | uint32_t sumOfSizes = 0; |
252 | 0 | for (uint32_t i = 0; i < length; i++) { |
253 | 0 | sumOfSizes += indices[i]; |
254 | 0 | MOZ_ASSERT(sumOfSizes <= length); |
255 | 0 | indices[i] = sumOfSizes; |
256 | 0 | } |
257 | 0 |
|
258 | 0 | // 3 |
259 | 0 | for (uint32_t i = 0; i < length; i++) { |
260 | 0 | auto idxOfDom = doms[i]; |
261 | 0 | indices[idxOfDom]--; |
262 | 0 | dominated[indices[idxOfDom]] = i; |
263 | 0 | } |
264 | 0 |
|
265 | | #ifdef DEBUG |
266 | | // Assert that our buckets are non-overlapping and don't run off the |
267 | | // end of the vector. |
268 | | uint32_t lastIndex = 0; |
269 | | for (uint32_t i = 0; i < length; i++) { |
270 | | MOZ_ASSERT(indices[i] >= lastIndex); |
271 | | MOZ_ASSERT(indices[i] < length); |
272 | | lastIndex = indices[i]; |
273 | | } |
274 | | #endif |
275 | |
|
276 | 0 | return mozilla::Some(DominatedSets(std::move(dominated), std::move(indices))); |
277 | 0 | } |
278 | | |
279 | | /** |
280 | | * Get the set of nodes immediately dominated by the node at |
281 | | * `postOrder[nodeIndex]`. |
282 | | */ |
283 | 0 | DominatedSetRange dominatedSet(JS::ubi::Vector<Node>& postOrder, uint32_t nodeIndex) const { |
284 | 0 | MOZ_ASSERT(postOrder.length() == indices.length()); |
285 | 0 | MOZ_ASSERT(nodeIndex < indices.length()); |
286 | 0 | auto end = nodeIndex == indices.length() - 1 |
287 | 0 | ? dominated.end() |
288 | 0 | : &dominated[indices[nodeIndex + 1]]; |
289 | 0 | return DominatedSetRange(postOrder, &dominated[indices[nodeIndex]], end); |
290 | 0 | } |
291 | | }; |
292 | | |
293 | | private: |
294 | | // Data members. |
295 | | JS::ubi::Vector<Node> postOrder; |
296 | | NodeToIndexMap nodeToPostOrderIndex; |
297 | | JS::ubi::Vector<uint32_t> doms; |
298 | | DominatedSets dominatedSets; |
299 | | mozilla::Maybe<JS::ubi::Vector<JS::ubi::Node::Size>> retainedSizes; |
300 | | |
301 | | private: |
302 | | // We use `UNDEFINED` as a sentinel value in the `doms` vector to signal |
303 | | // that we haven't found any dominators for the node at the corresponding |
304 | | // index in `postOrder` yet. |
305 | | static const uint32_t UNDEFINED = UINT32_MAX; |
306 | | |
307 | | DominatorTree(JS::ubi::Vector<Node>&& postOrder, NodeToIndexMap&& nodeToPostOrderIndex, |
308 | | JS::ubi::Vector<uint32_t>&& doms, DominatedSets&& dominatedSets) |
309 | | : postOrder(std::move(postOrder)) |
310 | | , nodeToPostOrderIndex(std::move(nodeToPostOrderIndex)) |
311 | | , doms(std::move(doms)) |
312 | | , dominatedSets(std::move(dominatedSets)) |
313 | | , retainedSizes(mozilla::Nothing()) |
314 | 0 | { } |
315 | | |
316 | 0 | static uint32_t intersect(JS::ubi::Vector<uint32_t>& doms, uint32_t finger1, uint32_t finger2) { |
317 | 0 | while (finger1 != finger2) { |
318 | 0 | if (finger1 < finger2) { |
319 | 0 | finger1 = doms[finger1]; |
320 | 0 | } else if (finger2 < finger1) { |
321 | 0 | finger2 = doms[finger2]; |
322 | 0 | } |
323 | 0 | } |
324 | 0 | return finger1; |
325 | 0 | } |
326 | | |
327 | | // Do the post order traversal of the heap graph and populate our |
328 | | // predecessor sets. |
329 | | static MOZ_MUST_USE bool doTraversal(JSContext* cx, AutoCheckCannotGC& noGC, const Node& root, |
330 | | JS::ubi::Vector<Node>& postOrder, |
331 | 0 | PredecessorSets& predecessorSets) { |
332 | 0 | uint32_t nodeCount = 0; |
333 | 0 | auto onNode = [&](const Node& node) { |
334 | 0 | nodeCount++; |
335 | 0 | if (MOZ_UNLIKELY(nodeCount == UINT32_MAX)) { |
336 | 0 | return false; |
337 | 0 | } |
338 | 0 | return postOrder.append(node); |
339 | 0 | }; |
340 | 0 |
|
341 | 0 | auto onEdge = [&](const Node& origin, const Edge& edge) { |
342 | 0 | auto p = predecessorSets.lookupForAdd(edge.referent); |
343 | 0 | if (!p) { |
344 | 0 | mozilla::UniquePtr<NodeSet, DeletePolicy<NodeSet>> set(js_new<NodeSet>()); |
345 | 0 | if (!set || |
346 | 0 | !predecessorSets.add(p, edge.referent, std::move(set))) |
347 | 0 | { |
348 | 0 | return false; |
349 | 0 | } |
350 | 0 | } |
351 | 0 | MOZ_ASSERT(p && p->value()); |
352 | 0 | return p->value()->put(origin); |
353 | 0 | }; |
354 | 0 |
|
355 | 0 | PostOrder traversal(cx, noGC); |
356 | 0 | return traversal.addStart(root) && |
357 | 0 | traversal.traverse(onNode, onEdge); |
358 | 0 | } |
359 | | |
360 | | // Populates the given `map` with an entry for each node to its index in |
361 | | // `postOrder`. |
362 | | static MOZ_MUST_USE bool mapNodesToTheirIndices(JS::ubi::Vector<Node>& postOrder, |
363 | 0 | NodeToIndexMap& map) { |
364 | 0 | MOZ_ASSERT(map.empty()); |
365 | 0 | MOZ_ASSERT(postOrder.length() < UINT32_MAX); |
366 | 0 | uint32_t length = postOrder.length(); |
367 | 0 | if (!map.reserve(length)) { |
368 | 0 | return false; |
369 | 0 | } |
370 | 0 | for (uint32_t i = 0; i < length; i++) { |
371 | 0 | map.putNewInfallible(postOrder[i], i); |
372 | 0 | } |
373 | 0 | return true; |
374 | 0 | } |
375 | | |
376 | | // Convert the Node -> NodeSet predecessorSets to a index -> Vector<index> |
377 | | // form. |
378 | | static MOZ_MUST_USE bool convertPredecessorSetsToVectors( |
379 | | const Node& root, |
380 | | JS::ubi::Vector<Node>& postOrder, |
381 | | PredecessorSets& predecessorSets, |
382 | | NodeToIndexMap& nodeToPostOrderIndex, |
383 | | JS::ubi::Vector<JS::ubi::Vector<uint32_t>>& predecessorVectors) |
384 | 0 | { |
385 | 0 | MOZ_ASSERT(postOrder.length() < UINT32_MAX); |
386 | 0 | uint32_t length = postOrder.length(); |
387 | 0 |
|
388 | 0 | MOZ_ASSERT(predecessorVectors.length() == 0); |
389 | 0 | if (!predecessorVectors.growBy(length)) { |
390 | 0 | return false; |
391 | 0 | } |
392 | 0 | |
393 | 0 | for (uint32_t i = 0; i < length - 1; i++) { |
394 | 0 | auto& node = postOrder[i]; |
395 | 0 | MOZ_ASSERT(node != root, |
396 | 0 | "Only the last node should be root, since this was a post order traversal."); |
397 | 0 |
|
398 | 0 | auto ptr = predecessorSets.lookup(node); |
399 | 0 | MOZ_ASSERT(ptr, |
400 | 0 | "Because this isn't the root, it had better have predecessors, or else how " |
401 | 0 | "did we even find it."); |
402 | 0 |
|
403 | 0 | auto& predecessors = ptr->value(); |
404 | 0 | if (!predecessorVectors[i].reserve(predecessors->count())) { |
405 | 0 | return false; |
406 | 0 | } |
407 | 0 | for (auto range = predecessors->all(); !range.empty(); range.popFront()) { |
408 | 0 | auto ptr = nodeToPostOrderIndex.lookup(range.front()); |
409 | 0 | MOZ_ASSERT(ptr); |
410 | 0 | predecessorVectors[i].infallibleAppend(ptr->value()); |
411 | 0 | } |
412 | 0 | } |
413 | 0 | predecessorSets.clearAndCompact(); |
414 | 0 | return true; |
415 | 0 | } |
416 | | |
417 | | // Initialize `doms` such that the immediate dominator of the `root` is the |
418 | | // `root` itself and all others are `UNDEFINED`. |
419 | | static MOZ_MUST_USE bool initializeDominators(JS::ubi::Vector<uint32_t>& doms, |
420 | 0 | uint32_t length) { |
421 | 0 | MOZ_ASSERT(doms.length() == 0); |
422 | 0 | if (!doms.growByUninitialized(length)) { |
423 | 0 | return false; |
424 | 0 | } |
425 | 0 | doms[length - 1] = length - 1; |
426 | 0 | for (uint32_t i = 0; i < length - 1; i++) { |
427 | 0 | doms[i] = UNDEFINED; |
428 | 0 | } |
429 | 0 | return true; |
430 | 0 | } |
431 | | |
432 | 0 | void assertSanity() const { |
433 | 0 | MOZ_ASSERT(postOrder.length() == doms.length()); |
434 | 0 | MOZ_ASSERT(postOrder.length() == nodeToPostOrderIndex.count()); |
435 | 0 | MOZ_ASSERT_IF(retainedSizes.isSome(), postOrder.length() == retainedSizes->length()); |
436 | 0 | } |
437 | | |
438 | 0 | MOZ_MUST_USE bool computeRetainedSizes(mozilla::MallocSizeOf mallocSizeOf) { |
439 | 0 | MOZ_ASSERT(retainedSizes.isNothing()); |
440 | 0 | auto length = postOrder.length(); |
441 | 0 |
|
442 | 0 | retainedSizes.emplace(); |
443 | 0 | if (!retainedSizes->growBy(length)) { |
444 | 0 | retainedSizes = mozilla::Nothing(); |
445 | 0 | return false; |
446 | 0 | } |
447 | 0 | |
448 | 0 | // Iterate in forward order so that we know all of a node's children in |
449 | 0 | // the dominator tree have already had their retained size |
450 | 0 | // computed. Then we can simply say that the retained size of a node is |
451 | 0 | // its shallow size (JS::ubi::Node::size) plus the retained sizes of its |
452 | 0 | // immediate children in the tree. |
453 | 0 | |
454 | 0 | for (uint32_t i = 0; i < length; i++) { |
455 | 0 | auto size = postOrder[i].size(mallocSizeOf); |
456 | 0 |
|
457 | 0 | for (const auto& dominated : dominatedSets.dominatedSet(postOrder, i)) { |
458 | 0 | // The root node dominates itself, but shouldn't contribute to |
459 | 0 | // its own retained size. |
460 | 0 | if (dominated == postOrder[length - 1]) { |
461 | 0 | MOZ_ASSERT(i == length - 1); |
462 | 0 | continue; |
463 | 0 | } |
464 | 0 |
|
465 | 0 | auto ptr = nodeToPostOrderIndex.lookup(dominated); |
466 | 0 | MOZ_ASSERT(ptr); |
467 | 0 | auto idxOfDominated = ptr->value(); |
468 | 0 | MOZ_ASSERT(idxOfDominated < i); |
469 | 0 | size += retainedSizes.ref()[idxOfDominated]; |
470 | 0 | } |
471 | 0 |
|
472 | 0 | retainedSizes.ref()[i] = size; |
473 | 0 | } |
474 | 0 |
|
475 | 0 | return true; |
476 | 0 | } |
477 | | |
478 | | public: |
479 | | // DominatorTree is not copy-able. |
480 | | DominatorTree(const DominatorTree&) = delete; |
481 | | DominatorTree& operator=(const DominatorTree&) = delete; |
482 | | |
483 | | // DominatorTree is move-able. |
484 | | DominatorTree(DominatorTree&& rhs) |
485 | | : postOrder(std::move(rhs.postOrder)) |
486 | | , nodeToPostOrderIndex(std::move(rhs.nodeToPostOrderIndex)) |
487 | | , doms(std::move(rhs.doms)) |
488 | | , dominatedSets(std::move(rhs.dominatedSets)) |
489 | | , retainedSizes(std::move(rhs.retainedSizes)) |
490 | 0 | { |
491 | 0 | MOZ_ASSERT(this != &rhs, "self-move is not allowed"); |
492 | 0 | } |
493 | 0 | DominatorTree& operator=(DominatorTree&& rhs) { |
494 | 0 | this->~DominatorTree(); |
495 | 0 | new (this) DominatorTree(std::move(rhs)); |
496 | 0 | return *this; |
497 | 0 | } |
498 | | |
499 | | /** |
500 | | * Construct a `DominatorTree` of the heap graph visible from `root`. The |
501 | | * `root` is also used as the root of the resulting dominator tree. |
502 | | * |
503 | | * The resulting `DominatorTree` instance must not outlive the |
504 | | * `JS::ubi::Node` graph it was constructed from. |
505 | | * |
506 | | * - For `JS::ubi::Node` graphs backed by the live heap graph, this means |
507 | | * that the `DominatorTree`'s lifetime _must_ be contained within the |
508 | | * scope of the provided `AutoCheckCannotGC` reference because a GC will |
509 | | * invalidate the nodes. |
510 | | * |
511 | | * - For `JS::ubi::Node` graphs backed by some other offline structure |
512 | | * provided by the embedder, the resulting `DominatorTree`'s lifetime is |
513 | | * bounded by that offline structure's lifetime. |
514 | | * |
515 | | * In practice, this means that within SpiderMonkey we must treat |
516 | | * `DominatorTree` as if it were backed by the live heap graph and trust |
517 | | * that embedders with knowledge of the graph's implementation will do the |
518 | | * Right Thing. |
519 | | * |
520 | | * Returns `mozilla::Nothing()` on OOM failure. It is the caller's |
521 | | * responsibility to handle and report the OOM. |
522 | | */ |
523 | | static mozilla::Maybe<DominatorTree> |
524 | 0 | Create(JSContext* cx, AutoCheckCannotGC& noGC, const Node& root) { |
525 | 0 | JS::ubi::Vector<Node> postOrder; |
526 | 0 | PredecessorSets predecessorSets; |
527 | 0 | if (!doTraversal(cx, noGC, root, postOrder, predecessorSets)) { |
528 | 0 | return mozilla::Nothing(); |
529 | 0 | } |
530 | 0 | |
531 | 0 | MOZ_ASSERT(postOrder.length() < UINT32_MAX); |
532 | 0 | uint32_t length = postOrder.length(); |
533 | 0 | MOZ_ASSERT(postOrder[length - 1] == root); |
534 | 0 |
|
535 | 0 | // From here on out we wish to avoid hash table lookups, and we use |
536 | 0 | // indices into `postOrder` instead of actual nodes wherever |
537 | 0 | // possible. This greatly improves the performance of this |
538 | 0 | // implementation, but we have to pay a little bit of upfront cost to |
539 | 0 | // convert our data structures to play along first. |
540 | 0 |
|
541 | 0 | NodeToIndexMap nodeToPostOrderIndex(postOrder.length()); |
542 | 0 | if (!mapNodesToTheirIndices(postOrder, nodeToPostOrderIndex)) { |
543 | 0 | return mozilla::Nothing(); |
544 | 0 | } |
545 | 0 | |
546 | 0 | JS::ubi::Vector<JS::ubi::Vector<uint32_t>> predecessorVectors; |
547 | 0 | if (!convertPredecessorSetsToVectors(root, postOrder, predecessorSets, nodeToPostOrderIndex, |
548 | 0 | predecessorVectors)) |
549 | 0 | return mozilla::Nothing(); |
550 | 0 | |
551 | 0 | JS::ubi::Vector<uint32_t> doms; |
552 | 0 | if (!initializeDominators(doms, length)) { |
553 | 0 | return mozilla::Nothing(); |
554 | 0 | } |
555 | 0 | |
556 | 0 | bool changed = true; |
557 | 0 | while (changed) { |
558 | 0 | changed = false; |
559 | 0 |
|
560 | 0 | // Iterate over the non-root nodes in reverse post order. |
561 | 0 | for (uint32_t indexPlusOne = length - 1; indexPlusOne > 0; indexPlusOne--) { |
562 | 0 | MOZ_ASSERT(postOrder[indexPlusOne - 1] != root); |
563 | 0 |
|
564 | 0 | // Take the intersection of every predecessor's dominator set; |
565 | 0 | // that is the current best guess at the immediate dominator for |
566 | 0 | // this node. |
567 | 0 |
|
568 | 0 | uint32_t newIDomIdx = UNDEFINED; |
569 | 0 |
|
570 | 0 | auto& predecessors = predecessorVectors[indexPlusOne - 1]; |
571 | 0 | auto range = predecessors.all(); |
572 | 0 | for ( ; !range.empty(); range.popFront()) { |
573 | 0 | auto idx = range.front(); |
574 | 0 | if (doms[idx] != UNDEFINED) { |
575 | 0 | newIDomIdx = idx; |
576 | 0 | break; |
577 | 0 | } |
578 | 0 | } |
579 | 0 |
|
580 | 0 | MOZ_ASSERT(newIDomIdx != UNDEFINED, |
581 | 0 | "Because the root is initialized to dominate itself and is the first " |
582 | 0 | "node in every path, there must exist a predecessor to this node that " |
583 | 0 | "also has a dominator."); |
584 | 0 |
|
585 | 0 | for ( ; !range.empty(); range.popFront()) { |
586 | 0 | auto idx = range.front(); |
587 | 0 | if (doms[idx] != UNDEFINED) { |
588 | 0 | newIDomIdx = intersect(doms, newIDomIdx, idx); |
589 | 0 | } |
590 | 0 | } |
591 | 0 |
|
592 | 0 | // If the immediate dominator changed, we will have to do |
593 | 0 | // another pass of the outer while loop to continue the forward |
594 | 0 | // dataflow. |
595 | 0 | if (newIDomIdx != doms[indexPlusOne - 1]) { |
596 | 0 | doms[indexPlusOne - 1] = newIDomIdx; |
597 | 0 | changed = true; |
598 | 0 | } |
599 | 0 | } |
600 | 0 | } |
601 | 0 |
|
602 | 0 | auto maybeDominatedSets = DominatedSets::Create(doms); |
603 | 0 | if (maybeDominatedSets.isNothing()) { |
604 | 0 | return mozilla::Nothing(); |
605 | 0 | } |
606 | 0 | |
607 | 0 | return mozilla::Some(DominatorTree(std::move(postOrder), |
608 | 0 | std::move(nodeToPostOrderIndex), |
609 | 0 | std::move(doms), |
610 | 0 | std::move(*maybeDominatedSets))); |
611 | 0 | } |
612 | | |
613 | | /** |
614 | | * Get the root node for this dominator tree. |
615 | | */ |
616 | 0 | const Node& root() const { |
617 | 0 | return postOrder[postOrder.length() - 1]; |
618 | 0 | } |
619 | | |
620 | | /** |
621 | | * Return the immediate dominator of the given `node`. If `node` was not |
622 | | * reachable from the `root` that this dominator tree was constructed from, |
623 | | * then return the null `JS::ubi::Node`. |
624 | | */ |
625 | 0 | Node getImmediateDominator(const Node& node) const { |
626 | 0 | assertSanity(); |
627 | 0 | auto ptr = nodeToPostOrderIndex.lookup(node); |
628 | 0 | if (!ptr) { |
629 | 0 | return Node(); |
630 | 0 | } |
631 | 0 | |
632 | 0 | auto idx = ptr->value(); |
633 | 0 | MOZ_ASSERT(idx < postOrder.length()); |
634 | 0 | return postOrder[doms[idx]]; |
635 | 0 | } |
636 | | |
637 | | /** |
638 | | * Get the set of nodes immediately dominated by the given `node`. If `node` |
639 | | * is not a member of this dominator tree, return `Nothing`. |
640 | | * |
641 | | * Example usage: |
642 | | * |
643 | | * mozilla::Maybe<DominatedSetRange> range = myDominatorTree.getDominatedSet(myNode); |
644 | | * if (range.isNothing()) { |
645 | | * // Handle unknown node however you see fit... |
646 | | * return false; |
647 | | * } |
648 | | * |
649 | | * for (const JS::ubi::Node& dominatedNode : *range) { |
650 | | * // Do something with each immediately dominated node... |
651 | | * } |
652 | | */ |
653 | 0 | mozilla::Maybe<DominatedSetRange> getDominatedSet(const Node& node) { |
654 | 0 | assertSanity(); |
655 | 0 | auto ptr = nodeToPostOrderIndex.lookup(node); |
656 | 0 | if (!ptr) { |
657 | 0 | return mozilla::Nothing(); |
658 | 0 | } |
659 | 0 | |
660 | 0 | auto idx = ptr->value(); |
661 | 0 | MOZ_ASSERT(idx < postOrder.length()); |
662 | 0 | return mozilla::Some(dominatedSets.dominatedSet(postOrder, idx)); |
663 | 0 | } |
664 | | |
665 | | /** |
666 | | * Get the retained size of the given `node`. The size is placed in |
667 | | * `outSize`, or 0 if `node` is not a member of the dominator tree. Returns |
668 | | * false on OOM failure, leaving `outSize` unchanged. |
669 | | */ |
670 | | MOZ_MUST_USE bool getRetainedSize(const Node& node, mozilla::MallocSizeOf mallocSizeOf, |
671 | 0 | Node::Size& outSize) { |
672 | 0 | assertSanity(); |
673 | 0 | auto ptr = nodeToPostOrderIndex.lookup(node); |
674 | 0 | if (!ptr) { |
675 | 0 | outSize = 0; |
676 | 0 | return true; |
677 | 0 | } |
678 | 0 | |
679 | 0 | if (retainedSizes.isNothing() && !computeRetainedSizes(mallocSizeOf)) { |
680 | 0 | return false; |
681 | 0 | } |
682 | 0 | |
683 | 0 | auto idx = ptr->value(); |
684 | 0 | MOZ_ASSERT(idx < postOrder.length()); |
685 | 0 | outSize = retainedSizes.ref()[idx]; |
686 | 0 | return true; |
687 | 0 | } |
688 | | }; |
689 | | |
690 | | } // namespace ubi |
691 | | } // namespace JS |
692 | | |
693 | | #endif // js_UbiNodeDominatorTree_h |