Chromium Code Reviews| 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..221a4a1e22e70f24c061a3b8c97835ef692965f0 100644 |
| --- a/src/compiler/ast-graph-builder.cc |
| +++ b/src/compiler/ast-graph-builder.cc |
| @@ -21,6 +21,122 @@ 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); |
| + |
| + 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_; |
| +}; |
| + |
| + |
| +// 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; |
|
titzer
2015/02/03 09:50:13
Would be nice to have the bodies inline here :-)
Michael Starzinger
2015/02/03 09:59:21
Done. You slight preference is my command. :)
|
| + |
| + 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; |
| + |
| + 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; |
| + |
| + 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; |
| + |
| + private: |
| + DeferredCommands* commands_; |
| + TryFinallyBuilder* control_; |
| +}; |
| + |
| + |
| AstGraphBuilder::AstGraphBuilder(Zone* local_zone, CompilationInfo* info, |
| JSGraph* jsgraph, LoopAssignmentAnalysis* loop) |
| : local_zone_(local_zone), |
| @@ -29,7 +145,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 +186,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 +248,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 +419,185 @@ 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_); |
| +// 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_; |
| +}; |
| + |
| + |
| +bool AstGraphBuilder::ControlScope::Execute(Command command, Statement* target, |
| + Node* value) { |
| + // For function-level control. |
| + switch (command) { |
| + 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; |
| +} |
| + |
| + |
| +bool AstGraphBuilder::ControlScopeForBreakable::Execute(Command command, |
| + Statement* target, |
| + Node* value) { |
| + if (target != target_) return false; // We are not the command target. |
| + switch (command) { |
| + case CMD_BREAK: |
| + control_->Break(); |
| + return true; |
| + case CMD_CONTINUE: |
| + case CMD_THROW: |
| + case CMD_RETURN: |
| + break; |
| + } |
| + return false; |
| +} |
| + |
| + |
| +bool AstGraphBuilder::ControlScopeForIteration::Execute(Command command, |
| + Statement* target, |
| + Node* value) { |
| + if (target != target_) return false; // We are not the command target. |
| + switch (command) { |
| + case CMD_BREAK: |
| + control_->Break(); |
| + return true; |
| + case CMD_CONTINUE: |
| + control_->Continue(); |
| + return true; |
| + case CMD_THROW: |
| + case CMD_RETURN: |
| + break; |
| + } |
| + return false; |
| +} |
| + |
| + |
| +bool AstGraphBuilder::ControlScopeForCatch::Execute(Command command, |
| + Statement* target, |
| + Node* value) { |
| + switch (command) { |
| + case CMD_THROW: |
| + control_->Throw(value); |
| + return true; |
| + case CMD_BREAK: |
| + case CMD_CONTINUE: |
| + case CMD_RETURN: |
| + break; |
| + } |
| + return false; |
| +} |
| + |
| + |
| +bool AstGraphBuilder::ControlScopeForFinally::Execute(Command cmd, |
| + Statement* target, |
| + Node* value) { |
| + Node* token = commands_->RecordCommand(cmd, target, value); |
| + control_->LeaveTry(token); |
| + return true; |
| +} |
| + |
| + |
| +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::BreakableScope::BreakTarget(BreakableStatement* stmt) { |
| - FindBreakable(stmt)->control_->Break(); |
| +void AstGraphBuilder::ControlScope::BreakTo(BreakableStatement* stmt) { |
| + PerformCommand(CMD_BREAK, stmt, nullptr); |
| } |
| -void AstGraphBuilder::BreakableScope::ContinueTarget(BreakableStatement* stmt) { |
| - FindBreakable(stmt)->control_->Continue(); |
| +void AstGraphBuilder::ControlScope::ContinueTo(BreakableStatement* stmt) { |
| + PerformCommand(CMD_CONTINUE, stmt, nullptr); |
| +} |
| + |
| + |
| +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 +760,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 +805,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 +835,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 +1088,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 +1715,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 +2166,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 +2694,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) { |