 Chromium Code Reviews
 Chromium Code Reviews Issue 873423004:
  First stab at try-catch and try-finally in TurboFan.  (Closed) 
  Base URL: https://chromium.googlesource.com/v8/v8.git@master
    
  
    Issue 873423004:
  First stab at try-catch and try-finally in TurboFan.  (Closed) 
  Base URL: https://chromium.googlesource.com/v8/v8.git@master| Index: src/compiler/ast-graph-builder.cc | 
| diff --git a/src/compiler/ast-graph-builder.cc b/src/compiler/ast-graph-builder.cc | 
| index 9e8b951b04f36eb0bf1fbe981f92661f8ecaead0..54b0ad3bca8740ab0097920c558f92f4a1fc09a4 100644 | 
| --- a/src/compiler/ast-graph-builder.cc | 
| +++ b/src/compiler/ast-graph-builder.cc | 
| @@ -26,7 +26,7 @@ AstGraphBuilder::AstGraphBuilder(Zone* local_zone, CompilationInfo* info, | 
| info_(info), | 
| jsgraph_(jsgraph), | 
| globals_(0, local_zone), | 
| - breakable_(NULL), | 
| + execution_control_(NULL), | 
| execution_context_(NULL), | 
| loop_assignment_analysis_(loop) { | 
| InitializeAstVisitor(info->isolate(), local_zone); | 
| @@ -63,9 +63,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()) { | 
| @@ -123,8 +125,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()); | 
| @@ -291,25 +292,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) { | 
| + StructuredGraphBuilder::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::BreakTarget(BreakableStatement* stmt) { | 
| 
titzer
2015/02/03 09:01:22
The names {BreakTarget} and {ContinueTarget} have
 
Michael Starzinger
2015/02/03 09:18:53
Done.
 | 
| + PerformCommand(CMD_BREAK, stmt, nullptr); | 
| +} | 
| + | 
| + | 
| +void AstGraphBuilder::ControlScope::ContinueTarget(BreakableStatement* stmt) { | 
| + PerformCommand(CMD_CONTINUE, stmt, nullptr); | 
| } | 
| -void AstGraphBuilder::BreakableScope::BreakTarget(BreakableStatement* stmt) { | 
| - FindBreakable(stmt)->control_->Break(); | 
| +void AstGraphBuilder::ControlScope::ReturnValue(Node* return_value) { | 
| + PerformCommand(CMD_RETURN, nullptr, return_value); | 
| } | 
| -void AstGraphBuilder::BreakableScope::ContinueTarget(BreakableStatement* stmt) { | 
| - FindBreakable(stmt)->control_->Continue(); | 
| +void AstGraphBuilder::ControlScope::ThrowValue(Node* exception_value) { | 
| + PerformCommand(CMD_THROW, nullptr, exception_value); | 
| } | 
| @@ -472,7 +633,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. | 
| @@ -517,24 +678,19 @@ void AstGraphBuilder::VisitIfStatement(IfStatement* stmt) { | 
| void AstGraphBuilder::VisitContinueStatement(ContinueStatement* stmt) { | 
| - StructuredGraphBuilder::Environment* env = environment()->CopyAsUnreachable(); | 
| - breakable()->ContinueTarget(stmt->target()); | 
| - set_environment(env); | 
| + execution_control()->ContinueTarget(stmt->target()); | 
| } | 
| void AstGraphBuilder::VisitBreakStatement(BreakStatement* stmt) { | 
| - StructuredGraphBuilder::Environment* env = environment()->CopyAsUnreachable(); | 
| - breakable()->BreakTarget(stmt->target()); | 
| - set_environment(env); | 
| + execution_control()->BreakTarget(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); | 
| } | 
| @@ -552,7 +708,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; | 
| @@ -805,14 +961,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(); | 
| } | 
| @@ -1376,10 +1588,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); | 
| + } | 
| } | 
| @@ -1821,8 +2039,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()); | 
| } | 
| @@ -2349,6 +2567,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) { |