blob: f68bbcaef3379903f93cec3ce13d18b07e604846 [file] [log] [blame]
// 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/branch_optimizer.h"
#include "vm/flow_graph.h"
#include "vm/intermediate_language.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() != NULL) || (phi->input_use_list() != use)) {
return false;
}
BlockEntryInstr* block = phi->block();
for (Value* env_use = phi->env_use_list(); env_use != NULL;
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 != NULL);
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 == NULL) ? NULL : right->definition()->AsConstant();
return (phi != NULL) && (constant != NULL) && (phi->GetBlock() == block) &&
PhiHasSingleUse(phi, left) && (block->next() == branch) &&
(block->phis()->length() == 1);
}
JoinEntryInstr* BranchSimplifier::ToJoinEntry(Zone* zone,
TargetEntryInstr* 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());
join->InheritDeoptTarget(zone, target);
join->LinkTo(target->next());
join->set_last_instruction(target->last_instruction());
target->UnuseAllInputs();
return join;
}
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);
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 != NULL);
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 != NULL);
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 != NULL);
// 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 != NULL);
// 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() == NULL) {
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.
for (Environment::DeepIterator it(new_branch->env()); !it.Done();
it.Advance()) {
Value* use = it.CurrentValue();
if (use->definition() == phi) {
Definition* replacement = phi->InputAt(i)->definition();
use->RemoveFromUseList();
use->set_definition(replacement);
replacement->AddEnvUse(use);
}
}
}
new_branch->InsertBefore(old_goto);
new_branch->set_next(NULL); // 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());
true_target->InheritDeoptTarget(zone, join_true);
TargetEntryInstr* false_target = new (zone) TargetEntryInstr(
flow_graph->max_block_id() + 2, block->try_index());
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);
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);
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 != NULL) && (join->phis() != NULL) &&
(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();
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));
flow_graph->InsertBefore(branch, if_then_else, NULL,
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