Chromium Code Reviews| Index: src/interpreter/bytecode-generator.cc |
| diff --git a/src/interpreter/bytecode-generator.cc b/src/interpreter/bytecode-generator.cc |
| index 5f640854f751764938498f4fe2b33a00fc4c9543..ffd6f5a76f9a8d90ca61e9a1bc13d9e9290c82fb 100644 |
| --- a/src/interpreter/bytecode-generator.cc |
| +++ b/src/interpreter/bytecode-generator.cc |
| @@ -88,9 +88,13 @@ class BytecodeGenerator::ControlScope BASE_EMBEDDED { |
| void Break(Statement* stmt) { PerformCommand(CMD_BREAK, stmt); } |
| void Continue(Statement* stmt) { PerformCommand(CMD_CONTINUE, stmt); } |
| + void ReturnAccumulator() { PerformCommand(CMD_RETURN, nullptr); } |
| + void ReThrowAccumulator() { PerformCommand(CMD_RETHROW, nullptr); } |
| + |
| + class DeferredCommands; |
| protected: |
| - enum Command { CMD_BREAK, CMD_CONTINUE }; |
| + enum Command { CMD_BREAK, CMD_CONTINUE, CMD_RETURN, CMD_RETHROW }; |
| void PerformCommand(Command command, Statement* statement); |
| virtual bool Execute(Command command, Statement* statement) = 0; |
| @@ -105,6 +109,108 @@ class BytecodeGenerator::ControlScope BASE_EMBEDDED { |
| }; |
| +// 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 BytecodeGenerator::ControlScope::DeferredCommands final { |
| + public: |
| + DeferredCommands(BytecodeGenerator* generator, Register token_register, |
| + Register result_register) |
| + : generator_(generator), |
| + deferred_(generator->zone()), |
| + token_register_(token_register), |
| + result_register_(result_register) {} |
| + |
| + // 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 {nullptr}. |
| + int 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. |
|
rmcilroy
2016/01/21 14:30:49
Add a comment that this expects the result to be i
Michael Starzinger
2016/01/22 10:18:11
Done.
|
| + void RecordCommand(Command command, Statement* statement) { |
| + int token = static_cast<int>(deferred_.size()); |
| + deferred_.push_back({command, statement, token}); |
| + builder()->StoreAccumulatorInRegister(result_register_); |
|
rmcilroy
2016/01/21 14:30:49
nit - newline above here
Michael Starzinger
2016/01/22 10:18:11
Done.
|
| + builder()->LoadLiteral(Smi::FromInt(token)); |
| + builder()->StoreAccumulatorInRegister(token_register_); |
| + } |
| + |
| + // Records the dispatch token to be used to identify the re-throw path when |
| + // the finally-block has been entered through the exception handler. |
|
rmcilroy
2016/01/21 14:30:49
Ditto (comment that exception should be in accumul
Michael Starzinger
2016/01/22 10:18:11
Done.
|
| + void RecordHandlerReThrowPath() { |
| + // The accumulator contains the exception object. |
| + RecordCommand(CMD_RETHROW, nullptr); |
| + } |
| + |
| + // Records 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. |
| + void RecordFallThroughPath() { |
| + builder()->LoadLiteral(Smi::FromInt(-1)); |
| + builder()->StoreAccumulatorInRegister(token_register_); |
| + } |
| + |
| + // 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() { |
| + SwitchBuilder dispatch(builder(), static_cast<int>(deferred_.size() + 1)); |
|
oth
2016/01/21 14:06:44
Comment on the +1.
Michael Starzinger
2016/01/21 14:24:53
Done.
|
| + for (size_t i = 0; i < deferred_.size(); ++i) { |
| + Entry& entry = deferred_[i]; |
| + builder()->LoadLiteral(Smi::FromInt(entry.token)); |
| + builder()->CompareOperation(Token::EQ_STRICT, token_register_, |
| + Strength::WEAK); |
| + dispatch.Case(static_cast<int>(i)); |
| + } |
| + dispatch.DefaultAt(static_cast<int>(deferred_.size())); |
|
rmcilroy
2016/01/21 14:30:49
I'm guessing the default case here only for the fa
Michael Starzinger
2016/01/22 10:18:11
Acknowledged. Comment already added due to Orions
|
| + for (size_t i = 0; i < deferred_.size(); ++i) { |
| + Entry& entry = deferred_[i]; |
| + dispatch.SetCaseTarget(static_cast<int>(i)); |
| + builder()->LoadAccumulatorWithRegister(result_register_); |
| + execution_control()->PerformCommand(entry.command, entry.statement); |
| + } |
| + dispatch.SetCaseTarget(static_cast<int>(deferred_.size())); |
| + } |
| + |
| + BytecodeArrayBuilder* builder() { return generator_->builder(); } |
| + ControlScope* execution_control() { return generator_->execution_control(); } |
| + |
| + private: |
| + BytecodeGenerator* generator_; |
| + ZoneVector<Entry> deferred_; |
| + Register token_register_; |
| + Register result_register_; |
| +}; |
| + |
| + |
| +// Scoped class for dealing with control flow reaching the function level. |
| +class BytecodeGenerator::ControlScopeForTopLevel final |
| + : public BytecodeGenerator::ControlScope { |
| + public: |
| + explicit ControlScopeForTopLevel(BytecodeGenerator* generator) |
| + : ControlScope(generator) {} |
| + |
| + protected: |
| + virtual bool Execute(Command command, Statement* statement) { |
| + switch (command) { |
| + case CMD_BREAK: |
| + case CMD_CONTINUE: |
| + break; |
| + case CMD_RETURN: |
| + generator()->builder()->Return(); |
| + return true; |
| + case CMD_RETHROW: |
| + // TODO(mstarzinger): Should be a ReThrow instead. |
| + generator()->builder()->Throw(); |
| + return true; |
| + } |
| + return false; |
| + } |
| +}; |
| + |
| + |
| // Scoped class for enabling break inside blocks and switch blocks. |
| class BytecodeGenerator::ControlScopeForBreakable final |
| : public BytecodeGenerator::ControlScope { |
| @@ -124,6 +230,8 @@ class BytecodeGenerator::ControlScopeForBreakable final |
| control_builder_->Break(); |
| return true; |
| case CMD_CONTINUE: |
| + case CMD_RETURN: |
| + case CMD_RETHROW: |
| break; |
| } |
| return false; |
| @@ -157,6 +265,9 @@ class BytecodeGenerator::ControlScopeForIteration final |
| case CMD_CONTINUE: |
| loop_builder_->Continue(); |
| return true; |
| + case CMD_RETURN: |
| + case CMD_RETHROW: |
| + break; |
| } |
| return false; |
| } |
| @@ -167,6 +278,65 @@ class BytecodeGenerator::ControlScopeForIteration final |
| }; |
| +// Scoped class for enabling 'throw' in try-catch constructs. |
| +class BytecodeGenerator::ControlScopeForTryCatch final |
| + : public BytecodeGenerator::ControlScope { |
| + public: |
| + ControlScopeForTryCatch(BytecodeGenerator* generator, |
| + TryCatchBuilder* try_catch_builder) |
| + : ControlScope(generator), try_catch_builder_(try_catch_builder) {} |
| + |
| + protected: |
| + virtual bool Execute(Command command, Statement* statement) { |
| + switch (command) { |
| + case CMD_BREAK: |
| + case CMD_CONTINUE: |
| + case CMD_RETURN: |
| + break; |
| + case CMD_RETHROW: |
| + USE(try_catch_builder_); |
|
rmcilroy
2016/01/21 14:30:49
nit - add a TODO here for now?
Michael Starzinger
2016/01/22 10:18:11
Done.
|
| + UNIMPLEMENTED(); |
| + return true; |
| + } |
| + return false; |
| + } |
| + |
| + private: |
| + TryCatchBuilder* try_catch_builder_; |
| +}; |
| + |
| + |
| +// Scoped class for enabling control flow through try-finally constructs. |
| +class BytecodeGenerator::ControlScopeForTryFinally final |
| + : public BytecodeGenerator::ControlScope { |
| + public: |
| + ControlScopeForTryFinally(BytecodeGenerator* generator, |
| + TryFinallyBuilder* try_finally_builder, |
| + DeferredCommands* commands) |
| + : ControlScope(generator), |
| + try_finally_builder_(try_finally_builder), |
| + commands_(commands) {} |
| + |
| + protected: |
| + virtual bool Execute(Command command, Statement* statement) { |
| + switch (command) { |
| + case CMD_BREAK: |
| + case CMD_CONTINUE: |
| + case CMD_RETURN: |
| + case CMD_RETHROW: |
| + commands_->RecordCommand(command, statement); |
| + try_finally_builder_->LeaveTry(); |
| + return true; |
| + } |
| + return false; |
| + } |
| + |
| + private: |
| + TryFinallyBuilder* try_finally_builder_; |
| + DeferredCommands* commands_; |
| +}; |
| + |
| + |
| void BytecodeGenerator::ControlScope::PerformCommand(Command command, |
| Statement* statement) { |
| ControlScope* current = this; |
| @@ -365,6 +535,9 @@ Handle<BytecodeArray> BytecodeGenerator::MakeBytecode(CompilationInfo* info) { |
| // Initialize the incoming context. |
| ContextScope incoming_context(this, scope(), false); |
| + // Initialize control scope. |
| + ControlScopeForTopLevel control(this); |
| + |
| builder()->set_parameter_count(info->num_parameters_including_this()); |
| builder()->set_locals_count(scope()->num_stack_slots()); |
| builder()->set_context_count(scope()->MaxNestedContextChainLength()); |
| @@ -661,7 +834,7 @@ void BytecodeGenerator::VisitBreakStatement(BreakStatement* stmt) { |
| void BytecodeGenerator::VisitReturnStatement(ReturnStatement* stmt) { |
| VisitForAccumulatorValue(stmt->expression()); |
| - builder()->Return(); |
| + execution_control()->ReturnAccumulator(); |
| } |
| @@ -909,8 +1082,10 @@ void BytecodeGenerator::VisitTryCatchStatement(TryCatchStatement* stmt) { |
| // Evaluate the try-block inside a control scope. This simulates a handler |
| // that is intercepting 'throw' control commands. |
| try_control_builder.BeginTry(context); |
| - // TODO(mstarzinger): Control scope is missing! |
| - Visit(stmt->try_block()); |
| + { |
| + ControlScopeForTryCatch scope(this, &try_control_builder); |
| + Visit(stmt->try_block()); |
| + } |
| try_control_builder.EndTry(); |
| // Clear message object as we enter the catch block. |
| @@ -928,6 +1103,25 @@ void BytecodeGenerator::VisitTryCatchStatement(TryCatchStatement* stmt) { |
| void BytecodeGenerator::VisitTryFinallyStatement(TryFinallyStatement* stmt) { |
| TryFinallyBuilder try_control_builder(builder()); |
| + // 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. |
| + // |
| + // The result register semantics depend on how the block was entered: |
| + // - ReturnStatement: It represents the return value being returned. |
| + // - ThrowStatement: It represents the exception being thrown. |
| + // - BreakStatement/ContinueStatement: Undefined and not used. |
| + // - Falling through into finally-block: Undefined and not used. |
| + Register token = register_allocator()->NewRegister(); |
| + Register result = register_allocator()->NewRegister(); |
| + ControlScope::DeferredCommands commands(this, token, result); |
| + |
| // Preserve the context in a dedicated register, so that it can be restored |
| // when the handler is entered by the stack-unwinding machinery. |
| // TODO(mstarzinger): Be smarter about register allocation. |
| @@ -936,16 +1130,28 @@ void BytecodeGenerator::VisitTryFinallyStatement(TryFinallyStatement* stmt) { |
| // Evaluate the try-block inside a control scope. This simulates a handler |
| // that is intercepting all control commands. |
| try_control_builder.BeginTry(context); |
| - // TODO(mstarzinger): Control scope is missing! |
| - Visit(stmt->try_block()); |
| + { |
| + ControlScopeForTryFinally scope(this, &try_control_builder, &commands); |
| + Visit(stmt->try_block()); |
| + } |
| try_control_builder.EndTry(); |
| + // Record fall-through and exception cases. |
| + commands.RecordFallThroughPath(); |
| + try_control_builder.LeaveTry(); |
| + try_control_builder.BeginHandler(); |
| + commands.RecordHandlerReThrowPath(); |
| + try_control_builder.BeginFinally(); |
|
rmcilroy
2016/01/21 14:30:49
nit - newline before BeginFinally (I think it woul
Michael Starzinger
2016/01/22 10:18:11
Done. Also note that there is still functionality
|
| + |
| // Clear message object as we enter the finally block. |
| // TODO(mstarzinger): Implement this! |
| // Evaluate the finally-block. |
| Visit(stmt->finally_block()); |
| try_control_builder.EndFinally(); |
| + |
| + // Dynamic dispatch after the finally-block. |
| + commands.ApplyDeferredCommands(); |
| } |