Index: test/unittests/compiler/instruction-sequence-unittest.cc |
diff --git a/test/unittests/compiler/instruction-sequence-unittest.cc b/test/unittests/compiler/instruction-sequence-unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..05d3d0ae14a497f9bcbb3706a8d5b45909488a82 |
--- /dev/null |
+++ b/test/unittests/compiler/instruction-sequence-unittest.cc |
@@ -0,0 +1,452 @@ |
+// Copyright 2014 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/base/utils/random-number-generator.h" |
+#include "src/compiler/pipeline.h" |
+#include "test/unittests/compiler/instruction-sequence-unittest.h" |
+#include "test/unittests/test-utils.h" |
+#include "testing/gmock/include/gmock/gmock.h" |
+ |
+namespace v8 { |
+namespace internal { |
+namespace compiler { |
+ |
+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; |
+ } |
+} |
+ |
+ |
+InstructionSequenceTest::InstructionSequenceTest() |
+ : 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 InstructionSequenceTest::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* InstructionSequenceTest::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(); |
+} |
+ |
+ |
+InstructionSequence* InstructionSequenceTest::sequence() { |
+ if (sequence_ == nullptr) { |
+ sequence_ = new (zone()) InstructionSequence(zone(), &instruction_blocks_); |
+ } |
+ return sequence_; |
+} |
+ |
+ |
+void InstructionSequenceTest::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 InstructionSequenceTest::EndLoop() { |
+ CHECK(current_block_ == nullptr); |
+ CHECK(!loop_blocks_.empty()); |
+ CHECK_EQ(0, loop_blocks_.back().expected_blocks_); |
+ loop_blocks_.pop_back(); |
+} |
+ |
+ |
+void InstructionSequenceTest::StartBlock() { |
+ block_returns_ = false; |
+ NewBlock(); |
+} |
+ |
+ |
+int InstructionSequenceTest::EndBlock(BlockCompletion completion) { |
+ 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; |
+} |
+ |
+ |
+InstructionSequenceTest::TestOperand InstructionSequenceTest::Imm(int32_t imm) { |
+ int index = sequence()->AddImmediate(Constant(imm)); |
+ return TestOperand(kImmediate, index); |
+} |
+ |
+ |
+InstructionSequenceTest::VReg InstructionSequenceTest::Define( |
+ TestOperand output_op) { |
+ VReg vreg = NewReg(); |
+ InstructionOperand* outputs[1]{ConvertOutputOp(vreg, output_op)}; |
+ Emit(vreg.value_, kArchNop, 1, outputs); |
+ return vreg; |
+} |
+ |
+ |
+int InstructionSequenceTest::Return(TestOperand input_op_0) { |
+ block_returns_ = true; |
+ InstructionOperand* inputs[1]{ConvertInputOp(input_op_0)}; |
+ return Emit(NewIndex(), kArchRet, 0, nullptr, 1, inputs); |
+} |
+ |
+ |
+PhiInstruction* InstructionSequenceTest::Phi(VReg incoming_vreg_0, |
+ VReg incoming_vreg_1, |
+ VReg incoming_vreg_2, |
+ VReg incoming_vreg_3) { |
+ auto phi = new (zone()) PhiInstruction(zone(), NewReg().value_, 10); |
+ VReg inputs[] = {incoming_vreg_0, incoming_vreg_1, incoming_vreg_2, |
+ incoming_vreg_3}; |
+ for (size_t i = 0; i < arraysize(inputs); ++i) { |
+ if (inputs[i].value_ == kNoValue) break; |
+ Extend(phi, inputs[i]); |
+ } |
+ current_block_->AddPhi(phi); |
+ return phi; |
+} |
+ |
+ |
+void InstructionSequenceTest::Extend(PhiInstruction* phi, VReg vreg) { |
+ phi->Extend(zone(), vreg.value_); |
+} |
+ |
+ |
+InstructionSequenceTest::VReg InstructionSequenceTest::DefineConstant( |
+ int32_t imm) { |
+ 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; |
+} |
+ |
+ |
+int InstructionSequenceTest::EmitNop() { return Emit(NewIndex(), kArchNop); } |
+ |
+ |
+int InstructionSequenceTest::EmitI(TestOperand input_op_0) { |
+ InstructionOperand* inputs[1]{ConvertInputOp(input_op_0)}; |
+ return Emit(NewIndex(), kArchNop, 0, nullptr, 1, inputs); |
+} |
+ |
+ |
+InstructionSequenceTest::VReg InstructionSequenceTest::EmitOI( |
+ TestOperand output_op, TestOperand input_op_0) { |
+ VReg output_vreg = NewReg(); |
+ InstructionOperand* outputs[1]{ConvertOutputOp(output_vreg, output_op)}; |
+ InstructionOperand* inputs[1]{ConvertInputOp(input_op_0)}; |
+ Emit(output_vreg.value_, kArchNop, 1, outputs, 1, inputs); |
+ return output_vreg; |
+} |
+ |
+ |
+InstructionSequenceTest::VReg InstructionSequenceTest::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; |
+} |
+ |
+ |
+InstructionSequenceTest::VReg InstructionSequenceTest::EmitCall( |
+ TestOperand output_op, size_t input_size, TestOperand* inputs) { |
+ VReg output_vreg = NewReg(); |
+ InstructionOperand* outputs[1]{ConvertOutputOp(output_vreg, output_op)}; |
+ CHECK(UnallocatedOperand::cast(outputs[0])->HasFixedPolicy()); |
+ 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, 0, nullptr, true); |
+ return output_vreg; |
+} |
+ |
+ |
+InstructionSequenceTest::VReg InstructionSequenceTest::EmitCall( |
+ TestOperand output_op, TestOperand input_op_0, TestOperand input_op_1, |
+ TestOperand input_op_2, TestOperand input_op_3) { |
+ TestOperand inputs[] = {input_op_0, input_op_1, input_op_2, input_op_3}; |
+ size_t size = 0; |
+ for (; size < arraysize(inputs); ++size) { |
+ if (inputs[size].type_ == kInvalid) break; |
+ } |
+ return EmitCall(output_op, size, inputs); |
+} |
+ |
+ |
+const Instruction* InstructionSequenceTest::GetInstruction( |
+ int instruction_index) { |
+ auto it = instructions_.find(instruction_index); |
+ CHECK(it != instructions_.end()); |
+ return it->second; |
+} |
+ |
+ |
+int InstructionSequenceTest::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 InstructionSequenceTest::EmitFallThrough() { |
+ auto instruction = NewInstruction(kArchNop, 0, nullptr)->MarkAsControl(); |
+ return AddInstruction(NewIndex(), instruction); |
+} |
+ |
+ |
+int InstructionSequenceTest::EmitJump() { |
+ InstructionOperand* inputs[1]{ConvertInputOp(Imm())}; |
+ auto instruction = |
+ NewInstruction(kArchJmp, 0, nullptr, 1, inputs)->MarkAsControl(); |
+ return AddInstruction(NewIndex(), instruction); |
+} |
+ |
+ |
+Instruction* InstructionSequenceTest::NewInstruction( |
+ InstructionCode code, size_t outputs_size, InstructionOperand** outputs, |
+ size_t inputs_size, InstructionOperand** inputs, size_t temps_size, |
+ InstructionOperand** temps) { |
+ CHECK_NE(nullptr, current_block_); |
+ return Instruction::New(zone(), code, outputs_size, outputs, inputs_size, |
+ inputs, temps_size, temps); |
+} |
+ |
+ |
+InstructionOperand* InstructionSequenceTest::Unallocated( |
+ TestOperand op, UnallocatedOperand::ExtendedPolicy policy) { |
+ auto unallocated = new (zone()) UnallocatedOperand(policy); |
+ unallocated->set_virtual_register(op.vreg_.value_); |
+ return unallocated; |
+} |
+ |
+ |
+InstructionOperand* InstructionSequenceTest::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* InstructionSequenceTest::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* InstructionSequenceTest::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* InstructionSequenceTest::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_); |
+ case kFixedSlot: |
+ return Unallocated(op, UnallocatedOperand::FIXED_SLOT, op.value_); |
+ default: |
+ break; |
+ } |
+ CHECK(false); |
+ return NULL; |
+} |
+ |
+ |
+InstructionOperand* InstructionSequenceTest::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* InstructionSequenceTest::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 InstructionSequenceTest::WireBlocks() { |
+ CHECK_EQ(nullptr, current_block()); |
+ 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 InstructionSequenceTest::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 InstructionSequenceTest::Emit(int instruction_index, InstructionCode code, |
+ size_t outputs_size, |
+ InstructionOperand** outputs, |
+ size_t inputs_size, |
+ InstructionOperand** inputs, |
+ size_t temps_size, InstructionOperand** temps, |
+ bool is_call) { |
+ auto instruction = NewInstruction(code, outputs_size, outputs, inputs_size, |
+ inputs, temps_size, temps); |
+ if (is_call) instruction->MarkAsCall(); |
+ return AddInstruction(instruction_index, instruction); |
+} |
+ |
+ |
+int InstructionSequenceTest::AddInstruction(int instruction_index, |
+ Instruction* instruction) { |
+ sequence()->AddInstruction(instruction); |
+ return instruction_index; |
+} |
+ |
+} // namespace compiler |
+} // namespace internal |
+} // namespace v8 |