Index: src/compiler/ast-graph-builder.cc |
diff --git a/src/compiler/ast-graph-builder.cc b/src/compiler/ast-graph-builder.cc |
index 08f0fa2581f7044c2feeec55afd3ca7d99772da1..98d80e8a9d2ce17dc065a0574c4f8c9de629fde9 100644 |
--- a/src/compiler/ast-graph-builder.cc |
+++ b/src/compiler/ast-graph-builder.cc |
@@ -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) { |