/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 * 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/Attributes.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/Maybe.h"
#include "mozilla/UniquePtr.h"

#include <utility>

#include "js/AllocPolicy.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 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 JS::ubi::Vector<Node>& postOrder;
    const uint32_t* ptr;

    DominatedNodePtr(const JS::ubi::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 JS::ubi::Vector<Node>& postOrder;
    const uint32_t* beginPtr;
    const uint32_t* endPtr;

    DominatedSetRange(JS::ubi::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 {
    JS::ubi::Vector<uint32_t> dominated;
    JS::ubi::Vector<uint32_t> indices;

    DominatedSets(JS::ubi::Vector<uint32_t>&& dominated,
                  JS::ubi::Vector<uint32_t>&& indices)
        : dominated(std::move(dominated)), indices(std::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(std::move(rhs.dominated)), indices(std::move(rhs.indices)) {
      MOZ_ASSERT(this != &rhs, "self-move not allowed");
    }
    DominatedSets& operator=(DominatedSets&& rhs) {
      this->~DominatedSets();
      new (this) DominatedSets(std::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 JS::ubi::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.

      JS::ubi::Vector<uint32_t> dominated;
      JS::ubi::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(std::move(dominated), std::move(indices)));
    }

    /**
     * Get the set of nodes immediately dominated by the node at
     * `postOrder[nodeIndex]`.
     */
    DominatedSetRange dominatedSet(JS::ubi::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.
  JS::ubi::Vector<Node> postOrder;
  NodeToIndexMap nodeToPostOrderIndex;
  JS::ubi::Vector<uint32_t> doms;
  DominatedSets dominatedSets;
  mozilla::Maybe<JS::ubi::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(JS::ubi::Vector<Node>&& postOrder,
                NodeToIndexMap&& nodeToPostOrderIndex,
                JS::ubi::Vector<uint32_t>&& doms, DominatedSets&& dominatedSets)
      : postOrder(std::move(postOrder)),
        nodeToPostOrderIndex(std::move(nodeToPostOrderIndex)),
        doms(std::move(doms)),
        dominatedSets(std::move(dominatedSets)),
        retainedSizes(mozilla::Nothing()) {}

  static uint32_t intersect(JS::ubi::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.
  [[nodiscard]] static bool doTraversal(JSContext* cx, AutoCheckCannotGC& noGC,
                                        const Node& root,
                                        JS::ubi::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 || !predecessorSets.add(p, edge.referent, std::move(set))) {
          return false;
        }
      }
      MOZ_ASSERT(p && p->value());
      return p->value()->put(origin);
    };

    PostOrder traversal(cx, noGC);
    return traversal.addStart(root) && traversal.traverse(onNode, onEdge);
  }

  // Populates the given `map` with an entry for each node to its index in
  // `postOrder`.
  [[nodiscard]] static bool mapNodesToTheirIndices(
      JS::ubi::Vector<Node>& postOrder, NodeToIndexMap& map) {
    MOZ_ASSERT(map.empty());
    MOZ_ASSERT(postOrder.length() < UINT32_MAX);
    uint32_t length = postOrder.length();
    if (!map.reserve(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.
  [[nodiscard]] static bool convertPredecessorSetsToVectors(
      const Node& root, JS::ubi::Vector<Node>& postOrder,
      PredecessorSets& predecessorSets, NodeToIndexMap& nodeToPostOrderIndex,
      JS::ubi::Vector<JS::ubi::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.clearAndCompact();
    return true;
  }

  // Initialize `doms` such that the immediate dominator of the `root` is the
  // `root` itself and all others are `UNDEFINED`.
  [[nodiscard]] static bool initializeDominators(
      JS::ubi::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());
  }

  [[nodiscard]] 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(std::move(rhs.postOrder)),
        nodeToPostOrderIndex(std::move(rhs.nodeToPostOrderIndex)),
        doms(std::move(rhs.doms)),
        dominatedSets(std::move(rhs.dominatedSets)),
        retainedSizes(std::move(rhs.retainedSizes)) {
    MOZ_ASSERT(this != &rhs, "self-move is not allowed");
  }
  DominatorTree& operator=(DominatorTree&& rhs) {
    this->~DominatorTree();
    new (this) DominatorTree(std::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(JSContext* cx,
                                              AutoCheckCannotGC& noGC,
                                              const Node& root) {
    JS::ubi::Vector<Node> postOrder;
    PredecessorSets predecessorSets;
    if (!doTraversal(cx, 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(postOrder.length());
    if (!mapNodesToTheirIndices(postOrder, nodeToPostOrderIndex)) {
      return mozilla::Nothing();
    }

    JS::ubi::Vector<JS::ubi::Vector<uint32_t>> predecessorVectors;
    if (!convertPredecessorSetsToVectors(root, postOrder, predecessorSets,
                                         nodeToPostOrderIndex,
                                         predecessorVectors))
      return mozilla::Nothing();

    JS::ubi::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(std::move(postOrder), std::move(nodeToPostOrderIndex),
                      std::move(doms), std::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.
   */
  [[nodiscard]] 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
