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 1c7aeee4fd35dff2bf9f158c7744ceba5b639564..b1597c186511b8a4a63616e54fa2d5815fbd2dcf 100644 |
--- a/test/unittests/compiler/register-allocator-unittest.cc |
+++ b/test/unittests/compiler/register-allocator-unittest.cc |
@@ -37,11 +37,37 @@ static void InitializeRegisterNames() { |
} |
} |
+enum BlockCompletionType { kFallThrough, kBranch, kJump }; |
+ |
+struct BlockCompletion { |
+ BlockCompletionType type_; |
+ int vreg_; |
+ int offset_0_; |
+ int offset_1_; |
+}; |
+ |
+static const int kInvalidJumpOffset = kMinInt; |
+ |
+BlockCompletion FallThrough() { |
+ BlockCompletion completion = {kFallThrough, -1, 1, kInvalidJumpOffset}; |
+ return completion; |
+} |
+ |
+ |
+BlockCompletion Jump(int offset) { |
+ BlockCompletion completion = {kJump, -1, offset, kInvalidJumpOffset}; |
+ return completion; |
+} |
+ |
+ |
+BlockCompletion Branch(int vreg, int left_offset, int right_offset) { |
+ BlockCompletion completion = {kBranch, vreg, left_offset, right_offset}; |
+ return completion; |
+} |
+ |
} // namespace |
-// TODO(dcarney): fake opcodes. |
-// TODO(dcarney): fix printing of sequence w.r.t fake opcodes and registers. |
class RegisterAllocatorTest : public TestWithZone { |
public: |
static const int kDefaultNRegs = 4; |
@@ -49,12 +75,18 @@ class RegisterAllocatorTest : public TestWithZone { |
RegisterAllocatorTest() |
: num_general_registers_(kDefaultNRegs), |
num_double_registers_(kDefaultNRegs), |
- basic_blocks_(zone()), |
instruction_blocks_(zone()), |
- current_block_(NULL) { |
+ current_block_(nullptr), |
+ is_last_block_(false) { |
InitializeRegisterNames(); |
} |
+ void SetNumRegs(int num_general_registers, int num_double_registers) { |
+ 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( |
@@ -86,42 +118,64 @@ class RegisterAllocatorTest : public TestWithZone { |
return allocator_.get(); |
} |
- InstructionBlock* StartBlock(Rpo loop_header = Rpo::Invalid(), |
- Rpo loop_end = Rpo::Invalid()) { |
- CHECK(current_block_ == NULL); |
- BasicBlock::Id block_id = |
- BasicBlock::Id::FromSize(instruction_blocks_.size()); |
- BasicBlock* basic_block = new (zone()) BasicBlock(zone(), block_id); |
- basic_block->set_rpo_number(block_id.ToInt()); |
- basic_block->set_ao_number(block_id.ToInt()); |
- if (loop_header.IsValid()) { |
- basic_block->set_loop_depth(1); |
- basic_block->set_loop_header(basic_blocks_[loop_header.ToSize()]); |
- basic_block->set_loop_end(basic_blocks_[loop_end.ToSize()]); |
+ void StartLoop(int loop_blocks) { |
+ CHECK(current_block_ == nullptr); |
+ if (!loop_blocks_.empty()) { |
+ CHECK(!loop_blocks_.back().loop_header_.IsValid()); |
} |
- InstructionBlock* instruction_block = |
- new (zone()) InstructionBlock(zone(), basic_block); |
- basic_blocks_.push_back(basic_block); |
- instruction_blocks_.push_back(instruction_block); |
- current_block_ = instruction_block; |
- sequence()->StartBlock(basic_block); |
- return instruction_block; |
+ 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 StartLastBlock() { |
+ CHECK(!is_last_block_); |
+ is_last_block_ = true; |
+ NewBlock(); |
+ } |
+ |
+ void StartBlock() { |
+ CHECK(!is_last_block_); |
+ NewBlock(); |
} |
- void EndBlock() { |
- CHECK(current_block_ != NULL); |
- sequence()->EndBlock(basic_blocks_[current_block_->rpo_number().ToSize()]); |
- current_block_ = NULL; |
+ void EndBlock(BlockCompletion completion = FallThrough()) { |
+ completions_.push_back(completion); |
+ switch (completion.type_) { |
+ case kFallThrough: |
+ if (is_last_block_) break; |
+ // TODO(dcarney): we don't emit this after returns. |
+ EmitFallThrough(); |
+ break; |
+ case kJump: |
+ EmitJump(); |
+ break; |
+ case kBranch: |
+ EmitBranch(completion.vreg_); |
+ break; |
+ } |
+ CHECK(current_block_ != nullptr); |
+ sequence()->EndBlock(current_block_->rpo_number()); |
+ current_block_ = nullptr; |
} |
void Allocate() { |
- if (FLAG_trace_alloc) { |
+ CHECK_EQ(nullptr, current_block_); |
+ CHECK(is_last_block_); |
+ WireBlocks(); |
+ if (FLAG_trace_alloc || FLAG_trace_turbo) { |
OFStream os(stdout); |
PrintableInstructionSequence printable = {config(), sequence()}; |
os << "Before: " << std::endl << printable << std::endl; |
} |
allocator()->Allocate(); |
- if (FLAG_trace_alloc) { |
+ if (FLAG_trace_alloc || FLAG_trace_turbo) { |
OFStream os(stdout); |
PrintableInstructionSequence printable = {config(), sequence()}; |
os << "After: " << std::endl << printable << std::endl; |
@@ -131,61 +185,223 @@ class RegisterAllocatorTest : public TestWithZone { |
int NewReg() { return sequence()->NextVirtualRegister(); } |
int Parameter() { |
- // TODO(dcarney): assert parameters before other instructions. |
int vreg = NewReg(); |
- InstructionOperand* outputs[1]{ |
- Unallocated(UnallocatedOperand::MUST_HAVE_REGISTER, vreg)}; |
- sequence()->AddInstruction( |
- Instruction::New(zone(), kArchNop, 1, outputs, 0, NULL, 0, NULL)); |
+ InstructionOperand* outputs[1]{UseRegister(vreg)}; |
+ Emit(kArchNop, 1, outputs); |
return vreg; |
} |
- void Return(int vreg) { |
- InstructionOperand* inputs[1]{ |
- Unallocated(UnallocatedOperand::MUST_HAVE_REGISTER, vreg)}; |
- sequence()->AddInstruction( |
- Instruction::New(zone(), kArchNop, 0, NULL, 1, inputs, 0, NULL)); |
+ Instruction* Return(int vreg) { |
+ InstructionOperand* inputs[1]{UseRegister(vreg)}; |
+ return Emit(kArchRet, 0, nullptr, 1, inputs); |
+ } |
+ |
+ PhiInstruction* Phi(int vreg) { |
+ PhiInstruction* phi = new (zone()) PhiInstruction(zone(), NewReg()); |
+ phi->operands().push_back(vreg); |
+ current_block_->AddPhi(phi); |
+ return phi; |
} |
- Instruction* Emit(int output_vreg, int input_vreg_0, int input_vreg_1) { |
+ int DefineConstant(int32_t imm = 0) { |
+ int virtual_register = NewReg(); |
+ sequence()->AddConstant(virtual_register, Constant(imm)); |
InstructionOperand* outputs[1]{ |
- Unallocated(UnallocatedOperand::MUST_HAVE_REGISTER, output_vreg)}; |
- InstructionOperand* inputs[2]{ |
- Unallocated(UnallocatedOperand::MUST_HAVE_REGISTER, input_vreg_0), |
- Unallocated(UnallocatedOperand::MUST_HAVE_REGISTER, input_vreg_1)}; |
- Instruction* instruction = |
- Instruction::New(zone(), kArchNop, 1, outputs, 2, inputs, 0, NULL); |
- sequence()->AddInstruction(instruction); |
- return instruction; |
+ ConstantOperand::Create(virtual_register, zone())}; |
+ Emit(kArchNop, 1, outputs); |
+ return virtual_register; |
+ } |
+ |
+ ImmediateOperand* Immediate(int32_t imm = 0) { |
+ int index = sequence()->AddImmediate(Constant(imm)); |
+ return ImmediateOperand::Create(index, zone()); |
+ } |
+ |
+ Instruction* EmitFRI(int output_vreg, int input_vreg_0) { |
+ InstructionOperand* outputs[1]{DefineSameAsFirst(output_vreg)}; |
+ InstructionOperand* inputs[2]{UseRegister(input_vreg_0), Immediate()}; |
+ return Emit(kArchNop, 1, outputs, 2, inputs); |
+ } |
+ |
+ Instruction* EmitFRU(int output_vreg, int input_vreg_0, int input_vreg_1) { |
+ InstructionOperand* outputs[1]{DefineSameAsFirst(output_vreg)}; |
+ InstructionOperand* inputs[2]{UseRegister(input_vreg_0), Use(input_vreg_1)}; |
+ return Emit(kArchNop, 1, outputs, 2, inputs); |
+ } |
+ |
+ Instruction* EmitRRR(int output_vreg, int input_vreg_0, int input_vreg_1) { |
+ InstructionOperand* outputs[1]{UseRegister(output_vreg)}; |
+ InstructionOperand* inputs[2]{UseRegister(input_vreg_0), |
+ UseRegister(input_vreg_1)}; |
+ return Emit(kArchNop, 1, outputs, 2, inputs); |
} |
private: |
- InstructionOperand* Unallocated(UnallocatedOperand::ExtendedPolicy policy, |
- int vreg) { |
- UnallocatedOperand* op = |
- new (zone()) UnallocatedOperand(UnallocatedOperand::MUST_HAVE_REGISTER); |
+ InstructionOperand* Unallocated(int vreg, |
+ UnallocatedOperand::ExtendedPolicy policy) { |
+ UnallocatedOperand* op = new (zone()) UnallocatedOperand(policy); |
op->set_virtual_register(vreg); |
return op; |
} |
- int num_general_registers_; |
- int num_double_registers_; |
+ InstructionOperand* Unallocated(int vreg, |
+ UnallocatedOperand::ExtendedPolicy policy, |
+ UnallocatedOperand::Lifetime lifetime) { |
+ UnallocatedOperand* op = new (zone()) UnallocatedOperand(policy, lifetime); |
+ op->set_virtual_register(vreg); |
+ return op; |
+ } |
+ |
+ InstructionOperand* UseRegister(int vreg) { |
+ return Unallocated(vreg, UnallocatedOperand::MUST_HAVE_REGISTER); |
+ } |
+ |
+ InstructionOperand* DefineSameAsFirst(int vreg) { |
+ return Unallocated(vreg, UnallocatedOperand::SAME_AS_FIRST_INPUT); |
+ } |
+ |
+ InstructionOperand* Use(int vreg) { |
+ return Unallocated(vreg, UnallocatedOperand::NONE, |
+ UnallocatedOperand::USED_AT_START); |
+ } |
+ |
+ void EmitBranch(int vreg) { |
+ InstructionOperand* inputs[4]{UseRegister(vreg), Immediate(), Immediate(), |
+ Immediate()}; |
+ InstructionCode opcode = kArchJmp | FlagsModeField::encode(kFlags_branch) | |
+ FlagsConditionField::encode(kEqual); |
+ Instruction* instruction = |
+ NewInstruction(opcode, 0, nullptr, 4, inputs)->MarkAsControl(); |
+ sequence()->AddInstruction(instruction); |
+ } |
+ |
+ void EmitFallThrough() { |
+ Instruction* instruction = |
+ NewInstruction(kArchNop, 0, nullptr)->MarkAsControl(); |
+ sequence()->AddInstruction(instruction); |
+ } |
+ |
+ void EmitJump() { |
+ InstructionOperand* inputs[1]{Immediate()}; |
+ Instruction* instruction = |
+ NewInstruction(kArchJmp, 0, nullptr, 1, inputs)->MarkAsControl(); |
+ sequence()->AddInstruction(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); |
+ } |
+ |
+ Instruction* Emit(InstructionCode code, size_t outputs_size, |
+ InstructionOperand** outputs, size_t inputs_size = 0, |
+ InstructionOperand* *inputs = nullptr, |
+ size_t temps_size = 0, |
+ InstructionOperand* *temps = nullptr) { |
+ Instruction* instruction = NewInstruction( |
+ code, outputs_size, outputs, inputs_size, inputs, temps_size, temps); |
+ sequence()->AddInstruction(instruction); |
+ return instruction; |
+ } |
+ |
+ InstructionBlock* NewBlock() { |
+ CHECK(current_block_ == nullptr); |
+ BasicBlock::Id 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. |
+ InstructionBlock* 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; |
+ size_t size = instruction_blocks_.size(); |
+ for (const auto& completion : completions_) { |
+ switch (completion.type_) { |
+ case kFallThrough: |
+ if (offset == size - 1) break; |
+ // 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()); |
+ InstructionBlock* block = instruction_blocks_[block_offset]; |
+ InstructionBlock* target = instruction_blocks_[target_block_offset]; |
+ block->successors().push_back(target->rpo_number()); |
+ target->predecessors().push_back(block->rpo_number()); |
+ } |
+ |
+ struct LoopData { |
+ Rpo loop_header_; |
+ int expected_blocks_; |
+ }; |
+ typedef std::vector<LoopData> LoopBlocks; |
+ typedef std::vector<BlockCompletion> Completions; |
+ |
SmartPointer<RegisterConfiguration> config_; |
- ZoneVector<BasicBlock*> basic_blocks_; |
- InstructionBlocks instruction_blocks_; |
- InstructionBlock* current_block_; |
SmartPointer<Frame> frame_; |
SmartPointer<RegisterAllocator> allocator_; |
SmartPointer<InstructionSequence> sequence_; |
+ int num_general_registers_; |
+ int num_double_registers_; |
+ |
+ // Block building state. |
+ InstructionBlocks instruction_blocks_; |
+ Completions completions_; |
+ LoopBlocks loop_blocks_; |
+ InstructionBlock* current_block_; |
+ bool is_last_block_; |
}; |
TEST_F(RegisterAllocatorTest, CanAllocateThreeRegisters) { |
- StartBlock(); |
+ StartLastBlock(); |
int a_reg = Parameter(); |
int b_reg = Parameter(); |
int c_reg = NewReg(); |
- Instruction* res = Emit(c_reg, a_reg, b_reg); |
+ Instruction* res = EmitRRR(c_reg, a_reg, b_reg); |
Return(c_reg); |
EndBlock(); |
@@ -194,6 +410,49 @@ TEST_F(RegisterAllocatorTest, CanAllocateThreeRegisters) { |
ASSERT_TRUE(res->OutputAt(0)->IsRegister()); |
} |
+ |
+TEST_F(RegisterAllocatorTest, SimpleLoop) { |
+ // i = K; |
+ // while(true) { i++ } |
+ |
+ StartBlock(); |
+ int i_reg = DefineConstant(); |
+ EndBlock(); |
+ |
+ { |
+ StartLoop(1); |
+ |
+ StartLastBlock(); |
+ PhiInstruction* phi = Phi(i_reg); |
+ int ipp = NewReg(); |
+ EmitFRU(ipp, phi->virtual_register(), DefineConstant()); |
+ phi->operands().push_back(ipp); |
+ EndBlock(Jump(0)); |
+ |
+ EndLoop(); |
+ } |
+ |
+ Allocate(); |
+} |
+ |
+ |
+TEST_F(RegisterAllocatorTest, SimpleBranch) { |
+ // return i ? K1 : K2 |
+ StartBlock(); |
+ int i_reg = DefineConstant(); |
+ EndBlock(Branch(i_reg, 1, 2)); |
+ |
+ StartBlock(); |
+ Return(DefineConstant()); |
+ EndBlock(); |
+ |
+ StartLastBlock(); |
+ Return(DefineConstant()); |
+ EndBlock(); |
+ |
+ Allocate(); |
+} |
+ |
} // namespace compiler |
} // namespace internal |
} // namespace v8 |