Index: test/unittests/compiler/register-allocator-unittest.cc |
diff --git a/test/unittests/compiler/register-allocator-unittest.cc b/test/unittests/compiler/register-allocator-unittest.cc |
index d4dd49fa3f712c21d90a9bdbb972abf077c8d292..038a016ffde8ec102cf2c97e275d98881448e1f0 100644 |
--- a/test/unittests/compiler/register-allocator-unittest.cc |
+++ b/test/unittests/compiler/register-allocator-unittest.cc |
@@ -2,516 +2,19 @@ |
// Use of this source code is governed by a BSD-style license that can be |
// found in the LICENSE file. |
-#include "src/base/utils/random-number-generator.h" |
-#include "src/compiler/instruction.h" |
#include "src/compiler/pipeline.h" |
-#include "test/unittests/test-utils.h" |
-#include "testing/gmock/include/gmock/gmock.h" |
+#include "test/unittests/compiler/instruction-sequence-unittest.h" |
namespace v8 { |
namespace internal { |
namespace compiler { |
-typedef BasicBlock::RpoNumber Rpo; |
- |
-static const char* |
- general_register_names_[RegisterConfiguration::kMaxGeneralRegisters]; |
-static const char* |
- double_register_names_[RegisterConfiguration::kMaxDoubleRegisters]; |
-static char register_names_[10 * (RegisterConfiguration::kMaxGeneralRegisters + |
- RegisterConfiguration::kMaxDoubleRegisters)]; |
- |
-static void InitializeRegisterNames() { |
- char* loc = register_names_; |
- for (int i = 0; i < RegisterConfiguration::kMaxGeneralRegisters; ++i) { |
- general_register_names_[i] = loc; |
- loc += base::OS::SNPrintF(loc, 100, "gp_%d", i); |
- *loc++ = 0; |
- } |
- for (int i = 0; i < RegisterConfiguration::kMaxDoubleRegisters; ++i) { |
- double_register_names_[i] = loc; |
- loc += base::OS::SNPrintF(loc, 100, "fp_%d", i) + 1; |
- *loc++ = 0; |
- } |
-} |
- |
- |
-class RegisterAllocatorTest : public TestWithZone { |
+class RegisterAllocatorTest : public InstructionSequenceTest { |
public: |
- static const int kDefaultNRegs = 4; |
- static const int kNoValue = kMinInt; |
- |
- struct VReg { |
- VReg() : value_(kNoValue) {} |
- VReg(PhiInstruction* phi) : value_(phi->virtual_register()) {} // NOLINT |
- explicit VReg(int value) : value_(value) {} |
- int value_; |
- }; |
- |
- enum TestOperandType { |
- kInvalid, |
- kSameAsFirst, |
- kRegister, |
- kFixedRegister, |
- kSlot, |
- kFixedSlot, |
- kImmediate, |
- kNone |
- }; |
- |
- struct TestOperand { |
- TestOperand() : type_(kInvalid), vreg_(), value_(kNoValue) {} |
- TestOperand(TestOperandType type, int imm) |
- : type_(type), vreg_(), value_(imm) {} |
- TestOperand(TestOperandType type, VReg vreg, int value = kNoValue) |
- : type_(type), vreg_(vreg), value_(value) {} |
- |
- TestOperandType type_; |
- VReg vreg_; |
- int value_; |
- }; |
- |
- static TestOperand Same() { return TestOperand(kSameAsFirst, VReg()); } |
- |
- static TestOperand Reg(VReg vreg, int index = kNoValue) { |
- TestOperandType type = kRegister; |
- if (index != kNoValue) type = kFixedRegister; |
- return TestOperand(type, vreg, index); |
- } |
- |
- static TestOperand Reg(int index = kNoValue) { return Reg(VReg(), index); } |
- |
- static TestOperand Slot(VReg vreg, int index = kNoValue) { |
- TestOperandType type = kSlot; |
- if (index != kNoValue) type = kFixedSlot; |
- return TestOperand(type, vreg, index); |
- } |
- |
- static TestOperand Slot(int index = kNoValue) { return Slot(VReg(), index); } |
- |
- static TestOperand Use(VReg vreg) { return TestOperand(kNone, vreg); } |
- |
- static TestOperand Use() { return Use(VReg()); } |
- |
- enum BlockCompletionType { kBlockEnd, kFallThrough, kBranch, kJump }; |
- |
- struct BlockCompletion { |
- BlockCompletionType type_; |
- TestOperand op_; |
- int offset_0_; |
- int offset_1_; |
- }; |
- |
- static BlockCompletion FallThrough() { |
- BlockCompletion completion = {kFallThrough, TestOperand(), 1, kNoValue}; |
- return completion; |
- } |
- |
- static BlockCompletion Jump(int offset) { |
- BlockCompletion completion = {kJump, TestOperand(), offset, kNoValue}; |
- return completion; |
- } |
- |
- static BlockCompletion Branch(TestOperand op, int left_offset, |
- int right_offset) { |
- BlockCompletion completion = {kBranch, op, left_offset, right_offset}; |
- return completion; |
- } |
- |
- static BlockCompletion Last() { |
- BlockCompletion completion = {kBlockEnd, TestOperand(), kNoValue, kNoValue}; |
- return completion; |
- } |
- |
- RegisterAllocatorTest() |
- : frame_(nullptr), |
- sequence_(nullptr), |
- num_general_registers_(kDefaultNRegs), |
- num_double_registers_(kDefaultNRegs), |
- instruction_blocks_(zone()), |
- current_instruction_index_(-1), |
- current_block_(nullptr), |
- block_returns_(false) { |
- InitializeRegisterNames(); |
- } |
- |
- void SetNumRegs(int num_general_registers, int num_double_registers) { |
- CHECK(config_.is_empty()); |
- CHECK(instructions_.empty()); |
- CHECK(instruction_blocks_.empty()); |
- num_general_registers_ = num_general_registers; |
- num_double_registers_ = num_double_registers; |
- } |
- |
- RegisterConfiguration* config() { |
- if (config_.is_empty()) { |
- config_.Reset(new RegisterConfiguration( |
- num_general_registers_, num_double_registers_, num_double_registers_, |
- general_register_names_, double_register_names_)); |
- } |
- return config_.get(); |
- } |
- |
- Frame* frame() { |
- if (frame_ == nullptr) { |
- frame_ = new (zone()) Frame(); |
- } |
- return frame_; |
- } |
- |
- InstructionSequence* sequence() { |
- if (sequence_ == nullptr) { |
- sequence_ = |
- new (zone()) InstructionSequence(zone(), &instruction_blocks_); |
- } |
- return sequence_; |
- } |
- |
- void StartLoop(int loop_blocks) { |
- CHECK(current_block_ == nullptr); |
- if (!loop_blocks_.empty()) { |
- CHECK(!loop_blocks_.back().loop_header_.IsValid()); |
- } |
- LoopData loop_data = {Rpo::Invalid(), loop_blocks}; |
- loop_blocks_.push_back(loop_data); |
- } |
- |
- void EndLoop() { |
- CHECK(current_block_ == nullptr); |
- CHECK(!loop_blocks_.empty()); |
- CHECK_EQ(0, loop_blocks_.back().expected_blocks_); |
- loop_blocks_.pop_back(); |
- } |
- |
- void StartBlock() { |
- block_returns_ = false; |
- NewBlock(); |
- } |
- |
- int EndBlock(BlockCompletion completion = FallThrough()) { |
- int instruction_index = kMinInt; |
- if (block_returns_) { |
- CHECK(completion.type_ == kBlockEnd || completion.type_ == kFallThrough); |
- completion.type_ = kBlockEnd; |
- } |
- switch (completion.type_) { |
- case kBlockEnd: |
- break; |
- case kFallThrough: |
- instruction_index = EmitFallThrough(); |
- break; |
- case kJump: |
- CHECK(!block_returns_); |
- instruction_index = EmitJump(); |
- break; |
- case kBranch: |
- CHECK(!block_returns_); |
- instruction_index = EmitBranch(completion.op_); |
- break; |
- } |
- completions_.push_back(completion); |
- CHECK(current_block_ != nullptr); |
- sequence()->EndBlock(current_block_->rpo_number()); |
- current_block_ = nullptr; |
- return instruction_index; |
- } |
- |
void Allocate() { |
- CHECK_EQ(nullptr, current_block_); |
WireBlocks(); |
Pipeline::AllocateRegistersForTesting(config(), sequence(), true); |
} |
- |
- TestOperand Imm(int32_t imm = 0) { |
- int index = sequence()->AddImmediate(Constant(imm)); |
- return TestOperand(kImmediate, index); |
- } |
- |
- VReg Parameter(TestOperand output_op = Reg()) { |
- VReg vreg = NewReg(); |
- InstructionOperand* outputs[1]{ConvertOutputOp(vreg, output_op)}; |
- Emit(vreg.value_, kArchNop, 1, outputs); |
- return vreg; |
- } |
- |
- int Return(TestOperand input_op_0) { |
- block_returns_ = true; |
- InstructionOperand* inputs[1]{ConvertInputOp(input_op_0)}; |
- return Emit(NewIndex(), kArchRet, 0, nullptr, 1, inputs); |
- } |
- |
- int Return(VReg vreg) { return Return(Reg(vreg, 0)); } |
- |
- PhiInstruction* Phi(VReg incoming_vreg) { |
- PhiInstruction* phi = |
- new (zone()) PhiInstruction(zone(), NewReg().value_, 10); |
- phi->Extend(zone(), incoming_vreg.value_); |
- current_block_->AddPhi(phi); |
- return phi; |
- } |
- |
- PhiInstruction* Phi(VReg incoming_vreg_0, VReg incoming_vreg_1) { |
- auto phi = Phi(incoming_vreg_0); |
- Extend(phi, incoming_vreg_1); |
- return phi; |
- } |
- |
- void Extend(PhiInstruction* phi, VReg vreg) { |
- phi->Extend(zone(), vreg.value_); |
- } |
- |
- VReg DefineConstant(int32_t imm = 0) { |
- VReg vreg = NewReg(); |
- sequence()->AddConstant(vreg.value_, Constant(imm)); |
- InstructionOperand* outputs[1]{ |
- ConstantOperand::Create(vreg.value_, zone())}; |
- Emit(vreg.value_, kArchNop, 1, outputs); |
- return vreg; |
- } |
- |
- VReg EmitOII(TestOperand output_op, TestOperand input_op_0, |
- TestOperand input_op_1) { |
- VReg output_vreg = NewReg(); |
- InstructionOperand* outputs[1]{ConvertOutputOp(output_vreg, output_op)}; |
- InstructionOperand* inputs[2]{ConvertInputOp(input_op_0), |
- ConvertInputOp(input_op_1)}; |
- Emit(output_vreg.value_, kArchNop, 1, outputs, 2, inputs); |
- return output_vreg; |
- } |
- |
- VReg EmitCall(TestOperand output_op, size_t input_size, TestOperand* inputs) { |
- VReg output_vreg = NewReg(); |
- InstructionOperand* outputs[1]{ConvertOutputOp(output_vreg, output_op)}; |
- InstructionOperand** mapped_inputs = |
- zone()->NewArray<InstructionOperand*>(static_cast<int>(input_size)); |
- for (size_t i = 0; i < input_size; ++i) { |
- mapped_inputs[i] = ConvertInputOp(inputs[i]); |
- } |
- Emit(output_vreg.value_, kArchCallCodeObject, 1, outputs, input_size, |
- mapped_inputs); |
- return output_vreg; |
- } |
- |
- // Get defining instruction vreg or value returned at instruction creation |
- // time when there is no return value. |
- const Instruction* GetInstruction(int instruction_index) { |
- auto it = instructions_.find(instruction_index); |
- CHECK(it != instructions_.end()); |
- return it->second; |
- } |
- |
- private: |
- VReg NewReg() { return VReg(sequence()->NextVirtualRegister()); } |
- int NewIndex() { return current_instruction_index_--; } |
- |
- static TestOperand Invalid() { return TestOperand(kInvalid, VReg()); } |
- |
- int EmitBranch(TestOperand input_op) { |
- InstructionOperand* inputs[4]{ConvertInputOp(input_op), |
- ConvertInputOp(Imm()), ConvertInputOp(Imm()), |
- ConvertInputOp(Imm())}; |
- InstructionCode opcode = kArchJmp | FlagsModeField::encode(kFlags_branch) | |
- FlagsConditionField::encode(kEqual); |
- auto instruction = |
- NewInstruction(opcode, 0, nullptr, 4, inputs)->MarkAsControl(); |
- return AddInstruction(NewIndex(), instruction); |
- } |
- |
- int EmitFallThrough() { |
- auto instruction = NewInstruction(kArchNop, 0, nullptr)->MarkAsControl(); |
- return AddInstruction(NewIndex(), instruction); |
- } |
- |
- int EmitJump() { |
- InstructionOperand* inputs[1]{ConvertInputOp(Imm())}; |
- auto instruction = |
- NewInstruction(kArchJmp, 0, nullptr, 1, inputs)->MarkAsControl(); |
- return AddInstruction(NewIndex(), instruction); |
- } |
- |
- Instruction* NewInstruction(InstructionCode code, size_t outputs_size, |
- InstructionOperand** outputs, |
- size_t inputs_size = 0, |
- InstructionOperand* *inputs = nullptr, |
- size_t temps_size = 0, |
- InstructionOperand* *temps = nullptr) { |
- CHECK_NE(nullptr, current_block_); |
- return Instruction::New(zone(), code, outputs_size, outputs, inputs_size, |
- inputs, temps_size, temps); |
- } |
- |
- InstructionOperand* Unallocated(TestOperand op, |
- UnallocatedOperand::ExtendedPolicy policy) { |
- auto unallocated = new (zone()) UnallocatedOperand(policy); |
- unallocated->set_virtual_register(op.vreg_.value_); |
- return unallocated; |
- } |
- |
- InstructionOperand* Unallocated(TestOperand op, |
- UnallocatedOperand::ExtendedPolicy policy, |
- UnallocatedOperand::Lifetime lifetime) { |
- auto unallocated = new (zone()) UnallocatedOperand(policy, lifetime); |
- unallocated->set_virtual_register(op.vreg_.value_); |
- return unallocated; |
- } |
- |
- InstructionOperand* Unallocated(TestOperand op, |
- UnallocatedOperand::ExtendedPolicy policy, |
- int index) { |
- auto unallocated = new (zone()) UnallocatedOperand(policy, index); |
- unallocated->set_virtual_register(op.vreg_.value_); |
- return unallocated; |
- } |
- |
- InstructionOperand* Unallocated(TestOperand op, |
- UnallocatedOperand::BasicPolicy policy, |
- int index) { |
- auto unallocated = new (zone()) UnallocatedOperand(policy, index); |
- unallocated->set_virtual_register(op.vreg_.value_); |
- return unallocated; |
- } |
- |
- InstructionOperand* ConvertInputOp(TestOperand op) { |
- if (op.type_ == kImmediate) { |
- CHECK_EQ(op.vreg_.value_, kNoValue); |
- return ImmediateOperand::Create(op.value_, zone()); |
- } |
- CHECK_NE(op.vreg_.value_, kNoValue); |
- switch (op.type_) { |
- case kNone: |
- return Unallocated(op, UnallocatedOperand::NONE, |
- UnallocatedOperand::USED_AT_START); |
- case kRegister: |
- return Unallocated(op, UnallocatedOperand::MUST_HAVE_REGISTER, |
- UnallocatedOperand::USED_AT_START); |
- case kFixedRegister: |
- CHECK(0 <= op.value_ && op.value_ < num_general_registers_); |
- return Unallocated(op, UnallocatedOperand::FIXED_REGISTER, op.value_); |
- default: |
- break; |
- } |
- CHECK(false); |
- return NULL; |
- } |
- |
- InstructionOperand* ConvertOutputOp(VReg vreg, TestOperand op) { |
- CHECK_EQ(op.vreg_.value_, kNoValue); |
- op.vreg_ = vreg; |
- switch (op.type_) { |
- case kSameAsFirst: |
- return Unallocated(op, UnallocatedOperand::SAME_AS_FIRST_INPUT); |
- case kRegister: |
- return Unallocated(op, UnallocatedOperand::MUST_HAVE_REGISTER); |
- case kFixedSlot: |
- return Unallocated(op, UnallocatedOperand::FIXED_SLOT, op.value_); |
- case kFixedRegister: |
- CHECK(0 <= op.value_ && op.value_ < num_general_registers_); |
- return Unallocated(op, UnallocatedOperand::FIXED_REGISTER, op.value_); |
- default: |
- break; |
- } |
- CHECK(false); |
- return NULL; |
- } |
- |
- InstructionBlock* NewBlock() { |
- CHECK(current_block_ == nullptr); |
- auto block_id = BasicBlock::Id::FromSize(instruction_blocks_.size()); |
- Rpo rpo = Rpo::FromInt(block_id.ToInt()); |
- Rpo loop_header = Rpo::Invalid(); |
- Rpo loop_end = Rpo::Invalid(); |
- if (!loop_blocks_.empty()) { |
- auto& loop_data = loop_blocks_.back(); |
- // This is a loop header. |
- if (!loop_data.loop_header_.IsValid()) { |
- loop_end = Rpo::FromInt(block_id.ToInt() + loop_data.expected_blocks_); |
- loop_data.expected_blocks_--; |
- loop_data.loop_header_ = rpo; |
- } else { |
- // This is a loop body. |
- CHECK_NE(0, loop_data.expected_blocks_); |
- // TODO(dcarney): handle nested loops. |
- loop_data.expected_blocks_--; |
- loop_header = loop_data.loop_header_; |
- } |
- } |
- // Construct instruction block. |
- auto instruction_block = new (zone()) InstructionBlock( |
- zone(), block_id, rpo, rpo, loop_header, loop_end, false); |
- instruction_blocks_.push_back(instruction_block); |
- current_block_ = instruction_block; |
- sequence()->StartBlock(rpo); |
- return instruction_block; |
- } |
- |
- void WireBlocks() { |
- CHECK(instruction_blocks_.size() == completions_.size()); |
- size_t offset = 0; |
- for (const auto& completion : completions_) { |
- switch (completion.type_) { |
- case kBlockEnd: |
- break; |
- case kFallThrough: // Fallthrough. |
- case kJump: |
- WireBlock(offset, completion.offset_0_); |
- break; |
- case kBranch: |
- WireBlock(offset, completion.offset_0_); |
- WireBlock(offset, completion.offset_1_); |
- break; |
- } |
- ++offset; |
- } |
- } |
- |
- void WireBlock(size_t block_offset, int jump_offset) { |
- size_t target_block_offset = |
- block_offset + static_cast<size_t>(jump_offset); |
- CHECK(block_offset < instruction_blocks_.size()); |
- CHECK(target_block_offset < instruction_blocks_.size()); |
- auto block = instruction_blocks_[block_offset]; |
- auto target = instruction_blocks_[target_block_offset]; |
- block->successors().push_back(target->rpo_number()); |
- target->predecessors().push_back(block->rpo_number()); |
- } |
- |
- int Emit(int instruction_index, InstructionCode code, size_t outputs_size, |
- InstructionOperand** outputs, size_t inputs_size = 0, |
- InstructionOperand* *inputs = nullptr, size_t temps_size = 0, |
- InstructionOperand* *temps = nullptr) { |
- auto instruction = NewInstruction(code, outputs_size, outputs, inputs_size, |
- inputs, temps_size, temps); |
- return AddInstruction(instruction_index, instruction); |
- } |
- |
- int AddInstruction(int instruction_index, Instruction* instruction) { |
- sequence()->AddInstruction(instruction); |
- return instruction_index; |
- } |
- |
- struct LoopData { |
- Rpo loop_header_; |
- int expected_blocks_; |
- }; |
- |
- typedef std::vector<LoopData> LoopBlocks; |
- typedef std::map<int, const Instruction*> Instructions; |
- typedef std::vector<BlockCompletion> Completions; |
- |
- SmartPointer<RegisterConfiguration> config_; |
- Frame* frame_; |
- InstructionSequence* sequence_; |
- int num_general_registers_; |
- int num_double_registers_; |
- |
- // Block building state. |
- InstructionBlocks instruction_blocks_; |
- Instructions instructions_; |
- int current_instruction_index_; |
- Completions completions_; |
- LoopBlocks loop_blocks_; |
- InstructionBlock* current_block_; |
- bool block_returns_; |
}; |
@@ -635,7 +138,7 @@ TEST_F(RegisterAllocatorTest, DiamondManyPhis) { |
for (int i = 0; i < kPhis; ++i) { |
merged[i] = Use(Phi(t_vals[i], f_vals[i])); |
} |
- Return(EmitCall(Reg(), kPhis, merged)); |
+ Return(EmitCall(Slot(-1), kPhis, merged)); |
EndBlock(); |
Allocate(); |
@@ -674,7 +177,7 @@ TEST_F(RegisterAllocatorTest, DoubleDiamondManyRedundantPhis) { |
for (int i = 0; i < kPhis; ++i) { |
merged[i] = Use(Phi(vals[i], vals[i])); |
} |
- Return(EmitCall(Reg(), kPhis, merged)); |
+ Return(EmitCall(Reg(0), kPhis, merged)); |
EndBlock(); |
Allocate(); |
@@ -729,6 +232,48 @@ TEST_F(RegisterAllocatorTest, RegressionPhisNeedTooManyRegisters) { |
Allocate(); |
} |
+ |
+TEST_F(RegisterAllocatorTest, SpillPhi) { |
+ StartBlock(); |
+ EndBlock(Branch(Imm(), 1, 2)); |
+ |
+ StartBlock(); |
+ auto left = Define(Reg(0)); |
+ EndBlock(Jump(2)); |
+ |
+ StartBlock(); |
+ auto right = Define(Reg(0)); |
+ EndBlock(); |
+ |
+ StartBlock(); |
+ auto phi = Phi(left, right); |
+ EmitCall(Slot(-1)); |
+ Return(Reg(phi)); |
+ EndBlock(); |
+ |
+ Allocate(); |
+} |
+ |
+ |
+TEST_F(RegisterAllocatorTest, MoveLotsOfConstants) { |
+ StartBlock(); |
+ VReg constants[kDefaultNRegs]; |
+ for (size_t i = 0; i < arraysize(constants); ++i) { |
+ constants[i] = DefineConstant(); |
+ } |
+ TestOperand call_ops[kDefaultNRegs * 2]; |
+ for (int i = 0; i < kDefaultNRegs; ++i) { |
+ call_ops[i] = Reg(constants[i], i); |
+ } |
+ for (int i = 0; i < kDefaultNRegs; ++i) { |
+ call_ops[i + kDefaultNRegs] = Slot(constants[i], i); |
+ } |
+ EmitCall(Slot(-1), arraysize(call_ops), call_ops); |
+ EndBlock(Last()); |
+ |
+ Allocate(); |
+} |
+ |
} // namespace compiler |
} // namespace internal |
} // namespace v8 |