From 8eb5c1524d1121e95ae994051be950b17da2b63c Mon Sep 17 00:00:00 2001 From: "jarin@chromium.org" Date: Wed, 27 Aug 2014 15:56:11 +0000 Subject: [PATCH] Unit test of instruction selection for calls with deoptimization. BUG= R=bmeurer@chromium.org Review URL: https://codereview.chromium.org/505133002 git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@23467 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- src/compiler/arm/code-generator-arm.cc | 4 +- src/compiler/arm64/code-generator-arm64.cc | 4 +- src/compiler/code-generator.cc | 23 +-- src/compiler/code-generator.h | 3 +- src/compiler/ia32/code-generator-ia32.cc | 3 +- src/compiler/instruction-selector.cc | 12 +- src/compiler/raw-machine-assembler.cc | 25 +++ src/compiler/raw-machine-assembler.h | 4 + src/compiler/x64/code-generator-x64.cc | 4 +- test/cctest/compiler/test-codegen-deopt.cc | 1 - .../arm/instruction-selector-arm-unittest.cc | 1 - .../instruction-selector-unittest.cc | 159 ++++++++++++++++++ .../instruction-selector-unittest.h | 16 +- 13 files changed, 231 insertions(+), 28 deletions(-) diff --git a/src/compiler/arm/code-generator-arm.cc b/src/compiler/arm/code-generator-arm.cc index 4ae67e3ff0..b11cdbd92c 100644 --- a/src/compiler/arm/code-generator-arm.cc +++ b/src/compiler/arm/code-generator-arm.cc @@ -166,8 +166,8 @@ void CodeGenerator::AssembleArchInstruction(Instruction* instr) { break; } case kArchDeoptimize: { - int deoptimization_id = MiscField::decode(instr->opcode()); - BuildTranslation(instr, 0, deoptimization_id); + int deoptimization_id = BuildTranslation(instr, 0); + Address deopt_entry = Deoptimizer::GetDeoptimizationEntry( isolate(), deoptimization_id, Deoptimizer::LAZY); __ Call(deopt_entry, RelocInfo::RUNTIME_ENTRY); diff --git a/src/compiler/arm64/code-generator-arm64.cc b/src/compiler/arm64/code-generator-arm64.cc index 51d377f89f..f73e0146b2 100644 --- a/src/compiler/arm64/code-generator-arm64.cc +++ b/src/compiler/arm64/code-generator-arm64.cc @@ -160,8 +160,8 @@ void CodeGenerator::AssembleArchInstruction(Instruction* instr) { break; } case kArchDeoptimize: { - int deoptimization_id = MiscField::decode(instr->opcode()); - BuildTranslation(instr, 0, deoptimization_id); + int deoptimization_id = BuildTranslation(instr, 0); + Address deopt_entry = Deoptimizer::GetDeoptimizationEntry( isolate(), deoptimization_id, Deoptimizer::LAZY); __ Call(deopt_entry, RelocInfo::RUNTIME_ENTRY); diff --git a/src/compiler/code-generator.cc b/src/compiler/code-generator.cc index 243ef02fa7..f1a5c01fed 100644 --- a/src/compiler/code-generator.cc +++ b/src/compiler/code-generator.cc @@ -258,10 +258,9 @@ void CodeGenerator::AddSafepointAndDeopt(Instruction* instr) { // If the frame state is present, it starts at argument 1 // (just after the code address). InstructionOperandConverter converter(this, instr); - // Argument 1 is deoptimization id. - int deoptimization_id = converter.ToConstant(instr->InputAt(1)).ToInt32(); - // The actual frame state values start with argument 2. - int first_state_value_offset = 2; + // Deoptimization info starts at argument 1 + int frame_state_offset = 1; + int deoptimization_id = BuildTranslation(instr, frame_state_offset); #if DEBUG // Make sure all the values live in stack slots or they are immediates. // (The values should not live in register because registers are clobbered @@ -269,11 +268,10 @@ void CodeGenerator::AddSafepointAndDeopt(Instruction* instr) { FrameStateDescriptor* descriptor = code()->GetDeoptimizationEntry(deoptimization_id); for (int i = 0; i < descriptor->size(); i++) { - InstructionOperand* op = instr->InputAt(first_state_value_offset + i); + InstructionOperand* op = instr->InputAt(frame_state_offset + 1 + i); CHECK(op->IsStackSlot() || op->IsImmediate()); } #endif - BuildTranslation(instr, first_state_value_offset, deoptimization_id); safepoints()->RecordLazyDeoptimizationIndex(deoptimization_id); } } @@ -310,9 +308,12 @@ int CodeGenerator::DefineDeoptimizationLiteral(Handle literal) { } -void CodeGenerator::BuildTranslation(Instruction* instr, - int first_argument_index, - int deoptimization_id) { +int CodeGenerator::BuildTranslation(Instruction* instr, + int frame_state_offset) { + InstructionOperandConverter i(this, instr); + int deoptimization_id = i.InputInt32(frame_state_offset); + frame_state_offset++; + // We should build translation only once. DCHECK_EQ(NULL, deoptimization_states_[deoptimization_id]); @@ -325,11 +326,13 @@ void CodeGenerator::BuildTranslation(Instruction* instr, for (int i = 0; i < descriptor->size(); i++) { AddTranslationForOperand(&translation, instr, - instr->InputAt(i + first_argument_index)); + instr->InputAt(i + frame_state_offset)); } deoptimization_states_[deoptimization_id] = new (zone()) DeoptimizationState(translation.index()); + + return deoptimization_id; } diff --git a/src/compiler/code-generator.h b/src/compiler/code-generator.h index f06bb65eba..469c435f8c 100644 --- a/src/compiler/code-generator.h +++ b/src/compiler/code-generator.h @@ -87,8 +87,7 @@ class CodeGenerator V8_FINAL : public GapResolver::Assembler { Safepoint::Id safepoint_id); void PopulateDeoptimizationData(Handle code); int DefineDeoptimizationLiteral(Handle literal); - void BuildTranslation(Instruction* instr, int first_argument_index, - int deoptimization_id); + int BuildTranslation(Instruction* instr, int frame_state_offset); void AddTranslationForOperand(Translation* translation, Instruction* instr, InstructionOperand* op); void AddNopForSmiCodeInlining(); diff --git a/src/compiler/ia32/code-generator-ia32.cc b/src/compiler/ia32/code-generator-ia32.cc index b84e7ec64c..489c77f5bf 100644 --- a/src/compiler/ia32/code-generator-ia32.cc +++ b/src/compiler/ia32/code-generator-ia32.cc @@ -141,8 +141,7 @@ void CodeGenerator::AssembleArchInstruction(Instruction* instr) { break; } case kArchDeoptimize: { - int deoptimization_id = MiscField::decode(instr->opcode()); - BuildTranslation(instr, 0, deoptimization_id); + int deoptimization_id = BuildTranslation(instr, 0); Address deopt_entry = Deoptimizer::GetDeoptimizationEntry( isolate(), deoptimization_id, Deoptimizer::LAZY); diff --git a/src/compiler/instruction-selector.cc b/src/compiler/instruction-selector.cc index 3e3943f372..733624e866 100644 --- a/src/compiler/instruction-selector.cc +++ b/src/compiler/instruction-selector.cc @@ -1082,17 +1082,19 @@ void InstructionSelector::VisitDeoptimize(Node* deopt) { DCHECK(deopt->op()->opcode() == IrOpcode::kDeoptimize); Node* state = deopt->InputAt(0); FrameStateDescriptor* descriptor = GetFrameStateDescriptor(state); + int deoptimization_id = sequence()->AddDeoptimizationEntry(descriptor); InstructionOperandVector inputs(zone()); - inputs.reserve(descriptor->size()); + inputs.reserve(descriptor->size() + 1); + + OperandGenerator g(this); + inputs.push_back(g.TempImmediate(deoptimization_id)); AddFrameStateInputs(state, &inputs, descriptor); - DCHECK_EQ(descriptor->size(), inputs.size()); + DCHECK_EQ(descriptor->size() + 1, inputs.size()); - int deoptimization_id = sequence()->AddDeoptimizationEntry(descriptor); - Emit(kArchDeoptimize | MiscField::encode(deoptimization_id), 0, NULL, - inputs.size(), &inputs.front(), 0, NULL); + Emit(kArchDeoptimize, 0, NULL, inputs.size(), &inputs.front(), 0, NULL); } diff --git a/src/compiler/raw-machine-assembler.cc b/src/compiler/raw-machine-assembler.cc index 2fa443f2b2..8d19015c4b 100644 --- a/src/compiler/raw-machine-assembler.cc +++ b/src/compiler/raw-machine-assembler.cc @@ -83,6 +83,31 @@ void RawMachineAssembler::Deoptimize(Node* state) { } +Node* RawMachineAssembler::CallFunctionStub0(Node* function, Node* receiver, + Node* context, Node* frame_state, + Label* continuation, + Label* deoptimization, + CallFunctionFlags flags) { + CallFunctionStub stub(isolate(), 0, flags); + CodeStubInterfaceDescriptor* d = isolate()->code_stub_interface_descriptor( + reinterpret_cast(&stub)->MajorKey()); + stub.InitializeInterfaceDescriptor(d); + + CallDescriptor* desc = Linkage::GetStubCallDescriptor( + d, 1, static_cast( + CallDescriptor::kLazyDeoptimization | + CallDescriptor::kNeedsFrameState), + zone()); + Node* stub_code = HeapConstant(stub.GetCode()); + Node* call = graph()->NewNode(common()->Call(desc), stub_code, function, + receiver, context, frame_state); + schedule()->AddCall(CurrentBlock(), call, Use(continuation), + Use(deoptimization)); + current_block_ = NULL; + return call; +} + + Node* RawMachineAssembler::CallJS0(Node* function, Node* receiver, Label* continuation, Label* deoptimization) { CallDescriptor* descriptor = Linkage::GetJSCallDescriptor(1, zone()); diff --git a/src/compiler/raw-machine-assembler.h b/src/compiler/raw-machine-assembler.h index 4ada99df3e..f7cc8a311d 100644 --- a/src/compiler/raw-machine-assembler.h +++ b/src/compiler/raw-machine-assembler.h @@ -71,6 +71,10 @@ class RawMachineAssembler : public GraphBuilder, Label* Exit(); void Goto(Label* label); void Branch(Node* condition, Label* true_val, Label* false_val); + // Call through CallFunctionStub with lazy deopt and frame-state. + Node* CallFunctionStub0(Node* function, Node* receiver, Node* context, + Node* frame_state, Label* continuation, + Label* deoptimization, CallFunctionFlags flags); // Call to a JS function with zero parameters. Node* CallJS0(Node* function, Node* receiver, Label* continuation, Label* deoptimization); diff --git a/src/compiler/x64/code-generator-x64.cc b/src/compiler/x64/code-generator-x64.cc index 1d86693620..f51801639f 100644 --- a/src/compiler/x64/code-generator-x64.cc +++ b/src/compiler/x64/code-generator-x64.cc @@ -235,8 +235,8 @@ void CodeGenerator::AssembleArchInstruction(Instruction* instr) { break; } case kArchDeoptimize: { - int deoptimization_id = MiscField::decode(instr->opcode()); - BuildTranslation(instr, 0, deoptimization_id); + int deoptimization_id = BuildTranslation(instr, 0); + Address deopt_entry = Deoptimizer::GetDeoptimizationEntry( isolate(), deoptimization_id, Deoptimizer::LAZY); __ call(deopt_entry, RelocInfo::RUNTIME_ENTRY); diff --git a/test/cctest/compiler/test-codegen-deopt.cc b/test/cctest/compiler/test-codegen-deopt.cc index fd2463e664..4cc0fde17d 100644 --- a/test/cctest/compiler/test-codegen-deopt.cc +++ b/test/cctest/compiler/test-codegen-deopt.cc @@ -210,7 +210,6 @@ TEST(TurboTrivialDeoptCodegen) { // Check that we deoptimize to the right AST id. CHECK_EQ(1, data->DeoptCount()); - CHECK_EQ(1, data->DeoptCount()); CHECK_EQ(t.bailout_id.ToInt(), data->AstId(0).ToInt()); } diff --git a/test/compiler-unittests/arm/instruction-selector-arm-unittest.cc b/test/compiler-unittests/arm/instruction-selector-arm-unittest.cc index f3fa9c5e15..7929992a9d 100644 --- a/test/compiler-unittests/arm/instruction-selector-arm-unittest.cc +++ b/test/compiler-unittests/arm/instruction-selector-arm-unittest.cc @@ -1895,7 +1895,6 @@ TEST_F(InstructionSelectorTest, Word32AndWithWord32ShrWithImmediateForARMv7) { } } } - } // namespace compiler } // namespace internal } // namespace v8 diff --git a/test/compiler-unittests/instruction-selector-unittest.cc b/test/compiler-unittests/instruction-selector-unittest.cc index e53be7ede1..7641d3a247 100644 --- a/test/compiler-unittests/instruction-selector-unittest.cc +++ b/test/compiler-unittests/instruction-selector-unittest.cc @@ -57,6 +57,9 @@ InstructionSelectorTest::Stream InstructionSelectorTest::StreamBuilder::Build( continue; } } + if (mode == kAllExceptNopInstructions && instr->arch_opcode() == kArchNop) { + continue; + } for (size_t i = 0; i < instr->OutputCount(); ++i) { InstructionOperand* output = instr->OutputAt(i); EXPECT_NE(InstructionOperand::IMMEDIATE, output->kind()); @@ -94,6 +97,9 @@ InstructionSelectorTest::Stream InstructionSelectorTest::StreamBuilder::Build( s.references_.insert(virtual_register); } } + for (int i = 0; i < sequence.GetDeoptimizationEntryCount(); i++) { + s.deoptimization_entries_.push_back(sequence.GetDeoptimizationEntry(i)); + } return s; } @@ -307,6 +313,159 @@ TARGET_TEST_F(InstructionSelectorTest, ValueEffect) { } } + +// ----------------------------------------------------------------------------- +// Calls with deoptimization. +TEST_F(InstructionSelectorTest, CallJSFunctionWithDeopt) { + StreamBuilder m(this, kMachAnyTagged, kMachAnyTagged, kMachAnyTagged); + + BailoutId bailout_id(42); + + Node* function_node = m.Parameter(0); + Node* receiver = m.Parameter(1); + StreamBuilder::Label deopt, cont; + + // TODO(jarin) Add frame state. + Node* call = m.CallJS0(function_node, receiver, &cont, &deopt); + + m.Bind(&cont); + m.NewNode(m.common()->Continuation(), call); + m.Return(call); + + m.Bind(&deopt); + m.NewNode(m.common()->LazyDeoptimization(), call); + + Node* parameters = m.NewNode(m.common()->StateValues(1), m.Int32Constant(1)); + Node* locals = m.NewNode(m.common()->StateValues(0)); + Node* stack = m.NewNode(m.common()->StateValues(0)); + + Node* state_node = + m.NewNode(m.common()->FrameState(bailout_id), parameters, locals, stack); + m.Deoptimize(state_node); + + Stream s = m.Build(kAllExceptNopInstructions); + + // Skip until kArchCallJSFunction. + size_t index = 0; + for (; index < s.size() && s[index]->arch_opcode() != kArchCallJSFunction; + index++) { + } + // Now we should have three instructions: call, return and deoptimize. + ASSERT_EQ(index + 3, s.size()); + + EXPECT_EQ(kArchCallJSFunction, s[index++]->arch_opcode()); + EXPECT_EQ(kArchRet, s[index++]->arch_opcode()); + EXPECT_EQ(kArchDeoptimize, s[index++]->arch_opcode()); + EXPECT_EQ(index, s.size()); +} + + +TEST_F(InstructionSelectorTest, CallFunctionStubWithDeopt) { + StreamBuilder m(this, kMachAnyTagged, kMachAnyTagged, kMachAnyTagged, + kMachAnyTagged); + + BailoutId bailout_id_before(42); + BailoutId bailout_id_after(54); + + // Some arguments for the call node. + Node* function_node = m.Parameter(0); + Node* receiver = m.Parameter(1); + Node* context = m.Int32Constant(1); // Context is ignored. + + // Build frame state for the state before the call. + Node* parameters = m.NewNode(m.common()->StateValues(1), m.Int32Constant(43)); + Node* locals = m.NewNode(m.common()->StateValues(1), m.Int32Constant(44)); + Node* stack = m.NewNode(m.common()->StateValues(1), m.Int32Constant(45)); + Node* frame_state_before = m.NewNode( + m.common()->FrameState(bailout_id_before), parameters, locals, stack); + + StreamBuilder::Label deopt, cont; + // Build the call. + Node* call = + m.CallFunctionStub0(function_node, receiver, context, frame_state_before, + &cont, &deopt, CALL_AS_METHOD); + + // Create the continuation branch. + m.Bind(&cont); + m.NewNode(m.common()->Continuation(), call); + m.Return(call); + + // Create the lazy deoptimization block (with a different frame state). + m.Bind(&deopt); + m.NewNode(m.common()->LazyDeoptimization(), call); + + Node* stack_after = + m.NewNode(m.common()->StateValues(2), m.Int32Constant(55), call); + + Node* frame_state_after = m.NewNode(m.common()->FrameState(bailout_id_after), + parameters, locals, stack_after); + m.Deoptimize(frame_state_after); + + Stream s = m.Build(kAllExceptNopInstructions); + + // Skip until kArchCallJSFunction. + size_t index = 0; + for (; index < s.size() && s[index]->arch_opcode() != kArchCallCodeObject; + index++) { + } + // Now we should have three instructions: call, return and deoptimize. + ASSERT_EQ(index + 3, s.size()); + + // Check the call instruction + const Instruction* call_instr = s[index++]; + EXPECT_EQ(kArchCallCodeObject, call_instr->arch_opcode()); + size_t num_operands = + 1 + // Code object. + 1 + + 3 + // Frame state deopt id + one input for each value in frame state. + 1 + // Function. + 1 + // Context. + 2; // Continuation and deoptimization block labels. + ASSERT_EQ(num_operands, call_instr->InputCount()); + + // Code object. + EXPECT_TRUE(call_instr->InputAt(0)->IsImmediate()); + + // Deoptimization id. + int32_t deopt_id_before = s.ToInt32(call_instr->InputAt(1)); + FrameStateDescriptor* desc_before = s.GetDeoptimizationEntry(deopt_id_before); + EXPECT_EQ(bailout_id_before, desc_before->bailout_id()); + EXPECT_EQ(1, desc_before->parameters_count()); + EXPECT_EQ(1, desc_before->locals_count()); + EXPECT_EQ(1, desc_before->stack_count()); + EXPECT_EQ(43, s.ToInt32(call_instr->InputAt(2))); + EXPECT_EQ(44, s.ToInt32(call_instr->InputAt(3))); + EXPECT_EQ(45, s.ToInt32(call_instr->InputAt(4))); + + // Function. + EXPECT_EQ(function_node->id(), s.ToVreg(call_instr->InputAt(5))); + // Context. + EXPECT_EQ(context->id(), s.ToVreg(call_instr->InputAt(6))); + // Continuation. + EXPECT_EQ(cont.block()->id(), s.ToInt32(call_instr->InputAt(7))); + // Deoptimization. + EXPECT_EQ(deopt.block()->id(), s.ToInt32(call_instr->InputAt(8))); + + EXPECT_EQ(kArchRet, s[index++]->arch_opcode()); + + // Check the deoptimize instruction. + const Instruction* deopt_instr = s[index++]; + EXPECT_EQ(kArchDeoptimize, deopt_instr->arch_opcode()); + ASSERT_EQ(5U, deopt_instr->InputCount()); + int32_t deopt_id_after = s.ToInt32(deopt_instr->InputAt(0)); + FrameStateDescriptor* desc_after = s.GetDeoptimizationEntry(deopt_id_after); + EXPECT_EQ(bailout_id_after, desc_after->bailout_id()); + EXPECT_EQ(1, desc_after->parameters_count()); + EXPECT_EQ(1, desc_after->locals_count()); + EXPECT_EQ(2, desc_after->stack_count()); + // Parameter value from the frame state. + EXPECT_EQ(43, s.ToInt32(deopt_instr->InputAt(1))); + EXPECT_EQ(44, s.ToInt32(deopt_instr->InputAt(2))); + EXPECT_EQ(55, s.ToInt32(deopt_instr->InputAt(3))); + EXPECT_EQ(call->id(), s.ToVreg(deopt_instr->InputAt(4))); + EXPECT_EQ(index, s.size()); +} + } // namespace compiler } // namespace internal } // namespace v8 diff --git a/test/compiler-unittests/instruction-selector-unittest.h b/test/compiler-unittests/instruction-selector-unittest.h index 127cc1d479..80e5344dea 100644 --- a/test/compiler-unittests/instruction-selector-unittest.h +++ b/test/compiler-unittests/instruction-selector-unittest.h @@ -26,7 +26,11 @@ class InstructionSelectorTest : public CompilerTest { class Stream; - enum StreamBuilderMode { kAllInstructions, kTargetInstructions }; + enum StreamBuilderMode { + kAllInstructions, + kTargetInstructions, + kAllExceptNopInstructions + }; class StreamBuilder V8_FINAL : public RawMachineAssembler { public: @@ -146,6 +150,15 @@ class InstructionSelectorTest : public CompilerTest { return UnallocatedOperand::cast(operand)->virtual_register(); } + FrameStateDescriptor* GetDeoptimizationEntry(int deoptimization_id) { + EXPECT_LT(deoptimization_id, GetDeoptimizationEntryCount()); + return deoptimization_entries_[deoptimization_id]; + } + + int GetDeoptimizationEntryCount() { + return static_cast(deoptimization_entries_.size()); + } + private: Constant ToConstant(const InstructionOperand* operand) const { ConstantMap::const_iterator i; @@ -170,6 +183,7 @@ class InstructionSelectorTest : public CompilerTest { std::deque instructions_; std::set doubles_; std::set references_; + std::deque deoptimization_entries_; }; base::RandomNumberGenerator rng_;