Index: src/interpreter/bytecode-generator.cc |
diff --git a/src/interpreter/bytecode-generator.cc b/src/interpreter/bytecode-generator.cc |
index c28d4687d8a8cf5b742626739d8cbeb50ee9dc54..e8f5e442209d61ba9d5696f6f4855a74f6cc39f1 100644 |
--- a/src/interpreter/bytecode-generator.cc |
+++ b/src/interpreter/bytecode-generator.cc |
@@ -601,6 +601,7 @@ BytecodeGenerator::BytecodeGenerator(CompilationInfo* info) |
generator_resume_points_(info->literal()->yield_count(), info->zone()), |
generator_state_(), |
loop_depth_(0), |
+ catch_prediction_(HandlerTable::UNCAUGHT), |
home_object_symbol_(info->isolate()->factory()->home_object_symbol()), |
iterator_symbol_(info->isolate()->factory()->iterator_symbol()), |
prototype_string_(info->isolate()->factory()->prototype_string()), |
@@ -696,14 +697,6 @@ void BytecodeGenerator::GenerateBytecode(uintptr_t stack_limit) { |
GenerateBytecodeBody(); |
} |
- // In generator functions, we may not have visited every yield in the AST |
- // since we skip some obviously dead code. Hence the generated bytecode may |
- // contain jumps to unbound labels (resume points that will never be used). |
- // We bind these now. |
- for (auto& label : generator_resume_points_) { |
- if (!label.is_bound()) builder()->Bind(&label); |
- } |
- |
// Emit an implicit return instruction in case control flow can fall off the |
// end of the function without an explicit return being present on all paths. |
if (builder()->RequiresImplicitReturn()) { |
@@ -739,10 +732,220 @@ void BytecodeGenerator::GenerateBytecodeBody() { |
// Perform a stack-check before the body. |
builder()->StackCheck(info()->literal()->start_position()); |
+ // Build assignment to variable <function name> if function is a named |
+ // expression and the variable is used. |
+ if (info()->literal()->is_named_expression()) { |
+ Variable* function_var = scope()->function_var(); |
+ if (function_var != nullptr && !function_var->IsUnallocated()) { |
+ builder()->LoadAccumulatorWithRegister(Register::function_closure()); |
+ BuildVariableAssignment(function_var, Token::INIT, |
+ FeedbackVectorSlot::Invalid(), |
+ HoleCheckMode::kElided); |
+ } |
+ } |
rmcilroy
2017/02/06 22:27:50
This change doesn't seem related to the generator
caitp
2017/02/06 22:48:23
well, I would love to do it in a separate CL, but
rmcilroy
2017/02/07 17:24:41
OK I can live with this change in this CL, thanks
caitp
2017/02/07 18:13:08
Actually, I brought this up in the meeting today,
|
+ |
+ if (IsAsyncFunction(info()->literal()->kind())) { |
+ return GenerateBytecodeBodyForAsyncFunction(); |
+ } |
+ if (IsGeneratorFunction(info()->literal()->kind())) { |
+ return GenerateBytecodeBodyForGenerator(); |
+ } |
+ if (IsModule(info()->literal()->kind())) { |
+ return GenerateBytecodeBodyForModule(); |
+ } |
rmcilroy
2017/02/06 22:27:50
Thanks for splitting the logic out of GenerateByte
caitp
2017/02/06 22:48:23
I'm not entirely sure how this would work due to h
rmcilroy
2017/02/07 17:24:41
Yeah your right regarding RAII ControlScopes. I'd
|
+ |
+ Block* parameter_init_block = info()->literal()->parameter_init_block(); |
// Visit statements in the function body. |
+ if (parameter_init_block != nullptr) { |
+ VisitBlock(parameter_init_block); |
+ } |
rmcilroy
2017/02/06 22:27:50
Any reason for this change? Could we just do this
caitp
2017/02/06 22:48:24
Async functions need to incorporate the parameter
rmcilroy
2017/02/07 17:24:41
I see. Is it necessary that the parameter initiali
|
VisitStatements(info()->literal()->body()); |
} |
+void BytecodeGenerator::BuildAllocateAndStoreJSGeneratorObject( |
+ BuildJSGeneratorObject tag) { |
+ RegisterAllocationScope register_scope(this); |
+ RegisterList args = register_allocator()->NewRegisterList(2); |
+ builder()->MoveRegister(Register::function_closure(), args[0]); |
+ |
+ int yield_position = info()->literal()->position(); |
rmcilroy
2017/02/06 22:27:50
Could you pass through literal as an argument rath
caitp
2017/02/06 22:48:23
For the cases that would use this, aren't they alw
caitp
2017/02/06 23:29:36
nvm, thought this was specifically about yield_pos
rmcilroy
2017/02/07 17:24:41
Yeah I was meaning for all the uses in the new fun
|
+ builder()->SetExpressionPosition(yield_position); |
+ |
+ if (IsArrowFunction(info()->literal()->kind())) { |
+ // Lexical `this` |
+ builder()->LoadUndefined().StoreAccumulatorInRegister(args[1]); |
+ } else { |
+ // Receiver parameter |
+ builder()->MoveRegister(builder()->Parameter(0), args[1]); |
+ } |
+ builder()->CallRuntime(Runtime::kCreateJSGeneratorObject, args); |
+ |
+ Variable* var_generator = scope()->generator_object_var(); |
+ DCHECK_NOT_NULL(var_generator); |
+ |
+ switch (tag) { |
+ case BuildJSGeneratorObject::kInitialYield: { |
+ Register generator = args[0]; |
+ builder()->StoreAccumulatorInRegister(generator); |
+ BuildVariableAssignment(var_generator, Token::INIT, |
+ FeedbackVectorSlot::Invalid(), |
+ HoleCheckMode::kElided); |
+ BuildYield(0, generator, generator); |
+ BuildYieldResumePoint(0, Yield::kOnExceptionThrow, generator, |
+ yield_position); |
+ break; |
+ } |
+ case BuildJSGeneratorObject::kNoInitialYield: { |
+ BuildVariableAssignment(var_generator, Token::INIT, |
+ FeedbackVectorSlot::Invalid(), |
+ HoleCheckMode::kElided); |
+ break; |
+ } |
+ } |
+} |
+ |
+void BytecodeGenerator::BuildAllocateAndStoreJSPromise() { |
+ Variable* var_promise = scope()->promise_var(); |
+ DCHECK_NOT_NULL(var_promise); |
+ |
+ RegisterAllocationScope register_scope(this); |
+ RegisterList args = register_allocator()->NewRegisterList(1); |
+ builder()->LoadUndefined().StoreAccumulatorInRegister(args[0]).CallJSRuntime( |
+ Context::ASYNC_FUNCTION_PROMISE_CREATE_INDEX, args); |
+ BuildVariableAssignment(var_promise, Token::INIT, |
+ FeedbackVectorSlot::Invalid(), |
+ HoleCheckMode::kElided); |
+} |
+ |
+void BytecodeGenerator::BindUnboundGeneratorResumePoints() { |
+ DCHECK(IsResumableFunction(info()->literal()->kind())); |
+ for (auto& label : generator_resume_points_) { |
+ if (!label.is_bound()) builder()->Bind(&label); |
+ } |
+} |
+ |
+void BytecodeGenerator::GenerateBytecodeBodyForGenerator() { |
+ DCHECK(IsGeneratorFunction(info()->literal()->kind())); |
+ |
+ // Desugar parameters before generator is allocated. |
+ Block* const parameter_init_block = info()->literal()->parameter_init_block(); |
+ if (parameter_init_block != nullptr) { |
+ // Initialize non-simple parameters before initial yield. |
+ VisitBlock(parameter_init_block); |
+ } |
+ |
+ BuildTryFinally( |
+ [=]() { // Try ... |
+ BuildAllocateAndStoreJSGeneratorObject( |
+ BuildJSGeneratorObject::kInitialYield); |
+ VisitStatements(info()->literal()->body()); |
+ }, |
+ [=]() { // Finally ... |
+ Variable* var_generator = scope()->generator_object_var(); |
+ DCHECK_NOT_NULL(var_generator); |
+ |
+ BuildVariableLoad(var_generator, FeedbackVectorSlot::Invalid(), |
+ HoleCheckMode::kElided); |
+ RegisterAllocationScope register_scope(this); |
+ Register generator = register_allocator()->NewRegister(); |
+ builder()->StoreAccumulatorInRegister(generator).CallRuntime( |
+ Runtime::kInlineGeneratorClose, generator); |
+ }); |
+ |
+ // In generator functions, we may not have visited every yield in the AST |
+ // since we skip some obviously dead code. Hence the generated bytecode may |
+ // contain jumps to unbound labels (resume points that will never be used). |
+ // We bind these now. |
+ BindUnboundGeneratorResumePoints(); |
+ |
+ if (builder()->RequiresImplicitReturn()) { |
rmcilroy
2017/02/06 22:27:50
From here to the end shouldn't be necessary (it's
caitp
2017/02/06 22:48:24
The one in GenerateBytecode uses BuildReturn(), wh
rmcilroy
2017/02/07 17:24:41
Let's make that change in the followup CL which ch
|
+ // Unreachable, but required for BytecodeArrayBuilder |
+ builder()->LoadUndefined().Return(); |
+ } |
+ DCHECK(!builder()->RequiresImplicitReturn()); |
+} |
+ |
+void BytecodeGenerator::GenerateBytecodeBodyForModule() { |
+ DCHECK(IsModule(info()->literal()->kind())); |
+ |
+ // Modules do not have non-simple parameters |
+ DCHECK_NULL(info()->literal()->parameter_init_block()); |
+ |
+ BuildAllocateAndStoreJSGeneratorObject(BuildJSGeneratorObject::kInitialYield); |
+ VisitStatements(info()->literal()->body()); |
+ |
+ // In modules, we may not have visited every yield in the AST |
+ // since we skip some obviously dead code. Hence the generated bytecode may |
+ // contain jumps to unbound labels (resume points that will never be used). |
+ // We bind these now. |
+ BindUnboundGeneratorResumePoints(); |
+} |
+ |
+void BytecodeGenerator::GenerateBytecodeBodyForAsyncFunction() { |
+ DCHECK(IsAsyncFunction(info()->literal()->kind())); |
+ |
+ BuildAllocateAndStoreJSPromise(); |
+ |
+ BuildTryCatch( |
+ HandlerTable::ASYNC_AWAIT, |
+ [=]() { |
rmcilroy
2017/02/06 22:27:49
I don't like these lambdas, and the function point
caitp
2017/02/06 22:48:24
I'll give that a try.
|
+ // Try ... |
+ Block* const parameter_init_block = |
+ info()->literal()->parameter_init_block(); |
+ if (parameter_init_block != nullptr) { |
+ // Initialize non-simple parameters if necessary, reject Promise in |
+ // case of exception |
+ VisitBlock(parameter_init_block); |
+ } |
+ |
+ BuildAllocateAndStoreJSGeneratorObject( |
+ BuildJSGeneratorObject::kNoInitialYield); |
+ |
+ // Finish generating function body |
+ VisitStatements(info()->literal()->body()); |
+ }, |
+ [=](Register) { |
+ // Catch ... |
+ // Caught exception is used to reject Promise without emitting a debug |
+ // event. |
+ RegisterAllocationScope register_scope(this); |
+ RegisterList args = register_allocator()->NewRegisterList(4); |
+ Register promise = args[1]; |
+ Register exception = args[2]; |
+ |
+ builder() |
+ ->StoreAccumulatorInRegister(exception) |
+ .LoadTheHole() |
+ .SetPendingMessage(); |
+ |
+ Variable* var_promise = scope()->promise_var(); |
+ DCHECK_NOT_NULL(var_promise); |
+ BuildVariableLoad(var_promise, FeedbackVectorSlot::Invalid(), |
+ HoleCheckMode::kElided); |
+ builder() |
+ ->StoreAccumulatorInRegister(promise) |
+ .LoadUndefined() |
+ .StoreAccumulatorInRegister(args[0]) |
+ .LoadFalse() |
+ .StoreAccumulatorInRegister(args[3]) |
+ .CallJSRuntime(Context::PROMISE_INTERNAL_REJECT_INDEX, args) |
+ .LoadAccumulatorWithRegister(promise) |
+ .Return(); |
+ }); |
+ |
+ // In async functions, we may not have visited every yield in the AST |
+ // since we skip some obviously dead code. Hence the generated bytecode may |
+ // contain jumps to unbound labels (resume points that will never be used). |
+ // We bind these now. |
+ BindUnboundGeneratorResumePoints(); |
+ |
+ if (builder()->RequiresImplicitReturn()) { |
rmcilroy
2017/02/06 22:27:50
Ditto
|
+ // Unreachable, but required for BytecodeArrayBuilder |
+ builder()->LoadUndefined().Return(); |
+ } |
+ DCHECK(!builder()->RequiresImplicitReturn()); |
+} |
+ |
void BytecodeGenerator::BuildIndexedJump(Register index, size_t start_index, |
size_t size, |
ZoneVector<BytecodeLabel>& targets) { |
@@ -1334,8 +1537,11 @@ void BytecodeGenerator::VisitForOfStatement(ForOfStatement* stmt) { |
loop_builder.EndLoop(); |
} |
-void BytecodeGenerator::VisitTryCatchStatement(TryCatchStatement* stmt) { |
- TryCatchBuilder try_control_builder(builder(), stmt->catch_prediction()); |
+void BytecodeGenerator::BuildTryCatch( |
+ HandlerTable::CatchPrediction catch_prediction, |
+ std::function<void()> build_try, |
+ std::function<void(Register)> build_catch) { |
+ TryCatchBuilder try_control_builder(builder(), catch_prediction); |
// Preserve the context in a dedicated register, so that it can be restored |
// when the handler is entered by the stack-unwinding machinery. |
@@ -1348,29 +1554,43 @@ void BytecodeGenerator::VisitTryCatchStatement(TryCatchStatement* stmt) { |
try_control_builder.BeginTry(context); |
{ |
ControlScopeForTryCatch scope(this, &try_control_builder); |
- Visit(stmt->try_block()); |
+ HandlerTable::CatchPrediction last_catch_prediction = catch_prediction_; |
+ if (catch_prediction != HandlerTable::UNCAUGHT) { |
+ catch_prediction_ = catch_prediction; |
+ } |
+ build_try(); |
+ catch_prediction_ = last_catch_prediction; |
} |
try_control_builder.EndTry(); |
- // Create a catch scope that binds the exception. |
- BuildNewLocalCatchContext(stmt->variable(), stmt->scope()); |
- builder()->StoreAccumulatorInRegister(context); |
+ // Evaluate the catch-block. |
+ build_catch(context); |
+ try_control_builder.EndCatch(); |
+} |
- // If requested, clear message object as we enter the catch block. |
- if (stmt->clear_pending_message()) { |
- builder()->LoadTheHole().SetPendingMessage(); |
- } |
+void BytecodeGenerator::VisitTryCatchStatement(TryCatchStatement* stmt) { |
+ BuildTryCatch(stmt->catch_prediction(), [=]() { Visit(stmt->try_block()); }, |
+ [=](Register context) { |
+ // Create a catch scope that binds the exception. |
+ BuildNewLocalCatchContext(stmt->variable(), stmt->scope()); |
+ builder()->StoreAccumulatorInRegister(context); |
- // Load the catch context into the accumulator. |
- builder()->LoadAccumulatorWithRegister(context); |
+ // I requested, clear message object as we enter the catch |
+ // block. |
+ if (stmt->clear_pending_message()) { |
+ builder()->LoadTheHole().SetPendingMessage(); |
+ } |
- // Evaluate the catch-block. |
- VisitInScope(stmt->catch_block(), stmt->scope()); |
- try_control_builder.EndCatch(); |
+ // Load the catch context into the accumulator. |
+ builder()->LoadAccumulatorWithRegister(context); |
+ |
+ VisitInScope(stmt->catch_block(), stmt->scope()); |
+ }); |
} |
-void BytecodeGenerator::VisitTryFinallyStatement(TryFinallyStatement* stmt) { |
- TryFinallyBuilder try_control_builder(builder(), stmt->catch_prediction()); |
+void BytecodeGenerator::BuildTryFinally(std::function<void()> build_try, |
+ std::function<void()> build_finally) { |
+ TryFinallyBuilder try_control_builder(builder(), catch_prediction_); |
// 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 |
@@ -1402,7 +1622,7 @@ void BytecodeGenerator::VisitTryFinallyStatement(TryFinallyStatement* stmt) { |
try_control_builder.BeginTry(context); |
{ |
ControlScopeForTryFinally scope(this, &try_control_builder, &commands); |
- Visit(stmt->try_block()); |
+ build_try(); |
} |
try_control_builder.EndTry(); |
@@ -1421,7 +1641,7 @@ void BytecodeGenerator::VisitTryFinallyStatement(TryFinallyStatement* stmt) { |
message); |
// Evaluate the finally-block. |
- Visit(stmt->finally_block()); |
+ build_finally(); |
try_control_builder.EndFinally(); |
// Pending message object is restored on exit. |
@@ -1431,6 +1651,11 @@ void BytecodeGenerator::VisitTryFinallyStatement(TryFinallyStatement* stmt) { |
commands.ApplyDeferredCommands(); |
} |
+void BytecodeGenerator::VisitTryFinallyStatement(TryFinallyStatement* stmt) { |
+ BuildTryFinally([=]() { Visit(stmt->try_block()); }, |
+ [=]() { Visit(stmt->finally_block()); }); |
+} |
+ |
void BytecodeGenerator::VisitDebuggerStatement(DebuggerStatement* stmt) { |
builder()->SetStatementPosition(stmt); |
builder()->Debugger(); |
@@ -2287,20 +2512,31 @@ void BytecodeGenerator::VisitAssignment(Assignment* expr) { |
} |
} |
-void BytecodeGenerator::VisitYield(Yield* expr) { |
- builder()->SetExpressionPosition(expr); |
- Register value = VisitForRegisterValue(expr->expression()); |
- |
- Register generator = VisitForRegisterValue(expr->generator_object()); |
+void BytecodeGenerator::BuildYield(int yield_id, Register generator, |
+ Register value) { |
+ DCHECK_GE(yield_id, 0); |
+ DCHECK_LT(yield_id, generator_resume_points_.size()); |
+ DCHECK(generator.is_valid()); |
+ DCHECK(value.is_valid()); |
// Save context, registers, and state. Then return. |
builder() |
- ->LoadLiteral(Smi::FromInt(expr->yield_id())) |
+ ->LoadLiteral(Smi::FromInt(yield_id)) |
.SuspendGenerator(generator) |
.LoadAccumulatorWithRegister(value) |
.Return(); // Hard return (ignore any finally blocks). |
+} |
- builder()->Bind(&(generator_resume_points_[expr->yield_id()])); |
+void BytecodeGenerator::BuildYieldResumePoint(int yield_id, |
+ Yield::OnException on_exception, |
+ Register generator, |
+ int position) { |
+ DCHECK_GE(yield_id, 0); |
+ DCHECK_LT(yield_id, generator_resume_points_.size()); |
+ DCHECK(generator.is_valid()); |
+ |
+ DCHECK(!generator_resume_points_[yield_id].is_bound()); |
+ builder()->Bind(&(generator_resume_points_[yield_id])); |
// Upon resume, we continue here. |
{ |
@@ -2328,6 +2564,7 @@ void BytecodeGenerator::VisitYield(Yield* expr) { |
BytecodeLabel resume_with_return; |
BytecodeLabel resume_with_throw; |
+ // TODO(caitp): Don't generate `resume_with_return` block for non-generators |
builder() |
->LoadLiteral(Smi::FromInt(JSGeneratorObject::kNext)) |
.CompareOperation(Token::EQ_STRICT, resume_mode) |
@@ -2349,9 +2586,9 @@ void BytecodeGenerator::VisitYield(Yield* expr) { |
} |
builder()->Bind(&resume_with_throw); |
- builder()->SetExpressionPosition(expr); |
+ builder()->SetExpressionPosition(position); |
builder()->LoadAccumulatorWithRegister(input); |
- if (expr->rethrow_on_exception()) { |
+ if (on_exception == Yield::kOnExceptionRethrow) { |
builder()->ReThrow(); |
} else { |
builder()->Throw(); |
@@ -2362,6 +2599,17 @@ void BytecodeGenerator::VisitYield(Yield* expr) { |
} |
} |
+void BytecodeGenerator::VisitYield(Yield* expr) { |
+ Register generator = VisitForRegisterValue(expr->generator_object()); |
+ |
+ builder()->SetExpressionPosition(expr); |
+ Register value = VisitForRegisterValue(expr->expression()); |
+ |
+ BuildYield(expr->yield_id(), generator, value); |
+ BuildYieldResumePoint(expr->yield_id(), expr->on_exception(), generator, |
+ expr->position()); |
+} |
+ |
void BytecodeGenerator::VisitThrow(Throw* expr) { |
VisitForAccumulatorValue(expr->exception()); |
builder()->SetExpressionPosition(expr); |