[turbofan] Redundant branch elimination.

Removes a branch that checks for a condition that has been checked on dominators of the branch.

This introduces a new reducer that propagates the list of checked conditions (and their boolean values) through the control flow graph. If it encounters a branch checking a condition with a known value, the branch is eliminated.

The analysis relies on loops being reducible: if a condition has been checked on all paths to loop entry, then it is checked in the loop (regardless what of the conditions checked inside the loop).

The implementation is fairly naive and could be improved:

- all the operation on the condition lists could be made allocation-free when revisited.

- we could try to use a map structure rather than a linked list (to make
lookups faster).

- the merging of control flow could be changed to take into account
  conditions from non-dominating paths (as long as all paths check
  the condition).

Review URL: https://codereview.chromium.org/1376293005

Cr-Commit-Position: refs/heads/master@{#31347}
This commit is contained in:
jarin 2015-10-17 10:50:11 -07:00 committed by Commit bot
parent e07d7028ff
commit 106aecf262
7 changed files with 595 additions and 0 deletions

View File

@ -692,6 +692,8 @@ source_set("v8_base") {
"src/compiler/ast-loop-assignment-analyzer.h",
"src/compiler/basic-block-instrumentor.cc",
"src/compiler/basic-block-instrumentor.h",
"src/compiler/branch-elimination.cc",
"src/compiler/branch-elimination.h",
"src/compiler/bytecode-graph-builder.cc",
"src/compiler/bytecode-graph-builder.h",
"src/compiler/change-lowering.cc",

View File

@ -0,0 +1,269 @@
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/compiler/branch-elimination.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/node-properties.h"
#include "src/compiler/simplified-operator.h"
namespace v8 {
namespace internal {
namespace compiler {
BranchElimination::BranchElimination(Editor* editor, JSGraph* js_graph,
Zone* zone)
: AdvancedReducer(editor),
node_conditions_(zone, js_graph->graph()->NodeCount()),
zone_(zone),
dead_(js_graph->graph()->NewNode(js_graph->common()->Dead())) {}
BranchElimination::~BranchElimination() {}
Reduction BranchElimination::Reduce(Node* node) {
switch (node->opcode()) {
case IrOpcode::kDead:
return NoChange();
case IrOpcode::kMerge:
return ReduceMerge(node);
case IrOpcode::kLoop:
return ReduceLoop(node);
case IrOpcode::kBranch:
return ReduceBranch(node);
case IrOpcode::kIfFalse:
return ReduceIf(node, false);
case IrOpcode::kIfTrue:
return ReduceIf(node, true);
case IrOpcode::kStart:
return ReduceStart(node);
default:
if (node->op()->ControlOutputCount() > 0) {
return ReduceOtherControl(node);
}
break;
}
return NoChange();
}
Reduction BranchElimination::ReduceBranch(Node* node) {
Node* condition = node->InputAt(0);
Node* control_input = NodeProperties::GetControlInput(node, 0);
const ControlPathConditions* from_input = node_conditions_.Get(control_input);
if (from_input != nullptr) {
Maybe<bool> condition_value = from_input->LookupCondition(condition);
// If we know the condition we can discard the branch.
if (condition_value.IsJust()) {
bool known_value = condition_value.FromJust();
for (Node* const use : node->uses()) {
switch (use->opcode()) {
case IrOpcode::kIfTrue:
Replace(use, known_value ? control_input : dead());
break;
case IrOpcode::kIfFalse:
Replace(use, known_value ? dead() : control_input);
break;
default:
UNREACHABLE();
}
}
return Replace(dead());
}
}
return TakeConditionsFromFirstControl(node);
}
Reduction BranchElimination::ReduceIf(Node* node, bool is_true_branch) {
// Add the condition to the list arriving from the input branch.
Node* branch = NodeProperties::GetControlInput(node, 0);
const ControlPathConditions* from_branch = node_conditions_.Get(branch);
// If we do not know anything about the predecessor, do not propagate just
// yet because we will have to recompute anyway once we compute the
// predecessor.
if (from_branch == nullptr) {
DCHECK(node_conditions_.Get(node) == nullptr);
return NoChange();
}
Node* condition = branch->InputAt(0);
return UpdateConditions(
node, from_branch->AddCondition(zone_, condition, is_true_branch));
}
Reduction BranchElimination::ReduceLoop(Node* node) {
// Here we rely on having only reducible loops:
// The loop entry edge always dominates the header, so we can just use
// the information from the loop entry edge.
return TakeConditionsFromFirstControl(node);
}
Reduction BranchElimination::ReduceMerge(Node* node) {
// Shortcut for the case when we do not know anything about some
// input.
for (int i = 0; i < node->InputCount(); i++) {
if (node_conditions_.Get(node->InputAt(i)) == nullptr) {
DCHECK(node_conditions_.Get(node) == nullptr);
return NoChange();
}
}
const ControlPathConditions* first = node_conditions_.Get(node->InputAt(0));
// Make a copy of the first input's conditions and merge with the conditions
// from other inputs.
ControlPathConditions* conditions =
new (zone_->New(sizeof(ControlPathConditions)))
ControlPathConditions(*first);
for (int i = 1; i < node->InputCount(); i++) {
conditions->Merge(*(node_conditions_.Get(node->InputAt(i))));
}
return UpdateConditions(node, conditions);
}
Reduction BranchElimination::ReduceStart(Node* node) {
return UpdateConditions(node, ControlPathConditions::Empty(zone_));
}
const BranchElimination::ControlPathConditions*
BranchElimination::PathConditionsForControlNodes::Get(Node* node) {
if (static_cast<size_t>(node->id()) < info_for_node_.size()) {
return info_for_node_[node->id()];
}
return nullptr;
}
void BranchElimination::PathConditionsForControlNodes::Set(
Node* node, const ControlPathConditions* conditions) {
size_t index = static_cast<size_t>(node->id());
if (index >= info_for_node_.size()) {
info_for_node_.resize(index + 1, nullptr);
}
info_for_node_[index] = conditions;
}
Reduction BranchElimination::ReduceOtherControl(Node* node) {
DCHECK_EQ(1, node->op()->ControlInputCount());
return TakeConditionsFromFirstControl(node);
}
Reduction BranchElimination::TakeConditionsFromFirstControl(Node* node) {
// We just propagate the information from the control input (ideally,
// we would only revisit control uses if there is change).
const ControlPathConditions* from_input =
node_conditions_.Get(NodeProperties::GetControlInput(node, 0));
return UpdateConditions(node, from_input);
}
Reduction BranchElimination::UpdateConditions(
Node* node, const ControlPathConditions* conditions) {
const ControlPathConditions* original = node_conditions_.Get(node);
// Only signal that the node has Changed if the condition information has
// changed.
if (conditions != original) {
if (original == nullptr || *conditions != *original) {
node_conditions_.Set(node, conditions);
return Changed(node);
}
}
return NoChange();
}
// static
const BranchElimination::ControlPathConditions*
BranchElimination::ControlPathConditions::Empty(Zone* zone) {
return new (zone->New(sizeof(ControlPathConditions)))
ControlPathConditions(nullptr, 0);
}
void BranchElimination::ControlPathConditions::Merge(
const ControlPathConditions& other) {
// Change the current condition list to a longest common tail
// of this condition list and the other list. (The common tail
// should correspond to the list from the common dominator.)
// First, we throw away the prefix of the longer list, so that
// we have lists of the same length.
size_t other_size = other.condition_count_;
BranchCondition* other_condition = other.head_;
while (other_size > condition_count_) {
other_condition = other_condition->next;
other_size--;
}
while (condition_count_ > other_size) {
head_ = head_->next;
condition_count_--;
}
// Then we go through both lists in lock-step until we find
// the common tail.
while (head_ != other_condition) {
DCHECK(condition_count_ > 0);
condition_count_--;
other_condition = other_condition->next;
head_ = head_->next;
}
}
const BranchElimination::ControlPathConditions*
BranchElimination::ControlPathConditions::AddCondition(Zone* zone,
Node* condition,
bool is_true) const {
DCHECK(LookupCondition(condition).IsNothing());
BranchCondition* new_head = new (zone->New(sizeof(BranchCondition)))
BranchCondition(condition, is_true, head_);
ControlPathConditions* conditions =
new (zone->New(sizeof(ControlPathConditions)))
ControlPathConditions(new_head, condition_count_ + 1);
return conditions;
}
Maybe<bool> BranchElimination::ControlPathConditions::LookupCondition(
Node* condition) const {
for (BranchCondition* current = head_; current != nullptr;
current = current->next) {
if (current->condition == condition) {
return Just<bool>(current->is_true);
}
}
return Nothing<bool>();
}
bool BranchElimination::ControlPathConditions::operator==(
const ControlPathConditions& other) const {
if (condition_count_ != other.condition_count_) return false;
BranchCondition* this_condition = head_;
BranchCondition* other_condition = other.head_;
while (true) {
if (this_condition == other_condition) return true;
if (this_condition->condition != other_condition->condition ||
this_condition->is_true != other_condition->is_true) {
return false;
}
this_condition = this_condition->next;
other_condition = other_condition->next;
}
UNREACHABLE();
return false;
}
} // namespace compiler
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,97 @@
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_COMPILER_BRANCH_CONDITION_ELIMINATION_H_
#define V8_COMPILER_BRANCH_CONDITION_ELIMINATION_H_
#include "src/compiler/graph-reducer.h"
namespace v8 {
namespace internal {
namespace compiler {
class JSGraph;
class BranchElimination final : public AdvancedReducer {
public:
BranchElimination(Editor* editor, JSGraph* js_graph, Zone* zone);
~BranchElimination() final;
Reduction Reduce(Node* node) final;
private:
struct BranchCondition {
Node* condition;
bool is_true;
BranchCondition* next;
BranchCondition(Node* condition, bool is_true, BranchCondition* next)
: condition(condition), is_true(is_true), next(next) {}
};
// Class for tracking information about branch conditions.
// At the moment it is a linked list of conditions and their values
// (true or false).
class ControlPathConditions {
public:
Maybe<bool> LookupCondition(Node* condition) const;
const ControlPathConditions* AddCondition(Zone* zone, Node* condition,
bool is_true) const;
static const ControlPathConditions* Empty(Zone* zone);
void Merge(const ControlPathConditions& other);
bool operator==(const ControlPathConditions& other) const;
bool operator!=(const ControlPathConditions& other) const {
return !(*this == other);
}
private:
ControlPathConditions(BranchCondition* head, size_t condition_count)
: head_(head), condition_count_(condition_count) {}
BranchCondition* head_;
// We keep track of the list length so that we can find the longest
// common tail easily.
size_t condition_count_;
};
// Maps each control node to the condition information known about the node.
// If the information is nullptr, then we have not calculated the information
// yet.
class PathConditionsForControlNodes {
public:
PathConditionsForControlNodes(Zone* zone, size_t size_hint)
: info_for_node_(size_hint, nullptr, zone) {}
const ControlPathConditions* Get(Node* node);
void Set(Node* node, const ControlPathConditions* conditions);
private:
ZoneVector<const ControlPathConditions*> info_for_node_;
};
Reduction ReduceBranch(Node* node);
Reduction ReduceIf(Node* node, bool is_true_branch);
Reduction ReduceLoop(Node* node);
Reduction ReduceMerge(Node* node);
Reduction ReduceStart(Node* node);
Reduction ReduceOtherControl(Node* node);
Reduction TakeConditionsFromFirstControl(Node* node);
Reduction UpdateConditions(Node* node,
const ControlPathConditions* conditions);
Node* dead() const { return dead_; }
PathConditionsForControlNodes node_conditions_;
Zone* zone_;
Node* dead_;
};
} // namespace compiler
} // namespace internal
} // namespace v8
#endif // V8_COMPILER_BRANCH_CONDITION_ELIMINATION_H_

View File

@ -12,6 +12,7 @@
#include "src/compiler/ast-graph-builder.h"
#include "src/compiler/ast-loop-assignment-analyzer.h"
#include "src/compiler/basic-block-instrumentor.h"
#include "src/compiler/branch-elimination.h"
#include "src/compiler/bytecode-graph-builder.h"
#include "src/compiler/change-lowering.h"
#include "src/compiler/code-generator.h"
@ -644,6 +645,22 @@ struct TypedLoweringPhase {
};
struct BranchEliminationPhase {
static const char* phase_name() { return "branch condition elimination"; }
void Run(PipelineData* data, Zone* temp_zone) {
JSGraphReducer graph_reducer(data->jsgraph(), temp_zone);
BranchElimination branch_condition_elimination(&graph_reducer,
data->jsgraph(), temp_zone);
DeadCodeElimination dead_code_elimination(&graph_reducer, data->graph(),
data->common());
AddReducer(data, &graph_reducer, &branch_condition_elimination);
AddReducer(data, &graph_reducer, &dead_code_elimination);
graph_reducer.ReduceGraph();
}
};
struct SimplifiedLoweringPhase {
static const char* phase_name() { return "simplified lowering"; }
@ -1158,6 +1175,9 @@ Handle<Code> Pipeline::GenerateCode() {
Run<SimplifiedLoweringPhase>();
RunPrintAndVerify("Lowered simplified");
Run<BranchEliminationPhase>();
RunPrintAndVerify("Branch conditions eliminated");
// Optimize control flow.
if (FLAG_turbo_cf_optimization) {
Run<ControlFlowOptimizationPhase>();

View File

@ -0,0 +1,204 @@
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/compiler/branch-elimination.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/linkage.h"
#include "src/compiler/node-properties.h"
#include "test/unittests/compiler/compiler-test-utils.h"
#include "test/unittests/compiler/graph-unittest.h"
#include "test/unittests/compiler/node-test-utils.h"
#include "testing/gmock-support.h"
namespace v8 {
namespace internal {
namespace compiler {
class BranchEliminationTest : public TypedGraphTest {
public:
BranchEliminationTest()
: machine_(zone(), kMachPtr, MachineOperatorBuilder::kNoFlags) {}
MachineOperatorBuilder* machine() { return &machine_; }
void Reduce() {
JSOperatorBuilder javascript(zone());
JSGraph jsgraph(isolate(), graph(), common(), &javascript, nullptr,
machine());
GraphReducer graph_reducer(zone(), graph(), jsgraph.Dead());
BranchElimination branch_condition_elimination(&graph_reducer, &jsgraph,
zone());
graph_reducer.AddReducer(&branch_condition_elimination);
graph_reducer.ReduceGraph();
}
private:
MachineOperatorBuilder machine_;
};
TEST_F(BranchEliminationTest, NestedBranchSameTrue) {
// { return (x ? (x ? 1 : 2) : 3; }
// should be reduced to
// { return (x ? 1 : 3; }
Node* condition = Parameter(0);
Node* outer_branch =
graph()->NewNode(common()->Branch(), condition, graph()->start());
Node* outer_if_true = graph()->NewNode(common()->IfTrue(), outer_branch);
Node* inner_branch =
graph()->NewNode(common()->Branch(), condition, outer_if_true);
Node* inner_if_true = graph()->NewNode(common()->IfTrue(), inner_branch);
Node* inner_if_false = graph()->NewNode(common()->IfFalse(), inner_branch);
Node* inner_merge =
graph()->NewNode(common()->Merge(2), inner_if_true, inner_if_false);
Node* inner_phi =
graph()->NewNode(common()->Phi(kMachInt32, 2), Int32Constant(1),
Int32Constant(2), inner_merge);
Node* outer_if_false = graph()->NewNode(common()->IfFalse(), outer_branch);
Node* outer_merge =
graph()->NewNode(common()->Merge(2), inner_merge, outer_if_false);
Node* outer_phi = graph()->NewNode(common()->Phi(kMachInt32, 2), inner_phi,
Int32Constant(3), outer_merge);
Node* ret = graph()->NewNode(common()->Return(), outer_phi, graph()->start(),
outer_merge);
graph()->SetEnd(graph()->NewNode(common()->End(1), ret));
Reduce();
// Outer branch should not be rewritten, the inner branch should be discarded.
EXPECT_THAT(outer_branch, IsBranch(condition, graph()->start()));
EXPECT_THAT(inner_phi,
IsPhi(kMachInt32, IsInt32Constant(1), IsInt32Constant(2),
IsMerge(outer_if_true, IsDead())));
}
TEST_F(BranchEliminationTest, NestedBranchSameFalse) {
// { return (x ? 1 : (x ? 2 : 3); }
// should be reduced to
// { return (x ? 1 : 3; }
Node* condition = Parameter(0);
Node* outer_branch =
graph()->NewNode(common()->Branch(), condition, graph()->start());
Node* outer_if_true = graph()->NewNode(common()->IfTrue(), outer_branch);
Node* outer_if_false = graph()->NewNode(common()->IfFalse(), outer_branch);
Node* inner_branch =
graph()->NewNode(common()->Branch(), condition, outer_if_false);
Node* inner_if_true = graph()->NewNode(common()->IfTrue(), inner_branch);
Node* inner_if_false = graph()->NewNode(common()->IfFalse(), inner_branch);
Node* inner_merge =
graph()->NewNode(common()->Merge(2), inner_if_true, inner_if_false);
Node* inner_phi =
graph()->NewNode(common()->Phi(kMachInt32, 2), Int32Constant(2),
Int32Constant(3), inner_merge);
Node* outer_merge =
graph()->NewNode(common()->Merge(2), outer_if_true, inner_merge);
Node* outer_phi = graph()->NewNode(common()->Phi(kMachInt32, 2),
Int32Constant(1), inner_phi, outer_merge);
Node* ret = graph()->NewNode(common()->Return(), outer_phi, graph()->start(),
outer_merge);
graph()->SetEnd(graph()->NewNode(common()->End(1), ret));
Reduce();
// Outer branch should not be rewritten, the inner branch should be discarded.
EXPECT_THAT(outer_branch, IsBranch(condition, graph()->start()));
EXPECT_THAT(inner_phi,
IsPhi(kMachInt32, IsInt32Constant(2), IsInt32Constant(3),
IsMerge(IsDead(), outer_if_false)));
}
TEST_F(BranchEliminationTest, BranchAfterDiamond) {
// { var y = x ? 1 : 2; return y + x ? 3 : 4; }
// should not be reduced.
Node* condition = Parameter(0);
Node* branch1 =
graph()->NewNode(common()->Branch(), condition, graph()->start());
Node* if_true1 = graph()->NewNode(common()->IfTrue(), branch1);
Node* if_false1 = graph()->NewNode(common()->IfFalse(), branch1);
Node* merge1 = graph()->NewNode(common()->Merge(2), if_true1, if_false1);
Node* phi1 = graph()->NewNode(common()->Phi(kMachInt32, 2), Int32Constant(1),
Int32Constant(2), merge1);
Node* branch2 = graph()->NewNode(common()->Branch(), condition, merge1);
Node* if_true2 = graph()->NewNode(common()->IfTrue(), branch2);
Node* if_false2 = graph()->NewNode(common()->IfFalse(), branch2);
Node* merge2 = graph()->NewNode(common()->Merge(2), if_true2, if_false2);
Node* phi2 = graph()->NewNode(common()->Phi(kMachInt32, 2), Int32Constant(3),
Int32Constant(4), merge1);
Node* add = graph()->NewNode(machine()->Int32Add(), phi1, phi2);
Node* ret =
graph()->NewNode(common()->Return(), add, graph()->start(), merge2);
graph()->SetEnd(graph()->NewNode(common()->End(1), ret));
Reduce();
// Outer branch should not be rewritten, the inner branch condition should
// be true.
EXPECT_THAT(branch1, IsBranch(condition, graph()->start()));
EXPECT_THAT(branch2, IsBranch(condition, merge1));
}
TEST_F(BranchEliminationTest, BranchInsideLoopSame) {
// if (x) while (x) { return 2; } else { return 1; }
// should be rewritten to
// if (x) while (true) { return 2; } else { return 1; }
Node* condition = Parameter(0);
Node* outer_branch =
graph()->NewNode(common()->Branch(), condition, graph()->start());
Node* outer_if_true = graph()->NewNode(common()->IfTrue(), outer_branch);
Node* loop = graph()->NewNode(common()->Loop(1), outer_if_true);
Node* effect =
graph()->NewNode(common()->EffectPhi(1), graph()->start(), loop);
Node* inner_branch = graph()->NewNode(common()->Branch(), condition, loop);
Node* inner_if_true = graph()->NewNode(common()->IfTrue(), inner_branch);
Node* ret1 = graph()->NewNode(common()->Return(), Int32Constant(2), effect,
inner_if_true);
Node* inner_if_false = graph()->NewNode(common()->IfFalse(), inner_branch);
loop->AppendInput(zone(), inner_if_false);
NodeProperties::ChangeOp(loop, common()->Loop(2));
effect->InsertInput(zone(), 1, effect);
NodeProperties::ChangeOp(effect, common()->EffectPhi(2));
Node* outer_if_false = graph()->NewNode(common()->IfFalse(), outer_branch);
Node* outer_merge =
graph()->NewNode(common()->Merge(2), loop, outer_if_false);
Node* outer_ephi = graph()->NewNode(common()->EffectPhi(2), effect,
graph()->start(), outer_merge);
Node* ret2 = graph()->NewNode(common()->Return(), Int32Constant(1),
outer_ephi, outer_merge);
Node* terminate = graph()->NewNode(common()->Terminate(), effect, loop);
graph()->SetEnd(graph()->NewNode(common()->End(3), ret1, ret2, terminate));
Reduce();
// Outer branch should not be rewritten, the inner branch should be discarded.
EXPECT_THAT(outer_branch, IsBranch(condition, graph()->start()));
EXPECT_THAT(ret1, IsReturn(IsInt32Constant(2), effect, loop));
}
} // namespace compiler
} // namespace internal
} // namespace v8

View File

@ -43,6 +43,7 @@
'base/sys-info-unittest.cc',
'base/utils/random-number-generator-unittest.cc',
'char-predicates-unittest.cc',
'compiler/branch-elimination-unittest.cc',
'compiler/bytecode-graph-builder-unittest.cc',
'compiler/change-lowering-unittest.cc',
'compiler/coalesced-live-ranges-unittest.cc',

View File

@ -457,6 +457,8 @@
'../../src/compiler/ast-loop-assignment-analyzer.h',
'../../src/compiler/basic-block-instrumentor.cc',
'../../src/compiler/basic-block-instrumentor.h',
'../../src/compiler/branch-elimination.cc',
'../../src/compiler/branch-elimination.h',
'../../src/compiler/bytecode-graph-builder.cc',
'../../src/compiler/bytecode-graph-builder.h',
'../../src/compiler/change-lowering.cc',