Add support for lazy deoptimization from deferred stack checks
authorsgjesse@chromium.org <sgjesse@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Tue, 5 Jul 2011 13:21:29 +0000 (13:21 +0000)
committersgjesse@chromium.org <sgjesse@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Tue, 5 Jul 2011 13:21:29 +0000 (13:21 +0000)
The debugger can be entered from the deferred stack check in optimized code. This can cause both lazy deoptimization and debugger deoptimization (setting the first break point and inspecting the stack for optimized code respectively). This required deoptimization support from the deferred stack check.

The lazy deoptimiztion call is inserted when the deferred code is done including restoring the registers. The bailout to the full code is the begining of the loop body as that is where the stack check is sitting in the optimized code. The bailout is not to the stack check in the full code as that is sitting at the end of the loop.

R=kmillikin@chromium.org

BUG=none
TEST=none

Review URL: http://codereview.chromium.org//7212025

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@8535 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

src/arm/lithium-codegen-arm.cc
src/ast.h
src/hydrogen.cc
src/hydrogen.h
src/ia32/lithium-codegen-ia32.cc
src/x64/lithium-codegen-x64.cc
test/cctest/test-debug.cc

index 24e2044..a947cb8 100644 (file)
@@ -257,11 +257,28 @@ LInstruction* LCodeGen::GetNextInstruction() {
 
 bool LCodeGen::GenerateDeferredCode() {
   ASSERT(is_generating());
-  for (int i = 0; !is_aborted() && i < deferred_.length(); i++) {
-    LDeferredCode* code = deferred_[i];
-    __ bind(code->entry());
-    code->Generate();
-    __ jmp(code->exit());
+  Label last_jump;
+  if (deferred_.length() > 0) {
+    for (int i = 0; !is_aborted() && i < deferred_.length(); i++) {
+      LDeferredCode* code = deferred_[i];
+      __ bind(code->entry());
+      code->Generate();
+#ifdef DEBUG
+      if (i == deferred_.length() - 1) {
+        __ bind(&last_jump);
+      }
+#endif
+      __ jmp(code->exit());
+    }
+
+    // Reserve some space to ensure that the last piece of deferred code
+    // have room for lazy bailout.
+    __ nop();
+    __ nop();
+
+    int code_generated =
+        masm_->InstructionsGeneratedSince(&last_jump) * Assembler::kInstrSize;
+    ASSERT(Deoptimizer::patch_size() <= code_generated);
   }
 
   // Force constant pool emission at the end of the deferred code to make
@@ -4322,8 +4339,16 @@ void LCodeGen::DoIn(LIn* instr) {
 
 
 void LCodeGen::DoDeferredStackCheck(LStackCheck* instr) {
-  PushSafepointRegistersScope scope(this, Safepoint::kWithRegisters);
-  CallRuntimeFromDeferred(Runtime::kStackGuard, 0, instr);
+  {
+    PushSafepointRegistersScope scope(this, Safepoint::kWithRegisters);
+    __ CallRuntimeSaveDoubles(Runtime::kStackGuard);
+    RegisterLazyDeoptimization(
+        instr, RECORD_SAFEPOINT_WITH_REGISTERS_AND_NO_ARGUMENTS);
+  }
+
+  // The gap code includes the restoring of the safepoint registers.
+  int pc = masm()->pc_offset();
+  safepoints_.SetPcAfterGap(pc);
 }
 
 
index 805a406..27d82cb 100644 (file)
--- a/src/ast.h
+++ b/src/ast.h
@@ -383,6 +383,7 @@ class IterationStatement: public BreakableStatement {
   // Bailout support.
   int OsrEntryId() const { return osr_entry_id_; }
   virtual int ContinueId() const = 0;
+  virtual int StackCheckId() const = 0;
 
   // Code generation
   Label* continue_target()  { return &continue_target_; }
@@ -421,6 +422,7 @@ class DoWhileStatement: public IterationStatement {
 
   // Bailout support.
   virtual int ContinueId() const { return continue_id_; }
+  virtual int StackCheckId() const { return back_edge_id_; }
   int BackEdgeId() const { return back_edge_id_; }
 
   virtual bool IsInlineable() const;
@@ -455,6 +457,7 @@ class WhileStatement: public IterationStatement {
 
   // Bailout support.
   virtual int ContinueId() const { return EntryId(); }
+  virtual int StackCheckId() const { return body_id_; }
   int BodyId() const { return body_id_; }
 
  private:
@@ -494,6 +497,7 @@ class ForStatement: public IterationStatement {
 
   // Bailout support.
   virtual int ContinueId() const { return continue_id_; }
+  virtual int StackCheckId() const { return body_id_; }
   int BodyId() const { return body_id_; }
 
   bool is_fast_smi_loop() { return loop_variable_ != NULL; }
@@ -532,6 +536,7 @@ class ForInStatement: public IterationStatement {
   // Bailout support.
   int AssignmentId() const { return assignment_id_; }
   virtual int ContinueId() const { return EntryId(); }
+  virtual int StackCheckId() const { return EntryId(); }
 
  private:
   Expression* each_;
index 338c12e..887ba73 100644 (file)
@@ -2789,17 +2789,18 @@ void HGraphBuilder::PreProcessOsrEntry(IterationStatement* statement) {
 }
 
 
-void HGraphBuilder::VisitLoopBody(Statement* body,
+void HGraphBuilder::VisitLoopBody(IterationStatement* stmt,
                                   HBasicBlock* loop_entry,
                                   BreakAndContinueInfo* break_info) {
   BreakAndContinueScope push(break_info, this);
+  AddSimulate(stmt->StackCheckId());
   HValue* context = environment()->LookupContext();
   HStackCheck* stack_check =
     new(zone()) HStackCheck(context, HStackCheck::kBackwardsBranch);
   AddInstruction(stack_check);
   ASSERT(loop_entry->IsLoopHeader());
   loop_entry->loop_information()->set_stack_check(stack_check);
-  CHECK_BAILOUT(Visit(body));
+  CHECK_BAILOUT(Visit(stmt->body()));
 }
 
 
@@ -2814,7 +2815,7 @@ void HGraphBuilder::VisitDoWhileStatement(DoWhileStatement* stmt) {
   set_current_block(loop_entry);
 
   BreakAndContinueInfo break_info(stmt);
-  CHECK_BAILOUT(VisitLoopBody(stmt->body(), loop_entry, &break_info));
+  CHECK_BAILOUT(VisitLoopBody(stmt, loop_entry, &break_info));
   HBasicBlock* body_exit =
       JoinContinue(stmt, current_block(), break_info.continue_block());
   HBasicBlock* loop_successor = NULL;
@@ -2875,7 +2876,7 @@ void HGraphBuilder::VisitWhileStatement(WhileStatement* stmt) {
   BreakAndContinueInfo break_info(stmt);
   if (current_block() != NULL) {
     BreakAndContinueScope push(&break_info, this);
-    CHECK_BAILOUT(VisitLoopBody(stmt->body(), loop_entry, &break_info));
+    CHECK_BAILOUT(VisitLoopBody(stmt, loop_entry, &break_info));
   }
   HBasicBlock* body_exit =
       JoinContinue(stmt, current_block(), break_info.continue_block());
@@ -2920,7 +2921,7 @@ void HGraphBuilder::VisitForStatement(ForStatement* stmt) {
   BreakAndContinueInfo break_info(stmt);
   if (current_block() != NULL) {
     BreakAndContinueScope push(&break_info, this);
-    CHECK_BAILOUT(VisitLoopBody(stmt->body(), loop_entry, &break_info));
+    CHECK_BAILOUT(VisitLoopBody(stmt, loop_entry, &break_info));
   }
   HBasicBlock* body_exit =
       JoinContinue(stmt, current_block(), break_info.continue_block());
index c53d7f7..c0d6323 100644 (file)
@@ -788,7 +788,7 @@ class HGraphBuilder: public AstVisitor {
   void PreProcessOsrEntry(IterationStatement* statement);
   // True iff. we are compiling for OSR and the statement is the entry.
   bool HasOsrEntryAt(IterationStatement* statement);
-  void VisitLoopBody(Statement* body,
+  void VisitLoopBody(IterationStatement* stmt,
                      HBasicBlock* loop_entry,
                      BreakAndContinueInfo* break_info);
 
index ec1adb9..ab741b7 100644 (file)
@@ -255,11 +255,28 @@ LInstruction* LCodeGen::GetNextInstruction() {
 
 bool LCodeGen::GenerateDeferredCode() {
   ASSERT(is_generating());
-  for (int i = 0; !is_aborted() && i < deferred_.length(); i++) {
-    LDeferredCode* code = deferred_[i];
-    __ bind(code->entry());
-    code->Generate();
-    __ jmp(code->exit());
+  Label last_jump;
+  if (deferred_.length() > 0) {
+    for (int i = 0; !is_aborted() && i < deferred_.length(); i++) {
+      LDeferredCode* code = deferred_[i];
+      __ bind(code->entry());
+      code->Generate();
+#ifdef DEBUG
+      if (i == deferred_.length() - 1) {
+        __ bind(&last_jump);
+      }
+#endif
+      __ jmp(code->exit());
+    }
+
+    // Reserve some space to ensure that the last piece of deferred code
+    // have room for lazy bailout.
+    __ nop();
+    __ nop();
+    __ nop();
+
+    ASSERT(Deoptimizer::patch_size() <=
+           masm_->SizeOfCodeGeneratedSince(&last_jump));
   }
 
   // Deferred code is the last part of the instruction sequence. Mark
@@ -4156,8 +4173,17 @@ void LCodeGen::DoDeleteProperty(LDeleteProperty* instr) {
 
 
 void LCodeGen::DoDeferredStackCheck(LStackCheck* instr) {
-  PushSafepointRegistersScope scope(this);
-  CallRuntimeFromDeferred(Runtime::kStackGuard, 0, instr, instr->context());
+  {
+    PushSafepointRegistersScope scope(this);
+    __ mov(esi, Operand(ebp, StandardFrameConstants::kContextOffset));
+    __ CallRuntimeSaveDoubles(Runtime::kStackGuard);
+    RegisterLazyDeoptimization(
+        instr, RECORD_SAFEPOINT_WITH_REGISTERS_AND_NO_ARGUMENTS);
+  }
+
+  // The gap code includes the restoring of the safepoint registers.
+  int pc = masm()->pc_offset();
+  safepoints_.SetPcAfterGap(pc);
 }
 
 
index 952174d..d8d7af6 100644 (file)
@@ -275,11 +275,34 @@ bool LCodeGen::GenerateJumpTable() {
 
 bool LCodeGen::GenerateDeferredCode() {
   ASSERT(is_generating());
-  for (int i = 0; !is_aborted() && i < deferred_.length(); i++) {
-    LDeferredCode* code = deferred_[i];
-    __ bind(code->entry());
-    code->Generate();
-    __ jmp(code->exit());
+  Label last_jump;
+  if (deferred_.length() > 0) {
+    for (int i = 0; !is_aborted() && i < deferred_.length(); i++) {
+      LDeferredCode* code = deferred_[i];
+      __ bind(code->entry());
+      code->Generate();
+#ifdef DEBUG
+      if (i == (deferred_.length() - 1)) {
+        __ bind(&last_jump);
+      }
+#endif
+      __ jmp(code->exit());
+    }
+
+    // Reserve some space to ensure that the last piece of deferred code
+    // have room for lazy bailout.
+    int padding =
+        Deoptimizer::patch_size() - masm_->SizeOfCodeGeneratedSince(&last_jump);
+    if (padding > 0) {
+      while (padding > 9) {
+        __ nop(9);
+        padding -= 9;
+      }
+      __ nop(padding);
+    }
+
+    ASSERT(Deoptimizer::patch_size() <=
+           masm_->SizeOfCodeGeneratedSince(&last_jump));
   }
 
   // Deferred code is the last part of the instruction sequence. Mark
@@ -3987,8 +4010,16 @@ void LCodeGen::DoIn(LIn* instr) {
 
 
 void LCodeGen::DoDeferredStackCheck(LStackCheck* instr) {
-  PushSafepointRegistersScope scope(this);
-  CallRuntimeFromDeferred(Runtime::kStackGuard, 0, instr);
+  {
+    PushSafepointRegistersScope scope(this);
+    __ movq(rsi, Operand(rbp, StandardFrameConstants::kContextOffset));
+    __ CallRuntimeSaveDoubles(Runtime::kStackGuard);
+    RegisterLazyDeoptimization(instr, RECORD_SAFEPOINT_WITH_REGISTERS, 0);
+  }
+
+  // The gap code includes the restoring of the safepoint registers.
+  int pc = masm()->pc_offset();
+  safepoints_.SetPcAfterGap(pc);
 }
 
 
index 4886a8a..73b84b7 100644 (file)
@@ -649,6 +649,7 @@ int last_source_column = -1;
 
 // Debug event handler which counts the break points which have been hit.
 int break_point_hit_count = 0;
+int break_point_hit_count_deoptimize = 0;
 static void DebugEventBreakPointHitCount(v8::DebugEvent event,
                                          v8::Handle<v8::Object> exec_state,
                                          v8::Handle<v8::Object> event_data,
@@ -725,6 +726,12 @@ static void DebugEventBreakPointHitCount(v8::DebugEvent event,
         script_data->WriteAscii(last_script_data_hit);
       }
     }
+
+    // Perform a full deoptimization when the specified number of
+    // breaks have been hit.
+    if (break_point_hit_count == break_point_hit_count_deoptimize) {
+      i::Deoptimizer::DeoptimizeAll();
+    }
   } else if (event == v8::AfterCompile && !compiled_script_data.IsEmpty()) {
     const int argc = 1;
     v8::Handle<v8::Value> argv[argc] = { event_data };
@@ -983,12 +990,30 @@ static void DebugEventBreakMax(v8::DebugEvent event,
       // Count the number of breaks.
       break_point_hit_count++;
 
+      // Collect the JavsScript stack height if the function frame_count is
+      // compiled.
+      if (!frame_count.IsEmpty()) {
+        static const int kArgc = 1;
+        v8::Handle<v8::Value> argv[kArgc] = { exec_state };
+        // Using exec_state as receiver is just to have a receiver.
+        v8::Handle<v8::Value> result =
+            frame_count->Call(exec_state, kArgc, argv);
+        last_js_stack_height = result->Int32Value();
+      }
+
       // Set the break flag again to come back here as soon as possible.
       v8::Debug::DebugBreak();
+
     } else if (terminate_after_max_break_point_hit) {
       // Terminate execution after the last break if requested.
       v8::V8::TerminateExecution();
     }
+
+    // Perform a full deoptimization when the specified number of
+    // breaks have been hit.
+    if (break_point_hit_count == break_point_hit_count_deoptimize) {
+      i::Deoptimizer::DeoptimizeAll();
+    }
   }
 }
 
@@ -7195,29 +7220,38 @@ static void TestDebugBreakInLoop(const char* loop_head,
                                  const char** loop_bodies,
                                  const char* loop_tail) {
   // Receive 100 breaks for each test and then terminate JavaScript execution.
-  static int count = 0;
+  static const int kBreaksPerTest = 100;
+
+  for (int i = 0; i < 1 && loop_bodies[i] != NULL; i++) {
+    // Perform a lazy deoptimization after various numbers of breaks
+    // have been hit.
+    for (int j = 0; j < 10; j++) {
+      break_point_hit_count_deoptimize = j;
+      if (j == 10) {
+        break_point_hit_count_deoptimize = kBreaksPerTest;
+      }
 
-  for (int i = 0; loop_bodies[i] != NULL; i++) {
-    count++;
-    max_break_point_hit_count = count * 100;
-    terminate_after_max_break_point_hit = true;
+      break_point_hit_count = 0;
+      max_break_point_hit_count = kBreaksPerTest;
+      terminate_after_max_break_point_hit = true;
 
-    EmbeddedVector<char, 1024> buffer;
-    OS::SNPrintF(buffer,
-                 "function f() {%s%s%s}",
-                 loop_head, loop_bodies[i], loop_tail);
+      EmbeddedVector<char, 1024> buffer;
+      OS::SNPrintF(buffer,
+                   "function f() {%s%s%s}",
+                   loop_head, loop_bodies[i], loop_tail);
 
-    // Function with infinite loop.
-    CompileRun(buffer.start());
+      // Function with infinite loop.
+      CompileRun(buffer.start());
 
-    // Set the debug break to enter the debugger as soon as possible.
-    v8::Debug::DebugBreak();
+      // Set the debug break to enter the debugger as soon as possible.
+      v8::Debug::DebugBreak();
 
-    // Call function with infinite loop.
-    CompileRun("f();");
-    CHECK_EQ(count * 100, break_point_hit_count);
+      // Call function with infinite loop.
+      CompileRun("f();");
+      CHECK_EQ(kBreaksPerTest, break_point_hit_count);
 
-    CHECK(!v8::V8::IsExecutionTerminating());
+      CHECK(!v8::V8::IsExecutionTerminating());
+    }
   }
 }
 
@@ -7229,6 +7263,9 @@ TEST(DebugBreakLoop) {
   // Register a debug event listener which sets the break flag and counts.
   v8::Debug::SetDebugEventListener(DebugEventBreakMax);
 
+  // Create a function for getting the frame count when hitting the break.
+  frame_count = CompileFunction(&env, frame_count_source, "frame_count");
+
   CompileRun("var a = 1;");
   CompileRun("function g() { }");
   CompileRun("function h() { }");