Unit test of instruction selection for calls with deoptimization.
authorjarin@chromium.org <jarin@chromium.org>
Wed, 27 Aug 2014 15:56:11 +0000 (15:56 +0000)
committerjarin@chromium.org <jarin@chromium.org>
Wed, 27 Aug 2014 15:56:11 +0000 (15:56 +0000)
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

13 files changed:
src/compiler/arm/code-generator-arm.cc
src/compiler/arm64/code-generator-arm64.cc
src/compiler/code-generator.cc
src/compiler/code-generator.h
src/compiler/ia32/code-generator-ia32.cc
src/compiler/instruction-selector.cc
src/compiler/raw-machine-assembler.cc
src/compiler/raw-machine-assembler.h
src/compiler/x64/code-generator-x64.cc
test/cctest/compiler/test-codegen-deopt.cc
test/compiler-unittests/arm/instruction-selector-arm-unittest.cc
test/compiler-unittests/instruction-selector-unittest.cc
test/compiler-unittests/instruction-selector-unittest.h

index 4ae67e3..b11cdbd 100644 (file)
@@ -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);
index 51d377f..f73e014 100644 (file)
@@ -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);
index 243ef02..f1a5c01 100644 (file)
@@ -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<Object> 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;
 }
 
 
index f06bb65..469c435 100644 (file)
@@ -87,8 +87,7 @@ class CodeGenerator V8_FINAL : public GapResolver::Assembler {
                                      Safepoint::Id safepoint_id);
   void PopulateDeoptimizationData(Handle<Code> code);
   int DefineDeoptimizationLiteral(Handle<Object> 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();
index b84e7ec..489c77f 100644 (file)
@@ -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);
index 3e3943f..733624e 100644 (file)
@@ -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);
 }
 
 
index 2fa443f..8d19015 100644 (file)
@@ -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<CodeStub*>(&stub)->MajorKey());
+  stub.InitializeInterfaceDescriptor(d);
+
+  CallDescriptor* desc = Linkage::GetStubCallDescriptor(
+      d, 1, static_cast<CallDescriptor::DeoptimizationSupport>(
+                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());
index 4ada99d..f7cc8a3 100644 (file)
@@ -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);
index 1d86693..f518016 100644 (file)
@@ -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);
index fd2463e..4cc0fde 100644 (file)
@@ -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());
 }
 
index f3fa9c5..7929992 100644 (file)
@@ -1895,7 +1895,6 @@ TEST_F(InstructionSelectorTest, Word32AndWithWord32ShrWithImmediateForARMv7) {
     }
   }
 }
-
 }  // namespace compiler
 }  // namespace internal
 }  // namespace v8
index e53be7e..7641d3a 100644 (file)
@@ -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
index 127cc1d..80e5344 100644 (file)
@@ -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<int>(deoptimization_entries_.size());
+    }
+
    private:
     Constant ToConstant(const InstructionOperand* operand) const {
       ConstantMap::const_iterator i;
@@ -170,6 +183,7 @@ class InstructionSelectorTest : public CompilerTest {
     std::deque<Instruction*> instructions_;
     std::set<int> doubles_;
     std::set<int> references_;
+    std::deque<FrameStateDescriptor*> deoptimization_entries_;
   };
 
   base::RandomNumberGenerator rng_;