[turboshaft] compute dominators on the fly, fix ValueNumbering

ValueNumberingAssembler is now based on the dominator tree of the
new graph rather than the one of the old graph (since using the old
graph could be invalid, because control flow can change during the
OptimizationPhase).
To do so, the dominator tree of the new graph is now computed on the
fly.

Bug: v8:12783
Change-Id: I2d628b4c0e50931b32e6c0455bf00a119fbf57eb
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3937978
Commit-Queue: Darius Mercadier <dmercadier@chromium.org>
Reviewed-by: Tobias Tebbi <tebbi@chromium.org>
Cr-Commit-Position: refs/heads/main@{#83628}
This commit is contained in:
Darius M 2022-10-11 14:11:48 +02:00 committed by V8 LUCI CQ
parent c6c865e4b9
commit f45728af06
4 changed files with 156 additions and 119 deletions

View File

@ -4,103 +4,13 @@
#include "src/compiler/turboshaft/graph.h"
#include <algorithm>
#include <iomanip>
#include "src/base/logging.h"
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

View File

@ -183,6 +183,11 @@ class OperationBuffer {
uint16_t* operation_sizes_;
};
template <class Derived>
class DominatorForwardTreeNode;
template <class Derived>
class RandomAccessStackDominatorNode;
template <class Derived>
class DominatorForwardTreeNode {
// A class storing a forward representation of the dominator tree, since the
@ -210,8 +215,9 @@ class DominatorForwardTreeNode {
}
private:
friend class Block;
#ifdef DEBUG
friend class RandomAccessStackDominatorNode<Derived>;
#endif
Derived* neighboring_child_ = nullptr;
Derived* last_child_ = nullptr;
};
@ -226,16 +232,26 @@ class RandomAccessStackDominatorNode
// the height of the dominator tree.
public:
void SetDominator(Derived* dominator);
void SetAsDominatorRoot();
Derived* GetDominator() { return nxt_; }
// Returns the lowest common dominator of {this} and {other}.
Derived* GetCommonDominator(RandomAccessStackDominatorNode<Derived>* other);
Derived* GetCommonDominator(
RandomAccessStackDominatorNode<Derived>* other) const;
bool IsDominatedBy(const Derived* other) const {
// TODO(dmercadier): we don't have to call GetCommonDominator and could
// determine quicker that {this} isn't dominated by {other}.
return GetCommonDominator(other) == other;
}
int Depth() const { return len_; }
private:
friend class Graph;
friend class DominatorForwardTreeNode<Derived>;
#ifdef DEBUG
friend class Block;
#endif
int len_ = 0;
Derived* nxt_ = nullptr;
@ -254,9 +270,11 @@ class Block : public RandomAccessStackDominatorNode<Block> {
bool IsLoopOrMerge() const { return IsLoop() || IsMerge(); }
bool IsLoop() const { return kind_ == Kind::kLoopHeader; }
bool IsMerge() const { return kind_ == Kind::kMerge; }
bool IsBranchTarget() const { return kind_ == Kind::kBranchTarget; }
bool IsHandler() const { return false; }
bool IsSwitchCase() const { return false; }
Kind kind() const { return kind_; }
void SetKind(Kind kind) { kind_ = kind; }
BlockIndex index() const { return index_; }
@ -318,6 +336,10 @@ class Block : public RandomAccessStackDominatorNode<Block> {
return end_;
}
// Computes the dominators of the this block, assuming that the dominators of
// its predecessors are already computed.
void ComputeDominator();
void PrintDominatorTree(
std::vector<const char*> tree_symbols = std::vector<const char*>(),
bool has_next = false) const;
@ -361,8 +383,6 @@ 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.
@ -473,6 +493,7 @@ class Graph {
DCHECK_EQ(block->index_, BlockIndex::Invalid());
block->index_ = BlockIndex(static_cast<uint32_t>(bound_blocks_.size()));
bound_blocks_.push_back(block);
block->ComputeDominator();
return true;
}
@ -728,6 +749,106 @@ std::ostream& operator<<(std::ostream& os, PrintAsBlockHeader block);
std::ostream& operator<<(std::ostream& os, const Graph& graph);
std::ostream& operator<<(std::ostream& os, const Block::Kind& kind);
inline void Block::ComputeDominator() {
if (V8_UNLIKELY(LastPredecessor() == nullptr)) {
// If the block has no predecessors, then it's the start block. 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.
SetAsDominatorRoot();
} else {
// If the block has one or more predecessors, the dominator is the lowest
// common ancestor (LCA) of all of the predecessors.
// Note that for BranchTarget, there is a single predecessor. This doesn't
// change the logic: the loop won't be entered, and the first (and only)
// predecessor is set as the dominator.
// Similarly, since we compute dominators on the fly, when we reach a
// kLoopHeader, we haven't visited its body yet, and it should only have one
// predecessor (the backedge is not here yet), which is its dominator.
DCHECK_IMPLIES(kind_ == Block::Kind::kLoopHeader, PredecessorCount() == 1);
Block* dominator = LastPredecessor();
for (Block* pred = dominator->NeighboringPredecessor(); pred != nullptr;
pred = pred->NeighboringPredecessor()) {
dominator = dominator->GetCommonDominator(pred);
}
SetDominator(dominator);
}
DCHECK_NE(jmp_, nullptr);
DCHECK_IMPLIES(nxt_ == nullptr, LastPredecessor() == nullptr);
DCHECK_IMPLIES(len_ == 0, LastPredecessor() == nullptr);
}
template <class Derived>
inline void RandomAccessStackDominatorNode<Derived>::SetAsDominatorRoot() {
jmp_ = static_cast<Derived*>(this);
nxt_ = nullptr;
len_ = 0;
jmp_len_ = 0;
}
template <class Derived>
inline void RandomAccessStackDominatorNode<Derived>::SetDominator(
Derived* dominator) {
DCHECK_NOT_NULL(dominator);
DCHECK_NULL(static_cast<Block*>(this)->neighboring_child_);
DCHECK_NULL(static_cast<Block*>(this)->last_child_);
// 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>
inline Derived* RandomAccessStackDominatorNode<Derived>::GetCommonDominator(
RandomAccessStackDominatorNode<Derived>* other) const {
const RandomAccessStackDominatorNode* a = this;
const RandomAccessStackDominatorNode* b = other;
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*>(
const_cast<RandomAccessStackDominatorNode<Derived>*>(a));
}
} // namespace v8::internal::compiler::turboshaft
#endif // V8_COMPILER_TURBOSHAFT_GRAPH_H_

View File

@ -184,7 +184,6 @@ struct OptimizationPhase<Analyzer, Assembler>::Impl {
template <bool trace_reduction>
void RunDominatorOrder() {
base::SmallVector<Block*, 128> dominator_visit_stack;
input_graph.GenerateDominatorTree();
dominator_visit_stack.push_back(input_graph.GetPtr(0));
while (!dominator_visit_stack.empty()) {

View File

@ -77,14 +77,15 @@ class ValueNumberingAssembler : public Assembler {
public:
ValueNumberingAssembler(Graph* graph, Zone* phase_zone)
: Assembler(graph, phase_zone), depths_heads_(phase_zone) {
: Assembler(graph, phase_zone),
dominator_path_(phase_zone),
depths_heads_(phase_zone) {
table_ = phase_zone->NewVector<Entry>(
base::bits::RoundUpToPowerOfTwo(
std::max<size_t>(128, graph->op_id_capacity() / 2)),
Entry());
entry_count_ = 0;
mask_ = table_.size() - 1;
current_depth_ = -1;
}
#define EMIT_OP(Name) \
@ -99,25 +100,30 @@ class ValueNumberingAssembler : public Assembler {
TURBOSHAFT_OPERATION_LIST(EMIT_OP)
#undef EMIT_OP
void EnterBlock(const Block& block) {
int new_depth = block.Depth();
// Remember that this assembler should only be used for OptimizationPhases
// that visit the graph in VisitOrder::kDominator order. We can't properly
// check that here, but we do two checks, which should be enough to ensure
// that we are actually visiting the graph in dominator order:
// - There should be only one block at depth 0 (the root).
// - There should be no "jumps" downward in the dominator tree ({new_depth}
// cannot be lower than {current_depth}+1).
DCHECK_IMPLIES(current_depth_ == 0, new_depth != 0);
DCHECK_LE(new_depth, current_depth_ + 1);
if (new_depth <= current_depth_) {
while (current_depth_ >= new_depth) {
bool Bind(Block* block) {
if (!Base::Bind(block)) return false;
ResetToBlock(block);
dominator_path_.push_back(block);
depths_heads_.push_back(nullptr);
return true;
}
// Resets {table_} up to the first dominator of {block} that it contains.
void ResetToBlock(Block* block) {
Block* target = block->GetDominator();
while (!dominator_path_.empty() && target != nullptr &&
dominator_path_.back() != target) {
if (dominator_path_.back()->Depth() > target->Depth()) {
ClearCurrentDepthEntries();
--current_depth_;
} else if (dominator_path_.back()->Depth() < target->Depth()) {
target = target->GetDominator();
} else {
// {target} and {dominator_path.back} have the same depth but are not
// equal, so we go one level up for both.
ClearCurrentDepthEntries();
target = target->GetDominator();
}
}
current_depth_ = new_depth;
depths_heads_.push_back(nullptr);
}
private:
@ -176,6 +182,7 @@ class ValueNumberingAssembler : public Assembler {
--entry_count_;
}
depths_heads_.pop_back();
dominator_path_.pop_back();
}
// If the table is too full, double its size and re-insert the old entries.
@ -254,7 +261,7 @@ class ValueNumberingAssembler : public Assembler {
return V8_LIKELY(entry > table_.begin()) ? entry - 1 : table_.end() - 1;
}
int current_depth_;
ZoneVector<Block*> dominator_path_;
base::Vector<Entry> table_;
size_t mask_;
size_t entry_count_;