de2abc6091
TEST=build Change-Id: I54cd75bbc942e11c4d719edcc64640da67077634 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/291965 Reviewed-by: Alexander Markov <alexmarkov@google.com> Commit-Queue: Ryan Macnak <rmacnak@google.com>
352 lines
14 KiB
C++
352 lines
14 KiB
C++
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
#include "vm/compiler/backend/branch_optimizer.h"
|
|
|
|
#include "vm/compiler/backend/flow_graph.h"
|
|
#include "vm/compiler/backend/il.h"
|
|
|
|
namespace dart {
|
|
|
|
// Returns true if the given phi has a single input use and
|
|
// is used in the environments either at the corresponding block entry or
|
|
// at the same instruction where input use is.
|
|
static bool PhiHasSingleUse(PhiInstr* phi, Value* use) {
|
|
if ((use->next_use() != nullptr) || (phi->input_use_list() != use)) {
|
|
return false;
|
|
}
|
|
|
|
BlockEntryInstr* block = phi->block();
|
|
for (Value* env_use = phi->env_use_list(); env_use != nullptr;
|
|
env_use = env_use->next_use()) {
|
|
if ((env_use->instruction() != block) &&
|
|
(env_use->instruction() != use->instruction())) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool BranchSimplifier::Match(JoinEntryInstr* block) {
|
|
// Match the pattern of a branch on a comparison whose left operand is a
|
|
// phi from the same block, and whose right operand is a constant.
|
|
//
|
|
// Branch(Comparison(kind, Phi, Constant))
|
|
//
|
|
// These are the branches produced by inlining in a test context. Also,
|
|
// the phi has no other uses so they can simply be eliminated. The block
|
|
// has no other phis and no instructions intervening between the phi and
|
|
// branch so the block can simply be eliminated.
|
|
BranchInstr* branch = block->last_instruction()->AsBranch();
|
|
ASSERT(branch != nullptr);
|
|
ComparisonInstr* comparison = branch->comparison();
|
|
if (comparison->InputCount() != 2) {
|
|
return false;
|
|
}
|
|
if (comparison->CanDeoptimize() || comparison->MayThrow()) {
|
|
return false;
|
|
}
|
|
Value* left = comparison->left();
|
|
PhiInstr* phi = left->definition()->AsPhi();
|
|
Value* right = comparison->right();
|
|
ConstantInstr* constant =
|
|
(right == nullptr) ? nullptr : right->definition()->AsConstant();
|
|
return (phi != nullptr) && (constant != nullptr) &&
|
|
(phi->GetBlock() == block) && PhiHasSingleUse(phi, left) &&
|
|
(block->next() == branch) && (block->phis()->length() == 1);
|
|
}
|
|
|
|
JoinEntryInstr* BranchSimplifier::ToJoinEntry(Zone* zone,
|
|
BlockEntryInstr* target) {
|
|
// Convert a target block into a join block. Branches will be duplicated
|
|
// so the former true and false targets become joins of the control flows
|
|
// from all the duplicated branches.
|
|
JoinEntryInstr* join = new (zone)
|
|
JoinEntryInstr(target->block_id(), target->try_index(), DeoptId::kNone);
|
|
join->InheritDeoptTarget(zone, target);
|
|
join->LinkTo(target->next());
|
|
join->set_last_instruction(target->last_instruction());
|
|
target->UnuseAllInputs();
|
|
return join;
|
|
}
|
|
|
|
TargetEntryInstr* BranchSimplifier::ToTargetEntry(Zone* zone,
|
|
BlockEntryInstr* target) {
|
|
auto replacement = new (zone)
|
|
TargetEntryInstr(target->block_id(), target->try_index(), DeoptId::kNone);
|
|
replacement->InheritDeoptTarget(zone, target);
|
|
replacement->LinkTo(target->next());
|
|
replacement->set_last_instruction(target->last_instruction());
|
|
target->UnuseAllInputs();
|
|
return replacement;
|
|
}
|
|
|
|
BranchInstr* BranchSimplifier::CloneBranch(Zone* zone,
|
|
BranchInstr* branch,
|
|
Value* new_left,
|
|
Value* new_right) {
|
|
ComparisonInstr* comparison = branch->comparison();
|
|
ComparisonInstr* new_comparison =
|
|
comparison->CopyWithNewOperands(new_left, new_right);
|
|
BranchInstr* new_branch =
|
|
new (zone) BranchInstr(new_comparison, DeoptId::kNone);
|
|
return new_branch;
|
|
}
|
|
|
|
void BranchSimplifier::Simplify(FlowGraph* flow_graph) {
|
|
// Optimize some branches that test the value of a phi. When it is safe
|
|
// to do so, push the branch to each of the predecessor blocks. This is
|
|
// an optimization when (a) it can avoid materializing a boolean object at
|
|
// the phi only to test its value, and (b) it can expose opportunities for
|
|
// constant propagation and unreachable code elimination. This
|
|
// optimization is intended to run after inlining which creates
|
|
// opportunities for optimization (a) and before constant folding which
|
|
// can perform optimization (b).
|
|
|
|
// Begin with a worklist of join blocks ending in branches. They are
|
|
// candidates for the pattern below.
|
|
Zone* zone = flow_graph->zone();
|
|
const GrowableArray<BlockEntryInstr*>& postorder = flow_graph->postorder();
|
|
GrowableArray<BlockEntryInstr*> worklist(postorder.length());
|
|
for (BlockIterator it(postorder); !it.Done(); it.Advance()) {
|
|
BlockEntryInstr* block = it.Current();
|
|
if (block->IsJoinEntry() && block->last_instruction()->IsBranch()) {
|
|
worklist.Add(block);
|
|
}
|
|
}
|
|
|
|
// Rewrite until no more instance of the pattern exists.
|
|
bool changed = false;
|
|
while (!worklist.is_empty()) {
|
|
// All blocks in the worklist are join blocks (ending with a branch).
|
|
JoinEntryInstr* block = worklist.RemoveLast()->AsJoinEntry();
|
|
ASSERT(block != nullptr);
|
|
|
|
if (Match(block)) {
|
|
changed = true;
|
|
|
|
// The branch will be copied and pushed to all the join's
|
|
// predecessors. Convert the true and false target blocks into join
|
|
// blocks to join the control flows from all of the true
|
|
// (respectively, false) targets of the copied branches.
|
|
//
|
|
// The converted join block will have no phis, so it cannot be another
|
|
// instance of the pattern. There is thus no need to add it to the
|
|
// worklist.
|
|
BranchInstr* branch = block->last_instruction()->AsBranch();
|
|
ASSERT(branch != nullptr);
|
|
JoinEntryInstr* join_true = ToJoinEntry(zone, branch->true_successor());
|
|
JoinEntryInstr* join_false = ToJoinEntry(zone, branch->false_successor());
|
|
|
|
ComparisonInstr* comparison = branch->comparison();
|
|
PhiInstr* phi = comparison->left()->definition()->AsPhi();
|
|
ConstantInstr* constant = comparison->right()->definition()->AsConstant();
|
|
ASSERT(constant != nullptr);
|
|
// Copy the constant and branch and push it to all the predecessors.
|
|
for (intptr_t i = 0, count = block->PredecessorCount(); i < count; ++i) {
|
|
GotoInstr* old_goto =
|
|
block->PredecessorAt(i)->last_instruction()->AsGoto();
|
|
ASSERT(old_goto != nullptr);
|
|
|
|
// Replace the goto in each predecessor with a rewritten branch,
|
|
// rewritten to use the corresponding phi input instead of the phi.
|
|
Value* new_left = phi->InputAt(i)->Copy(zone);
|
|
Value* new_right = new (zone) Value(constant);
|
|
BranchInstr* new_branch =
|
|
CloneBranch(zone, branch, new_left, new_right);
|
|
if (branch->env() == nullptr) {
|
|
new_branch->InheritDeoptTarget(zone, old_goto);
|
|
} else {
|
|
// Take the environment from the branch if it has one.
|
|
new_branch->InheritDeoptTarget(zone, branch);
|
|
// InheritDeoptTarget gave the new branch's comparison the same
|
|
// deopt id that it gave the new branch. The id should be the
|
|
// deopt id of the original comparison.
|
|
new_branch->comparison()->SetDeoptId(*comparison);
|
|
// The phi can be used in the branch's environment. Rename such
|
|
// uses.
|
|
Definition* replacement = phi->InputAt(i)->definition();
|
|
new_branch->ReplaceInEnvironment(phi, replacement);
|
|
}
|
|
|
|
new_branch->InsertBefore(old_goto);
|
|
new_branch->set_next(nullptr); // Detaching the goto from the graph.
|
|
old_goto->UnuseAllInputs();
|
|
|
|
// Update the predecessor block. We may have created another
|
|
// instance of the pattern so add it to the worklist if necessary.
|
|
BlockEntryInstr* branch_block = new_branch->GetBlock();
|
|
branch_block->set_last_instruction(new_branch);
|
|
if (branch_block->IsJoinEntry()) worklist.Add(branch_block);
|
|
|
|
// Connect the branch to the true and false joins, via empty target
|
|
// blocks.
|
|
TargetEntryInstr* true_target = new (zone) TargetEntryInstr(
|
|
flow_graph->max_block_id() + 1, block->try_index(), DeoptId::kNone);
|
|
true_target->InheritDeoptTarget(zone, join_true);
|
|
TargetEntryInstr* false_target = new (zone) TargetEntryInstr(
|
|
flow_graph->max_block_id() + 2, block->try_index(), DeoptId::kNone);
|
|
false_target->InheritDeoptTarget(zone, join_false);
|
|
flow_graph->set_max_block_id(flow_graph->max_block_id() + 2);
|
|
*new_branch->true_successor_address() = true_target;
|
|
*new_branch->false_successor_address() = false_target;
|
|
GotoInstr* goto_true = new (zone) GotoInstr(join_true, DeoptId::kNone);
|
|
goto_true->InheritDeoptTarget(zone, join_true);
|
|
true_target->LinkTo(goto_true);
|
|
true_target->set_last_instruction(goto_true);
|
|
GotoInstr* goto_false =
|
|
new (zone) GotoInstr(join_false, DeoptId::kNone);
|
|
goto_false->InheritDeoptTarget(zone, join_false);
|
|
false_target->LinkTo(goto_false);
|
|
false_target->set_last_instruction(goto_false);
|
|
}
|
|
// When all predecessors have been rewritten, the original block is
|
|
// unreachable from the graph.
|
|
phi->UnuseAllInputs();
|
|
branch->UnuseAllInputs();
|
|
block->UnuseAllInputs();
|
|
ASSERT(!phi->HasUses());
|
|
}
|
|
}
|
|
|
|
if (changed) {
|
|
// We may have changed the block order and the dominator tree.
|
|
flow_graph->DiscoverBlocks();
|
|
GrowableArray<BitVector*> dominance_frontier;
|
|
flow_graph->ComputeDominators(&dominance_frontier);
|
|
}
|
|
}
|
|
|
|
static bool IsTrivialBlock(BlockEntryInstr* block, Definition* defn) {
|
|
return (block->IsTargetEntry() && (block->PredecessorCount() == 1)) &&
|
|
((block->next() == block->last_instruction()) ||
|
|
((block->next() == defn) &&
|
|
(defn->next() == block->last_instruction())));
|
|
}
|
|
|
|
static void EliminateTrivialBlock(BlockEntryInstr* block,
|
|
Definition* instr,
|
|
IfThenElseInstr* before) {
|
|
block->UnuseAllInputs();
|
|
block->last_instruction()->UnuseAllInputs();
|
|
|
|
if ((block->next() == instr) &&
|
|
(instr->next() == block->last_instruction())) {
|
|
before->previous()->LinkTo(instr);
|
|
instr->LinkTo(before);
|
|
}
|
|
}
|
|
|
|
void IfConverter::Simplify(FlowGraph* flow_graph) {
|
|
Zone* zone = flow_graph->zone();
|
|
bool changed = false;
|
|
|
|
const GrowableArray<BlockEntryInstr*>& postorder = flow_graph->postorder();
|
|
for (BlockIterator it(postorder); !it.Done(); it.Advance()) {
|
|
BlockEntryInstr* block = it.Current();
|
|
JoinEntryInstr* join = block->AsJoinEntry();
|
|
|
|
// Detect diamond control flow pattern which materializes a value depending
|
|
// on the result of the comparison:
|
|
//
|
|
// B_pred:
|
|
// ...
|
|
// Branch if COMP goto (B_pred1, B_pred2)
|
|
// B_pred1: -- trivial block that contains at most one definition
|
|
// v1 = Constant(...)
|
|
// goto B_block
|
|
// B_pred2: -- trivial block that contains at most one definition
|
|
// v2 = Constant(...)
|
|
// goto B_block
|
|
// B_block:
|
|
// v3 = phi(v1, v2) -- single phi
|
|
//
|
|
// and replace it with
|
|
//
|
|
// Ba:
|
|
// v3 = IfThenElse(COMP ? v1 : v2)
|
|
//
|
|
if ((join != nullptr) && (join->phis() != nullptr) &&
|
|
(join->phis()->length() == 1) && (block->PredecessorCount() == 2)) {
|
|
BlockEntryInstr* pred1 = block->PredecessorAt(0);
|
|
BlockEntryInstr* pred2 = block->PredecessorAt(1);
|
|
|
|
PhiInstr* phi = (*join->phis())[0];
|
|
Value* v1 = phi->InputAt(0);
|
|
Value* v2 = phi->InputAt(1);
|
|
|
|
if (IsTrivialBlock(pred1, v1->definition()) &&
|
|
IsTrivialBlock(pred2, v2->definition()) &&
|
|
(pred1->PredecessorAt(0) == pred2->PredecessorAt(0))) {
|
|
BlockEntryInstr* pred = pred1->PredecessorAt(0);
|
|
BranchInstr* branch = pred->last_instruction()->AsBranch();
|
|
|
|
if (branch == nullptr) {
|
|
// There is no "B_pred" block, or the block is the IndirectGoto
|
|
// of a switch that uses it as a jump table.
|
|
ASSERT(pred->last_instruction()->IsGraphEntry() ||
|
|
pred->last_instruction()->IsIndirectGoto());
|
|
continue;
|
|
}
|
|
|
|
ComparisonInstr* comparison = branch->comparison();
|
|
|
|
// Check if the platform supports efficient branchless IfThenElseInstr
|
|
// for the given combination of comparison and values flowing from
|
|
// false and true paths.
|
|
if (IfThenElseInstr::Supports(comparison, v1, v2)) {
|
|
Value* if_true = (pred1 == branch->true_successor()) ? v1 : v2;
|
|
Value* if_false = (pred2 == branch->true_successor()) ? v1 : v2;
|
|
|
|
ComparisonInstr* new_comparison = comparison->CopyWithNewOperands(
|
|
comparison->left()->Copy(zone), comparison->right()->Copy(zone));
|
|
IfThenElseInstr* if_then_else =
|
|
new (zone) IfThenElseInstr(new_comparison, if_true->Copy(zone),
|
|
if_false->Copy(zone), DeoptId::kNone);
|
|
flow_graph->InsertBefore(branch, if_then_else, nullptr,
|
|
FlowGraph::kValue);
|
|
|
|
phi->ReplaceUsesWith(if_then_else);
|
|
|
|
// Connect IfThenElseInstr to the first instruction in the merge block
|
|
// effectively eliminating diamond control flow.
|
|
// Current block as well as pred1 and pred2 blocks are no longer in
|
|
// the graph at this point.
|
|
if_then_else->LinkTo(join->next());
|
|
pred->set_last_instruction(join->last_instruction());
|
|
|
|
// Resulting block must inherit block id from the eliminated current
|
|
// block to guarantee that ordering of phi operands in its successor
|
|
// stays consistent.
|
|
pred->set_block_id(block->block_id());
|
|
|
|
// If v1 and v2 were defined inside eliminated blocks pred1/pred2
|
|
// move them out to the place before inserted IfThenElse instruction.
|
|
EliminateTrivialBlock(pred1, v1->definition(), if_then_else);
|
|
EliminateTrivialBlock(pred2, v2->definition(), if_then_else);
|
|
|
|
// Update use lists to reflect changes in the graph.
|
|
phi->UnuseAllInputs();
|
|
branch->UnuseAllInputs();
|
|
block->UnuseAllInputs();
|
|
|
|
// The graph has changed. Recompute dominators and block orders after
|
|
// this pass is finished.
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (changed) {
|
|
// We may have changed the block order and the dominator tree.
|
|
flow_graph->DiscoverBlocks();
|
|
GrowableArray<BitVector*> dominance_frontier;
|
|
flow_graph->ComputeDominators(&dominance_frontier);
|
|
}
|
|
}
|
|
|
|
} // namespace dart
|