Commit 7b0861c0 authored by Darius M's avatar Darius M Committed by V8 LUCI CQ

[turboshaft] Add Dominator graph

Bug: v8:12783
Change-Id: I5495aac4213b0f9783b5e239b2d90047d25552d8
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3721497
Commit-Queue: Darius Mercadier <dmercadier@chromium.org>
Reviewed-by: 's avatarTobias Tebbi <tebbi@chromium.org>
Cr-Commit-Position: refs/heads/main@{#81583}
parent 43cef7a6
......@@ -8,6 +8,148 @@
namespace v8::internal::compiler::turboshaft {
void Graph::GenerateDominatorTree() {
for (Block* block : bound_blocks_) {
if (block->index() == StartBlock().index()) {
// Start block has no dominators. We create a jmp_ edge to itself, so that
// the SetDominator algorithm does not need a special case for when the
// start block is reached.
block->jmp_ = block;
block->nxt_ = nullptr;
block->len_ = 0;
block->jmp_len_ = 0;
continue;
}
if (block->kind_ == Block::Kind::kBranchTarget) {
// kBranchTarget blocks always have a single predecessor, which dominates
// them.
DCHECK_EQ(block->PredecessorCount(), 1);
block->SetDominator(block->LastPredecessor());
} else if (block->kind_ == Block::Kind::kLoopHeader) {
// kLoopHeader blocks have 2 predecessors, but their dominator is
// always their first predecessor (the 2nd one is the loop's backedge).
DCHECK_EQ(block->PredecessorCount(), 2);
block->SetDominator(block->LastPredecessor()->NeighboringPredecessor());
} else {
// kMerge has (more or less) an arbitrary number of predecessors. We need
// to find the lowest common ancestor (LCA) of all of the predecessors.
DCHECK_EQ(block->kind_, Block::Kind::kMerge);
Block* dominator = block->LastPredecessor();
for (Block* pred = dominator->NeighboringPredecessor(); pred != nullptr;
pred = pred->NeighboringPredecessor()) {
dominator = dominator->GetCommonDominator(pred);
}
block->SetDominator(dominator);
}
}
}
template <class Derived>
void RandomAccessStackDominatorNode<Derived>::SetDominator(Derived* dominator) {
DCHECK_NOT_NULL(dominator);
// Determining the jmp pointer
Derived* t = dominator->jmp_;
if (dominator->len_ - t->len_ == t->len_ - t->jmp_len_) {
t = t->jmp_;
} else {
t = dominator;
}
// Initializing fields
nxt_ = dominator;
jmp_ = t;
len_ = dominator->len_ + 1;
jmp_len_ = jmp_->len_;
dominator->AddChild(static_cast<Derived*>(this));
}
template <class Derived>
Derived* RandomAccessStackDominatorNode<Derived>::GetCommonDominator(
RandomAccessStackDominatorNode<Derived>* b) {
RandomAccessStackDominatorNode* a = this;
if (b->len_ > a->len_) {
// Swapping |a| and |b| so that |a| always has a greater length.
std::swap(a, b);
}
DCHECK_GE(a->len_, 0);
DCHECK_GE(b->len_, 0);
// Going up the dominators of |a| in order to reach the level of |b|.
while (a->len_ != b->len_) {
DCHECK_GE(a->len_, 0);
if (a->jmp_len_ >= b->len_) {
a = a->jmp_;
} else {
a = a->nxt_;
}
}
// Going up the dominators of |a| and |b| simultaneously until |a| == |b|
while (a != b) {
DCHECK_EQ(a->len_, b->len_);
DCHECK_GE(a->len_, 0);
if (a->jmp_ == b->jmp_) {
// We found a common dominator, but we actually want to find the smallest
// one, so we go down in the current subtree.
a = a->nxt_;
b = b->nxt_;
} else {
a = a->jmp_;
b = b->jmp_;
}
}
return static_cast<Derived*>(a);
}
// PrintDominatorTree prints the dominator tree in a format that looks like:
//
// 0
// ╠ 1
// ╠ 2
// ╚ 3
// ╠ 4
// ║ ╠ 5
// ║ ╚ 6
// ╚ 7
// ╠ 8
// ╚ 16
//
// Where the numbers are the IDs of the Blocks.
// Doing so is mostly straight forward, with the subtelty that we need to know
// where to put "║" symbols (eg, in from of "╠ 5" above). The logic to do this
// is basically: "if the current node is not the last of its siblings, then,
// when going down to print its content, we add a "║" in front of each of its
// children; otherwise (current node is the last of its siblings), we add a
// blank space " " in front of its children". We maintain this information
// using a stack (implemented with a std::vector).
void Block::PrintDominatorTree(std::vector<const char*> tree_symbols,
bool has_next) const {
// Printing the current node.
if (tree_symbols.empty()) {
// This node is the root of the tree.
PrintF("%d\n", index().id());
tree_symbols.push_back("");
} else {
// This node is not the root of the tree; we start by printing the
// connectors of the previous levels.
for (const char* s : tree_symbols) PrintF("%s", s);
// Then, we print the node id, preceeded by a ╠ or ╚ connector.
const char* tree_connector_symbol = has_next ? "╠" : "╚";
PrintF("%s %d\n", tree_connector_symbol, index().id());
// And we add to the stack a connector to continue this path (if needed)
// while printing the current node's children.
const char* tree_cont_symbol = has_next ? "║ " : " ";
tree_symbols.push_back(tree_cont_symbol);
}
// Recursively printing the children of this node.
base::SmallVector<Block*, 8> children = Children();
for (Block* child : children) {
child->PrintDominatorTree(tree_symbols, child != children.back());
}
// Removing from the stack the "║" or " " corresponding to this node.
tree_symbols.pop_back();
}
std::ostream& operator<<(std::ostream& os, PrintAsBlockHeader block_header) {
const Block& block = block_header.block;
os << "\n" << block.kind() << " " << block.index();
......
......@@ -181,8 +181,69 @@ class OperationBuffer {
uint16_t* operation_sizes_;
};
template <class Derived>
class DominatorForwardTreeNode {
// A class storing a forward representation of the dominator tree, since the
// regular dominator tree is represented as pointers from the children to
// parents rather than parents to children.
public:
void AddChild(Derived* next) {
DCHECK_EQ(static_cast<Derived*>(this)->len_ + 1, next->len_);
next->neighboring_child_ = last_child_;
last_child_ = next;
}
Derived* LastChild() const { return last_child_; }
Derived* NeighboringChild() const { return neighboring_child_; }
bool HasChildren() const { return last_child_ != nullptr; }
base::SmallVector<Derived*, 8> Children() const {
base::SmallVector<Derived*, 8> result;
for (Derived* child = last_child_; child != nullptr;
child = child->neighboring_child_) {
result.push_back(child);
}
std::reverse(result.begin(), result.end());
return result;
}
private:
friend class Block;
Derived* neighboring_child_ = nullptr;
Derived* last_child_ = nullptr;
};
template <class Derived>
class RandomAccessStackDominatorNode
: public DominatorForwardTreeNode<Derived> {
// This class represents a node of a dominator tree implemented using Myers'
// Random-Access Stack (see
// https://publications.mpi-cbg.de/Myers_1983_6328.pdf). This datastructure
// enables searching for a predecessor of a node in log(h) time, where h is
// the height of the dominator tree.
public:
void SetDominator(Derived* dominator);
Derived* GetDominator() { return nxt_; }
// Returns the lowest common dominator of {this} and {other}.
Derived* GetCommonDominator(RandomAccessStackDominatorNode<Derived>* other);
private:
friend class Graph;
friend class DominatorForwardTreeNode<Derived>;
int len_ = 0;
Derived* nxt_ = nullptr;
Derived* jmp_ = nullptr;
// Myers' original datastructure requires to often check jmp_->len_, which is
// not so great on modern computers (memory access, caches & co). To speed up
// things a bit, we store here jmp_len_.
int jmp_len_ = 0;
};
// A basic block
class Block {
class Block : public RandomAccessStackDominatorNode<Block> {
public:
enum class Kind : uint8_t { kMerge, kLoopHeader, kBranchTarget };
......@@ -222,6 +283,17 @@ class Block {
return result;
}
#ifdef DEBUG
int PredecessorCount() {
int count = 0;
for (Block* pred = last_predecessor_; pred != nullptr;
pred = pred->neighboring_predecessor_) {
count++;
}
return count;
}
#endif
Block* LastPredecessor() const { return last_predecessor_; }
Block* NeighboringPredecessor() const { return neighboring_predecessor_; }
bool HasPredecessors() const { return last_predecessor_ != nullptr; }
......@@ -244,6 +316,10 @@ class Block {
return end_;
}
void PrintDominatorTree(
std::vector<const char*> tree_symbols = std::vector<const char*>(),
bool has_next = false) const;
explicit Block(Kind kind) : kind_(kind) {}
private:
......@@ -281,6 +357,8 @@ class Graph {
next_block_ = 0;
}
void GenerateDominatorTree();
const Operation& Get(OpIndex i) const {
// `Operation` contains const fields and can be overwritten with placement
// new. Therefore, std::launder is necessary to avoid undefined behavior.
......@@ -310,6 +388,10 @@ class Graph {
DCHECK_LT(i.id(), bound_blocks_.size());
return *bound_blocks_[i.id()];
}
Block* GetPtr(uint32_t index) {
DCHECK_LT(index, bound_blocks_.size());
return bound_blocks_[index];
}
OpIndex Index(const Operation& op) const { return operations_.Index(op); }
......@@ -345,7 +427,8 @@ class Graph {
V8_INLINE Block* NewBlock(Block::Kind kind) {
if (V8_UNLIKELY(next_block_ == all_blocks_.size())) {
constexpr size_t new_block_count = 64;
Block* blocks = graph_zone_->NewArray<Block>(new_block_count);
base::Vector<Block> blocks =
graph_zone_->NewVector<Block>(new_block_count, Block(kind));
for (size_t i = 0; i < new_block_count; ++i) {
all_blocks_.push_back(&blocks[i]);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment