diff --git a/BUILD.gn b/BUILD.gn index 2946d5ad218..2a1a8fa6412 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -527,6 +527,8 @@ source_set("v8_base") { "src/compiler/control-builders.cc", "src/compiler/control-builders.h", "src/compiler/control-equivalence.h", + "src/compiler/control-flow-optimizer.cc", + "src/compiler/control-flow-optimizer.h", "src/compiler/control-reducer.cc", "src/compiler/control-reducer.h", "src/compiler/diamond.h", diff --git a/src/compiler/arm/code-generator-arm.cc b/src/compiler/arm/code-generator-arm.cc index 52f0765e043..1ff7ea3acc6 100644 --- a/src/compiler/arm/code-generator-arm.cc +++ b/src/compiler/arm/code-generator-arm.cc @@ -336,8 +336,12 @@ void CodeGenerator::AssembleArchInstruction(Instruction* instr) { AssembleArchJump(i.InputRpo(0)); DCHECK_EQ(LeaveCC, i.OutputSBit()); break; - case kArchSwitch: - AssembleArchSwitch(instr); + case kArchLookupSwitch: + AssembleArchLookupSwitch(instr); + DCHECK_EQ(LeaveCC, i.OutputSBit()); + break; + case kArchTableSwitch: + AssembleArchTableSwitch(instr); DCHECK_EQ(LeaveCC, i.OutputSBit()); break; case kArchNop: @@ -741,18 +745,6 @@ void CodeGenerator::AssembleArchJump(BasicBlock::RpoNumber target) { } -void CodeGenerator::AssembleArchSwitch(Instruction* instr) { - ArmOperandConverter i(this, instr); - int const kNumLabels = static_cast(instr->InputCount() - 1); - __ BlockConstPoolFor(kNumLabels + 2); - __ ldr(pc, MemOperand(pc, i.InputRegister(0), LSL, 2)); - __ nop(); - for (int index = 0; index < kNumLabels; ++index) { - __ dd(GetLabel(i.InputRpo(index + 1))); - } -} - - // Assembles boolean materializations after an instruction. void CodeGenerator::AssembleArchBoolean(Instruction* instr, FlagsCondition condition) { @@ -768,6 +760,31 @@ void CodeGenerator::AssembleArchBoolean(Instruction* instr, } +void CodeGenerator::AssembleArchLookupSwitch(Instruction* instr) { + ArmOperandConverter i(this, instr); + Register input = i.InputRegister(0); + for (size_t index = 2; index < instr->InputCount(); index += 2) { + __ cmp(input, Operand(i.InputInt32(index + 0))); + __ b(eq, GetLabel(i.InputRpo(index + 1))); + } + AssembleArchJump(i.InputRpo(1)); +} + + +void CodeGenerator::AssembleArchTableSwitch(Instruction* instr) { + ArmOperandConverter i(this, instr); + Register input = i.InputRegister(0); + size_t const case_count = instr->InputCount() - 2; + __ cmp(input, Operand(case_count)); + __ BlockConstPoolFor(case_count + 2); + __ ldr(pc, MemOperand(pc, input, LSL, 2), lo); + __ b(GetLabel(i.InputRpo(1))); + for (size_t index = 0; index < case_count; ++index) { + __ dd(GetLabel(i.InputRpo(index + 2))); + } +} + + void CodeGenerator::AssembleDeoptimizerCall(int deoptimization_id) { Address deopt_entry = Deoptimizer::GetDeoptimizationEntry( isolate(), deoptimization_id, Deoptimizer::LAZY); diff --git a/src/compiler/arm/instruction-selector-arm.cc b/src/compiler/arm/instruction-selector-arm.cc index 9122359a293..47511a1ebf3 100644 --- a/src/compiler/arm/instruction-selector-arm.cc +++ b/src/compiler/arm/instruction-selector-arm.cc @@ -1254,6 +1254,67 @@ void InstructionSelector::VisitBranch(Node* branch, BasicBlock* tbranch, } +void InstructionSelector::VisitSwitch(Node* node, BasicBlock* default_branch, + BasicBlock** case_branches, + int32_t* case_values, size_t case_count, + int32_t min_value, int32_t max_value) { + ArmOperandGenerator g(this); + InstructionOperand value_operand = g.UseRegister(node->InputAt(0)); + InstructionOperand default_operand = g.Label(default_branch); + + // Note that {value_range} can be 0 if {min_value} is -2^31 and {max_value} + // is 2^31-1, so don't assume that it's non-zero below. + size_t value_range = + 1u + bit_cast(max_value) - bit_cast(min_value); + + // Determine whether to issue an ArchTableSwitch or an ArchLookupSwitch + // instruction. + size_t table_space_cost = 4 + value_range; + size_t table_time_cost = 3; + size_t lookup_space_cost = 3 + 2 * case_count; + size_t lookup_time_cost = case_count; + if (case_count > 0 && + table_space_cost + 3 * table_time_cost <= + lookup_space_cost + 3 * lookup_time_cost && + min_value > std::numeric_limits::min()) { + InstructionOperand index_operand = value_operand; + if (min_value) { + index_operand = g.TempRegister(); + Emit(kArmSub | AddressingModeField::encode(kMode_Operand2_I), + index_operand, value_operand, g.TempImmediate(min_value)); + } + size_t input_count = 2 + value_range; + auto* inputs = zone()->NewArray(input_count); + inputs[0] = index_operand; + std::fill(&inputs[1], &inputs[input_count], default_operand); + for (size_t index = 0; index < case_count; ++index) { + size_t value = case_values[index] - min_value; + BasicBlock* branch = case_branches[index]; + DCHECK_LE(0u, value); + DCHECK_LT(value + 2, input_count); + inputs[value + 2] = g.Label(branch); + } + Emit(kArchTableSwitch, 0, nullptr, input_count, inputs, 0, nullptr) + ->MarkAsControl(); + return; + } + + // Generate a sequence of conditional jumps. + size_t input_count = 2 + case_count * 2; + auto* inputs = zone()->NewArray(input_count); + inputs[0] = value_operand; + inputs[1] = default_operand; + for (size_t index = 0; index < case_count; ++index) { + int32_t value = case_values[index]; + BasicBlock* branch = case_branches[index]; + inputs[index * 2 + 2 + 0] = g.TempImmediate(value); + inputs[index * 2 + 2 + 1] = g.Label(branch); + } + Emit(kArchLookupSwitch, 0, nullptr, input_count, inputs, 0, nullptr) + ->MarkAsControl(); +} + + void InstructionSelector::VisitWord32Equal(Node* const node) { FlagsContinuation cont(kEqual, node); Int32BinopMatcher m(node); diff --git a/src/compiler/arm64/code-generator-arm64.cc b/src/compiler/arm64/code-generator-arm64.cc index f8016167863..89c2ffb6f8f 100644 --- a/src/compiler/arm64/code-generator-arm64.cc +++ b/src/compiler/arm64/code-generator-arm64.cc @@ -357,8 +357,11 @@ void CodeGenerator::AssembleArchInstruction(Instruction* instr) { case kArchJmp: AssembleArchJump(i.InputRpo(0)); break; - case kArchSwitch: - AssembleArchSwitch(instr); + case kArchTableSwitch: + AssembleArchTableSwitch(instr); + break; + case kArchLookupSwitch: + AssembleArchLookupSwitch(instr); break; case kArchNop: // don't emit code for nops. @@ -841,22 +844,6 @@ void CodeGenerator::AssembleArchJump(BasicBlock::RpoNumber target) { } -void CodeGenerator::AssembleArchSwitch(Instruction* instr) { - Arm64OperandConverter i(this, instr); - UseScratchRegisterScope scope(masm()); - Register reg = i.InputRegister(0); - Register tmp = scope.AcquireX(); - Label table; - __ Adr(tmp, &table); - __ Add(tmp, tmp, Operand(reg, LSL, 2)); - __ Br(tmp); - __ Bind(&table); - for (size_t index = 1; index < instr->InputCount(); ++index) { - __ B(GetLabel(i.InputRpo(index))); - } -} - - // Assemble boolean materializations after this instruction. void CodeGenerator::AssembleArchBoolean(Instruction* instr, FlagsCondition condition) { @@ -871,6 +858,38 @@ void CodeGenerator::AssembleArchBoolean(Instruction* instr, } +void CodeGenerator::AssembleArchLookupSwitch(Instruction* instr) { + Arm64OperandConverter i(this, instr); + Register input = i.InputRegister32(0); + for (size_t index = 2; index < instr->InputCount(); index += 2) { + __ Cmp(input, i.InputInt32(index + 0)); + __ B(eq, GetLabel(i.InputRpo(index + 1))); + } + AssembleArchJump(i.InputRpo(1)); +} + + +void CodeGenerator::AssembleArchTableSwitch(Instruction* instr) { + Arm64OperandConverter i(this, instr); + UseScratchRegisterScope scope(masm()); + Register input = i.InputRegister32(0); + Register temp = scope.AcquireX(); + size_t const case_count = instr->InputCount() - 2; + Label table; + __ Cmp(input, case_count); + __ B(hs, GetLabel(i.InputRpo(1))); + __ Adr(temp, &table); + __ Add(temp, temp, Operand(input, UXTW, 2)); + __ Br(temp); + __ StartBlockPools(); + __ Bind(&table); + for (size_t index = 0; index < case_count; ++index) { + __ B(GetLabel(i.InputRpo(index + 2))); + } + __ EndBlockPools(); +} + + void CodeGenerator::AssembleDeoptimizerCall(int deoptimization_id) { Address deopt_entry = Deoptimizer::GetDeoptimizationEntry( isolate(), deoptimization_id, Deoptimizer::LAZY); diff --git a/src/compiler/arm64/instruction-selector-arm64.cc b/src/compiler/arm64/instruction-selector-arm64.cc index d63b630c813..6afd3e8c125 100644 --- a/src/compiler/arm64/instruction-selector-arm64.cc +++ b/src/compiler/arm64/instruction-selector-arm64.cc @@ -1391,6 +1391,67 @@ void InstructionSelector::VisitBranch(Node* branch, BasicBlock* tbranch, } +void InstructionSelector::VisitSwitch(Node* node, BasicBlock* default_branch, + BasicBlock** case_branches, + int32_t* case_values, size_t case_count, + int32_t min_value, int32_t max_value) { + Arm64OperandGenerator g(this); + InstructionOperand value_operand = g.UseRegister(node->InputAt(0)); + InstructionOperand default_operand = g.Label(default_branch); + + // Note that {value_range} can be 0 if {min_value} is -2^31 and {max_value} + // is 2^31-1, so don't assume that it's non-zero below. + size_t value_range = + 1u + bit_cast(max_value) - bit_cast(min_value); + + // Determine whether to issue an ArchTableSwitch or an ArchLookupSwitch + // instruction. + size_t table_space_cost = 4 + value_range; + size_t table_time_cost = 3; + size_t lookup_space_cost = 3 + 2 * case_count; + size_t lookup_time_cost = case_count; + if (case_count > 0 && + table_space_cost + 3 * table_time_cost <= + lookup_space_cost + 3 * lookup_time_cost && + min_value > std::numeric_limits::min()) { + InstructionOperand index_operand = value_operand; + if (min_value) { + index_operand = g.TempRegister(); + Emit(kArm64Sub32, index_operand, value_operand, + g.TempImmediate(min_value)); + } + size_t input_count = 2 + value_range; + auto* inputs = zone()->NewArray(input_count); + inputs[0] = index_operand; + std::fill(&inputs[1], &inputs[input_count], default_operand); + for (size_t index = 0; index < case_count; ++index) { + size_t value = case_values[index] - min_value; + BasicBlock* branch = case_branches[index]; + DCHECK_LE(0u, value); + DCHECK_LT(value + 2, input_count); + inputs[value + 2] = g.Label(branch); + } + Emit(kArchTableSwitch, 0, nullptr, input_count, inputs, 0, nullptr) + ->MarkAsControl(); + return; + } + + // Generate a sequence of conditional jumps. + size_t input_count = 2 + case_count * 2; + auto* inputs = zone()->NewArray(input_count); + inputs[0] = value_operand; + inputs[1] = default_operand; + for (size_t index = 0; index < case_count; ++index) { + int32_t value = case_values[index]; + BasicBlock* branch = case_branches[index]; + inputs[index * 2 + 2 + 0] = g.TempImmediate(value); + inputs[index * 2 + 2 + 1] = g.Label(branch); + } + Emit(kArchLookupSwitch, 0, nullptr, input_count, inputs, 0, nullptr) + ->MarkAsControl(); +} + + void InstructionSelector::VisitWord32Equal(Node* const node) { Node* const user = node; FlagsContinuation cont(kEqual, node); diff --git a/src/compiler/code-generator.h b/src/compiler/code-generator.h index 99397eae8e4..658394b3217 100644 --- a/src/compiler/code-generator.h +++ b/src/compiler/code-generator.h @@ -69,9 +69,10 @@ class CodeGenerator FINAL : public GapResolver::Assembler { void AssembleArchInstruction(Instruction* instr); void AssembleArchJump(BasicBlock::RpoNumber target); - void AssembleArchSwitch(Instruction* instr); void AssembleArchBranch(Instruction* instr, BranchInfo* branch); void AssembleArchBoolean(Instruction* instr, FlagsCondition condition); + void AssembleArchLookupSwitch(Instruction* instr); + void AssembleArchTableSwitch(Instruction* instr); void AssembleDeoptimizerCall(int deoptimization_id); diff --git a/src/compiler/common-operator.cc b/src/compiler/common-operator.cc index eda99d4fee0..8aea3df5adc 100644 --- a/src/compiler/common-operator.cc +++ b/src/compiler/common-operator.cc @@ -36,12 +36,6 @@ BranchHint BranchHintOf(const Operator* const op) { } -size_t CaseIndexOf(const Operator* const op) { - DCHECK_EQ(IrOpcode::kCase, op->opcode()); - return OpParameter(op); -} - - bool operator==(SelectParameters const& lhs, SelectParameters const& rhs) { return lhs.type() == rhs.type() && lhs.hint() == rhs.hint(); } @@ -121,6 +115,7 @@ size_t ProjectionIndexOf(const Operator* const op) { V(End, Operator::kKontrol, 0, 0, 1, 0, 0, 0) \ V(IfTrue, Operator::kKontrol, 0, 0, 1, 0, 0, 1) \ V(IfFalse, Operator::kKontrol, 0, 0, 1, 0, 0, 1) \ + V(IfDefault, Operator::kKontrol, 0, 0, 1, 0, 0, 1) \ V(Throw, Operator::kFoldable, 1, 1, 1, 0, 0, 1) \ V(Return, Operator::kNoThrow, 1, 1, 1, 0, 0, 1) \ V(OsrNormalEntry, Operator::kFoldable, 0, 1, 1, 0, 1, 1) \ @@ -256,7 +251,7 @@ const Operator* CommonOperatorBuilder::Branch(BranchHint hint) { const Operator* CommonOperatorBuilder::Switch(size_t control_output_count) { - DCHECK_GE(control_output_count, 2u); // Disallow trivial switches. + DCHECK_GE(control_output_count, 3u); // Disallow trivial switches. return new (zone()) Operator( // -- IrOpcode::kSwitch, Operator::kKontrol, // opcode "Switch", // name @@ -264,12 +259,12 @@ const Operator* CommonOperatorBuilder::Switch(size_t control_output_count) { } -const Operator* CommonOperatorBuilder::Case(size_t index) { - return new (zone()) Operator1( // -- - IrOpcode::kCase, Operator::kKontrol, // opcode - "Case", // name - 0, 0, 1, 0, 0, 1, // counts - index); // parameter +const Operator* CommonOperatorBuilder::IfValue(int32_t index) { + return new (zone()) Operator1( // -- + IrOpcode::kIfValue, Operator::kKontrol, // opcode + "IfValue", // name + 0, 0, 1, 0, 0, 1, // counts + index); // parameter } diff --git a/src/compiler/common-operator.h b/src/compiler/common-operator.h index 55b1c8e590c..9f2c5751630 100644 --- a/src/compiler/common-operator.h +++ b/src/compiler/common-operator.h @@ -33,9 +33,6 @@ std::ostream& operator<<(std::ostream&, BranchHint); BranchHint BranchHintOf(const Operator* const); -size_t CaseIndexOf(const Operator* const); - - class SelectParameters FINAL { public: explicit SelectParameters(MachineType type, @@ -176,7 +173,8 @@ class CommonOperatorBuilder FINAL : public ZoneObject { const Operator* IfTrue(); const Operator* IfFalse(); const Operator* Switch(size_t control_output_count); - const Operator* Case(size_t index); + const Operator* IfValue(int32_t value); + const Operator* IfDefault(); const Operator* Throw(); const Operator* Return(); diff --git a/src/compiler/control-flow-optimizer.cc b/src/compiler/control-flow-optimizer.cc new file mode 100644 index 00000000000..c75d09f0cef --- /dev/null +++ b/src/compiler/control-flow-optimizer.cc @@ -0,0 +1,147 @@ +// 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/control-flow-optimizer.h" + +#include "src/compiler/js-graph.h" +#include "src/compiler/node-matchers.h" +#include "src/compiler/node-properties.h" + +namespace v8 { +namespace internal { +namespace compiler { + +ControlFlowOptimizer::ControlFlowOptimizer(JSGraph* jsgraph, Zone* zone) + : jsgraph_(jsgraph), + queue_(zone), + queued_(jsgraph->graph(), 2), + zone_(zone) {} + + +void ControlFlowOptimizer::Optimize() { + Enqueue(graph()->start()); + while (!queue_.empty()) { + Node* node = queue_.front(); + queue_.pop(); + if (node->IsDead()) continue; + switch (node->opcode()) { + case IrOpcode::kBranch: + VisitBranch(node); + break; + default: + VisitNode(node); + break; + } + } +} + + +void ControlFlowOptimizer::Enqueue(Node* node) { + DCHECK_NOT_NULL(node); + if (node->IsDead() || queued_.Get(node)) return; + queued_.Set(node, true); + queue_.push(node); +} + + +void ControlFlowOptimizer::VisitNode(Node* node) { + for (Node* use : node->uses()) { + if (NodeProperties::IsControl(use)) Enqueue(use); + } +} + + +void ControlFlowOptimizer::VisitBranch(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); + Int32BinopMatcher m(cond); + Node* index = m.left().node(); + if (!m.right().HasValue()) return VisitNode(node); + int32_t value = m.right().Value(); + ZoneSet values(zone()); + values.insert(value); + + Node* if_false; + Node* if_true; + while (true) { + // TODO(turbofan): use NodeProperties::CollectSuccessorProjections() here + // once available. + auto it = branch->uses().begin(); + DCHECK(it != branch->uses().end()); + if_true = *it++; + DCHECK(it != branch->uses().end()); + if_false = *it++; + DCHECK(it == branch->uses().end()); + if (if_true->opcode() != IrOpcode::kIfTrue) std::swap(if_true, if_false); + DCHECK_EQ(IrOpcode::kIfTrue, if_true->opcode()); + DCHECK_EQ(IrOpcode::kIfFalse, if_false->opcode()); + + it = if_false->uses().begin(); + if (it == if_false->uses().end()) break; + Node* branch1 = *it++; + if (branch1->opcode() != IrOpcode::kBranch) break; + if (it != if_false->uses().end()) break; + Node* cond1 = branch1->InputAt(0); + if (cond1->opcode() != IrOpcode::kWord32Equal) break; + Int32BinopMatcher m1(cond1); + if (m1.left().node() != index) break; + if (!m1.right().HasValue()) break; + int32_t value1 = m1.right().Value(); + if (values.find(value1) != values.end()) break; + DCHECK_NE(value, value1); + + if (branch != node) { + branch->RemoveAllInputs(); + if_true->ReplaceInput(0, node); + } + if_true->set_op(common()->IfValue(value)); + if_false->RemoveAllInputs(); + Enqueue(if_true); + + branch = branch1; + value = value1; + values.insert(value); + } + + DCHECK_EQ(IrOpcode::kBranch, node->opcode()); + DCHECK_EQ(IrOpcode::kBranch, branch->opcode()); + DCHECK_EQ(IrOpcode::kIfTrue, if_true->opcode()); + DCHECK_EQ(IrOpcode::kIfFalse, if_false->opcode()); + if (branch == node) { + DCHECK_EQ(1u, values.size()); + Enqueue(if_true); + Enqueue(if_false); + } else { + DCHECK_LT(1u, values.size()); + node->set_op(common()->Switch(values.size() + 1)); + node->ReplaceInput(0, index); + if_true->set_op(common()->IfValue(value)); + if_true->ReplaceInput(0, node); + Enqueue(if_true); + if_false->set_op(common()->IfDefault()); + if_false->ReplaceInput(0, node); + Enqueue(if_false); + branch->RemoveAllInputs(); + } +} + + +CommonOperatorBuilder* ControlFlowOptimizer::common() const { + return jsgraph()->common(); +} + + +Graph* ControlFlowOptimizer::graph() const { return jsgraph()->graph(); } + + +MachineOperatorBuilder* ControlFlowOptimizer::machine() const { + return jsgraph()->machine(); +} + +} // namespace compiler +} // namespace internal +} // namespace v8 diff --git a/src/compiler/control-flow-optimizer.h b/src/compiler/control-flow-optimizer.h new file mode 100644 index 00000000000..fb96e01734e --- /dev/null +++ b/src/compiler/control-flow-optimizer.h @@ -0,0 +1,52 @@ +// 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_CONTROL_FLOW_OPTIMIZER_H_ +#define V8_COMPILER_CONTROL_FLOW_OPTIMIZER_H_ + +#include "src/compiler/node-marker.h" +#include "src/zone-containers.h" + +namespace v8 { +namespace internal { +namespace compiler { + +// Forward declarations. +class CommonOperatorBuilder; +class Graph; +class JSGraph; +class MachineOperatorBuilder; +class Node; + + +class ControlFlowOptimizer FINAL { + public: + ControlFlowOptimizer(JSGraph* jsgraph, Zone* zone); + + void Optimize(); + + private: + void Enqueue(Node* node); + void VisitNode(Node* node); + void VisitBranch(Node* node); + + CommonOperatorBuilder* common() const; + Graph* graph() const; + JSGraph* jsgraph() const { return jsgraph_; } + MachineOperatorBuilder* machine() const; + Zone* zone() const { return zone_; } + + JSGraph* const jsgraph_; + ZoneQueue queue_; + NodeMarker queued_; + Zone* const zone_; + + DISALLOW_COPY_AND_ASSIGN(ControlFlowOptimizer); +}; + +} // namespace compiler +} // namespace internal +} // namespace v8 + +#endif // V8_COMPILER_CONTROL_FLOW_OPTIMIZER_H_ diff --git a/src/compiler/ia32/code-generator-ia32.cc b/src/compiler/ia32/code-generator-ia32.cc index f82ddc3d33b..d20848918d3 100644 --- a/src/compiler/ia32/code-generator-ia32.cc +++ b/src/compiler/ia32/code-generator-ia32.cc @@ -310,8 +310,11 @@ void CodeGenerator::AssembleArchInstruction(Instruction* instr) { case kArchJmp: AssembleArchJump(i.InputRpo(0)); break; - case kArchSwitch: - AssembleArchSwitch(instr); + case kArchLookupSwitch: + AssembleArchLookupSwitch(instr); + break; + case kArchTableSwitch: + AssembleArchTableSwitch(instr); break; case kArchNop: // don't emit code for nops. @@ -761,18 +764,6 @@ void CodeGenerator::AssembleArchJump(BasicBlock::RpoNumber target) { } -void CodeGenerator::AssembleArchSwitch(Instruction* instr) { - IA32OperandConverter i(this, instr); - size_t const label_count = instr->InputCount() - 1; - Label** labels = zone()->NewArray(label_count); - for (size_t index = 0; index < label_count; ++index) { - labels[index] = GetLabel(i.InputRpo(index + 1)); - } - Label* const table = AddJumpTable(labels, label_count); - __ jmp(Operand::JumpTable(i.InputRegister(0), times_4, table)); -} - - // Assembles boolean materializations after an instruction. void CodeGenerator::AssembleArchBoolean(Instruction* instr, FlagsCondition condition) { @@ -851,6 +842,32 @@ void CodeGenerator::AssembleArchBoolean(Instruction* instr, } +void CodeGenerator::AssembleArchLookupSwitch(Instruction* instr) { + IA32OperandConverter i(this, instr); + Register input = i.InputRegister(0); + for (size_t index = 2; index < instr->InputCount(); index += 2) { + __ cmp(input, Immediate(i.InputInt32(index + 0))); + __ j(equal, GetLabel(i.InputRpo(index + 1))); + } + AssembleArchJump(i.InputRpo(1)); +} + + +void CodeGenerator::AssembleArchTableSwitch(Instruction* instr) { + IA32OperandConverter i(this, instr); + Register input = i.InputRegister(0); + size_t const case_count = instr->InputCount() - 2; + Label** cases = zone()->NewArray(case_count); + for (size_t index = 0; index < case_count; ++index) { + cases[index] = GetLabel(i.InputRpo(index + 2)); + } + Label* const table = AddJumpTable(cases, case_count); + __ cmp(input, Immediate(case_count)); + __ j(above_equal, GetLabel(i.InputRpo(1))); + __ jmp(Operand::JumpTable(input, times_4, table)); +} + + void CodeGenerator::AssembleDeoptimizerCall(int deoptimization_id) { Address deopt_entry = Deoptimizer::GetDeoptimizationEntry( isolate(), deoptimization_id, Deoptimizer::LAZY); diff --git a/src/compiler/ia32/instruction-selector-ia32.cc b/src/compiler/ia32/instruction-selector-ia32.cc index da2a6594196..beec701903e 100644 --- a/src/compiler/ia32/instruction-selector-ia32.cc +++ b/src/compiler/ia32/instruction-selector-ia32.cc @@ -928,6 +928,67 @@ void InstructionSelector::VisitBranch(Node* branch, BasicBlock* tbranch, } +void InstructionSelector::VisitSwitch(Node* node, BasicBlock* default_branch, + BasicBlock** case_branches, + int32_t* case_values, size_t case_count, + int32_t min_value, int32_t max_value) { + IA32OperandGenerator g(this); + InstructionOperand value_operand = g.UseRegister(node->InputAt(0)); + InstructionOperand default_operand = g.Label(default_branch); + + // Note that {value_range} can be 0 if {min_value} is -2^31 and {max_value} + // is 2^31-1, so don't assume that it's non-zero below. + size_t value_range = + 1u + bit_cast(max_value) - bit_cast(min_value); + + // Determine whether to issue an ArchTableSwitch or an ArchLookupSwitch + // instruction. + size_t table_space_cost = 4 + value_range; + size_t table_time_cost = 3; + size_t lookup_space_cost = 3 + 2 * case_count; + size_t lookup_time_cost = case_count; + if (case_count > 4 && + table_space_cost + 3 * table_time_cost <= + lookup_space_cost + 3 * lookup_time_cost && + min_value > std::numeric_limits::min()) { + InstructionOperand index_operand = value_operand; + if (min_value) { + index_operand = g.TempRegister(); + Emit(kIA32Lea | AddressingModeField::encode(kMode_MRI), index_operand, + value_operand, g.TempImmediate(-min_value)); + } + size_t input_count = 2 + value_range; + auto* inputs = zone()->NewArray(input_count); + inputs[0] = index_operand; + std::fill(&inputs[1], &inputs[input_count], default_operand); + for (size_t index = 0; index < case_count; ++index) { + size_t value = case_values[index] - min_value; + BasicBlock* branch = case_branches[index]; + DCHECK_LE(0u, value); + DCHECK_LT(value + 2, input_count); + inputs[value + 2] = g.Label(branch); + } + Emit(kArchTableSwitch, 0, nullptr, input_count, inputs, 0, nullptr) + ->MarkAsControl(); + return; + } + + // Generate a sequence of conditional jumps. + size_t input_count = 2 + case_count * 2; + auto* inputs = zone()->NewArray(input_count); + inputs[0] = value_operand; + inputs[1] = default_operand; + for (size_t index = 0; index < case_count; ++index) { + int32_t value = case_values[index]; + BasicBlock* branch = case_branches[index]; + inputs[index * 2 + 2 + 0] = g.TempImmediate(value); + inputs[index * 2 + 2 + 1] = g.Label(branch); + } + Emit(kArchLookupSwitch, 0, nullptr, input_count, inputs, 0, nullptr) + ->MarkAsControl(); +} + + void InstructionSelector::VisitWord32Equal(Node* const node) { FlagsContinuation cont(kEqual, node); Int32BinopMatcher m(node); diff --git a/src/compiler/instruction-codes.h b/src/compiler/instruction-codes.h index 044b15d6caa..ef1e942ed47 100644 --- a/src/compiler/instruction-codes.h +++ b/src/compiler/instruction-codes.h @@ -37,7 +37,8 @@ namespace compiler { V(ArchCallCodeObject) \ V(ArchCallJSFunction) \ V(ArchJmp) \ - V(ArchSwitch) \ + V(ArchLookupSwitch) \ + V(ArchTableSwitch) \ V(ArchNop) \ V(ArchRet) \ V(ArchStackPointer) \ diff --git a/src/compiler/instruction-selector.cc b/src/compiler/instruction-selector.cc index e8a52a61e12..c170a01d4a2 100644 --- a/src/compiler/instruction-selector.cc +++ b/src/compiler/instruction-selector.cc @@ -4,6 +4,8 @@ #include "src/compiler/instruction-selector.h" +#include + #include "src/compiler/instruction-selector-impl.h" #include "src/compiler/node-matchers.h" #include "src/compiler/node-properties.h" @@ -519,15 +521,33 @@ void InstructionSelector::VisitControl(BasicBlock* block) { } case BasicBlock::kSwitch: { DCHECK_EQ(IrOpcode::kSwitch, input->opcode()); - BasicBlock** const branches = &block->successors().front(); - size_t const branch_count = block->SuccessorCount(); - DCHECK_LE(2u, branch_count); + // Last successor must be Default. + BasicBlock* default_branch = block->successors().back(); + DCHECK_EQ(IrOpcode::kIfDefault, default_branch->front()->opcode()); // SSA deconstruction requires targets of branches not to have phis. // Edge split form guarantees this property, but is more strict. - for (size_t index = 0; index < branch_count; ++index) { - CheckNoPhis(branches[index]); + CheckNoPhis(default_branch); + // All other successors must be cases. + size_t case_count = block->SuccessorCount() - 1; + DCHECK_LE(1u, case_count); + BasicBlock** case_branches = &block->successors().front(); + // Determine case values and their min/max. + int32_t* case_values = zone()->NewArray(case_count); + int32_t min_value = std::numeric_limits::max(); + int32_t max_value = std::numeric_limits::min(); + for (size_t index = 0; index < case_count; ++index) { + BasicBlock* branch = case_branches[index]; + int32_t value = OpParameter(branch->front()->op()); + case_values[index] = value; + if (min_value > value) min_value = value; + if (max_value < value) max_value = value; + // SSA deconstruction requires targets of branches not to have phis. + // Edge split form guarantees this property, but is more strict. + CheckNoPhis(branch); } - return VisitSwitch(input, branches, branch_count); + DCHECK_LE(min_value, max_value); + return VisitSwitch(input, default_branch, case_branches, case_values, + case_count, min_value, max_value); } case BasicBlock::kReturn: { // If the result itself is a return, return its input. @@ -561,7 +581,8 @@ MachineType InstructionSelector::GetMachineType(Node* node) { case IrOpcode::kIfTrue: case IrOpcode::kIfFalse: case IrOpcode::kSwitch: - case IrOpcode::kCase: + case IrOpcode::kIfValue: + case IrOpcode::kIfDefault: case IrOpcode::kEffectPhi: case IrOpcode::kEffectSet: case IrOpcode::kMerge: @@ -701,7 +722,8 @@ void InstructionSelector::VisitNode(Node* node) { case IrOpcode::kIfTrue: case IrOpcode::kIfFalse: case IrOpcode::kSwitch: - case IrOpcode::kCase: + case IrOpcode::kIfValue: + case IrOpcode::kIfDefault: case IrOpcode::kEffectPhi: case IrOpcode::kMerge: // No code needed for these graph artifacts. @@ -1058,22 +1080,6 @@ void InstructionSelector::VisitGoto(BasicBlock* target) { } -void InstructionSelector::VisitSwitch(Node* node, BasicBlock** branches, - size_t branch_count) { - OperandGenerator g(this); - Node* const value = node->InputAt(0); - size_t const input_count = branch_count + 1; - InstructionOperand* const inputs = - zone()->NewArray(input_count); - inputs[0] = g.UseRegister(value); - for (size_t index = 0; index < branch_count; ++index) { - inputs[index + 1] = g.Label(branches[index]); - } - Emit(kArchSwitch, 0, nullptr, input_count, inputs, 0, nullptr) - ->MarkAsControl(); -} - - void InstructionSelector::VisitReturn(Node* value) { OperandGenerator g(this); if (value != NULL) { diff --git a/src/compiler/instruction-selector.h b/src/compiler/instruction-selector.h index 3883b1556a2..5c31db74e9a 100644 --- a/src/compiler/instruction-selector.h +++ b/src/compiler/instruction-selector.h @@ -202,7 +202,9 @@ class InstructionSelector FINAL { void VisitCall(Node* call); void VisitGoto(BasicBlock* target); void VisitBranch(Node* input, BasicBlock* tbranch, BasicBlock* fbranch); - void VisitSwitch(Node* node, BasicBlock** branches, size_t branch_count); + void VisitSwitch(Node* node, BasicBlock* default_branch, + BasicBlock** case_branches, int32_t* case_values, + size_t case_count, int32_t min_value, int32_t max_value); void VisitReturn(Node* value); void VisitThrow(Node* value); void VisitDeoptimize(Node* deopt); diff --git a/src/compiler/opcodes.h b/src/compiler/opcodes.h index 2e1a9191ed3..a4f8d3ec169 100644 --- a/src/compiler/opcodes.h +++ b/src/compiler/opcodes.h @@ -13,7 +13,8 @@ V(IfTrue) \ V(IfFalse) \ V(Switch) \ - V(Case) \ + V(IfValue) \ + V(IfDefault) \ V(Merge) \ V(Return) \ V(OsrNormalEntry) \ diff --git a/src/compiler/operator-properties.cc b/src/compiler/operator-properties.cc index 81299e34ffe..53bd16c0afb 100644 --- a/src/compiler/operator-properties.cc +++ b/src/compiler/operator-properties.cc @@ -100,7 +100,8 @@ bool OperatorProperties::IsBasicBlockBegin(const Operator* op) { return opcode == IrOpcode::kStart || opcode == IrOpcode::kEnd || opcode == IrOpcode::kDead || opcode == IrOpcode::kLoop || opcode == IrOpcode::kMerge || opcode == IrOpcode::kIfTrue || - opcode == IrOpcode::kIfFalse; + opcode == IrOpcode::kIfFalse || opcode == IrOpcode::kIfValue || + opcode == IrOpcode::kIfDefault; } } // namespace compiler diff --git a/src/compiler/operator.cc b/src/compiler/operator.cc index e0c7ca369da..ae10348422c 100644 --- a/src/compiler/operator.cc +++ b/src/compiler/operator.cc @@ -21,6 +21,10 @@ V8_INLINE N CheckRange(size_t val) { } // namespace +// static +STATIC_CONST_MEMBER_DEFINITION const size_t Operator::kMaxControlOutputCount; + + Operator::Operator(Opcode opcode, Properties properties, const char* mnemonic, size_t value_in, size_t effect_in, size_t control_in, size_t value_out, size_t effect_out, size_t control_out) @@ -32,7 +36,7 @@ Operator::Operator(Opcode opcode, Properties properties, const char* mnemonic, control_in_(CheckRange(control_in)), value_out_(CheckRange(value_out)), effect_out_(CheckRange(effect_out)), - control_out_(CheckRange(control_out)) {} + control_out_(CheckRange(control_out)) {} std::ostream& operator<<(std::ostream& os, const Operator& op) { diff --git a/src/compiler/operator.h b/src/compiler/operator.h index 616b4cf7afe..6407499da55 100644 --- a/src/compiler/operator.h +++ b/src/compiler/operator.h @@ -85,6 +85,9 @@ class Operator : public ZoneObject { Properties properties() const { return properties_; } + // TODO(bmeurer): Use bit fields below? + static const size_t kMaxControlOutputCount = (1u << 16) - 1; + // TODO(titzer): convert return values here to size_t. int ValueInputCount() const { return value_in_; } int EffectInputCount() const { return effect_in_; } @@ -114,7 +117,7 @@ class Operator : public ZoneObject { uint16_t control_in_; uint16_t value_out_; uint8_t effect_out_; - uint8_t control_out_; + uint16_t control_out_; DISALLOW_COPY_AND_ASSIGN(Operator); }; diff --git a/src/compiler/pipeline.cc b/src/compiler/pipeline.cc index 19f0d6c45d7..83b784f8060 100644 --- a/src/compiler/pipeline.cc +++ b/src/compiler/pipeline.cc @@ -15,6 +15,7 @@ #include "src/compiler/change-lowering.h" #include "src/compiler/code-generator.h" #include "src/compiler/common-operator-reducer.h" +#include "src/compiler/control-flow-optimizer.h" #include "src/compiler/control-reducer.h" #include "src/compiler/graph-replay.h" #include "src/compiler/graph-visualizer.h" @@ -544,6 +545,16 @@ struct SimplifiedLoweringPhase { }; +struct ControlFlowOptimizationPhase { + static const char* phase_name() { return "control flow optimization"; } + + void Run(PipelineData* data, Zone* temp_zone) { + ControlFlowOptimizer optimizer(data->jsgraph(), temp_zone); + optimizer.Optimize(); + } +}; + + struct ChangeLoweringPhase { static const char* phase_name() { return "change lowering"; } @@ -961,6 +972,12 @@ Handle Pipeline::GenerateCode() { Run(); RunPrintAndVerify("Lowered simplified"); + // Optimize control flow. + if (FLAG_turbo_switch) { + Run(); + RunPrintAndVerify("Control flow optimized"); + } + // Lower changes that have been inserted before. Run(); // // TODO(jarin, rossberg): Remove UNTYPED once machine typing works. diff --git a/src/compiler/raw-machine-assembler.cc b/src/compiler/raw-machine-assembler.cc index 562f77d1445..489c2ca6d9c 100644 --- a/src/compiler/raw-machine-assembler.cc +++ b/src/compiler/raw-machine-assembler.cc @@ -76,15 +76,26 @@ void RawMachineAssembler::Branch(Node* condition, Label* true_val, } -void RawMachineAssembler::Switch(Node* index, Label** succ_labels, - size_t succ_count) { +void RawMachineAssembler::Switch(Node* index, Label* default_label, + int32_t* case_values, Label** case_labels, + size_t case_count) { DCHECK_NE(schedule()->end(), current_block_); - Node* sw = NewNode(common()->Switch(succ_count), index); + size_t succ_count = case_count + 1; + Node* switch_node = NewNode(common()->Switch(succ_count), index); BasicBlock** succ_blocks = zone()->NewArray(succ_count); - for (size_t index = 0; index < succ_count; ++index) { - succ_blocks[index] = Use(succ_labels[index]); + for (size_t index = 0; index < case_count; ++index) { + int32_t case_value = case_values[index]; + BasicBlock* case_block = Use(case_labels[index]); + Node* case_node = + graph()->NewNode(common()->IfValue(case_value), switch_node); + schedule()->AddNode(case_block, case_node); + succ_blocks[index] = case_block; } - schedule()->AddSwitch(CurrentBlock(), sw, succ_blocks, succ_count); + BasicBlock* default_block = Use(default_label); + Node* default_node = graph()->NewNode(common()->IfDefault(), switch_node); + schedule()->AddNode(default_block, default_node); + succ_blocks[case_count] = default_block; + schedule()->AddSwitch(CurrentBlock(), switch_node, succ_blocks, succ_count); current_block_ = nullptr; } diff --git a/src/compiler/raw-machine-assembler.h b/src/compiler/raw-machine-assembler.h index 646ace2e00e..04b1dc6f57c 100644 --- a/src/compiler/raw-machine-assembler.h +++ b/src/compiler/raw-machine-assembler.h @@ -400,7 +400,8 @@ class RawMachineAssembler : public GraphBuilder { Label* Exit(); void Goto(Label* label); void Branch(Node* condition, Label* true_val, Label* false_val); - void Switch(Node* index, Label** succ_labels, size_t succ_count); + void Switch(Node* index, Label* default_label, int32_t* case_values, + Label** case_labels, size_t case_count); // Call through CallFunctionStub with lazy deopt and frame-state. Node* CallFunctionStub0(Node* function, Node* receiver, Node* context, Node* frame_state, CallFunctionFlags flags); diff --git a/src/compiler/schedule.h b/src/compiler/schedule.h index aa479851f94..d4d64533b50 100644 --- a/src/compiler/schedule.h +++ b/src/compiler/schedule.h @@ -106,6 +106,9 @@ class BasicBlock FINAL : public ZoneObject { Node* NodeAt(size_t index) { return nodes_[index]; } size_t NodeCount() const { return nodes_.size(); } + value_type& front() { return nodes_.front(); } + value_type const& front() const { return nodes_.front(); } + typedef NodeVector::iterator iterator; iterator begin() { return nodes_.begin(); } iterator end() { return nodes_.end(); } diff --git a/src/compiler/scheduler.cc b/src/compiler/scheduler.cc index 25bdba4961c..a21d650ed31 100644 --- a/src/compiler/scheduler.cc +++ b/src/compiler/scheduler.cc @@ -373,13 +373,14 @@ class CFGBuilder : public ZoneObject { } // Collect the branch-related projections from a node, such as IfTrue, - // IfFalse, and Case. + // IfFalse, Case and Default. void CollectSuccessorProjections(Node* node, Node** successors, size_t successor_count) { #ifdef DEBUG DCHECK_EQ(static_cast(successor_count), node->UseCount()); std::memset(successors, 0, sizeof(*successors) * successor_count); #endif + size_t if_value_index = 0; for (Node* const use : node->uses()) { size_t index; switch (use->opcode()) { @@ -394,13 +395,18 @@ class CFGBuilder : public ZoneObject { DCHECK_EQ(IrOpcode::kBranch, node->opcode()); index = 1; break; - case IrOpcode::kCase: + case IrOpcode::kIfValue: DCHECK_EQ(IrOpcode::kSwitch, node->opcode()); - index = CaseIndexOf(use->op()); + index = if_value_index++; + break; + case IrOpcode::kIfDefault: + DCHECK_EQ(IrOpcode::kSwitch, node->opcode()); + index = successor_count - 1; break; } + DCHECK_LT(if_value_index, successor_count); DCHECK_LT(index, successor_count); - DCHECK(successors[index] == nullptr); + DCHECK_NULL(successors[index]); successors[index] = use; } #ifdef DEBUG diff --git a/src/compiler/simplified-lowering.cc b/src/compiler/simplified-lowering.cc index 4766ea2b9bc..59dd741ee47 100644 --- a/src/compiler/simplified-lowering.cc +++ b/src/compiler/simplified-lowering.cc @@ -501,18 +501,14 @@ class RepresentationSelector { case IrOpcode::kHeapConstant: return VisitLeaf(node, kRepTagged); - case IrOpcode::kEnd: - case IrOpcode::kIfTrue: - case IrOpcode::kIfFalse: - case IrOpcode::kReturn: - case IrOpcode::kMerge: - case IrOpcode::kThrow: - return VisitInputs(node); // default visit for all node inputs. - case IrOpcode::kBranch: ProcessInput(node, 0, kRepBit); Enqueue(NodeProperties::GetControlInput(node, 0)); break; + case IrOpcode::kSwitch: + ProcessInput(node, 0, kRepWord32); + Enqueue(NodeProperties::GetControlInput(node, 0)); + break; case IrOpcode::kSelect: return VisitSelect(node, use, lowering); case IrOpcode::kPhi: diff --git a/src/compiler/verifier.cc b/src/compiler/verifier.cc index 1cc8c9f32e7..26fd6933c63 100644 --- a/src/compiler/verifier.cc +++ b/src/compiler/verifier.cc @@ -216,7 +216,8 @@ void Verifier::Visitor::Check(Node* node) { if (use->opcode() == IrOpcode::kIfTrue) ++count_true; if (use->opcode() == IrOpcode::kIfFalse) ++count_false; } - CHECK(count_true == 1 && count_false == 1); + CHECK_EQ(1, count_true); + CHECK_EQ(1, count_false); // Type is empty. CheckNotTyped(node); break; @@ -229,21 +230,39 @@ void Verifier::Visitor::Check(Node* node) { CheckNotTyped(node); break; case IrOpcode::kSwitch: { - // Switch uses are Case. - std::vector uses; - uses.resize(node->UseCount()); + // Switch uses are Case and Default. + int count_case = 0, count_default = 0; for (auto use : node->uses()) { - CHECK_EQ(IrOpcode::kCase, use->opcode()); - size_t const index = CaseIndexOf(use->op()); - CHECK_LT(index, uses.size()); - CHECK(!uses[index]); - uses[index] = true; + switch (use->opcode()) { + case IrOpcode::kIfValue: { + for (auto user : node->uses()) { + if (user != use && user->opcode() == IrOpcode::kIfValue) { + CHECK_NE(OpParameter(use->op()), + OpParameter(user->op())); + } + } + ++count_case; + break; + } + case IrOpcode::kIfDefault: { + ++count_default; + break; + } + default: { + UNREACHABLE(); + break; + } + } } + CHECK_LE(1, count_case); + CHECK_EQ(1, count_default); + CHECK_EQ(node->op()->ControlOutputCount(), count_case + count_default); // Type is empty. CheckNotTyped(node); break; } - case IrOpcode::kCase: + case IrOpcode::kIfValue: + case IrOpcode::kIfDefault: CHECK_EQ(IrOpcode::kSwitch, NodeProperties::GetControlInput(node)->opcode()); // Type is empty. diff --git a/src/compiler/x64/code-generator-x64.cc b/src/compiler/x64/code-generator-x64.cc index e52326eac15..adf8966906c 100644 --- a/src/compiler/x64/code-generator-x64.cc +++ b/src/compiler/x64/code-generator-x64.cc @@ -532,8 +532,11 @@ void CodeGenerator::AssembleArchInstruction(Instruction* instr) { case kArchJmp: AssembleArchJump(i.InputRpo(0)); break; - case kArchSwitch: - AssembleArchSwitch(instr); + case kArchLookupSwitch: + AssembleArchLookupSwitch(instr); + break; + case kArchTableSwitch: + AssembleArchTableSwitch(instr); break; case kArchNop: // don't emit code for nops. @@ -1070,19 +1073,6 @@ void CodeGenerator::AssembleArchJump(BasicBlock::RpoNumber target) { } -void CodeGenerator::AssembleArchSwitch(Instruction* instr) { - X64OperandConverter i(this, instr); - size_t const label_count = instr->InputCount() - 1; - Label** labels = zone()->NewArray(label_count); - for (size_t index = 0; index < label_count; ++index) { - labels[index] = GetLabel(i.InputRpo(static_cast(index + 1))); - } - Label* const table = AddJumpTable(labels, label_count); - __ leaq(kScratchRegister, Operand(table)); - __ jmp(Operand(kScratchRegister, i.InputRegister(0), times_8, 0)); -} - - // Assembles boolean materializations after this instruction. void CodeGenerator::AssembleArchBoolean(Instruction* instr, FlagsCondition condition) { @@ -1150,6 +1140,33 @@ void CodeGenerator::AssembleArchBoolean(Instruction* instr, } +void CodeGenerator::AssembleArchLookupSwitch(Instruction* instr) { + X64OperandConverter i(this, instr); + Register input = i.InputRegister(0); + for (size_t index = 2; index < instr->InputCount(); index += 2) { + __ cmpl(input, Immediate(i.InputInt32(static_cast(index + 0)))); + __ j(equal, GetLabel(i.InputRpo(static_cast(index + 1)))); + } + AssembleArchJump(i.InputRpo(1)); +} + + +void CodeGenerator::AssembleArchTableSwitch(Instruction* instr) { + X64OperandConverter i(this, instr); + Register input = i.InputRegister(0); + int32_t const case_count = static_cast(instr->InputCount() - 2); + Label** cases = zone()->NewArray(case_count); + for (int32_t index = 0; index < case_count; ++index) { + cases[index] = GetLabel(i.InputRpo(index + 2)); + } + Label* const table = AddJumpTable(cases, case_count); + __ cmpl(input, Immediate(case_count)); + __ j(above_equal, GetLabel(i.InputRpo(1))); + __ leaq(kScratchRegister, Operand(table)); + __ jmp(Operand(kScratchRegister, input, times_8, 0)); +} + + void CodeGenerator::AssembleDeoptimizerCall(int deoptimization_id) { Address deopt_entry = Deoptimizer::GetDeoptimizationEntry( isolate(), deoptimization_id, Deoptimizer::LAZY); diff --git a/src/compiler/x64/instruction-selector-x64.cc b/src/compiler/x64/instruction-selector-x64.cc index 1722b43ae3b..d3becdbc9dd 100644 --- a/src/compiler/x64/instruction-selector-x64.cc +++ b/src/compiler/x64/instruction-selector-x64.cc @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include + #include "src/compiler/instruction-selector-impl.h" #include "src/compiler/node-matchers.h" #include "src/compiler/node-properties.h" @@ -1151,6 +1153,70 @@ void InstructionSelector::VisitBranch(Node* branch, BasicBlock* tbranch, } +void InstructionSelector::VisitSwitch(Node* node, BasicBlock* default_branch, + BasicBlock** case_branches, + int32_t* case_values, size_t case_count, + int32_t min_value, int32_t max_value) { + X64OperandGenerator g(this); + InstructionOperand value_operand = g.UseRegister(node->InputAt(0)); + InstructionOperand default_operand = g.Label(default_branch); + + // Note that {value_range} can be 0 if {min_value} is -2^31 and {max_value} + // is 2^31-1, so don't assume that it's non-zero below. + size_t value_range = + 1u + bit_cast(max_value) - bit_cast(min_value); + + // Determine whether to issue an ArchTableSwitch or an ArchLookupSwitch + // instruction. + size_t table_space_cost = 4 + value_range; + size_t table_time_cost = 3; + size_t lookup_space_cost = 3 + 2 * case_count; + size_t lookup_time_cost = case_count; + if (case_count > 4 && + table_space_cost + 3 * table_time_cost <= + lookup_space_cost + 3 * lookup_time_cost && + min_value > std::numeric_limits::min()) { + InstructionOperand index_operand = g.TempRegister(); + if (min_value) { + // The leal automatically zero extends, so result is a valid 64-bit index. + Emit(kX64Lea32 | AddressingModeField::encode(kMode_MRI), index_operand, + value_operand, g.TempImmediate(-min_value)); + } else { + // Zero extend, because we use it as 64-bit index into the jump table. + Emit(kX64Movl, index_operand, value_operand); + } + size_t input_count = 2 + value_range; + auto* inputs = zone()->NewArray(input_count); + inputs[0] = index_operand; + std::fill(&inputs[1], &inputs[input_count], default_operand); + for (size_t index = 0; index < case_count; ++index) { + size_t value = case_values[index] - min_value; + BasicBlock* branch = case_branches[index]; + DCHECK_LE(0u, value); + DCHECK_LT(value + 2, input_count); + inputs[value + 2] = g.Label(branch); + } + Emit(kArchTableSwitch, 0, nullptr, input_count, inputs, 0, nullptr) + ->MarkAsControl(); + return; + } + + // Generate a sequence of conditional jumps. + size_t input_count = 2 + case_count * 2; + auto* inputs = zone()->NewArray(input_count); + inputs[0] = value_operand; + inputs[1] = default_operand; + for (size_t index = 0; index < case_count; ++index) { + int32_t value = case_values[index]; + BasicBlock* branch = case_branches[index]; + inputs[index * 2 + 2 + 0] = g.TempImmediate(value); + inputs[index * 2 + 2 + 1] = g.Label(branch); + } + Emit(kArchLookupSwitch, 0, nullptr, input_count, inputs, 0, nullptr) + ->MarkAsControl(); +} + + void InstructionSelector::VisitWord32Equal(Node* const node) { Node* user = node; FlagsContinuation cont(kEqual, node); diff --git a/src/flag-definitions.h b/src/flag-definitions.h index a4b61aafb35..002fa9cdb5c 100644 --- a/src/flag-definitions.h +++ b/src/flag-definitions.h @@ -424,6 +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_INT(typed_array_max_size_in_heap, 64, "threshold for in-heap typed array") diff --git a/test/cctest/compiler/test-run-machops.cc b/test/cctest/compiler/test-run-machops.cc index 1511a01033a..5a55ce6e23b 100644 --- a/test/cctest/compiler/test-run-machops.cc +++ b/test/cctest/compiler/test-run-machops.cc @@ -452,13 +452,17 @@ TEST(RunSwitch1) { int constant = 11223344; - MLabel block0, block1, end; - MLabel* cases[] = {&block0, &block1}; - m.Switch(m.IntPtrConstant(0), cases, arraysize(cases)); + MLabel block0, block1, def, end; + MLabel* case_labels[] = {&block0, &block1}; + int32_t case_values[] = {0, 1}; + m.Switch(m.Int32Constant(0), &def, case_values, case_labels, + arraysize(case_labels)); m.Bind(&block0); m.Goto(&end); m.Bind(&block1); m.Goto(&end); + m.Bind(&def); + m.Goto(&end); m.Bind(&end); m.Return(m.Int32Constant(constant)); @@ -469,29 +473,84 @@ TEST(RunSwitch1) { TEST(RunSwitch2) { RawMachineAssemblerTester m(kMachInt32); - const size_t kNumCases = 255; - int32_t values[kNumCases]; + MLabel blocka, blockb, blockc; + MLabel* case_labels[] = {&blocka, &blockb}; + int32_t case_values[] = {std::numeric_limits::min(), + std::numeric_limits::max()}; + m.Switch(m.Parameter(0), &blockc, case_values, case_labels, + arraysize(case_labels)); + m.Bind(&blocka); + m.Return(m.Int32Constant(-1)); + m.Bind(&blockb); + m.Return(m.Int32Constant(1)); + m.Bind(&blockc); + m.Return(m.Int32Constant(0)); + + CHECK_EQ(1, m.Call(std::numeric_limits::max())); + CHECK_EQ(-1, m.Call(std::numeric_limits::min())); + for (int i = -100; i < 100; i += 25) { + CHECK_EQ(0, m.Call(i)); + } +} + + +TEST(RunSwitch3) { + RawMachineAssemblerTester m(kMachInt32); + + MLabel blocka, blockb, blockc; + MLabel* case_labels[] = {&blocka, &blockb}; + int32_t case_values[] = {std::numeric_limits::min() + 0, + std::numeric_limits::min() + 1}; + m.Switch(m.Parameter(0), &blockc, case_values, case_labels, + arraysize(case_labels)); + m.Bind(&blocka); + m.Return(m.Int32Constant(0)); + m.Bind(&blockb); + m.Return(m.Int32Constant(1)); + m.Bind(&blockc); + m.Return(m.Int32Constant(2)); + + CHECK_EQ(0, m.Call(std::numeric_limits::min() + 0)); + CHECK_EQ(1, m.Call(std::numeric_limits::min() + 1)); + for (int i = -100; i < 100; i += 25) { + CHECK_EQ(2, m.Call(i)); + } +} + + +TEST(RunSwitch4) { + RawMachineAssemblerTester m(kMachInt32); + + const size_t kNumCases = 512; + const size_t kNumValues = kNumCases + 1; + int32_t values[kNumValues]; m.main_isolate()->random_number_generator()->NextBytes(values, sizeof(values)); - MLabel end; - MLabel* cases[kNumCases]; - Node* results[kNumCases]; + MLabel end, def; + int32_t case_values[kNumCases]; + MLabel* case_labels[kNumCases]; + Node* results[kNumValues]; for (size_t i = 0; i < kNumCases; ++i) { - cases[i] = new (m.main_zone()->New(sizeof(MLabel))) MLabel; + case_values[i] = static_cast(i); + case_labels[i] = new (m.main_zone()->New(sizeof(MLabel))) MLabel; } - m.Switch(m.ConvertInt32ToIntPtr(m.Parameter(0)), cases, arraysize(cases)); + m.Switch(m.Parameter(0), &def, case_values, case_labels, + arraysize(case_labels)); for (size_t i = 0; i < kNumCases; ++i) { - m.Bind(cases[i]); + m.Bind(case_labels[i]); results[i] = m.Int32Constant(values[i]); m.Goto(&end); } + m.Bind(&def); + results[kNumCases] = m.Int32Constant(values[kNumCases]); + m.Goto(&end); m.Bind(&end); const int num_results = static_cast(arraysize(results)); Node* phi = m.NewNode(m.common()->Phi(kMachInt32, num_results), num_results, results); m.Return(phi); - for (size_t i = 0; i < kNumCases; ++i) { + for (size_t i = 0; i < kNumValues; ++i) { CHECK_EQ(values[i], m.Call(static_cast(i))); } } diff --git a/test/mjsunit/asm/switch.js b/test/mjsunit/asm/switch.js new file mode 100644 index 00000000000..5cb6329e433 --- /dev/null +++ b/test/mjsunit/asm/switch.js @@ -0,0 +1,120 @@ +// 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 stdlib = this; +var foreign = {}; +var heap = new ArrayBuffer(64 * 1024); + + +var switch1 = (function(stdlib, foreign, heap) { + "use asm"; + function switch1(i) { + i = i|0; + switch (i) { + case 0: return 1; + case 1: return 2; + default: return i|0; + } + } + return { switch1: switch1 }; +})(stdlib, foreign, heap).switch1; + +assertEquals(1, switch1(0)); +assertEquals(2, switch1(1)); +for (var i = -2147483648; i < 2147483648; i += 3999773) { + assertEquals(i, switch1(i)); +} + + +var switch2 = (function(stdlib, foreign, heap) { + "use asm"; + function switch2(i) { + i = i|0; + var j = 0; + switch (i) { + case 0: j = 1; break; + case 1: j = 2; break; + case 2: j = 3; break; + default: j = i|0; break; + } + return j|0; + } + return { switch2: switch2 }; +})(stdlib, foreign, heap).switch2; + +assertEquals(1, switch2(0)); +assertEquals(2, switch2(1)); +assertEquals(3, switch2(2)); +for (var i = -2147483648; i < 2147483648; i += 3999773) { + assertEquals(i, switch2(i)); +} + + +var switch3 = (function(stdlib, foreign, heap) { + "use asm"; + function switch3(i) { + i = i|0; + var j = 0; + switch (i) { + case 0: + case 1: j = 1; break; + case 2: + case 3: j = 2; break; + case 4: + case 5: j = 3; break; + default: j = 0; break; + } + return j|0; + } + return { switch3: switch3 }; +})(stdlib, foreign, heap).switch3; + +assertEquals(1, switch3(0)); +assertEquals(1, switch3(1)); +assertEquals(2, switch3(2)); +assertEquals(2, switch3(3)); +assertEquals(3, switch3(4)); +assertEquals(3, switch3(5)); +for (var i = -2147483648; i < 2147483648; i += 3999773) { + assertEquals(0, switch3(i)); +} + + +var switch4 = (function(stdlib, foreign, heap) { + "use asm"; + function switch4(i) { + i = i|0; + switch (i) { + case -1: + case 1: + return 0; + + case -2: + case 2: + return 1; + + case -3: + case 3: + return 2; + + case -8: + case 8: + return 3; + + default: + return 4; + } + } + return { switch4: switch4 }; +})(stdlib, foreign, heap).switch4; + +assertEquals(4, switch4(0)); +assertEquals(0, switch4(-1)); +assertEquals(0, switch4(1)); +assertEquals(1, switch4(-2)); +assertEquals(1, switch4(2)); +assertEquals(3, switch4(-8)); +assertEquals(3, switch4(8)); +assertEquals(4, switch4(-123456789)); +assertEquals(4, switch4(123456789)); diff --git a/test/unittests/compiler/common-operator-unittest.cc b/test/unittests/compiler/common-operator-unittest.cc index 1f0c0fdaa4a..6e60cfd12ae 100644 --- a/test/unittests/compiler/common-operator-unittest.cc +++ b/test/unittests/compiler/common-operator-unittest.cc @@ -133,7 +133,7 @@ class CommonOperatorTest : public TestWithZone { const int kArguments[] = {1, 5, 6, 42, 100, 10000, 65000}; -const size_t kCases[] = {2, 3, 4, 100, 255}; +const size_t kCases[] = {3, 4, 100, 255, 1024, 65000}; const float kFloatValues[] = {-std::numeric_limits::infinity(), @@ -160,6 +160,20 @@ const double kDoubleValues[] = {-std::numeric_limits::infinity(), std::numeric_limits::signaling_NaN()}; +const int32_t kInt32Values[] = { + std::numeric_limits::min(), -1914954528, -1698749618, -1578693386, + -1577976073, -1573998034, -1529085059, -1499540537, -1299205097, + -1090814845, -938186388, -806828902, -750927650, -520676892, -513661538, + -453036354, -433622833, -282638793, -28375, -27788, -22770, -18806, -14173, + -11956, -11200, -10212, -8160, -3751, -2758, -1522, -121, -120, -118, -117, + -106, -84, -80, -74, -59, -52, -48, -39, -35, -17, -11, -10, -9, -7, -5, 0, + 9, 12, 17, 23, 29, 31, 33, 35, 40, 47, 55, 56, 62, 64, 67, 68, 69, 74, 79, + 84, 89, 90, 97, 104, 118, 124, 126, 127, 7278, 17787, 24136, 24202, 25570, + 26680, 30242, 32399, 420886487, 642166225, 821912648, 822577803, 851385718, + 1212241078, 1411419304, 1589626102, 1596437184, 1876245816, 1954730266, + 2008792749, 2045320228, std::numeric_limits::max()}; + + const BranchHint kHints[] = {BranchHint::kNone, BranchHint::kTrue, BranchHint::kFalse}; @@ -199,12 +213,12 @@ TEST_F(CommonOperatorTest, Switch) { } -TEST_F(CommonOperatorTest, Case) { - TRACED_FORRANGE(size_t, index, 0, 1024) { - const Operator* const op = common()->Case(index); - EXPECT_EQ(IrOpcode::kCase, op->opcode()); +TEST_F(CommonOperatorTest, IfValue) { + TRACED_FOREACH(int32_t, value, kInt32Values) { + const Operator* const op = common()->IfValue(value); + EXPECT_EQ(IrOpcode::kIfValue, op->opcode()); EXPECT_EQ(Operator::kKontrol, op->properties()); - EXPECT_EQ(index, CaseIndexOf(op)); + EXPECT_EQ(value, OpParameter(op)); EXPECT_EQ(0, op->ValueInputCount()); EXPECT_EQ(0, op->EffectInputCount()); EXPECT_EQ(1, op->ControlInputCount()); diff --git a/test/unittests/compiler/control-flow-optimizer-unittest.cc b/test/unittests/compiler/control-flow-optimizer-unittest.cc new file mode 100644 index 00000000000..c083d4bab5c --- /dev/null +++ b/test/unittests/compiler/control-flow-optimizer-unittest.cc @@ -0,0 +1,70 @@ +// 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/control-flow-optimizer.h" +#include "src/compiler/js-graph.h" +#include "src/compiler/js-operator.h" +#include "src/compiler/machine-operator.h" +#include "test/unittests/compiler/graph-unittest.h" +#include "test/unittests/compiler/node-test-utils.h" +#include "testing/gmock-support.h" + +using testing::AllOf; +using testing::Capture; +using testing::CaptureEq; + +namespace v8 { +namespace internal { +namespace compiler { + +class ControlFlowOptimizerTest : public GraphTest { + public: + explicit ControlFlowOptimizerTest(int num_parameters = 3) + : GraphTest(num_parameters), machine_(zone()) {} + ~ControlFlowOptimizerTest() OVERRIDE {} + + protected: + void Optimize() { + JSOperatorBuilder javascript(zone()); + JSGraph jsgraph(isolate(), graph(), common(), &javascript, machine()); + ControlFlowOptimizer optimizer(&jsgraph, zone()); + optimizer.Optimize(); + } + + MachineOperatorBuilder* machine() { return &machine_; } + + private: + MachineOperatorBuilder machine_; +}; + + +TEST_F(ControlFlowOptimizerTest, Switch) { + Node* index = Parameter(0); + Node* branch0 = graph()->NewNode( + common()->Branch(), + graph()->NewNode(machine()->Word32Equal(), index, Int32Constant(0)), + start()); + Node* if_true0 = graph()->NewNode(common()->IfTrue(), branch0); + Node* if_false0 = graph()->NewNode(common()->IfFalse(), branch0); + Node* branch1 = graph()->NewNode( + common()->Branch(), + graph()->NewNode(machine()->Word32Equal(), index, Int32Constant(1)), + if_false0); + Node* if_true1 = graph()->NewNode(common()->IfTrue(), branch1); + Node* if_false1 = graph()->NewNode(common()->IfFalse(), branch1); + Node* merge = + graph()->NewNode(common()->Merge(3), if_true0, if_true1, if_false1); + graph()->SetEnd(graph()->NewNode(common()->End(), merge)); + Optimize(); + Capture switch_capture; + EXPECT_THAT(end(), + IsEnd(IsMerge(IsIfValue(0, CaptureEq(&switch_capture)), + IsIfValue(1, CaptureEq(&switch_capture)), + IsIfDefault(AllOf(CaptureEq(&switch_capture), + IsSwitch(index, start())))))); +} + +} // namespace compiler +} // namespace internal +} // namespace v8 diff --git a/test/unittests/compiler/graph-unittest.h b/test/unittests/compiler/graph-unittest.h index af07c93a75c..21f8ebf6b49 100644 --- a/test/unittests/compiler/graph-unittest.h +++ b/test/unittests/compiler/graph-unittest.h @@ -33,6 +33,8 @@ class GraphTest : public TestWithContext, public TestWithIsolateAndZone { protected: Node* start() { return graph()->start(); } + Node* end() { return graph()->end(); } + Node* Parameter(int32_t index = 0); Node* Float32Constant(volatile float value); Node* Float64Constant(volatile double value); diff --git a/test/unittests/compiler/node-test-utils.cc b/test/unittests/compiler/node-test-utils.cc index b709568d9b2..eccc96227e4 100644 --- a/test/unittests/compiler/node-test-utils.cc +++ b/test/unittests/compiler/node-test-utils.cc @@ -95,6 +95,68 @@ class IsBranchMatcher FINAL : public NodeMatcher { }; +class IsSwitchMatcher FINAL : public NodeMatcher { + public: + IsSwitchMatcher(const Matcher& value_matcher, + const Matcher& control_matcher) + : NodeMatcher(IrOpcode::kSwitch), + value_matcher_(value_matcher), + control_matcher_(control_matcher) {} + + void DescribeTo(std::ostream* os) const FINAL { + NodeMatcher::DescribeTo(os); + *os << " whose value ("; + value_matcher_.DescribeTo(os); + *os << ") and control ("; + control_matcher_.DescribeTo(os); + *os << ")"; + } + + bool MatchAndExplain(Node* node, MatchResultListener* listener) const FINAL { + return (NodeMatcher::MatchAndExplain(node, listener) && + PrintMatchAndExplain(NodeProperties::GetValueInput(node, 0), + "value", value_matcher_, listener) && + PrintMatchAndExplain(NodeProperties::GetControlInput(node), + "control", control_matcher_, listener)); + } + + private: + const Matcher value_matcher_; + const Matcher control_matcher_; +}; + + +class IsIfValueMatcher FINAL : public NodeMatcher { + public: + IsIfValueMatcher(const Matcher& value_matcher, + const Matcher& control_matcher) + : NodeMatcher(IrOpcode::kIfValue), + value_matcher_(value_matcher), + control_matcher_(control_matcher) {} + + void DescribeTo(std::ostream* os) const FINAL { + NodeMatcher::DescribeTo(os); + *os << " whose value ("; + value_matcher_.DescribeTo(os); + *os << ") and control ("; + control_matcher_.DescribeTo(os); + *os << ")"; + } + + bool MatchAndExplain(Node* node, MatchResultListener* listener) const FINAL { + return (NodeMatcher::MatchAndExplain(node, listener) && + PrintMatchAndExplain(OpParameter(node->op()), "value", + value_matcher_, listener) && + PrintMatchAndExplain(NodeProperties::GetControlInput(node), + "control", control_matcher_, listener)); + } + + private: + const Matcher value_matcher_; + const Matcher control_matcher_; +}; + + class IsControl1Matcher FINAL : public NodeMatcher { public: IsControl1Matcher(IrOpcode::Value opcode, @@ -1207,6 +1269,14 @@ Matcher IsMerge(const Matcher& control0_matcher, } +Matcher IsMerge(const Matcher& control0_matcher, + const Matcher& control1_matcher, + const Matcher& control2_matcher) { + return MakeMatcher(new IsControl3Matcher(IrOpcode::kMerge, control0_matcher, + control1_matcher, control2_matcher)); +} + + Matcher IsLoop(const Matcher& control0_matcher, const Matcher& control1_matcher) { return MakeMatcher(new IsControl2Matcher(IrOpcode::kLoop, control0_matcher, @@ -1233,6 +1303,24 @@ Matcher IsIfFalse(const Matcher& control_matcher) { } +Matcher IsSwitch(const Matcher& value_matcher, + const Matcher& control_matcher) { + return MakeMatcher(new IsSwitchMatcher(value_matcher, control_matcher)); +} + + +Matcher IsIfValue(const Matcher& value_matcher, + const Matcher& control_matcher) { + return MakeMatcher(new IsIfValueMatcher(value_matcher, control_matcher)); +} + + +Matcher IsIfDefault(const Matcher& control_matcher) { + return MakeMatcher( + new IsControl1Matcher(IrOpcode::kIfDefault, control_matcher)); +} + + Matcher IsValueEffect(const Matcher& value_matcher) { return MakeMatcher(new IsUnopMatcher(IrOpcode::kValueEffect, value_matcher)); } diff --git a/test/unittests/compiler/node-test-utils.h b/test/unittests/compiler/node-test-utils.h index 99187e4cfc2..03011972b7c 100644 --- a/test/unittests/compiler/node-test-utils.h +++ b/test/unittests/compiler/node-test-utils.h @@ -37,6 +37,9 @@ Matcher IsBranch(const Matcher& value_matcher, const Matcher& control_matcher); Matcher IsMerge(const Matcher& control0_matcher, const Matcher& control1_matcher); +Matcher IsMerge(const Matcher& control0_matcher, + const Matcher& control1_matcher, + const Matcher& control2_matcher); Matcher IsLoop(const Matcher& control0_matcher, const Matcher& control1_matcher); Matcher IsLoop(const Matcher& control0_matcher, @@ -44,6 +47,11 @@ Matcher IsLoop(const Matcher& control0_matcher, const Matcher& control2_matcher); Matcher IsIfTrue(const Matcher& control_matcher); Matcher IsIfFalse(const Matcher& control_matcher); +Matcher IsSwitch(const Matcher& value_matcher, + const Matcher& control_matcher); +Matcher IsIfValue(const Matcher& value_matcher, + const Matcher& control_matcher); +Matcher IsIfDefault(const Matcher& control_matcher); Matcher IsValueEffect(const Matcher& value_matcher); Matcher IsFinish(const Matcher& value_matcher, const Matcher& effect_matcher); diff --git a/test/unittests/compiler/scheduler-unittest.cc b/test/unittests/compiler/scheduler-unittest.cc index 66496e2baf0..860d5cd3255 100644 --- a/test/unittests/compiler/scheduler-unittest.cc +++ b/test/unittests/compiler/scheduler-unittest.cc @@ -1973,19 +1973,21 @@ TARGET_TEST_F(SchedulerTest, Switch) { graph()->SetStart(start); Node* p0 = graph()->NewNode(common()->Parameter(0), start); - Node* sw = graph()->NewNode(common()->Switch(2), p0, start); - Node* c0 = graph()->NewNode(common()->Case(0), sw); + Node* sw = graph()->NewNode(common()->Switch(3), p0, start); + Node* c0 = graph()->NewNode(common()->IfValue(0), sw); Node* v0 = graph()->NewNode(common()->Int32Constant(11)); - Node* c1 = graph()->NewNode(common()->Case(1), sw); + Node* c1 = graph()->NewNode(common()->IfValue(1), sw); Node* v1 = graph()->NewNode(common()->Int32Constant(22)); - Node* m = graph()->NewNode(common()->Merge(2), c0, c1); - Node* phi = graph()->NewNode(common()->Phi(kMachInt32, 2), v0, v1, m); + Node* d = graph()->NewNode(common()->IfDefault(), sw); + Node* vd = graph()->NewNode(common()->Int32Constant(33)); + Node* m = graph()->NewNode(common()->Merge(3), c0, c1, d); + Node* phi = graph()->NewNode(common()->Phi(kMachInt32, 3), v0, v1, vd, m); Node* ret = graph()->NewNode(common()->Return(), phi, start, m); Node* end = graph()->NewNode(common()->End(), ret); graph()->SetEnd(end); - ComputeAndVerifySchedule(13, graph()); + ComputeAndVerifySchedule(16, graph()); } @@ -1994,19 +1996,21 @@ TARGET_TEST_F(SchedulerTest, FloatingSwitch) { graph()->SetStart(start); Node* p0 = graph()->NewNode(common()->Parameter(0), start); - Node* sw = graph()->NewNode(common()->Switch(2), p0, start); - Node* c0 = graph()->NewNode(common()->Case(0), sw); + Node* sw = graph()->NewNode(common()->Switch(3), p0, start); + Node* c0 = graph()->NewNode(common()->IfValue(0), sw); Node* v0 = graph()->NewNode(common()->Int32Constant(11)); - Node* c1 = graph()->NewNode(common()->Case(1), sw); + Node* c1 = graph()->NewNode(common()->IfValue(1), sw); Node* v1 = graph()->NewNode(common()->Int32Constant(22)); - Node* m = graph()->NewNode(common()->Merge(2), c0, c1); - Node* phi = graph()->NewNode(common()->Phi(kMachInt32, 2), v0, v1, m); + Node* d = graph()->NewNode(common()->IfDefault(), sw); + Node* vd = graph()->NewNode(common()->Int32Constant(33)); + Node* m = graph()->NewNode(common()->Merge(3), c0, c1, d); + Node* phi = graph()->NewNode(common()->Phi(kMachInt32, 3), v0, v1, vd, m); Node* ret = graph()->NewNode(common()->Return(), phi, start, start); Node* end = graph()->NewNode(common()->End(), ret); graph()->SetEnd(end); - ComputeAndVerifySchedule(13, graph()); + ComputeAndVerifySchedule(16, graph()); } } // namespace compiler diff --git a/test/unittests/unittests.gyp b/test/unittests/unittests.gyp index 28f3119f8f6..a12d5e7f4bc 100644 --- a/test/unittests/unittests.gyp +++ b/test/unittests/unittests.gyp @@ -43,6 +43,7 @@ 'compiler/common-operator-unittest.cc', 'compiler/compiler-test-utils.h', 'compiler/control-equivalence-unittest.cc', + 'compiler/control-flow-optimizer-unittest.cc', 'compiler/control-reducer-unittest.cc', 'compiler/diamond-unittest.cc', 'compiler/graph-reducer-unittest.cc', diff --git a/tools/gyp/v8.gyp b/tools/gyp/v8.gyp index 7fa63e41e20..eef1b813f9a 100644 --- a/tools/gyp/v8.gyp +++ b/tools/gyp/v8.gyp @@ -434,6 +434,8 @@ '../../src/compiler/control-builders.cc', '../../src/compiler/control-builders.h', '../../src/compiler/control-equivalence.h', + '../../src/compiler/control-flow-optimizer.cc', + '../../src/compiler/control-flow-optimizer.h', '../../src/compiler/control-reducer.cc', '../../src/compiler/control-reducer.h', '../../src/compiler/diamond.h',