Commit 0882d3ff authored by bmeurer's avatar bmeurer Committed by Commit bot

[turbofan] Initial version of branch cloning.

This implements a special case of block cloning to recognize constructs like

 if (a ? b : c) { ... }

that happen to be generated by Emscripten quite often.

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

Cr-Commit-Position: refs/heads/master@{#26808}
parent 59275668
...@@ -54,13 +54,161 @@ void ControlFlowOptimizer::VisitNode(Node* node) { ...@@ -54,13 +54,161 @@ void ControlFlowOptimizer::VisitNode(Node* node) {
void ControlFlowOptimizer::VisitBranch(Node* node) { void ControlFlowOptimizer::VisitBranch(Node* node) {
DCHECK_EQ(IrOpcode::kBranch, node->opcode()); DCHECK_EQ(IrOpcode::kBranch, node->opcode());
if (TryBuildSwitch(node)) return;
if (TryCloneBranch(node)) return;
VisitNode(node);
}
bool ControlFlowOptimizer::TryCloneBranch(Node* node) {
DCHECK_EQ(IrOpcode::kBranch, node->opcode());
// This optimization is a special case of (super)block cloning. It takes an
// input graph as shown below and clones the Branch node for every predecessor
// to the Merge, essentially removing the Merge completely. This avoids
// materializing the bit for the Phi and may offer potential for further
// branch folding optimizations (i.e. because one or more inputs to the Phi is
// a constant). Note that there may be more Phi nodes hanging off the Merge,
// but we can only a certain subset of them currently (actually only Phi and
// EffectPhi nodes whose uses have either the IfTrue or IfFalse as control
// input).
// Control1 ... ControlN
// ^ ^
// | | Cond1 ... CondN
// +----+ +----+ ^ ^
// | | | |
// | | +----+ |
// Merge<--+ | +------------+
// ^ \|/
// | Phi
// | |
// Branch----+
// ^
// |
// +-----+-----+
// | |
// IfTrue IfFalse
// ^ ^
// | |
// The resulting graph (modulo the Phi and EffectPhi nodes) looks like this:
// Control1 Cond1 ... ControlN CondN
// ^ ^ ^ ^
// \ / \ /
// Branch ... Branch
// ^ ^
// | |
// +---+---+ +---+----+
// | | | |
// IfTrue IfFalse ... IfTrue IfFalse
// ^ ^ ^ ^
// | | | |
// +--+ +-------------+ |
// | | +--------------+ +--+
// | | | |
// Merge Merge
// ^ ^
// | |
Node* branch = node;
Node* cond = NodeProperties::GetValueInput(branch, 0);
if (!cond->OwnedBy(branch) || cond->opcode() != IrOpcode::kPhi) return false;
Node* merge = NodeProperties::GetControlInput(branch);
if (merge->opcode() != IrOpcode::kMerge ||
NodeProperties::GetControlInput(cond) != merge) {
return false;
}
// Grab the IfTrue/IfFalse projections of the Branch.
Node* control_projections[2];
NodeProperties::CollectControlProjections(branch, control_projections,
arraysize(control_projections));
Node* if_true = control_projections[0];
Node* if_false = control_projections[1];
DCHECK_EQ(IrOpcode::kIfTrue, if_true->opcode());
DCHECK_EQ(IrOpcode::kIfFalse, if_false->opcode());
// Check/collect other Phi/EffectPhi nodes hanging off the Merge.
NodeVector phis(zone());
for (Node* const use : merge->uses()) {
if (use == branch || use == cond) continue;
// We cannot currently deal with non-Phi/EffectPhi nodes hanging off the
// Merge. Ideally, we would just clone the nodes (and everything that
// depends on it to some distant join point), but that requires knowledge
// about dominance/post-dominance.
if (!NodeProperties::IsPhi(use)) return false;
for (Edge edge : use->use_edges()) {
// Right now we can only handle Phi/EffectPhi nodes whose uses are
// directly control-dependend on either the IfTrue or the IfFalse
// successor, because we know exactly how to update those uses.
// TODO(turbofan): Generalize this to all Phi/EffectPhi nodes using
// dominance/post-dominance on the sea of nodes.
if (edge.from()->op()->ControlInputCount() != 1) return false;
Node* control = NodeProperties::GetControlInput(edge.from());
if (NodeProperties::IsPhi(edge.from())) {
control = NodeProperties::GetControlInput(control, edge.index());
}
if (control != if_true && control != if_false) return false;
}
phis.push_back(use);
}
BranchHint const hint = BranchHintOf(branch->op());
int const input_count = merge->op()->ControlInputCount();
DCHECK_LE(1, input_count);
Node** const inputs = zone()->NewArray<Node*>(2 * input_count);
Node** const merge_true_inputs = &inputs[0];
Node** const merge_false_inputs = &inputs[input_count];
for (int index = 0; index < input_count; ++index) {
Node* cond1 = NodeProperties::GetValueInput(cond, index);
Node* control1 = NodeProperties::GetControlInput(merge, index);
Node* branch1 = graph()->NewNode(common()->Branch(hint), cond1, control1);
merge_true_inputs[index] = graph()->NewNode(common()->IfTrue(), branch1);
merge_false_inputs[index] = graph()->NewNode(common()->IfFalse(), branch1);
Enqueue(branch1);
}
Node* const merge_true = graph()->NewNode(common()->Merge(input_count),
input_count, merge_true_inputs);
Node* const merge_false = graph()->NewNode(common()->Merge(input_count),
input_count, merge_false_inputs);
for (Node* const phi : phis) {
for (int index = 0; index < input_count; ++index) {
inputs[index] = phi->InputAt(index);
}
inputs[input_count] = merge_true;
Node* phi_true = graph()->NewNode(phi->op(), input_count + 1, inputs);
inputs[input_count] = merge_false;
Node* phi_false = graph()->NewNode(phi->op(), input_count + 1, inputs);
for (Edge edge : phi->use_edges()) {
Node* control = NodeProperties::GetControlInput(edge.from());
if (NodeProperties::IsPhi(edge.from())) {
control = NodeProperties::GetControlInput(control, edge.index());
}
DCHECK(control == if_true || control == if_false);
edge.UpdateTo((control == if_true) ? phi_true : phi_false);
}
phi->Kill();
}
// Fix up IfTrue and IfFalse and kill all dead nodes.
if_false->ReplaceUses(merge_false);
if_true->ReplaceUses(merge_true);
if_false->Kill();
if_true->Kill();
branch->Kill();
cond->Kill();
merge->Kill();
return true;
}
bool ControlFlowOptimizer::TryBuildSwitch(Node* node) {
DCHECK_EQ(IrOpcode::kBranch, node->opcode());
Node* branch = node; Node* branch = node;
Node* cond = NodeProperties::GetValueInput(branch, 0); Node* cond = NodeProperties::GetValueInput(branch, 0);
if (cond->opcode() != IrOpcode::kWord32Equal) return VisitNode(node); if (cond->opcode() != IrOpcode::kWord32Equal) return false;
Int32BinopMatcher m(cond); Int32BinopMatcher m(cond);
Node* index = m.left().node(); Node* index = m.left().node();
if (!m.right().HasValue()) return VisitNode(node); if (!m.right().HasValue()) return false;
int32_t value = m.right().Value(); int32_t value = m.right().Value();
ZoneSet<int32_t> values(zone()); ZoneSet<int32_t> values(zone());
values.insert(value); values.insert(value);
...@@ -122,6 +270,7 @@ void ControlFlowOptimizer::VisitBranch(Node* node) { ...@@ -122,6 +270,7 @@ void ControlFlowOptimizer::VisitBranch(Node* node) {
Enqueue(if_false); Enqueue(if_false);
branch->RemoveAllInputs(); branch->RemoveAllInputs();
} }
return true;
} }
......
...@@ -31,6 +31,9 @@ class ControlFlowOptimizer FINAL { ...@@ -31,6 +31,9 @@ class ControlFlowOptimizer FINAL {
void VisitNode(Node* node); void VisitNode(Node* node);
void VisitBranch(Node* node); void VisitBranch(Node* node);
bool TryBuildSwitch(Node* node);
bool TryCloneBranch(Node* node);
CommonOperatorBuilder* common() const; CommonOperatorBuilder* common() const;
Graph* graph() const; Graph* graph() const;
JSGraph* jsgraph() const { return jsgraph_; } JSGraph* jsgraph() const { return jsgraph_; }
......
...@@ -533,6 +533,8 @@ struct ControlFlowOptimizationPhase { ...@@ -533,6 +533,8 @@ struct ControlFlowOptimizationPhase {
static const char* phase_name() { return "control flow optimization"; } static const char* phase_name() { return "control flow optimization"; }
void Run(PipelineData* data, Zone* temp_zone) { void Run(PipelineData* data, Zone* temp_zone) {
SourcePositionTable::Scope pos(data->source_positions(),
SourcePosition::Unknown());
ControlFlowOptimizer optimizer(data->jsgraph(), temp_zone); ControlFlowOptimizer optimizer(data->jsgraph(), temp_zone);
optimizer.Optimize(); optimizer.Optimize();
} }
...@@ -957,7 +959,7 @@ Handle<Code> Pipeline::GenerateCode() { ...@@ -957,7 +959,7 @@ Handle<Code> Pipeline::GenerateCode() {
RunPrintAndVerify("Lowered simplified"); RunPrintAndVerify("Lowered simplified");
// Optimize control flow. // Optimize control flow.
if (FLAG_turbo_switch) { if (FLAG_turbo_cf_optimization) {
Run<ControlFlowOptimizationPhase>(); Run<ControlFlowOptimizationPhase>();
RunPrintAndVerify("Control flow optimized"); RunPrintAndVerify("Control flow optimized");
} }
......
...@@ -424,7 +424,7 @@ DEFINE_BOOL(turbo_osr, false, "enable OSR in TurboFan") ...@@ -424,7 +424,7 @@ DEFINE_BOOL(turbo_osr, false, "enable OSR in TurboFan")
DEFINE_BOOL(turbo_exceptions, false, "enable exception handling in TurboFan") DEFINE_BOOL(turbo_exceptions, false, "enable exception handling in TurboFan")
DEFINE_BOOL(turbo_stress_loop_peeling, false, DEFINE_BOOL(turbo_stress_loop_peeling, false,
"stress loop peeling optimization") "stress loop peeling optimization")
DEFINE_BOOL(turbo_switch, true, "optimize switches in TurboFan") DEFINE_BOOL(turbo_cf_optimization, true, "optimize control flow in TurboFan")
DEFINE_INT(typed_array_max_size_in_heap, 64, DEFINE_INT(typed_array_max_size_in_heap, 64,
"threshold for in-heap typed array") "threshold for in-heap typed array")
......
// 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.
var if0 = (function Module() {
"use asm";
function if0(i, j) {
i = i|0;
j = j|0;
if (i == 0 ? j == 0 : 0) return 1;
return 0;
}
return {if0: if0};
})().if0;
assertEquals(1, if0(0, 0));
assertEquals(0, if0(11, 0));
assertEquals(0, if0(0, -1));
assertEquals(0, if0(-1024, 1));
var if1 = (function Module() {
"use asm";
function if1(i, j) {
i = i|0;
j = j|0;
if (i == 0 ? j == 0 : 1) return 0;
return 1;
}
return {if1: if1};
})().if1;
assertEquals(0, if1(0, 0));
assertEquals(0, if1(11, 0));
assertEquals(1, if1(0, -1));
assertEquals(0, if1(-1024, 9));
...@@ -39,7 +39,7 @@ class ControlFlowOptimizerTest : public GraphTest { ...@@ -39,7 +39,7 @@ class ControlFlowOptimizerTest : public GraphTest {
}; };
TEST_F(ControlFlowOptimizerTest, Switch) { TEST_F(ControlFlowOptimizerTest, BuildSwitch) {
Node* index = Parameter(0); Node* index = Parameter(0);
Node* branch0 = graph()->NewNode( Node* branch0 = graph()->NewNode(
common()->Branch(), common()->Branch(),
...@@ -65,6 +65,34 @@ TEST_F(ControlFlowOptimizerTest, Switch) { ...@@ -65,6 +65,34 @@ TEST_F(ControlFlowOptimizerTest, Switch) {
IsSwitch(index, start())))))); IsSwitch(index, start()))))));
} }
TEST_F(ControlFlowOptimizerTest, CloneBranch) {
Node* cond0 = Parameter(0);
Node* cond1 = Parameter(1);
Node* cond2 = Parameter(2);
Node* branch0 = graph()->NewNode(common()->Branch(), cond0, start());
Node* control1 = graph()->NewNode(common()->IfTrue(), branch0);
Node* control2 = graph()->NewNode(common()->IfFalse(), branch0);
Node* merge0 = graph()->NewNode(common()->Merge(2), control1, control2);
Node* phi0 =
graph()->NewNode(common()->Phi(kRepBit, 2), cond1, cond2, merge0);
Node* branch = graph()->NewNode(common()->Branch(), phi0, merge0);
Node* if_true = graph()->NewNode(common()->IfTrue(), branch);
Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
Node* merge = graph()->NewNode(common()->Merge(2), if_true, if_false);
graph()->SetEnd(graph()->NewNode(common()->End(), merge));
Optimize();
Capture<Node*> branch1_capture, branch2_capture;
EXPECT_THAT(
end(),
IsEnd(IsMerge(IsMerge(IsIfTrue(CaptureEq(&branch1_capture)),
IsIfTrue(CaptureEq(&branch2_capture))),
IsMerge(IsIfFalse(AllOf(CaptureEq(&branch1_capture),
IsBranch(cond1, control1))),
IsIfFalse(AllOf(CaptureEq(&branch2_capture),
IsBranch(cond2, control2)))))));
}
} // namespace compiler } // namespace compiler
} // namespace internal } // namespace internal
} // namespace v8 } // namespace v8
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