| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
| * vim: set ts=8 sts=4 et sw=4 tw=99: |
| * This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| |
| #ifndef js_UbiNodeDominatorTree_h |
| #define js_UbiNodeDominatorTree_h |
| |
| #include "mozilla/DebugOnly.h" |
| #include "mozilla/Maybe.h" |
| #include "mozilla/Move.h" |
| #include "mozilla/UniquePtr.h" |
| |
| #include "jsalloc.h" |
| |
| #include "js/UbiNode.h" |
| #include "js/UbiNodePostOrder.h" |
| #include "js/Utility.h" |
| #include "js/Vector.h" |
| |
| namespace JS { |
| namespace ubi { |
| |
| /** |
| * In a directed graph with a root node `R`, a node `A` is said to "dominate" a |
| * node `B` iff every path from `R` to `B` contains `A`. A node `A` is said to |
| * be the "immediate dominator" of a node `B` iff it dominates `B`, is not `B` |
| * itself, and does not dominate any other nodes which also dominate `B` in |
| * turn. |
| * |
| * If we take every node from a graph `G` and create a new graph `T` with edges |
| * to each node from its immediate dominator, then `T` is a tree (each node has |
| * only one immediate dominator, or none if it is the root). This tree is called |
| * a "dominator tree". |
| * |
| * This class represents a dominator tree constructed from a `JS::ubi::Node` |
| * heap graph. The domination relationship and dominator trees are useful tools |
| * for analyzing heap graphs because they tell you: |
| * |
| * - Exactly what could be reclaimed by the GC if some node `A` became |
| * unreachable: those nodes which are dominated by `A`, |
| * |
| * - The "retained size" of a node in the heap graph, in contrast to its |
| * "shallow size". The "shallow size" is the space taken by a node itself, |
| * not counting anything it references. The "retained size" of a node is its |
| * shallow size plus the size of all the things that would be collected if |
| * the original node wasn't (directly or indirectly) referencing them. In |
| * other words, the retained size is the shallow size of a node plus the |
| * shallow sizes of every other node it dominates. For example, the root |
| * node in a binary tree might have a small shallow size that does not take |
| * up much space itself, but it dominates the rest of the binary tree and |
| * its retained size is therefore significant (assuming no external |
| * references into the tree). |
| * |
| * The simple, engineered algorithm presented in "A Simple, Fast Dominance |
| * Algorithm" by Cooper el al[0] is used to find dominators and construct the |
| * dominator tree. This algorithm runs in O(n^2) time, but is faster in practice |
| * than alternative algorithms with better theoretical running times, such as |
| * Lengauer-Tarjan which runs in O(e * log(n)). The big caveat to that statement |
| * is that Cooper et al found it is faster in practice *on control flow graphs* |
| * and I'm not convinced that this property also holds on *heap* graphs. That |
| * said, the implementation of this algorithm is *much* simpler than |
| * Lengauer-Tarjan and has been found to be fast enough at least for the time |
| * being. |
| * |
| * [0]: http://www.cs.rice.edu/~keith/EMBED/dom.pdf |
| */ |
| class JS_PUBLIC_API(DominatorTree) |
| { |
| private: |
| // Types. |
| |
| using NodeSet = js::HashSet<Node, js::DefaultHasher<Node>, js::SystemAllocPolicy>; |
| using NodeSetPtr = mozilla::UniquePtr<NodeSet, JS::DeletePolicy<NodeSet>>; |
| using PredecessorSets = js::HashMap<Node, NodeSetPtr, js::DefaultHasher<Node>, |
| js::SystemAllocPolicy>; |
| using NodeToIndexMap = js::HashMap<Node, uint32_t, js::DefaultHasher<Node>, |
| js::SystemAllocPolicy>; |
| class DominatedSets; |
| |
| public: |
| class DominatedSetRange; |
| |
| /** |
| * A pointer to an immediately dominated node. |
| * |
| * Don't use this type directly; it is no safer than regular pointers. This |
| * is only for use indirectly with range-based for loops and |
| * `DominatedSetRange`. |
| * |
| * @see JS::ubi::DominatorTree::getDominatedSet |
| */ |
| class DominatedNodePtr |
| { |
| friend class DominatedSetRange; |
| |
| const mozilla::Vector<Node>& postOrder; |
| const uint32_t* ptr; |
| |
| DominatedNodePtr(const mozilla::Vector<Node>& postOrder, const uint32_t* ptr) |
| : postOrder(postOrder) |
| , ptr(ptr) |
| { } |
| |
| public: |
| bool operator!=(const DominatedNodePtr& rhs) const { return ptr != rhs.ptr; } |
| void operator++() { ptr++; } |
| const Node& operator*() const { return postOrder[*ptr]; } |
| }; |
| |
| /** |
| * A range of immediately dominated `JS::ubi::Node`s for use with |
| * range-based for loops. |
| * |
| * @see JS::ubi::DominatorTree::getDominatedSet |
| */ |
| class DominatedSetRange |
| { |
| friend class DominatedSets; |
| |
| const mozilla::Vector<Node>& postOrder; |
| const uint32_t* beginPtr; |
| const uint32_t* endPtr; |
| |
| DominatedSetRange(mozilla::Vector<Node>& postOrder, const uint32_t* begin, const uint32_t* end) |
| : postOrder(postOrder) |
| , beginPtr(begin) |
| , endPtr(end) |
| { |
| MOZ_ASSERT(begin <= end); |
| } |
| |
| public: |
| DominatedNodePtr begin() const { |
| MOZ_ASSERT(beginPtr <= endPtr); |
| return DominatedNodePtr(postOrder, beginPtr); |
| } |
| |
| DominatedNodePtr end() const { |
| return DominatedNodePtr(postOrder, endPtr); |
| } |
| |
| size_t length() const { |
| MOZ_ASSERT(beginPtr <= endPtr); |
| return endPtr - beginPtr; |
| } |
| |
| /** |
| * Safely skip ahead `n` dominators in the range, in O(1) time. |
| * |
| * Example usage: |
| * |
| * mozilla::Maybe<DominatedSetRange> range = myDominatorTree.getDominatedSet(myNode); |
| * if (range.isNothing()) { |
| * // Handle unknown nodes however you see fit... |
| * return false; |
| * } |
| * |
| * // Don't care about the first ten, for whatever reason. |
| * range->skip(10); |
| * for (const JS::ubi::Node& dominatedNode : *range) { |
| * // ... |
| * } |
| */ |
| void skip(size_t n) { |
| beginPtr += n; |
| if (beginPtr > endPtr) |
| beginPtr = endPtr; |
| } |
| }; |
| |
| private: |
| /** |
| * The set of all dominated sets in a dominator tree. |
| * |
| * Internally stores the sets in a contiguous array, with a side table of |
| * indices into that contiguous array to denote the start index of each |
| * individual set. |
| */ |
| class DominatedSets |
| { |
| mozilla::Vector<uint32_t> dominated; |
| mozilla::Vector<uint32_t> indices; |
| |
| DominatedSets(mozilla::Vector<uint32_t>&& dominated, mozilla::Vector<uint32_t>&& indices) |
| : dominated(mozilla::Move(dominated)) |
| , indices(mozilla::Move(indices)) |
| { } |
| |
| public: |
| // DominatedSets is not copy-able. |
| DominatedSets(const DominatedSets& rhs) = delete; |
| DominatedSets& operator=(const DominatedSets& rhs) = delete; |
| |
| // DominatedSets is move-able. |
| DominatedSets(DominatedSets&& rhs) |
| : dominated(mozilla::Move(rhs.dominated)) |
| , indices(mozilla::Move(rhs.indices)) |
| { |
| MOZ_ASSERT(this != &rhs, "self-move not allowed"); |
| } |
| DominatedSets& operator=(DominatedSets&& rhs) { |
| this->~DominatedSets(); |
| new (this) DominatedSets(mozilla::Move(rhs)); |
| return *this; |
| } |
| |
| /** |
| * Create the DominatedSets given the mapping of a node index to its |
| * immediate dominator. Returns `Some` on success, `Nothing` on OOM |
| * failure. |
| */ |
| static mozilla::Maybe<DominatedSets> Create(const mozilla::Vector<uint32_t>& doms) { |
| auto length = doms.length(); |
| MOZ_ASSERT(length < UINT32_MAX); |
| |
| // Create a vector `dominated` holding a flattened set of buckets of |
| // immediately dominated children nodes, with a lookup table |
| // `indices` mapping from each node to the beginning of its bucket. |
| // |
| // This has three phases: |
| // |
| // 1. Iterate over the full set of nodes and count up the size of |
| // each bucket. These bucket sizes are temporarily stored in the |
| // `indices` vector. |
| // |
| // 2. Convert the `indices` vector to store the cumulative sum of |
| // the sizes of all buckets before each index, resulting in a |
| // mapping from node index to one past the end of that node's |
| // bucket. |
| // |
| // 3. Iterate over the full set of nodes again, filling in bucket |
| // entries from the end of the bucket's range to its |
| // beginning. This decrements each index as a bucket entry is |
| // filled in. After having filled in all of a bucket's entries, |
| // the index points to the start of the bucket. |
| |
| mozilla::Vector<uint32_t> dominated; |
| mozilla::Vector<uint32_t> indices; |
| if (!dominated.growBy(length) || !indices.growBy(length)) |
| return mozilla::Nothing(); |
| |
| // 1 |
| memset(indices.begin(), 0, length * sizeof(uint32_t)); |
| for (uint32_t i = 0; i < length; i++) |
| indices[doms[i]]++; |
| |
| // 2 |
| uint32_t sumOfSizes = 0; |
| for (uint32_t i = 0; i < length; i++) { |
| sumOfSizes += indices[i]; |
| MOZ_ASSERT(sumOfSizes <= length); |
| indices[i] = sumOfSizes; |
| } |
| |
| // 3 |
| for (uint32_t i = 0; i < length; i++) { |
| auto idxOfDom = doms[i]; |
| indices[idxOfDom]--; |
| dominated[indices[idxOfDom]] = i; |
| } |
| |
| #ifdef DEBUG |
| // Assert that our buckets are non-overlapping and don't run off the |
| // end of the vector. |
| uint32_t lastIndex = 0; |
| for (uint32_t i = 0; i < length; i++) { |
| MOZ_ASSERT(indices[i] >= lastIndex); |
| MOZ_ASSERT(indices[i] < length); |
| lastIndex = indices[i]; |
| } |
| #endif |
| |
| return mozilla::Some(DominatedSets(mozilla::Move(dominated), mozilla::Move(indices))); |
| } |
| |
| /** |
| * Get the set of nodes immediately dominated by the node at |
| * `postOrder[nodeIndex]`. |
| */ |
| DominatedSetRange dominatedSet(mozilla::Vector<Node>& postOrder, uint32_t nodeIndex) const { |
| MOZ_ASSERT(postOrder.length() == indices.length()); |
| MOZ_ASSERT(nodeIndex < indices.length()); |
| auto end = nodeIndex == indices.length() - 1 |
| ? dominated.end() |
| : &dominated[indices[nodeIndex + 1]]; |
| return DominatedSetRange(postOrder, &dominated[indices[nodeIndex]], end); |
| } |
| }; |
| |
| private: |
| // Data members. |
| mozilla::Vector<Node> postOrder; |
| NodeToIndexMap nodeToPostOrderIndex; |
| mozilla::Vector<uint32_t> doms; |
| DominatedSets dominatedSets; |
| mozilla::Maybe<mozilla::Vector<JS::ubi::Node::Size>> retainedSizes; |
| |
| private: |
| // We use `UNDEFINED` as a sentinel value in the `doms` vector to signal |
| // that we haven't found any dominators for the node at the corresponding |
| // index in `postOrder` yet. |
| static const uint32_t UNDEFINED = UINT32_MAX; |
| |
| DominatorTree(mozilla::Vector<Node>&& postOrder, NodeToIndexMap&& nodeToPostOrderIndex, |
| mozilla::Vector<uint32_t>&& doms, DominatedSets&& dominatedSets) |
| : postOrder(mozilla::Move(postOrder)) |
| , nodeToPostOrderIndex(mozilla::Move(nodeToPostOrderIndex)) |
| , doms(mozilla::Move(doms)) |
| , dominatedSets(mozilla::Move(dominatedSets)) |
| , retainedSizes(mozilla::Nothing()) |
| { } |
| |
| static uint32_t intersect(mozilla::Vector<uint32_t>& doms, uint32_t finger1, uint32_t finger2) { |
| while (finger1 != finger2) { |
| if (finger1 < finger2) |
| finger1 = doms[finger1]; |
| else if (finger2 < finger1) |
| finger2 = doms[finger2]; |
| } |
| return finger1; |
| } |
| |
| // Do the post order traversal of the heap graph and populate our |
| // predecessor sets. |
| static bool doTraversal(JSRuntime* rt, AutoCheckCannotGC& noGC, const Node& root, |
| mozilla::Vector<Node>& postOrder, PredecessorSets& predecessorSets) { |
| uint32_t nodeCount = 0; |
| auto onNode = [&](const Node& node) { |
| nodeCount++; |
| if (MOZ_UNLIKELY(nodeCount == UINT32_MAX)) |
| return false; |
| return postOrder.append(node); |
| }; |
| |
| auto onEdge = [&](const Node& origin, const Edge& edge) { |
| auto p = predecessorSets.lookupForAdd(edge.referent); |
| if (!p) { |
| mozilla::UniquePtr<NodeSet, DeletePolicy<NodeSet>> set(js_new<NodeSet>()); |
| if (!set || |
| !set->init() || |
| !predecessorSets.add(p, edge.referent, mozilla::Move(set))) |
| { |
| return false; |
| } |
| } |
| MOZ_ASSERT(p && p->value()); |
| return p->value()->put(origin); |
| }; |
| |
| PostOrder traversal(rt, noGC); |
| return traversal.init() && |
| traversal.addStart(root) && |
| traversal.traverse(onNode, onEdge); |
| } |
| |
| // Populates the given `map` with an entry for each node to its index in |
| // `postOrder`. |
| static bool mapNodesToTheirIndices(mozilla::Vector<Node>& postOrder, NodeToIndexMap& map) { |
| MOZ_ASSERT(!map.initialized()); |
| MOZ_ASSERT(postOrder.length() < UINT32_MAX); |
| uint32_t length = postOrder.length(); |
| if (!map.init(length)) |
| return false; |
| for (uint32_t i = 0; i < length; i++) |
| map.putNewInfallible(postOrder[i], i); |
| return true; |
| } |
| |
| // Convert the Node -> NodeSet predecessorSets to a index -> Vector<index> |
| // form. |
| static bool convertPredecessorSetsToVectors( |
| const Node& root, |
| mozilla::Vector<Node>& postOrder, |
| PredecessorSets& predecessorSets, |
| NodeToIndexMap& nodeToPostOrderIndex, |
| mozilla::Vector<mozilla::Vector<uint32_t>>& predecessorVectors) |
| { |
| MOZ_ASSERT(postOrder.length() < UINT32_MAX); |
| uint32_t length = postOrder.length(); |
| |
| MOZ_ASSERT(predecessorVectors.length() == 0); |
| if (!predecessorVectors.growBy(length)) |
| return false; |
| |
| for (uint32_t i = 0; i < length - 1; i++) { |
| auto& node = postOrder[i]; |
| MOZ_ASSERT(node != root, |
| "Only the last node should be root, since this was a post order traversal."); |
| |
| auto ptr = predecessorSets.lookup(node); |
| MOZ_ASSERT(ptr, |
| "Because this isn't the root, it had better have predecessors, or else how " |
| "did we even find it."); |
| |
| auto& predecessors = ptr->value(); |
| if (!predecessorVectors[i].reserve(predecessors->count())) |
| return false; |
| for (auto range = predecessors->all(); !range.empty(); range.popFront()) { |
| auto ptr = nodeToPostOrderIndex.lookup(range.front()); |
| MOZ_ASSERT(ptr); |
| predecessorVectors[i].infallibleAppend(ptr->value()); |
| } |
| } |
| predecessorSets.finish(); |
| return true; |
| } |
| |
| // Initialize `doms` such that the immediate dominator of the `root` is the |
| // `root` itself and all others are `UNDEFINED`. |
| static bool initializeDominators(mozilla::Vector<uint32_t>& doms, uint32_t length) { |
| MOZ_ASSERT(doms.length() == 0); |
| if (!doms.growByUninitialized(length)) |
| return false; |
| doms[length - 1] = length - 1; |
| for (uint32_t i = 0; i < length - 1; i++) |
| doms[i] = UNDEFINED; |
| return true; |
| } |
| |
| void assertSanity() const { |
| MOZ_ASSERT(postOrder.length() == doms.length()); |
| MOZ_ASSERT(postOrder.length() == nodeToPostOrderIndex.count()); |
| MOZ_ASSERT_IF(retainedSizes.isSome(), postOrder.length() == retainedSizes->length()); |
| } |
| |
| bool computeRetainedSizes(mozilla::MallocSizeOf mallocSizeOf) { |
| MOZ_ASSERT(retainedSizes.isNothing()); |
| auto length = postOrder.length(); |
| |
| retainedSizes.emplace(); |
| if (!retainedSizes->growBy(length)) { |
| retainedSizes = mozilla::Nothing(); |
| return false; |
| } |
| |
| // Iterate in forward order so that we know all of a node's children in |
| // the dominator tree have already had their retained size |
| // computed. Then we can simply say that the retained size of a node is |
| // its shallow size (JS::ubi::Node::size) plus the retained sizes of its |
| // immediate children in the tree. |
| |
| for (uint32_t i = 0; i < length; i++) { |
| auto size = postOrder[i].size(mallocSizeOf); |
| |
| for (const auto& dominated : dominatedSets.dominatedSet(postOrder, i)) { |
| // The root node dominates itself, but shouldn't contribute to |
| // its own retained size. |
| if (dominated == postOrder[length - 1]) { |
| MOZ_ASSERT(i == length - 1); |
| continue; |
| } |
| |
| auto ptr = nodeToPostOrderIndex.lookup(dominated); |
| MOZ_ASSERT(ptr); |
| auto idxOfDominated = ptr->value(); |
| MOZ_ASSERT(idxOfDominated < i); |
| size += retainedSizes.ref()[idxOfDominated]; |
| } |
| |
| retainedSizes.ref()[i] = size; |
| } |
| |
| return true; |
| } |
| |
| public: |
| // DominatorTree is not copy-able. |
| DominatorTree(const DominatorTree&) = delete; |
| DominatorTree& operator=(const DominatorTree&) = delete; |
| |
| // DominatorTree is move-able. |
| DominatorTree(DominatorTree&& rhs) |
| : postOrder(mozilla::Move(rhs.postOrder)) |
| , nodeToPostOrderIndex(mozilla::Move(rhs.nodeToPostOrderIndex)) |
| , doms(mozilla::Move(rhs.doms)) |
| , dominatedSets(mozilla::Move(rhs.dominatedSets)) |
| , retainedSizes(mozilla::Move(rhs.retainedSizes)) |
| { |
| MOZ_ASSERT(this != &rhs, "self-move is not allowed"); |
| } |
| DominatorTree& operator=(DominatorTree&& rhs) { |
| this->~DominatorTree(); |
| new (this) DominatorTree(mozilla::Move(rhs)); |
| return *this; |
| } |
| |
| /** |
| * Construct a `DominatorTree` of the heap graph visible from `root`. The |
| * `root` is also used as the root of the resulting dominator tree. |
| * |
| * The resulting `DominatorTree` instance must not outlive the |
| * `JS::ubi::Node` graph it was constructed from. |
| * |
| * - For `JS::ubi::Node` graphs backed by the live heap graph, this means |
| * that the `DominatorTree`'s lifetime _must_ be contained within the |
| * scope of the provided `AutoCheckCannotGC` reference because a GC will |
| * invalidate the nodes. |
| * |
| * - For `JS::ubi::Node` graphs backed by some other offline structure |
| * provided by the embedder, the resulting `DominatorTree`'s lifetime is |
| * bounded by that offline structure's lifetime. |
| * |
| * In practice, this means that within SpiderMonkey we must treat |
| * `DominatorTree` as if it were backed by the live heap graph and trust |
| * that embedders with knowledge of the graph's implementation will do the |
| * Right Thing. |
| * |
| * Returns `mozilla::Nothing()` on OOM failure. It is the caller's |
| * responsibility to handle and report the OOM. |
| */ |
| static mozilla::Maybe<DominatorTree> |
| Create(JSRuntime* rt, AutoCheckCannotGC& noGC, const Node& root) { |
| mozilla::Vector<Node> postOrder; |
| PredecessorSets predecessorSets; |
| if (!predecessorSets.init() || !doTraversal(rt, noGC, root, postOrder, predecessorSets)) |
| return mozilla::Nothing(); |
| |
| MOZ_ASSERT(postOrder.length() < UINT32_MAX); |
| uint32_t length = postOrder.length(); |
| MOZ_ASSERT(postOrder[length - 1] == root); |
| |
| // From here on out we wish to avoid hash table lookups, and we use |
| // indices into `postOrder` instead of actual nodes wherever |
| // possible. This greatly improves the performance of this |
| // implementation, but we have to pay a little bit of upfront cost to |
| // convert our data structures to play along first. |
| |
| NodeToIndexMap nodeToPostOrderIndex; |
| if (!mapNodesToTheirIndices(postOrder, nodeToPostOrderIndex)) |
| return mozilla::Nothing(); |
| |
| mozilla::Vector<mozilla::Vector<uint32_t>> predecessorVectors; |
| if (!convertPredecessorSetsToVectors(root, postOrder, predecessorSets, nodeToPostOrderIndex, |
| predecessorVectors)) |
| return mozilla::Nothing(); |
| |
| mozilla::Vector<uint32_t> doms; |
| if (!initializeDominators(doms, length)) |
| return mozilla::Nothing(); |
| |
| bool changed = true; |
| while (changed) { |
| changed = false; |
| |
| // Iterate over the non-root nodes in reverse post order. |
| for (uint32_t indexPlusOne = length - 1; indexPlusOne > 0; indexPlusOne--) { |
| MOZ_ASSERT(postOrder[indexPlusOne - 1] != root); |
| |
| // Take the intersection of every predecessor's dominator set; |
| // that is the current best guess at the immediate dominator for |
| // this node. |
| |
| uint32_t newIDomIdx = UNDEFINED; |
| |
| auto& predecessors = predecessorVectors[indexPlusOne - 1]; |
| auto range = predecessors.all(); |
| for ( ; !range.empty(); range.popFront()) { |
| auto idx = range.front(); |
| if (doms[idx] != UNDEFINED) { |
| newIDomIdx = idx; |
| break; |
| } |
| } |
| |
| MOZ_ASSERT(newIDomIdx != UNDEFINED, |
| "Because the root is initialized to dominate itself and is the first " |
| "node in every path, there must exist a predecessor to this node that " |
| "also has a dominator."); |
| |
| for ( ; !range.empty(); range.popFront()) { |
| auto idx = range.front(); |
| if (doms[idx] != UNDEFINED) |
| newIDomIdx = intersect(doms, newIDomIdx, idx); |
| } |
| |
| // If the immediate dominator changed, we will have to do |
| // another pass of the outer while loop to continue the forward |
| // dataflow. |
| if (newIDomIdx != doms[indexPlusOne - 1]) { |
| doms[indexPlusOne - 1] = newIDomIdx; |
| changed = true; |
| } |
| } |
| } |
| |
| auto maybeDominatedSets = DominatedSets::Create(doms); |
| if (maybeDominatedSets.isNothing()) |
| return mozilla::Nothing(); |
| |
| return mozilla::Some(DominatorTree(mozilla::Move(postOrder), |
| mozilla::Move(nodeToPostOrderIndex), |
| mozilla::Move(doms), |
| mozilla::Move(*maybeDominatedSets))); |
| } |
| |
| /** |
| * Get the root node for this dominator tree. |
| */ |
| const Node& root() const { |
| return postOrder[postOrder.length() - 1]; |
| } |
| |
| /** |
| * Return the immediate dominator of the given `node`. If `node` was not |
| * reachable from the `root` that this dominator tree was constructed from, |
| * then return the null `JS::ubi::Node`. |
| */ |
| Node getImmediateDominator(const Node& node) const { |
| assertSanity(); |
| auto ptr = nodeToPostOrderIndex.lookup(node); |
| if (!ptr) |
| return Node(); |
| |
| auto idx = ptr->value(); |
| MOZ_ASSERT(idx < postOrder.length()); |
| return postOrder[doms[idx]]; |
| } |
| |
| /** |
| * Get the set of nodes immediately dominated by the given `node`. If `node` |
| * is not a member of this dominator tree, return `Nothing`. |
| * |
| * Example usage: |
| * |
| * mozilla::Maybe<DominatedSetRange> range = myDominatorTree.getDominatedSet(myNode); |
| * if (range.isNothing()) { |
| * // Handle unknown node however you see fit... |
| * return false; |
| * } |
| * |
| * for (const JS::ubi::Node& dominatedNode : *range) { |
| * // Do something with each immediately dominated node... |
| * } |
| */ |
| mozilla::Maybe<DominatedSetRange> getDominatedSet(const Node& node) { |
| assertSanity(); |
| auto ptr = nodeToPostOrderIndex.lookup(node); |
| if (!ptr) |
| return mozilla::Nothing(); |
| |
| auto idx = ptr->value(); |
| MOZ_ASSERT(idx < postOrder.length()); |
| return mozilla::Some(dominatedSets.dominatedSet(postOrder, idx)); |
| } |
| |
| /** |
| * Get the retained size of the given `node`. The size is placed in |
| * `outSize`, or 0 if `node` is not a member of the dominator tree. Returns |
| * false on OOM failure, leaving `outSize` unchanged. |
| */ |
| bool getRetainedSize(const Node& node, mozilla::MallocSizeOf mallocSizeOf, |
| Node::Size& outSize) { |
| assertSanity(); |
| auto ptr = nodeToPostOrderIndex.lookup(node); |
| if (!ptr) { |
| outSize = 0; |
| return true; |
| } |
| |
| if (retainedSizes.isNothing() && !computeRetainedSizes(mallocSizeOf)) |
| return false; |
| |
| auto idx = ptr->value(); |
| MOZ_ASSERT(idx < postOrder.length()); |
| outSize = retainedSizes.ref()[idx]; |
| return true; |
| } |
| }; |
| |
| } // namespace ubi |
| } // namespace JS |
| |
| #endif // js_UbiNodeDominatorTree_h |