First stab at try-catch and try-finally in TurboFan.
authormstarzinger <mstarzinger@chromium.org>
Tue, 3 Feb 2015 13:09:40 +0000 (05:09 -0800)
committerCommit bot <commit-bot@chromium.org>
Tue, 3 Feb 2015 13:10:01 +0000 (13:10 +0000)
R=titzer@chromium.org,jarin@chromium.org
TEST=cctest/test-run-jsexceptions

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

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

src/compiler/ast-graph-builder.cc
src/compiler/ast-graph-builder.h
src/compiler/control-builders.cc
src/compiler/control-builders.h
src/compiler/graph-builder.h
src/compiler/js-generic-lowering.cc
src/compiler/js-operator.cc
src/flag-definitions.h
test/cctest/compiler/test-run-jsexceptions.cc

index 08f0fa2581f7044c2feeec55afd3ca7d99772da1..98d80e8a9d2ce17dc065a0574c4f8c9de629fde9 100644 (file)
@@ -21,6 +21,244 @@ namespace internal {
 namespace compiler {
 
 
+// Scoped class tracking control statements entered by the visitor. There are
+// different types of statements participating in this stack to properly track
+// local as well as non-local control flow:
+//  - IterationStatement : Allows proper 'break' and 'continue' behavior.
+//  - BreakableStatement : Allows 'break' from block and switch statements.
+//  - TryCatchStatement  : Intercepts 'throw' and implicit exceptional edges.
+//  - TryFinallyStatement: Intercepts 'break', 'continue', 'throw' and 'return'.
+class AstGraphBuilder::ControlScope BASE_EMBEDDED {
+ public:
+  ControlScope(AstGraphBuilder* builder, int stack_delta)
+      : builder_(builder),
+        next_(builder->execution_control()),
+        stack_delta_(stack_delta) {
+    builder_->set_execution_control(this);  // Push.
+  }
+
+  virtual ~ControlScope() {
+    builder_->set_execution_control(next_);  // Pop.
+  }
+
+  // Either 'break' or 'continue' to the target statement.
+  void BreakTo(BreakableStatement* target);
+  void ContinueTo(BreakableStatement* target);
+
+  // Either 'return' or 'throw' the given value.
+  void ReturnValue(Node* return_value);
+  void ThrowValue(Node* exception_value);
+
+  class DeferredCommands;
+
+ protected:
+  enum Command { CMD_BREAK, CMD_CONTINUE, CMD_RETURN, CMD_THROW };
+
+  // Performs one of the above commands on this stack of control scopes. This
+  // walks through the stack giving each scope a chance to execute or defer the
+  // given command by overriding the {Execute} method appropriately. Note that
+  // this also drops extra operands from the environment for each skipped scope.
+  void PerformCommand(Command cmd, Statement* target, Node* value);
+
+  // Interface to execute a given command in this scope. Returning {true} here
+  // indicates successful execution whereas {false} requests to skip scope.
+  virtual bool Execute(Command cmd, Statement* target, Node* value) {
+    // For function-level control.
+    switch (cmd) {
+      case CMD_THROW:
+        builder()->BuildThrow(value);
+        return true;
+      case CMD_RETURN:
+        builder()->BuildReturn(value);
+        return true;
+      case CMD_BREAK:
+      case CMD_CONTINUE:
+        break;
+    }
+    return false;
+  }
+
+  Environment* environment() { return builder_->environment(); }
+  AstGraphBuilder* builder() const { return builder_; }
+  int stack_delta() const { return stack_delta_; }
+
+ private:
+  AstGraphBuilder* builder_;
+  ControlScope* next_;
+  int stack_delta_;
+};
+
+
+// Helper class for a try-finally control scope. It can record intercepted
+// control-flow commands that cause entry into a finally-block, and re-apply
+// them after again leaving that block. Special tokens are used to identify
+// paths going through the finally-block to dispatch after leaving the block.
+class AstGraphBuilder::ControlScope::DeferredCommands : public ZoneObject {
+ public:
+  explicit DeferredCommands(AstGraphBuilder* owner)
+      : owner_(owner), deferred_(owner->zone()) {}
+
+  // One recorded control-flow command.
+  struct Entry {
+    Command command;       // The command type being applied on this path.
+    Statement* statement;  // The target statement for the command or {NULL}.
+    Node* value;           // The passed value node for the command or {NULL}.
+    Node* token;           // A token identifying this particular path.
+  };
+
+  // Records a control-flow command while entering the finally-block. This also
+  // generates a new dispatch token that identifies one particular path.
+  Node* RecordCommand(Command cmd, Statement* stmt, Node* value) {
+    Node* token = NewPathTokenForDeferredCommand();
+    deferred_.push_back({cmd, stmt, value, token});
+    return token;
+  }
+
+  // Returns the dispatch token to be used to identify the implicit fall-through
+  // path at the end of a try-block into the corresponding finally-block.
+  Node* GetFallThroughToken() { return NewPathTokenForImplicitFallThrough(); }
+
+  // Applies all recorded control-flow commands after the finally-block again.
+  // This generates a dynamic dispatch on the token from the entry point.
+  void ApplyDeferredCommands(Node* token) {
+    SwitchBuilder dispatch(owner_, static_cast<int>(deferred_.size()));
+    dispatch.BeginSwitch();
+    for (size_t i = 0; i < deferred_.size(); ++i) {
+      Node* condition = NewPathDispatchCondition(token, deferred_[i].token);
+      dispatch.BeginLabel(static_cast<int>(i), condition);
+      dispatch.EndLabel();
+    }
+    for (size_t i = 0; i < deferred_.size(); ++i) {
+      dispatch.BeginCase(static_cast<int>(i));
+      owner_->execution_control()->PerformCommand(
+          deferred_[i].command, deferred_[i].statement, deferred_[i].value);
+      dispatch.EndCase();
+    }
+    dispatch.EndSwitch();
+  }
+
+ protected:
+  Node* NewPathTokenForDeferredCommand() {
+    return owner_->jsgraph()->Constant(static_cast<int>(deferred_.size()));
+  }
+  Node* NewPathTokenForImplicitFallThrough() {
+    return owner_->jsgraph()->Constant(-1);
+  }
+  Node* NewPathDispatchCondition(Node* t1, Node* t2) {
+    // TODO(mstarzinger): This should be machine()->WordEqual(), but our Phi
+    // nodes all have kRepTagged|kTypeAny, which causes representation mismatch.
+    return owner_->NewNode(owner_->javascript()->StrictEqual(), t1, t2);
+  }
+
+ private:
+  AstGraphBuilder* owner_;
+  ZoneVector<Entry> deferred_;
+};
+
+
+// Control scope implementation for a BreakableStatement.
+class AstGraphBuilder::ControlScopeForBreakable : public ControlScope {
+ public:
+  ControlScopeForBreakable(AstGraphBuilder* owner, BreakableStatement* target,
+                           ControlBuilder* control)
+      : ControlScope(owner, 0), target_(target), control_(control) {}
+
+ protected:
+  virtual bool Execute(Command cmd, Statement* target, Node* value) OVERRIDE {
+    if (target != target_) return false;  // We are not the command target.
+    switch (cmd) {
+      case CMD_BREAK:
+        control_->Break();
+        return true;
+      case CMD_CONTINUE:
+      case CMD_THROW:
+      case CMD_RETURN:
+        break;
+    }
+    return false;
+  }
+
+ private:
+  BreakableStatement* target_;
+  ControlBuilder* control_;
+};
+
+
+// Control scope implementation for an IterationStatement.
+class AstGraphBuilder::ControlScopeForIteration : public ControlScope {
+ public:
+  ControlScopeForIteration(AstGraphBuilder* owner, IterationStatement* target,
+                           LoopBuilder* control, int stack_delta)
+      : ControlScope(owner, stack_delta), target_(target), control_(control) {}
+
+ protected:
+  virtual bool Execute(Command cmd, Statement* target, Node* value) OVERRIDE {
+    if (target != target_) return false;  // We are not the command target.
+    switch (cmd) {
+      case CMD_BREAK:
+        control_->Break();
+        return true;
+      case CMD_CONTINUE:
+        control_->Continue();
+        return true;
+      case CMD_THROW:
+      case CMD_RETURN:
+        break;
+    }
+    return false;
+  }
+
+ private:
+  BreakableStatement* target_;
+  LoopBuilder* control_;
+};
+
+
+// Control scope implementation for a TryCatchStatement.
+class AstGraphBuilder::ControlScopeForCatch : public ControlScope {
+ public:
+  ControlScopeForCatch(AstGraphBuilder* owner, TryCatchBuilder* control)
+      : ControlScope(owner, 0), control_(control) {}
+
+ protected:
+  virtual bool Execute(Command cmd, Statement* target, Node* value) OVERRIDE {
+    switch (cmd) {
+      case CMD_THROW:
+        control_->Throw(value);
+        return true;
+      case CMD_BREAK:
+      case CMD_CONTINUE:
+      case CMD_RETURN:
+        break;
+    }
+    return false;
+  }
+
+ private:
+  TryCatchBuilder* control_;
+};
+
+
+// Control scope implementation for a TryFinallyStatement.
+class AstGraphBuilder::ControlScopeForFinally : public ControlScope {
+ public:
+  ControlScopeForFinally(AstGraphBuilder* owner, DeferredCommands* commands,
+                         TryFinallyBuilder* control)
+      : ControlScope(owner, 0), commands_(commands), control_(control) {}
+
+ protected:
+  virtual bool Execute(Command cmd, Statement* target, Node* value) OVERRIDE {
+    Node* token = commands_->RecordCommand(cmd, target, value);
+    control_->LeaveTry(token);
+    return true;
+  }
+
+ private:
+  DeferredCommands* commands_;
+  TryFinallyBuilder* control_;
+};
+
+
 AstGraphBuilder::AstGraphBuilder(Zone* local_zone, CompilationInfo* info,
                                  JSGraph* jsgraph, LoopAssignmentAnalysis* loop)
     : local_zone_(local_zone),
@@ -29,7 +267,7 @@ AstGraphBuilder::AstGraphBuilder(Zone* local_zone, CompilationInfo* info,
       environment_(nullptr),
       ast_context_(nullptr),
       globals_(0, local_zone),
-      breakable_(nullptr),
+      execution_control_(nullptr),
       execution_context_(nullptr),
       input_buffer_size_(0),
       input_buffer_(nullptr),
@@ -70,9 +308,11 @@ bool AstGraphBuilder::CreateGraph() {
   int parameter_count = info()->num_parameters();
   graph()->SetStart(graph()->NewNode(common()->Start(parameter_count)));
 
-  Node* start = graph()->start();
+  // Initialize control scope.
+  ControlScope control(this, 0);
+
   // Initialize the top-level environment.
-  Environment env(this, scope, start);
+  Environment env(this, scope, graph()->start());
   set_environment(&env);
 
   if (info()->is_osr()) {
@@ -130,8 +370,7 @@ bool AstGraphBuilder::CreateGraph() {
   }
 
   // Return 'undefined' in case we can fall off the end.
-  Node* control = NewNode(common()->Return(), jsgraph()->UndefinedConstant());
-  UpdateControlDependencyToLeaveFunction(control);
+  BuildReturn(jsgraph()->UndefinedConstant());
 
   // Finish the basic structure of the graph.
   environment()->UpdateControlDependency(exit_control());
@@ -302,25 +541,39 @@ Node* AstGraphBuilder::AstTestContext::ConsumeValue() {
 }
 
 
-AstGraphBuilder::BreakableScope* AstGraphBuilder::BreakableScope::FindBreakable(
-    BreakableStatement* target) {
-  BreakableScope* current = this;
-  while (current != NULL && current->target_ != target) {
-    owner_->environment()->Drop(current->drop_extra_);
+void AstGraphBuilder::ControlScope::PerformCommand(Command command,
+                                                   Statement* target,
+                                                   Node* value) {
+  Environment* env = environment()->CopyAsUnreachable();
+  ControlScope* current = this;
+  while (current != NULL) {
+    if (current->Execute(command, target, value)) break;
+    environment()->Drop(current->stack_delta());
     current = current->next_;
   }
-  DCHECK(current != NULL);  // Always found (unless stack is malformed).
-  return current;
+  // TODO(mstarzinger): Unconditionally kill environment once throw is control.
+  if (command != CMD_THROW) builder()->set_environment(env);
+  DCHECK(current != NULL);  // Always handled (unless stack is malformed).
+}
+
+
+void AstGraphBuilder::ControlScope::BreakTo(BreakableStatement* stmt) {
+  PerformCommand(CMD_BREAK, stmt, nullptr);
 }
 
 
-void AstGraphBuilder::BreakableScope::BreakTarget(BreakableStatement* stmt) {
-  FindBreakable(stmt)->control_->Break();
+void AstGraphBuilder::ControlScope::ContinueTo(BreakableStatement* stmt) {
+  PerformCommand(CMD_CONTINUE, stmt, nullptr);
 }
 
 
-void AstGraphBuilder::BreakableScope::ContinueTarget(BreakableStatement* stmt) {
-  FindBreakable(stmt)->control_->Continue();
+void AstGraphBuilder::ControlScope::ReturnValue(Node* return_value) {
+  PerformCommand(CMD_RETURN, nullptr, return_value);
+}
+
+
+void AstGraphBuilder::ControlScope::ThrowValue(Node* exception_value) {
+  PerformCommand(CMD_THROW, nullptr, exception_value);
 }
 
 
@@ -483,7 +736,7 @@ void AstGraphBuilder::VisitModuleUrl(ModuleUrl* modl) { UNREACHABLE(); }
 
 void AstGraphBuilder::VisitBlock(Block* stmt) {
   BlockBuilder block(this);
-  BreakableScope scope(this, stmt, &block, 0);
+  ControlScopeForBreakable scope(this, stmt, &block);
   if (stmt->labels() != NULL) block.BeginBlock();
   if (stmt->scope() == NULL) {
     // Visit statements in the same scope, no declarations.
@@ -528,24 +781,19 @@ void AstGraphBuilder::VisitIfStatement(IfStatement* stmt) {
 
 
 void AstGraphBuilder::VisitContinueStatement(ContinueStatement* stmt) {
-  Environment* env = environment()->CopyAsUnreachable();
-  breakable()->ContinueTarget(stmt->target());
-  set_environment(env);
+  execution_control()->ContinueTo(stmt->target());
 }
 
 
 void AstGraphBuilder::VisitBreakStatement(BreakStatement* stmt) {
-  Environment* env = environment()->CopyAsUnreachable();
-  breakable()->BreakTarget(stmt->target());
-  set_environment(env);
+  execution_control()->BreakTo(stmt->target());
 }
 
 
 void AstGraphBuilder::VisitReturnStatement(ReturnStatement* stmt) {
   VisitForValue(stmt->expression());
   Node* result = environment()->Pop();
-  Node* control = NewNode(common()->Return(), result);
-  UpdateControlDependencyToLeaveFunction(control);
+  execution_control()->ReturnValue(result);
 }
 
 
@@ -563,7 +811,7 @@ void AstGraphBuilder::VisitWithStatement(WithStatement* stmt) {
 void AstGraphBuilder::VisitSwitchStatement(SwitchStatement* stmt) {
   ZoneList<CaseClause*>* clauses = stmt->cases();
   SwitchBuilder compare_switch(this, clauses->length());
-  BreakableScope scope(this, stmt, &compare_switch, 0);
+  ControlScopeForBreakable scope(this, stmt, &compare_switch);
   compare_switch.BeginSwitch();
   int default_index = -1;
 
@@ -816,14 +1064,70 @@ void AstGraphBuilder::VisitForOfStatement(ForOfStatement* stmt) {
 
 
 void AstGraphBuilder::VisitTryCatchStatement(TryCatchStatement* stmt) {
-  // TODO(turbofan): Implement try-catch here.
-  SetStackOverflow();
+  TryCatchBuilder try_control(this);
+
+  // Evaluate the try-block inside a control scope. This simulates a handler
+  // that is intercepting 'throw' control commands.
+  try_control.BeginTry();
+  {
+    ControlScopeForCatch scope(this, &try_control);
+    Visit(stmt->try_block());
+  }
+  try_control.EndTry();
+
+  // Create a catch scope that binds the exception.
+  Node* exception = try_control.GetExceptionNode();
+  if (exception == NULL) exception = jsgraph()->NullConstant();
+  Unique<String> name = MakeUnique(stmt->variable()->name());
+  const Operator* op = javascript()->CreateCatchContext(name);
+  Node* context = NewNode(op, exception, GetFunctionClosure());
+  PrepareFrameState(context, BailoutId::None());
+  ContextScope scope(this, stmt->scope(), context);
+  DCHECK(stmt->scope()->declarations()->is_empty());
+
+  // Evaluate the catch-block.
+  Visit(stmt->catch_block());
+  try_control.EndCatch();
+
+  // TODO(mstarzinger): Remove bailout once everything works.
+  if (!FLAG_turbo_exceptions) SetStackOverflow();
 }
 
 
 void AstGraphBuilder::VisitTryFinallyStatement(TryFinallyStatement* stmt) {
-  // TODO(turbofan): Implement try-catch here.
-  SetStackOverflow();
+  TryFinallyBuilder try_control(this);
+
+  // We keep a record of all paths that enter the finally-block to be able to
+  // dispatch to the correct continuation point after the statements in the
+  // finally-block have been evaluated.
+  //
+  // The try-finally construct can enter the finally-block in three ways:
+  // 1. By exiting the try-block normally, falling through at the end.
+  // 2. By exiting the try-block with a function-local control flow transfer
+  //    (i.e. through break/continue/return statements).
+  // 3. By exiting the try-block with a thrown exception.
+  ControlScope::DeferredCommands* commands =
+      new (zone()) ControlScope::DeferredCommands(this);
+
+  // Evaluate the try-block inside a control scope. This simulates a handler
+  // that is intercepting all control commands.
+  try_control.BeginTry();
+  {
+    ControlScopeForFinally scope(this, commands, &try_control);
+    Visit(stmt->try_block());
+  }
+  try_control.EndTry(commands->GetFallThroughToken());
+
+  // Evaluate the finally-block.
+  Visit(stmt->finally_block());
+  try_control.EndFinally();
+
+  // Dynamic dispatch after the finally-block.
+  Node* token = try_control.GetDispatchTokenNode();
+  commands->ApplyDeferredCommands(token);
+
+  // TODO(mstarzinger): Remove bailout once everything works.
+  if (!FLAG_turbo_exceptions) SetStackOverflow();
 }
 
 
@@ -1387,10 +1691,16 @@ void AstGraphBuilder::VisitYield(Yield* expr) {
 void AstGraphBuilder::VisitThrow(Throw* expr) {
   VisitForValue(expr->exception());
   Node* exception = environment()->Pop();
-  const Operator* op = javascript()->CallRuntime(Runtime::kThrow, 1);
-  Node* value = NewNode(op, exception);
-  PrepareFrameState(value, expr->id(), ast_context()->GetStateCombine());
-  ast_context()->ProduceValue(value);
+  if (FLAG_turbo_exceptions) {
+    execution_control()->ThrowValue(exception);
+    ast_context()->ProduceValue(exception);
+  } else {
+    // TODO(mstarzinger): Temporary workaround for bailout-id for debugger.
+    const Operator* op = javascript()->CallRuntime(Runtime::kThrow, 1);
+    Node* value = NewNode(op, exception);
+    PrepareFrameState(value, expr->id(), ast_context()->GetStateCombine());
+    ast_context()->ProduceValue(value);
+  }
 }
 
 
@@ -1832,8 +2142,8 @@ void AstGraphBuilder::VisitIfNotNull(Statement* stmt) {
 
 
 void AstGraphBuilder::VisitIterationBody(IterationStatement* stmt,
-                                         LoopBuilder* loop, int drop_extra) {
-  BreakableScope scope(this, stmt, loop, drop_extra);
+                                         LoopBuilder* loop, int stack_delta) {
+  ControlScopeForIteration scope(this, stmt, loop, stack_delta);
   Visit(stmt->body());
 }
 
@@ -2360,6 +2670,23 @@ Node* AstGraphBuilder::BuildThrowConstAssignError(BailoutId bailout_id) {
 }
 
 
+Node* AstGraphBuilder::BuildReturn(Node* return_value) {
+  Node* control = NewNode(common()->Return(), return_value);
+  UpdateControlDependencyToLeaveFunction(control);
+  return control;
+}
+
+
+Node* AstGraphBuilder::BuildThrow(Node* exception_value) {
+  const Operator* op = javascript()->CallRuntime(Runtime::kThrow, 1);
+  Node* control = NewNode(op, exception_value);
+  // TODO(mstarzinger): Thread through the correct bailout id to this point.
+  // PrepareFrameState(value, expr->id(), ast_context()->GetStateCombine());
+  PrepareFrameState(control, BailoutId::None());
+  return control;
+}
+
+
 Node* AstGraphBuilder::BuildBinaryOp(Node* left, Node* right, Token::Value op) {
   const Operator* js_op;
   switch (op) {
index 07a2a06821bebb3d3b40bc679b90ecf4bf10c375..390ab234a482dbb04d7169c59e1e019e9df60262 100644 (file)
@@ -63,8 +63,12 @@ class AstGraphBuilder : public AstVisitor {
   class AstEffectContext;
   class AstValueContext;
   class AstTestContext;
-  class BreakableScope;
   class ContextScope;
+  class ControlScope;
+  class ControlScopeForBreakable;
+  class ControlScopeForIteration;
+  class ControlScopeForCatch;
+  class ControlScopeForFinally;
   class Environment;
   friend class ControlBuilder;
 
@@ -77,8 +81,8 @@ class AstGraphBuilder : public AstVisitor {
   // List of global declarations for functions and variables.
   ZoneVector<Handle<Object>> globals_;
 
-  // Stack of breakable statements entered by the visitor.
-  BreakableScope* breakable_;
+  // Stack of control scopes currently entered by the visitor.
+  ControlScope* execution_control_;
 
   // Stack of context objects pushed onto the chain by the visitor.
   ContextScope* execution_context_;
@@ -110,7 +114,7 @@ class AstGraphBuilder : public AstVisitor {
   Zone* local_zone() const { return local_zone_; }
   Environment* environment() { return environment_; }
   AstContext* ast_context() const { return ast_context_; }
-  BreakableScope* breakable() const { return breakable_; }
+  ControlScope* execution_control() const { return execution_control_; }
   ContextScope* execution_context() const { return execution_context_; }
   CommonOperatorBuilder* common() const { return jsgraph_->common(); }
   CompilationInfo* info() const { return info_; }
@@ -126,7 +130,7 @@ class AstGraphBuilder : public AstVisitor {
   Node* exit_control() const { return exit_control_; }
 
   void set_ast_context(AstContext* ctx) { ast_context_ = ctx; }
-  void set_breakable(BreakableScope* brk) { breakable_ = brk; }
+  void set_execution_control(ControlScope* ctrl) { execution_control_ = ctrl; }
   void set_execution_context(ContextScope* ctx) { execution_context_ = ctx; }
   void set_exit_control(Node* exit) { exit_control_ = exit; }
   void set_current_context(Node* ctx) { current_context_ = ctx; }
@@ -237,6 +241,10 @@ class AstGraphBuilder : public AstVisitor {
   Node* BuildHoleCheckThrow(Node* value, Variable* var, Node* not_hole,
                             BailoutId bailout_id);
 
+  // Builders for non-local control flow.
+  Node* BuildReturn(Node* return_value);
+  Node* BuildThrow(Node* exception_value);
+
   // Builders for binary operations.
   Node* BuildBinaryOp(Node* left, Node* right, Token::Value op);
 
@@ -529,42 +537,6 @@ class AstGraphBuilder::AstTestContext FINAL : public AstContext {
 };
 
 
-// Scoped class tracking breakable statements entered by the visitor. Allows to
-// properly 'break' and 'continue' iteration statements as well as to 'break'
-// from blocks within switch statements.
-class AstGraphBuilder::BreakableScope BASE_EMBEDDED {
- public:
-  BreakableScope(AstGraphBuilder* owner, BreakableStatement* target,
-                 ControlBuilder* control, int drop_extra)
-      : owner_(owner),
-        target_(target),
-        next_(owner->breakable()),
-        control_(control),
-        drop_extra_(drop_extra) {
-    owner_->set_breakable(this);  // Push.
-  }
-
-  ~BreakableScope() {
-    owner_->set_breakable(next_);  // Pop.
-  }
-
-  // Either 'break' or 'continue' the target statement.
-  void BreakTarget(BreakableStatement* target);
-  void ContinueTarget(BreakableStatement* target);
-
- private:
-  AstGraphBuilder* owner_;
-  BreakableStatement* target_;
-  BreakableScope* next_;
-  ControlBuilder* control_;
-  int drop_extra_;
-
-  // Find the correct scope for the target statement. Note that this also drops
-  // extra operands from the environment for each scope skipped along the way.
-  BreakableScope* FindBreakable(BreakableStatement* target);
-};
-
-
 // Scoped class tracking context objects created by the visitor. Represents
 // mutations of the context chain within the function body and allows to
 // change the current {scope} and {context} during visitation.
index 6cf2905e613e3e6b4837a262cbe1c3588906d606..6dba2d3ad6083756aef4fbc1e43d392b9e85465b 100644 (file)
@@ -147,6 +147,61 @@ void BlockBuilder::EndBlock() {
   break_environment_->Merge(environment());
   set_environment(break_environment_);
 }
+
+
+void TryCatchBuilder::BeginTry() {
+  catch_environment_ = environment()->CopyAsUnreachable();
+  catch_environment_->Push(nullptr);
+}
+
+
+void TryCatchBuilder::Throw(Node* exception) {
+  environment()->Push(exception);
+  catch_environment_->Merge(environment());
+  environment()->Pop();
+  environment()->MarkAsUnreachable();
+}
+
+
+void TryCatchBuilder::EndTry() {
+  exit_environment_ = environment();
+  exception_node_ = catch_environment_->Pop();
+  set_environment(catch_environment_);
+}
+
+
+void TryCatchBuilder::EndCatch() {
+  exit_environment_->Merge(environment());
+  set_environment(exit_environment_);
 }
+
+
+void TryFinallyBuilder::BeginTry() {
+  finally_environment_ = environment()->CopyAsUnreachable();
+  finally_environment_->Push(nullptr);
+}
+
+
+void TryFinallyBuilder::LeaveTry(Node* token) {
+  environment()->Push(token);
+  finally_environment_->Merge(environment());
+  environment()->Pop();
+}
+
+
+void TryFinallyBuilder::EndTry(Node* fallthrough_token) {
+  environment()->Push(fallthrough_token);
+  finally_environment_->Merge(environment());
+  environment()->Pop();
+  token_node_ = finally_environment_->Pop();
+  set_environment(finally_environment_);
+}
+
+
+void TryFinallyBuilder::EndFinally() {
+  // Nothing to be done here.
 }
-}  // namespace v8::internal::compiler
+
+}  // namespace compiler
+}  // namespace internal
+}  // namespace v8
index 7ab12d892cdd6dc727a676fd18e1adb078fff0ce..d190f61f5dc2b480b2910fbebc1bb97c6e261577 100644 (file)
@@ -15,16 +15,15 @@ namespace internal {
 namespace compiler {
 
 // Base class for all control builders. Also provides a common interface for
-// control builders to handle 'break' and 'continue' statements when they are
-// used to model breakable statements.
+// control builders to handle 'break' statements when they are used to model
+// breakable statements.
 class ControlBuilder {
  public:
   explicit ControlBuilder(AstGraphBuilder* builder) : builder_(builder) {}
   virtual ~ControlBuilder() {}
 
-  // Interface for break and continue.
+  // Interface for break.
   virtual void Break() { UNREACHABLE(); }
-  virtual void Continue() { UNREACHABLE(); }
 
  protected:
   typedef AstGraphBuilder Builder;
@@ -69,11 +68,11 @@ class LoopBuilder FINAL : public ControlBuilder {
 
   // Primitive control commands.
   void BeginLoop(BitVector* assigned, bool is_osr = false);
+  void Continue();
   void EndBody();
   void EndLoop();
 
-  // Primitive support for break and continue.
-  void Continue() FINAL;
+  // Primitive support for break.
   void Break() FINAL;
 
   // Compound control commands for conditional break.
@@ -137,6 +136,54 @@ class BlockBuilder FINAL : public ControlBuilder {
   Environment* break_environment_;  // Environment after the block exits.
 };
 
+
+// Tracks control flow for a try-catch statement.
+class TryCatchBuilder FINAL : public ControlBuilder {
+ public:
+  explicit TryCatchBuilder(AstGraphBuilder* builder)
+      : ControlBuilder(builder),
+        catch_environment_(NULL),
+        exit_environment_(NULL),
+        exception_node_(NULL) {}
+
+  // Primitive control commands.
+  void BeginTry();
+  void Throw(Node* exception);
+  void EndTry();
+  void EndCatch();
+
+  // Returns the exception value inside the 'catch' body.
+  Node* GetExceptionNode() const { return exception_node_; }
+
+ private:
+  Environment* catch_environment_;  // Environment for the 'catch' body.
+  Environment* exit_environment_;   // Environment after the statement.
+  Node* exception_node_;            // Node for exception in 'catch' body.
+};
+
+
+// Tracks control flow for a try-finally statement.
+class TryFinallyBuilder FINAL : public ControlBuilder {
+ public:
+  explicit TryFinallyBuilder(AstGraphBuilder* builder)
+      : ControlBuilder(builder),
+        finally_environment_(NULL),
+        token_node_(NULL) {}
+
+  // Primitive control commands.
+  void BeginTry();
+  void LeaveTry(Node* token);
+  void EndTry(Node* token);
+  void EndFinally();
+
+  // Returns the dispatch token value inside the 'finally' body.
+  Node* GetDispatchTokenNode() const { return token_node_; }
+
+ private:
+  Environment* finally_environment_;  // Environment for the 'finally' body.
+  Node* token_node_;                  // Node for token in 'finally' body.
+};
+
 }  // namespace compiler
 }  // namespace internal
 }  // namespace v8
index 947c576d020da45db7e9720d538377d709aab4ed..02d6ff08fd55336e180d3264b701fbbae2dcdd63 100644 (file)
@@ -77,8 +77,8 @@ class GraphBuilder {
   Graph* graph_;
 };
 
-}
-}
-}  // namespace v8::internal::compiler
+}  // namespace compiler
+}  // namespace internal
+}  // namespace v8
 
 #endif  // V8_COMPILER_GRAPH_BUILDER_H__
index 797ddf9671cd2dbd556e19b3ebbc24075a9393db..c6f9011694edfe26fb7c5429a82b9f14043874f8 100644 (file)
@@ -104,7 +104,6 @@ REPLACE_COMPARE_IC_CALL(JSGreaterThanOrEqual, Token::GTE)
 REPLACE_RUNTIME_CALL(JSTypeOf, Runtime::kTypeof)
 REPLACE_RUNTIME_CALL(JSCreate, Runtime::kAbort)
 REPLACE_RUNTIME_CALL(JSCreateFunctionContext, Runtime::kNewFunctionContext)
-REPLACE_RUNTIME_CALL(JSCreateCatchContext, Runtime::kPushCatchContext)
 REPLACE_RUNTIME_CALL(JSCreateWithContext, Runtime::kPushWithContext)
 REPLACE_RUNTIME_CALL(JSCreateBlockContext, Runtime::kPushBlockContext)
 REPLACE_RUNTIME_CALL(JSCreateModuleContext, Runtime::kPushModuleContext)
@@ -364,6 +363,13 @@ void JSGenericLowering::LowerJSStoreContext(Node* node) {
 }
 
 
+void JSGenericLowering::LowerJSCreateCatchContext(Node* node) {
+  Unique<String> name = OpParameter<Unique<String>>(node);
+  PatchInsertInput(node, 0, jsgraph()->HeapConstant(name));
+  ReplaceWithRuntimeCall(node, Runtime::kPushCatchContext);
+}
+
+
 void JSGenericLowering::LowerJSCallConstruct(Node* node) {
   int arity = OpParameter<int>(node);
   CallConstructStub stub(isolate(), NO_CALL_CONSTRUCTOR_FLAGS);
index aa76a3b6f51e139865dd766f1845d7301499f0df..195bdbc8680827682441fcaf073454c7cbbedf94 100644 (file)
@@ -402,7 +402,7 @@ const Operator* JSOperatorBuilder::CreateCatchContext(
   return new (zone()) Operator1<Unique<String>>(                 // --
       IrOpcode::kJSCreateCatchContext, Operator::kNoProperties,  // opcode
       "JSCreateCatchContext",                                    // name
-      1, 1, 1, 1, 1, 0,                                          // counts
+      2, 1, 1, 1, 1, 0,                                          // counts
       name);                                                     // parameter
 }
 
index 5ccd47fbc7a7c87b320673102218863d33abf035..972cd437f8f87432f24e52cea2d8b5bd02f5a7c2 100644 (file)
@@ -416,6 +416,7 @@ DEFINE_BOOL(turbo_verify_allocation, DEBUG_BOOL,
 DEFINE_BOOL(turbo_move_optimization, true, "optimize gap moves in TurboFan")
 DEFINE_BOOL(turbo_jt, true, "enable jump threading in TurboFan")
 DEFINE_BOOL(turbo_osr, false, "enable OSR in TurboFan")
+DEFINE_BOOL(turbo_exceptions, false, "enable exception handling in TurboFan")
 DEFINE_BOOL(turbo_stress_loop_peeling, false,
             "stress loop peeling optimization")
 
index 0712ab62057d742eef946eb74b3c4cc0a6e5e0ab..43ce91b56b6928a5b0de65fc0dd66472a7e51edf 100644 (file)
@@ -43,3 +43,117 @@ TEST(ThrowSourcePosition) {
   CHECK_EQ(4, message->GetLineNumber());
   CHECK_EQ(95, message->GetStartPosition());
 }
+
+
+// TODO(mstarzinger): Increase test coverage by having similar tests within the
+// mjsunit suite to also test integration with other components (e.g. OSR).
+
+
+TEST(Catch) {
+  i::FLAG_turbo_exceptions = true;
+  const char* src =
+      "(function(a,b) {"
+      "  var r = '-';"
+      "  try {"
+      "    r += 'A-';"
+      "    throw 'B-';"
+      "  } catch (e) {"
+      "    r += e;"
+      "  }"
+      "  return r;"
+      "})";
+  FunctionTester T(src);
+
+  T.CheckCall(T.Val("-A-B-"));
+}
+
+
+TEST(CatchNested) {
+  i::FLAG_turbo_exceptions = true;
+  const char* src =
+      "(function(a,b) {"
+      "  var r = '-';"
+      "  try {"
+      "    r += 'A-';"
+      "    throw 'C-';"
+      "  } catch (e) {"
+      "    try {"
+      "      throw 'B-';"
+      "    } catch (e) {"
+      "      r += e;"
+      "    }"
+      "    r += e;"
+      "  }"
+      "  return r;"
+      "})";
+  FunctionTester T(src);
+
+  T.CheckCall(T.Val("-A-B-C-"));
+}
+
+
+TEST(CatchBreak) {
+  i::FLAG_turbo_exceptions = true;
+  const char* src =
+      "(function(a,b) {"
+      "  var r = '-';"
+      "  L: try {"
+      "    r += 'A-';"
+      "    if (a) break L;"
+      "    r += 'B-';"
+      "    throw 'C-';"
+      "  } catch (e) {"
+      "    if (b) break L;"
+      "    r += e;"
+      "  }"
+      "  r += 'D-';"
+      "  return r;"
+      "})";
+  FunctionTester T(src);
+
+  T.CheckCall(T.Val("-A-D-"), T.true_value(), T.false_value());
+  T.CheckCall(T.Val("-A-B-D-"), T.false_value(), T.true_value());
+  T.CheckCall(T.Val("-A-B-C-D-"), T.false_value(), T.false_value());
+}
+
+
+TEST(Finally) {
+  i::FLAG_turbo_exceptions = true;
+  const char* src =
+      "(function(a,b) {"
+      "  var r = '-';"
+      "  try {"
+      "    r += 'A-';"
+      "  } finally {"
+      "    r += 'B-';"
+      "  }"
+      "  return r;"
+      "})";
+  FunctionTester T(src);
+
+  T.CheckCall(T.Val("-A-B-"));
+}
+
+
+TEST(FinallyBreak) {
+  i::FLAG_turbo_exceptions = true;
+  const char* src =
+      "(function(a,b) {"
+      "  var r = '-';"
+      "  L: try {"
+      "    r += 'A-';"
+      "    if (a) return r;"
+      "    r += 'B-';"
+      "    if (b) break L;"
+      "    r += 'C-';"
+      "  } finally {"
+      "    r += 'D-';"
+      "  }"
+      "  return r;"
+      "})";
+  FunctionTester T(src);
+
+  T.CheckCall(T.Val("-A-"), T.true_value(), T.false_value());
+  T.CheckCall(T.Val("-A-B-D-"), T.false_value(), T.true_value());
+  T.CheckCall(T.Val("-A-B-C-D-"), T.false_value(), T.false_value());
+}