import changes from `dev' branch of rmottola/Arctic-Fox:

- Bug 1225941 - Add a method for getting the set of nodes immediately dominated by another node; r=sfink (d1507ec58f)
This commit is contained in:
2023-01-15 14:18:26 +08:00
parent 35abf9a4e9
commit 9ce925bf38
2 changed files with 298 additions and 46 deletions
+245 -3
View File
@@ -69,19 +69,227 @@ namespace ubi {
class JS_EXPORT_API(DominatorTree)
{
private:
// Type aliases.
// 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);
}
/**
* 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;
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<Node>&& postOrder, NodeToIndexMap&& nodeToPostOrderIndex,
mozilla::Vector<uint32_t>&& doms)
mozilla::Vector<uint32_t>&& 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<uint32_t>& 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<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));
};
};
} // namespace ubi
+53 -43
View File
@@ -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<FakeNode>()->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<FakeNode>()->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<FakeNode>()->name);
CHECK(tree.getImmediateDominator(&relation.dominated) == JS::ubi::Node(&relation.dominator));
fprintf(stderr, "b's immediate dominator is %c\n",
tree.getImmediateDominator(&b).as<FakeNode>()->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<FakeNode>()->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<FakeNode>()->name);
CHECK(tree.getImmediateDominator(&d) == JS::ubi::Node(&r));
js::HashSet<char> 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<FakeNode>()->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<FakeNode>()->name);
CHECK(tree.getImmediateDominator(&f) == JS::ubi::Node(&c));
for (const auto& dominated : actualDominatedSet) {
fprintf(stderr, " Found %c\n", dominated.as<FakeNode>()->name);
CHECK(expectedDominatedSet.has(dominated.as<FakeNode>()->name));
expectedDominatedSet.remove(dominated.as<FakeNode>()->name);
}
fprintf(stderr, "g's immediate dominator is %c\n",
tree.getImmediateDominator(&g).as<FakeNode>()->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<FakeNode>()->name);
CHECK(tree.getImmediateDominator(&h) == JS::ubi::Node(&r));
fprintf(stderr, "i's immediate dominator is %c\n",
tree.getImmediateDominator(&i).as<FakeNode>()->name);
CHECK(tree.getImmediateDominator(&i) == JS::ubi::Node(&r));
fprintf(stderr, "j's immediate dominator is %c\n",
tree.getImmediateDominator(&j).as<FakeNode>()->name);
CHECK(tree.getImmediateDominator(&j) == JS::ubi::Node(&g));
fprintf(stderr, "k's immediate dominator is %c\n",
tree.getImmediateDominator(&k).as<FakeNode>()->name);
CHECK(tree.getImmediateDominator(&k) == JS::ubi::Node(&r));
fprintf(stderr, "l's immediate dominator is %c\n",
tree.getImmediateDominator(&l).as<FakeNode>()->name);
CHECK(tree.getImmediateDominator(&l) == JS::ubi::Node(&d));
fprintf(stderr, "Done checking %c's dominated set.\n\n", node.name);
}
return true;
}