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) {
void ControlFlowOptimizer::VisitBranch(Node* node) {
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* cond = NodeProperties::GetValueInput(branch, 0);
if (cond->opcode() != IrOpcode::kWord32Equal) return VisitNode(node);
if (cond->opcode() != IrOpcode::kWord32Equal) return false;
Int32BinopMatcher m(cond);
Node* index = m.left().node();
if (!m.right().HasValue()) return VisitNode(node);
if (!m.right().HasValue()) return false;
int32_t value = m.right().Value();
ZoneSet<int32_t> values(zone());
values.insert(value);
......@@ -122,6 +270,7 @@ void ControlFlowOptimizer::VisitBranch(Node* node) {
Enqueue(if_false);
branch->RemoveAllInputs();
}
return true;
}
......
......@@ -31,6 +31,9 @@ class ControlFlowOptimizer FINAL {
void VisitNode(Node* node);
void VisitBranch(Node* node);
bool TryBuildSwitch(Node* node);
bool TryCloneBranch(Node* node);
CommonOperatorBuilder* common() const;
Graph* graph() const;
JSGraph* jsgraph() const { return jsgraph_; }
......
......@@ -533,6 +533,8 @@ struct ControlFlowOptimizationPhase {
static const char* phase_name() { return "control flow optimization"; }
void Run(PipelineData* data, Zone* temp_zone) {
SourcePositionTable::Scope pos(data->source_positions(),
SourcePosition::Unknown());
ControlFlowOptimizer optimizer(data->jsgraph(), temp_zone);
optimizer.Optimize();
}
......@@ -957,7 +959,7 @@ Handle<Code> Pipeline::GenerateCode() {
RunPrintAndVerify("Lowered simplified");
// Optimize control flow.
if (FLAG_turbo_switch) {
if (FLAG_turbo_cf_optimization) {
Run<ControlFlowOptimizationPhase>();
RunPrintAndVerify("Control flow optimized");
}
......
......@@ -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_stress_loop_peeling, false,
"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,
"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 {
};
TEST_F(ControlFlowOptimizerTest, Switch) {
TEST_F(ControlFlowOptimizerTest, BuildSwitch) {
Node* index = Parameter(0);
Node* branch0 = graph()->NewNode(
common()->Branch(),
......@@ -65,6 +65,34 @@ TEST_F(ControlFlowOptimizerTest, Switch) {
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 internal
} // 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