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