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 9e8b951b04f36eb0bf1fbe981f92661f8ecaead0..1943f74a7c176ea95b50f815b859d825c5f52003 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,183 @@ 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()->IntPtrConstant(deferred_.size()); |
| + } |
| + Node* NewPathTokenForImplicitFallThrough() { |
| + return owner_->jsgraph()->IntPtrConstant(-1); |
| + } |
| + Node* NewPathDispatchCondition(Node* t1, Node* t2) { |
| + return owner_->NewNode(owner_->javascript()->StrictEqual(), t1, t2); |
|
titzer
2015/02/02 09:47:29
I think you want pointer/integer equality here. JS
Michael Starzinger
2015/02/02 14:47:41
Done.
Michael Starzinger
2015/02/02 17:08:41
Yeah, well, ain't gonna happen as long as our Phi'
|
| + } |
| + |
| + 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: |
| + owner()->BuildThrow(value); |
| + return true; |
| + case CMD_RETURN: |
| + owner()->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->drop_extra()); |
| 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) owner()->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::BreakTarget(BreakableStatement* stmt) { |
| + PerformCommand(CMD_BREAK, stmt, nullptr); |
| } |
| -void AstGraphBuilder::BreakableScope::ContinueTarget(BreakableStatement* stmt) { |
| - FindBreakable(stmt)->control_->Continue(); |
| +void AstGraphBuilder::ControlScope::ContinueTarget(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); |
| } |
| @@ -472,7 +631,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 +676,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 +706,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 +959,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 +1586,8 @@ 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); |
| + execution_control()->ThrowValue(exception); |
| + ast_context()->ProduceValue(exception); |
| } |
| @@ -1822,7 +2030,7 @@ void AstGraphBuilder::VisitIfNotNull(Statement* stmt) { |
| void AstGraphBuilder::VisitIterationBody(IterationStatement* stmt, |
| LoopBuilder* loop, int drop_extra) { |
| - BreakableScope scope(this, stmt, loop, drop_extra); |
| + ControlScopeForIteration scope(this, stmt, loop, drop_extra); |
| Visit(stmt->body()); |
| } |
| @@ -2349,6 +2557,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. |
|
Michael Starzinger
2015/01/30 14:30:27
This is the TODO I still need to address.
Michael Starzinger
2015/02/02 15:08:02
I added a workaround that only uses the ControlSco
|
| + // 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) { |