tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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