diff --git a/js/public/UbiNodeDominatorTree.h b/js/public/UbiNodeDominatorTree.h index 65435c3e4c..8b15ff70c8 100644 --- a/js/public/UbiNodeDominatorTree.h +++ b/js/public/UbiNodeDominatorTree.h @@ -69,19 +69,227 @@ namespace ubi { class JS_EXPORT_API(DominatorTree) { private: - // Type aliases. + // Types. + using NodeSet = js::HashSet, js::SystemAllocPolicy>; using NodeSetPtr = mozilla::UniquePtr>; using PredecessorSets = js::HashMap, js::SystemAllocPolicy>; using NodeToIndexMap = js::HashMap, 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& postOrder; + const uint32_t* ptr; + + DominatedNodePtr(const mozilla::Vector& 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& postOrder; + const uint32_t* beginPtr; + const uint32_t* endPtr; + + DominatedSetRange(mozilla::Vector& 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); + } + + /** + * Safely skip ahead `n` dominators in the range, in O(1) time. + * + * Example usage: + * + * mozilla::Maybe 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 dominated; + mozilla::Vector indices; + + DominatedSets(mozilla::Vector&& dominated, mozilla::Vector&& 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 Create(const mozilla::Vector& 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 dominated; + mozilla::Vector 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& 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 postOrder; NodeToIndexMap nodeToPostOrderIndex; mozilla::Vector doms; + DominatedSets dominatedSets; private: // We use `UNDEFINED` as a sentinel value in the `doms` vector to signal @@ -90,10 +298,11 @@ class JS_EXPORT_API(DominatorTree) static const uint32_t UNDEFINED = UINT32_MAX; DominatorTree(mozilla::Vector&& postOrder, NodeToIndexMap&& nodeToPostOrderIndex, - mozilla::Vector&& doms) + mozilla::Vector&& doms, DominatedSets&& dominatedSets) : postOrder(mozilla::Move(postOrder)) , nodeToPostOrderIndex(mozilla::Move(nodeToPostOrderIndex)) , doms(mozilla::Move(doms)) + , dominatedSets(mozilla::Move(dominatedSets)) { } static uint32_t intersect(mozilla::Vector& doms, uint32_t finger1, uint32_t finger2) { @@ -218,6 +427,7 @@ class JS_EXPORT_API(DominatorTree) : postOrder(mozilla::Move(rhs.postOrder)) , nodeToPostOrderIndex(mozilla::Move(rhs.nodeToPostOrderIndex)) , doms(mozilla::Move(rhs.doms)) + , dominatedSets(mozilla::Move(rhs.dominatedSets)) { MOZ_ASSERT(this != &rhs, "self-move is not allowed"); } @@ -326,9 +536,14 @@ class JS_EXPORT_API(DominatorTree) } } + 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(doms), + mozilla::Move(*maybeDominatedSets))); } /** @@ -346,6 +561,33 @@ class JS_EXPORT_API(DominatorTree) 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 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 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)); + }; }; } // namespace ubi diff --git a/js/src/jsapi-tests/testUbiNode.cpp b/js/src/jsapi-tests/testUbiNode.cpp index 40e26a94e5..a42b8905ed 100644 --- a/js/src/jsapi-tests/testUbiNode.cpp +++ b/js/src/jsapi-tests/testUbiNode.cpp @@ -519,58 +519,68 @@ BEGIN_TEST(test_JS_ubi_DominatorTree) // graph when computing the dominator tree. FakeNode m('m'); CHECK(tree.getImmediateDominator(&m) == JS::ubi::Node()); + CHECK(tree.getDominatedSet(&m).isNothing()); - fprintf(stderr, "r's immediate dominator is %c\n", - tree.getImmediateDominator(&r).as()->name); - CHECK(tree.getImmediateDominator(&r) == JS::ubi::Node(&r)); + struct { + FakeNode& dominated; + FakeNode& dominator; + } domination[] = { + {r, r}, + {a, r}, + {b, r}, + {c, r}, + {d, r}, + {e, r}, + {f, c}, + {g, c}, + {h, r}, + {i, r}, + {j, g}, + {k, r}, + {l, d} + }; - fprintf(stderr, "a's immediate dominator is %c\n", - tree.getImmediateDominator(&a).as()->name); - CHECK(tree.getImmediateDominator(&a) == JS::ubi::Node(&r)); + for (auto& relation : domination) { + // Test immediate dominator. + fprintf(stderr, + "%c's immediate dominator is %c\n", + relation.dominated.name, + tree.getImmediateDominator(&relation.dominator).as()->name); + CHECK(tree.getImmediateDominator(&relation.dominated) == JS::ubi::Node(&relation.dominator)); - fprintf(stderr, "b's immediate dominator is %c\n", - tree.getImmediateDominator(&b).as()->name); - CHECK(tree.getImmediateDominator(&b) == JS::ubi::Node(&r)); + // Test the dominated set. Build up the expected dominated set based on + // the set of nodes immediately dominated by this one in `domination`, + // then iterate over the actual dominated set and check against the + // expected set. - fprintf(stderr, "c's immediate dominator is %c\n", - tree.getImmediateDominator(&c).as()->name); - CHECK(tree.getImmediateDominator(&c) == JS::ubi::Node(&r)); + auto& node = relation.dominated; + fprintf(stderr, "Checking %c's dominated set:\n", node.name); - fprintf(stderr, "d's immediate dominator is %c\n", - tree.getImmediateDominator(&d).as()->name); - CHECK(tree.getImmediateDominator(&d) == JS::ubi::Node(&r)); + js::HashSet expectedDominatedSet(cx); + CHECK(expectedDominatedSet.init()); + for (auto& rel : domination) { + if (&rel.dominator == &node) { + fprintf(stderr, " Expecting %c\n", rel.dominated.name); + CHECK(expectedDominatedSet.putNew(rel.dominated.name)); + } + } - fprintf(stderr, "e's immediate dominator is %c\n", - tree.getImmediateDominator(&e).as()->name); - CHECK(tree.getImmediateDominator(&e) == JS::ubi::Node(&r)); + auto maybeActualDominatedSet = tree.getDominatedSet(&node); + CHECK(maybeActualDominatedSet.isSome()); + auto& actualDominatedSet = *maybeActualDominatedSet; - fprintf(stderr, "f's immediate dominator is %c\n", - tree.getImmediateDominator(&f).as()->name); - CHECK(tree.getImmediateDominator(&f) == JS::ubi::Node(&c)); + for (const auto& dominated : actualDominatedSet) { + fprintf(stderr, " Found %c\n", dominated.as()->name); + CHECK(expectedDominatedSet.has(dominated.as()->name)); + expectedDominatedSet.remove(dominated.as()->name); + } - fprintf(stderr, "g's immediate dominator is %c\n", - tree.getImmediateDominator(&g).as()->name); - CHECK(tree.getImmediateDominator(&g) == JS::ubi::Node(&c)); + // Ensure we found them all and aren't still expecting nodes we never + // got. + CHECK(expectedDominatedSet.count() == 0); - fprintf(stderr, "h's immediate dominator is %c\n", - tree.getImmediateDominator(&h).as()->name); - CHECK(tree.getImmediateDominator(&h) == JS::ubi::Node(&r)); - - fprintf(stderr, "i's immediate dominator is %c\n", - tree.getImmediateDominator(&i).as()->name); - CHECK(tree.getImmediateDominator(&i) == JS::ubi::Node(&r)); - - fprintf(stderr, "j's immediate dominator is %c\n", - tree.getImmediateDominator(&j).as()->name); - CHECK(tree.getImmediateDominator(&j) == JS::ubi::Node(&g)); - - fprintf(stderr, "k's immediate dominator is %c\n", - tree.getImmediateDominator(&k).as()->name); - CHECK(tree.getImmediateDominator(&k) == JS::ubi::Node(&r)); - - fprintf(stderr, "l's immediate dominator is %c\n", - tree.getImmediateDominator(&l).as()->name); - CHECK(tree.getImmediateDominator(&l) == JS::ubi::Node(&d)); + fprintf(stderr, "Done checking %c's dominated set.\n\n", node.name); + } return true; }