[turbofan] Optimize certain chains of Branch into a Switch.
This adds a new ControlFlowOptimizer that - for now - recognizes chains of Branches generated by the SwitchBuilder for a subset of javascript switches into Switch nodes. Those Switch nodes are then lowered to either table or lookup switches. Also rename Case to IfValue (and introduce IfDefault) for consistency. BUG=v8:3872 LOG=n Review URL: https://codereview.chromium.org/931623002 Cr-Commit-Position: refs/heads/master@{#26691}
This commit is contained in:
parent
66ca91b9d2
commit
acd9c46ca7
2
BUILD.gn
2
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",
|
||||
|
@ -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<int>(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);
|
||||
|
@ -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<uint32_t>(max_value) - bit_cast<uint32_t>(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<int32_t>::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<InstructionOperand>(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<InstructionOperand>(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);
|
||||
|
@ -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);
|
||||
|
@ -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<uint32_t>(max_value) - bit_cast<uint32_t>(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<int32_t>::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<InstructionOperand>(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<InstructionOperand>(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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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<size_t>(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,10 +259,10 @@ const Operator* CommonOperatorBuilder::Switch(size_t control_output_count) {
|
||||
}
|
||||
|
||||
|
||||
const Operator* CommonOperatorBuilder::Case(size_t index) {
|
||||
return new (zone()) Operator1<size_t>( // --
|
||||
IrOpcode::kCase, Operator::kKontrol, // opcode
|
||||
"Case", // name
|
||||
const Operator* CommonOperatorBuilder::IfValue(int32_t index) {
|
||||
return new (zone()) Operator1<int32_t>( // --
|
||||
IrOpcode::kIfValue, Operator::kKontrol, // opcode
|
||||
"IfValue", // name
|
||||
0, 0, 1, 0, 0, 1, // counts
|
||||
index); // parameter
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
147
src/compiler/control-flow-optimizer.cc
Normal file
147
src/compiler/control-flow-optimizer.cc
Normal file
@ -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<int32_t> 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
|
52
src/compiler/control-flow-optimizer.h
Normal file
52
src/compiler/control-flow-optimizer.h
Normal file
@ -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<Node*> queue_;
|
||||
NodeMarker<bool> queued_;
|
||||
Zone* const zone_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ControlFlowOptimizer);
|
||||
};
|
||||
|
||||
} // namespace compiler
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
||||
#endif // V8_COMPILER_CONTROL_FLOW_OPTIMIZER_H_
|
@ -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*>(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<Label*>(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);
|
||||
|
@ -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<uint32_t>(max_value) - bit_cast<uint32_t>(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<int32_t>::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<InstructionOperand>(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<InstructionOperand>(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);
|
||||
|
@ -37,7 +37,8 @@ namespace compiler {
|
||||
V(ArchCallCodeObject) \
|
||||
V(ArchCallJSFunction) \
|
||||
V(ArchJmp) \
|
||||
V(ArchSwitch) \
|
||||
V(ArchLookupSwitch) \
|
||||
V(ArchTableSwitch) \
|
||||
V(ArchNop) \
|
||||
V(ArchRet) \
|
||||
V(ArchStackPointer) \
|
||||
|
@ -4,6 +4,8 @@
|
||||
|
||||
#include "src/compiler/instruction-selector.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
#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<int32_t>(case_count);
|
||||
int32_t min_value = std::numeric_limits<int32_t>::max();
|
||||
int32_t max_value = std::numeric_limits<int32_t>::min();
|
||||
for (size_t index = 0; index < case_count; ++index) {
|
||||
BasicBlock* branch = case_branches[index];
|
||||
int32_t value = OpParameter<int32_t>(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<InstructionOperand>(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) {
|
||||
|
@ -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);
|
||||
|
@ -13,7 +13,8 @@
|
||||
V(IfTrue) \
|
||||
V(IfFalse) \
|
||||
V(Switch) \
|
||||
V(Case) \
|
||||
V(IfValue) \
|
||||
V(IfDefault) \
|
||||
V(Merge) \
|
||||
V(Return) \
|
||||
V(OsrNormalEntry) \
|
||||
|
@ -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
|
||||
|
@ -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<uint16_t>(control_in)),
|
||||
value_out_(CheckRange<uint16_t>(value_out)),
|
||||
effect_out_(CheckRange<uint8_t>(effect_out)),
|
||||
control_out_(CheckRange<uint8_t>(control_out)) {}
|
||||
control_out_(CheckRange<uint16_t>(control_out)) {}
|
||||
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Operator& op) {
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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<Code> Pipeline::GenerateCode() {
|
||||
Run<SimplifiedLoweringPhase>();
|
||||
RunPrintAndVerify("Lowered simplified");
|
||||
|
||||
// Optimize control flow.
|
||||
if (FLAG_turbo_switch) {
|
||||
Run<ControlFlowOptimizationPhase>();
|
||||
RunPrintAndVerify("Control flow optimized");
|
||||
}
|
||||
|
||||
// Lower changes that have been inserted before.
|
||||
Run<ChangeLoweringPhase>();
|
||||
// // TODO(jarin, rossberg): Remove UNTYPED once machine typing works.
|
||||
|
@ -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<BasicBlock*>(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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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(); }
|
||||
|
@ -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<int>(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
|
||||
|
@ -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:
|
||||
|
@ -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<bool> 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<int32_t>(use->op()),
|
||||
OpParameter<int32_t>(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.
|
||||
|
@ -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*>(label_count);
|
||||
for (size_t index = 0; index < label_count; ++index) {
|
||||
labels[index] = GetLabel(i.InputRpo(static_cast<int>(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<int>(index + 0))));
|
||||
__ j(equal, GetLabel(i.InputRpo(static_cast<int>(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<int32_t>(instr->InputCount() - 2);
|
||||
Label** cases = zone()->NewArray<Label*>(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);
|
||||
|
@ -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 <algorithm>
|
||||
|
||||
#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<uint32_t>(max_value) - bit_cast<uint32_t>(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<int32_t>::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<InstructionOperand>(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<InstructionOperand>(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);
|
||||
|
@ -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")
|
||||
|
@ -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<int32_t> 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<int32_t>::min(),
|
||||
std::numeric_limits<int32_t>::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<int32_t>::max()));
|
||||
CHECK_EQ(-1, m.Call(std::numeric_limits<int32_t>::min()));
|
||||
for (int i = -100; i < 100; i += 25) {
|
||||
CHECK_EQ(0, m.Call(i));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(RunSwitch3) {
|
||||
RawMachineAssemblerTester<int32_t> m(kMachInt32);
|
||||
|
||||
MLabel blocka, blockb, blockc;
|
||||
MLabel* case_labels[] = {&blocka, &blockb};
|
||||
int32_t case_values[] = {std::numeric_limits<int32_t>::min() + 0,
|
||||
std::numeric_limits<int32_t>::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<int32_t>::min() + 0));
|
||||
CHECK_EQ(1, m.Call(std::numeric_limits<int32_t>::min() + 1));
|
||||
for (int i = -100; i < 100; i += 25) {
|
||||
CHECK_EQ(2, m.Call(i));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(RunSwitch4) {
|
||||
RawMachineAssemblerTester<int32_t> 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<int32_t>(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<int>(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<int>(i)));
|
||||
}
|
||||
}
|
||||
|
120
test/mjsunit/asm/switch.js
Normal file
120
test/mjsunit/asm/switch.js
Normal file
@ -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));
|
@ -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<float>::infinity(),
|
||||
@ -160,6 +160,20 @@ const double kDoubleValues[] = {-std::numeric_limits<double>::infinity(),
|
||||
std::numeric_limits<double>::signaling_NaN()};
|
||||
|
||||
|
||||
const int32_t kInt32Values[] = {
|
||||
std::numeric_limits<int32_t>::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<int32_t>::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<int32_t>(op));
|
||||
EXPECT_EQ(0, op->ValueInputCount());
|
||||
EXPECT_EQ(0, op->EffectInputCount());
|
||||
EXPECT_EQ(1, op->ControlInputCount());
|
||||
|
70
test/unittests/compiler/control-flow-optimizer-unittest.cc
Normal file
70
test/unittests/compiler/control-flow-optimizer-unittest.cc
Normal file
@ -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<Node*> 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
|
@ -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);
|
||||
|
@ -95,6 +95,68 @@ class IsBranchMatcher FINAL : public NodeMatcher {
|
||||
};
|
||||
|
||||
|
||||
class IsSwitchMatcher FINAL : public NodeMatcher {
|
||||
public:
|
||||
IsSwitchMatcher(const Matcher<Node*>& value_matcher,
|
||||
const Matcher<Node*>& 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<Node*> value_matcher_;
|
||||
const Matcher<Node*> control_matcher_;
|
||||
};
|
||||
|
||||
|
||||
class IsIfValueMatcher FINAL : public NodeMatcher {
|
||||
public:
|
||||
IsIfValueMatcher(const Matcher<int32_t>& value_matcher,
|
||||
const Matcher<Node*>& 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<int32_t>(node->op()), "value",
|
||||
value_matcher_, listener) &&
|
||||
PrintMatchAndExplain(NodeProperties::GetControlInput(node),
|
||||
"control", control_matcher_, listener));
|
||||
}
|
||||
|
||||
private:
|
||||
const Matcher<int32_t> value_matcher_;
|
||||
const Matcher<Node*> control_matcher_;
|
||||
};
|
||||
|
||||
|
||||
class IsControl1Matcher FINAL : public NodeMatcher {
|
||||
public:
|
||||
IsControl1Matcher(IrOpcode::Value opcode,
|
||||
@ -1207,6 +1269,14 @@ Matcher<Node*> IsMerge(const Matcher<Node*>& control0_matcher,
|
||||
}
|
||||
|
||||
|
||||
Matcher<Node*> IsMerge(const Matcher<Node*>& control0_matcher,
|
||||
const Matcher<Node*>& control1_matcher,
|
||||
const Matcher<Node*>& control2_matcher) {
|
||||
return MakeMatcher(new IsControl3Matcher(IrOpcode::kMerge, control0_matcher,
|
||||
control1_matcher, control2_matcher));
|
||||
}
|
||||
|
||||
|
||||
Matcher<Node*> IsLoop(const Matcher<Node*>& control0_matcher,
|
||||
const Matcher<Node*>& control1_matcher) {
|
||||
return MakeMatcher(new IsControl2Matcher(IrOpcode::kLoop, control0_matcher,
|
||||
@ -1233,6 +1303,24 @@ Matcher<Node*> IsIfFalse(const Matcher<Node*>& control_matcher) {
|
||||
}
|
||||
|
||||
|
||||
Matcher<Node*> IsSwitch(const Matcher<Node*>& value_matcher,
|
||||
const Matcher<Node*>& control_matcher) {
|
||||
return MakeMatcher(new IsSwitchMatcher(value_matcher, control_matcher));
|
||||
}
|
||||
|
||||
|
||||
Matcher<Node*> IsIfValue(const Matcher<int32_t>& value_matcher,
|
||||
const Matcher<Node*>& control_matcher) {
|
||||
return MakeMatcher(new IsIfValueMatcher(value_matcher, control_matcher));
|
||||
}
|
||||
|
||||
|
||||
Matcher<Node*> IsIfDefault(const Matcher<Node*>& control_matcher) {
|
||||
return MakeMatcher(
|
||||
new IsControl1Matcher(IrOpcode::kIfDefault, control_matcher));
|
||||
}
|
||||
|
||||
|
||||
Matcher<Node*> IsValueEffect(const Matcher<Node*>& value_matcher) {
|
||||
return MakeMatcher(new IsUnopMatcher(IrOpcode::kValueEffect, value_matcher));
|
||||
}
|
||||
|
@ -37,6 +37,9 @@ Matcher<Node*> IsBranch(const Matcher<Node*>& value_matcher,
|
||||
const Matcher<Node*>& control_matcher);
|
||||
Matcher<Node*> IsMerge(const Matcher<Node*>& control0_matcher,
|
||||
const Matcher<Node*>& control1_matcher);
|
||||
Matcher<Node*> IsMerge(const Matcher<Node*>& control0_matcher,
|
||||
const Matcher<Node*>& control1_matcher,
|
||||
const Matcher<Node*>& control2_matcher);
|
||||
Matcher<Node*> IsLoop(const Matcher<Node*>& control0_matcher,
|
||||
const Matcher<Node*>& control1_matcher);
|
||||
Matcher<Node*> IsLoop(const Matcher<Node*>& control0_matcher,
|
||||
@ -44,6 +47,11 @@ Matcher<Node*> IsLoop(const Matcher<Node*>& control0_matcher,
|
||||
const Matcher<Node*>& control2_matcher);
|
||||
Matcher<Node*> IsIfTrue(const Matcher<Node*>& control_matcher);
|
||||
Matcher<Node*> IsIfFalse(const Matcher<Node*>& control_matcher);
|
||||
Matcher<Node*> IsSwitch(const Matcher<Node*>& value_matcher,
|
||||
const Matcher<Node*>& control_matcher);
|
||||
Matcher<Node*> IsIfValue(const Matcher<int32_t>& value_matcher,
|
||||
const Matcher<Node*>& control_matcher);
|
||||
Matcher<Node*> IsIfDefault(const Matcher<Node*>& control_matcher);
|
||||
Matcher<Node*> IsValueEffect(const Matcher<Node*>& value_matcher);
|
||||
Matcher<Node*> IsFinish(const Matcher<Node*>& value_matcher,
|
||||
const Matcher<Node*>& effect_matcher);
|
||||
|
@ -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
|
||||
|
@ -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',
|
||||
|
@ -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',
|
||||
|
Loading…
Reference in New Issue
Block a user