[turbofan] Ensure lazy bailout point in exception handler.
authormstarzinger <mstarzinger@chromium.org>
Wed, 17 Jun 2015 05:40:28 +0000 (22:40 -0700)
committerCommit bot <commit-bot@chromium.org>
Wed, 17 Jun 2015 05:40:42 +0000 (05:40 +0000)
This ensures there is a lazy bailout point at the entry of every
exception handler so that deoptimized code is not re-entered through
caught exceptions.

R=jarin@chromium.org
TEST=cctest/test-run-deopt/DeoptExceptionHandler

Review URL: https://codereview.chromium.org/1173253004

Cr-Commit-Position: refs/heads/master@{#29061}

src/ast-numbering.cc
src/ast.h
src/compiler/ast-graph-builder.cc
src/compiler/code-generator.cc
src/compiler/instruction.cc
src/compiler/instruction.h
src/full-codegen.cc
test/cctest/compiler/test-jump-threading.cc
test/cctest/compiler/test-run-deopt.cc
test/unittests/compiler/instruction-sequence-unittest.cc

index 2a5c5ac..ce82998 100644 (file)
@@ -296,6 +296,7 @@ void AstNumberingVisitor::VisitWhileStatement(WhileStatement* node) {
 void AstNumberingVisitor::VisitTryCatchStatement(TryCatchStatement* node) {
   IncrementNodeCount();
   DisableOptimization(kTryCatchStatement);
+  node->set_base_id(ReserveIdRange(TryCatchStatement::num_ids()));
   Visit(node->try_block());
   Visit(node->catch_block());
 }
@@ -304,6 +305,7 @@ void AstNumberingVisitor::VisitTryCatchStatement(TryCatchStatement* node) {
 void AstNumberingVisitor::VisitTryFinallyStatement(TryFinallyStatement* node) {
   IncrementNodeCount();
   DisableOptimization(kTryFinallyStatement);
+  node->set_base_id(ReserveIdRange(TryFinallyStatement::num_ids()));
   Visit(node->try_block());
   Visit(node->finally_block());
 }
index 0dc9e60..7bdd520 100644 (file)
--- a/src/ast.h
+++ b/src/ast.h
@@ -1163,12 +1163,27 @@ class TryStatement : public Statement {
  public:
   Block* try_block() const { return try_block_; }
 
+  void set_base_id(int id) { base_id_ = id; }
+  static int num_ids() { return parent_num_ids() + 1; }
+  BailoutId HandlerId() const { return BailoutId(local_id(0)); }
+
  protected:
   TryStatement(Zone* zone, Block* try_block, int pos)
-      : Statement(zone, pos), try_block_(try_block) {}
+      : Statement(zone, pos),
+        try_block_(try_block),
+        base_id_(BailoutId::None().ToInt()) {}
+  static int parent_num_ids() { return 0; }
+
+  int base_id() const {
+    DCHECK(!BailoutId(base_id_).IsNone());
+    return base_id_;
+  }
 
  private:
+  int local_id(int n) const { return base_id() + parent_num_ids() + n; }
+
   Block* try_block_;
+  int base_id_;
 };
 
 
index cf7bbc9..167d285 100644 (file)
@@ -1415,16 +1415,21 @@ void AstGraphBuilder::VisitTryCatchStatement(TryCatchStatement* stmt) {
   }
   try_control.EndTry();
 
+  // TODO(mstarzinger): We are only using a runtime call to get a lazy bailout
+  // point, there is no need to really emit an actual call. Optimize this!
+  Node* guard = NewNode(javascript()->CallRuntime(Runtime::kMaxSmi, 0));
+  PrepareFrameState(guard, stmt->HandlerId());
+
+  // Clear message object as we enter the catch block.
+  Node* the_hole = jsgraph()->TheHoleConstant();
+  BuildStoreExternal(message_object, kMachAnyTagged, the_hole);
+
   // Create a catch scope that binds the exception.
   Node* exception = try_control.GetExceptionNode();
   Unique<String> name = MakeUnique(stmt->variable()->name());
   const Operator* op = javascript()->CreateCatchContext(name);
   Node* context = NewNode(op, exception, GetFunctionClosureForContext());
 
-  // Clear message object as we enter the catch block.
-  Node* the_hole = jsgraph()->TheHoleConstant();
-  BuildStoreExternal(message_object, kMachAnyTagged, the_hole);
-
   // Evaluate the catch-block.
   VisitInScope(stmt->catch_block(), stmt->scope(), context);
   try_control.EndCatch();
@@ -1464,6 +1469,11 @@ void AstGraphBuilder::VisitTryFinallyStatement(TryFinallyStatement* stmt) {
   }
   try_control.EndTry(commands->GetFallThroughToken(), fallthrough_result);
 
+  // TODO(mstarzinger): We are only using a runtime call to get a lazy bailout
+  // point, there is no need to really emit an actual call. Optimize this!
+  Node* guard = NewNode(javascript()->CallRuntime(Runtime::kMaxSmi, 0));
+  PrepareFrameState(guard, stmt->HandlerId());
+
   // The result value semantics depend on how the block was entered:
   //  - ReturnStatement: It represents the return value being returned.
   //  - ThrowStatement: It represents the exception being thrown.
index 374e677..1854c82 100644 (file)
@@ -92,6 +92,8 @@ Handle<Code> CodeGenerator::GenerateCode() {
       }
       // Align loop headers on 16-byte boundaries.
       if (block->IsLoopHeader()) masm()->Align(16);
+      // Ensure lazy deopt doesn't patch handler entry points.
+      if (block->IsHandler()) EnsureSpaceForLazyDeopt();
       // Bind a label for a block.
       current_block_ = block->rpo_number();
       if (FLAG_code_comments) {
index 29a31e0..815e91e 100644 (file)
@@ -403,7 +403,7 @@ void PhiInstruction::SetInput(size_t offset, int virtual_register) {
 
 InstructionBlock::InstructionBlock(Zone* zone, RpoNumber rpo_number,
                                    RpoNumber loop_header, RpoNumber loop_end,
-                                   bool deferred)
+                                   bool deferred, bool handler)
     : successors_(zone),
       predecessors_(zone),
       phis_(zone),
@@ -414,6 +414,7 @@ InstructionBlock::InstructionBlock(Zone* zone, RpoNumber rpo_number,
       code_start_(-1),
       code_end_(-1),
       deferred_(deferred),
+      handler_(handler),
       needs_frame_(false),
       must_construct_frame_(false),
       must_deconstruct_frame_(false) {}
@@ -443,9 +444,11 @@ static RpoNumber GetLoopEndRpo(const BasicBlock* block) {
 
 static InstructionBlock* InstructionBlockFor(Zone* zone,
                                              const BasicBlock* block) {
+  bool is_handler =
+      !block->empty() && block->front()->opcode() == IrOpcode::kIfException;
   InstructionBlock* instr_block = new (zone)
       InstructionBlock(zone, GetRpo(block), GetRpo(block->loop_header()),
-                       GetLoopEndRpo(block), block->deferred());
+                       GetLoopEndRpo(block), block->deferred(), is_handler);
   // Map successors and precessors
   instr_block->successors().reserve(block->SuccessorCount());
   for (BasicBlock* successor : block->successors()) {
index eff5099..7f20213 100644 (file)
@@ -930,7 +930,7 @@ class PhiInstruction final : public ZoneObject {
 class InstructionBlock final : public ZoneObject {
  public:
   InstructionBlock(Zone* zone, RpoNumber rpo_number, RpoNumber loop_header,
-                   RpoNumber loop_end, bool deferred);
+                   RpoNumber loop_end, bool deferred, bool handler);
 
   // Instruction indexes (used by the register allocator).
   int first_instruction_index() const {
@@ -953,6 +953,7 @@ class InstructionBlock final : public ZoneObject {
   void set_code_end(int32_t end) { code_end_ = end; }
 
   bool IsDeferred() const { return deferred_; }
+  bool IsHandler() const { return handler_; }
 
   RpoNumber ao_number() const { return ao_number_; }
   RpoNumber rpo_number() const { return rpo_number_; }
@@ -1000,6 +1001,7 @@ class InstructionBlock final : public ZoneObject {
   int32_t code_start_;   // start index of arch-specific code.
   int32_t code_end_;     // end index of arch-specific code.
   const bool deferred_;  // Block contains deferred code.
+  const bool handler_;   // Block is a handler entry point.
   bool needs_frame_;
   bool must_construct_frame_;
   bool must_deconstruct_frame_;
index 16f0b55..f1b3904 100644 (file)
@@ -1226,8 +1226,9 @@ void FullCodeGenerator::VisitTryCatchStatement(TryCatchStatement* stmt) {
   Label try_entry, handler_entry, exit;
   __ jmp(&try_entry);
   __ bind(&handler_entry);
-
+  PrepareForBailoutForId(stmt->HandlerId(), NO_REGISTERS);
   ClearPendingMessage();
+
   // Exception handler code, the exception is in the result register.
   // Extend the context before executing the catch block.
   { Comment cmnt(masm_, "[ Extend catch context");
@@ -1295,6 +1296,8 @@ void FullCodeGenerator::VisitTryFinallyStatement(TryFinallyStatement* stmt) {
   // Jump to try-handler setup and try-block code.
   __ jmp(&try_entry);
   __ bind(&handler_entry);
+  PrepareForBailoutForId(stmt->HandlerId(), NO_REGISTERS);
+
   // Exception handler code.  This code is only executed when an exception
   // is thrown.  The exception is in the result register, and must be
   // preserved by the finally block.  Call the finally block and then
index e0a4a2f..9fa3c9f 100644 (file)
@@ -85,7 +85,7 @@ class TestCode : public HandleAndZoneScope {
     if (current_ == NULL) {
       current_ = new (main_zone())
           InstructionBlock(main_zone(), rpo_number_, RpoNumber::Invalid(),
-                           RpoNumber::Invalid(), deferred);
+                           RpoNumber::Invalid(), deferred, false);
       blocks_.push_back(current_);
       sequence_.StartBlock(rpo_number_);
     }
index 71883e2..b08185f 100644 (file)
@@ -27,47 +27,90 @@ static void InstallIsOptimizedHelper(v8::Isolate* isolate) {
 }
 
 
-TEST(TurboSimpleDeopt) {
+TEST(DeoptSimple) {
   FLAG_allow_natives_syntax = true;
 
   FunctionTester T(
       "(function f(a) {"
-      "var b = 1;"
-      "if (!IsOptimized()) return 0;"
-      "%DeoptimizeFunction(f);"
-      "if (IsOptimized()) return 0;"
-      "return a + b; })");
+      "  var b = 1;"
+      "  if (!IsOptimized()) return 0;"
+      "  %DeoptimizeFunction(f);"
+      "  if (IsOptimized()) return 0;"
+      "  return a + b;"
+      "})");
 
   InstallIsOptimizedHelper(CcTest::isolate());
   T.CheckCall(T.Val(2), T.Val(1));
 }
 
 
-TEST(TurboSimpleDeoptInExpr) {
+TEST(DeoptSimpleInExpr) {
   FLAG_allow_natives_syntax = true;
 
   FunctionTester T(
       "(function f(a) {"
-      "var b = 1;"
-      "var c = 2;"
-      "if (!IsOptimized()) return 0;"
-      "var d = b + (%DeoptimizeFunction(f), c);"
-      "if (IsOptimized()) return 0;"
-      "return d + a; })");
+      "  var b = 1;"
+      "  var c = 2;"
+      "  if (!IsOptimized()) return 0;"
+      "  var d = b + (%DeoptimizeFunction(f), c);"
+      "  if (IsOptimized()) return 0;"
+      "  return d + a;"
+      "})");
 
   InstallIsOptimizedHelper(CcTest::isolate());
   T.CheckCall(T.Val(6), T.Val(3));
 }
 
+
+TEST(DeoptExceptionHandlerCatch) {
+  FLAG_allow_natives_syntax = true;
+  FLAG_turbo_try_catch = true;
+
+  FunctionTester T(
+      "(function f() {"
+      "  var is_opt = IsOptimized;"
+      "  try {"
+      "    DeoptAndThrow(f);"
+      "  } catch (e) {"
+      "    return is_opt();"
+      "  }"
+      "})");
+
+  CompileRun("function DeoptAndThrow(f) { %DeoptimizeFunction(f); throw 0; }");
+  InstallIsOptimizedHelper(CcTest::isolate());
+  T.CheckCall(T.false_value());
+}
+
+
+TEST(DeoptExceptionHandlerFinally) {
+  FLAG_allow_natives_syntax = true;
+  FLAG_turbo_try_finally = true;
+
+  FunctionTester T(
+      "(function f() {"
+      "  var is_opt = IsOptimized;"
+      "  try {"
+      "    DeoptAndThrow(f);"
+      "  } finally {"
+      "    return is_opt();"
+      "  }"
+      "})");
+
+  CompileRun("function DeoptAndThrow(f) { %DeoptimizeFunction(f); throw 0; }");
+  InstallIsOptimizedHelper(CcTest::isolate());
+  T.CheckCall(T.false_value());
+}
+
 #endif
 
-TEST(TurboTrivialDeopt) {
+TEST(DeoptTrivial) {
   FLAG_allow_natives_syntax = true;
 
   FunctionTester T(
       "(function foo() {"
-      "%DeoptimizeFunction(foo);"
-      "return 1; })");
+      "  %DeoptimizeFunction(foo);"
+      "  return 1;"
+      "})");
 
   T.CheckCall(T.Val(1));
 }
index f659b07..1ff441f 100644 (file)
@@ -429,8 +429,8 @@ InstructionBlock* InstructionSequenceTest::NewBlock() {
     }
   }
   // Construct instruction block.
-  auto instruction_block =
-      new (zone()) InstructionBlock(zone(), rpo, loop_header, loop_end, false);
+  auto instruction_block = new (zone())
+      InstructionBlock(zone(), rpo, loop_header, loop_end, false, false);
   instruction_blocks_.push_back(instruction_block);
   current_block_ = instruction_block;
   sequence()->StartBlock(rpo);