Chromium Code Reviews| Index: runtime/vm/flow_graph_builder.cc |
| diff --git a/runtime/vm/flow_graph_builder.cc b/runtime/vm/flow_graph_builder.cc |
| index aae3203a5d2c00beefa8c56363d163d22f45e81e..b4592697282fe9a583c9890d15df89498758d553 100644 |
| --- a/runtime/vm/flow_graph_builder.cc |
| +++ b/runtime/vm/flow_graph_builder.cc |
| @@ -260,7 +260,10 @@ FlowGraphBuilder::FlowGraphBuilder( |
| args_pushed_(0), |
| nesting_stack_(NULL), |
| osr_id_(osr_id), |
| - is_optimizing_(is_optimizing) { } |
| + is_optimizing_(is_optimizing), |
| + jump_cnt_(0), |
| + await_joins_(new(I) ZoneGrowableArray<JoinEntryInstr*>()), |
| + await_levels_(new(I) ZoneGrowableArray<intptr_t>()) { } |
| void FlowGraphBuilder::AddCatchEntry(CatchBlockEntryInstr* entry) { |
| @@ -1038,15 +1041,6 @@ void EffectGraphVisitor::VisitReturnNode(ReturnNode* node) { |
| } |
| } |
| - intptr_t current_context_level = owner()->context_level(); |
| - ASSERT(current_context_level >= 0); |
| - if (owner()->parsed_function()->saved_entry_context_var() != NULL) { |
| - // CTX on entry was saved, but not linked as context parent. |
| - BuildRestoreContext(*owner()->parsed_function()->saved_entry_context_var()); |
| - } else { |
| - UnchainContexts(current_context_level); |
| - } |
| - |
| // Async functions contain two types of return statements: |
| // 1) Returns that should complete the completer once all finally blocks have |
| // been inlined (call: :async_completer.complete(return_value)). These |
| @@ -1083,6 +1077,16 @@ void EffectGraphVisitor::VisitReturnNode(ReturnNode* node) { |
| return_value = BuildNullValue(); |
| } |
| + intptr_t current_context_level = owner()->context_level(); |
| + ASSERT(current_context_level >= 0); |
| + if (owner()->parsed_function()->saved_entry_context_var() != NULL) { |
| + // CTX on entry was saved, but not linked as context parent. |
| + BuildRestoreContext(*owner()->parsed_function()->saved_entry_context_var()); |
| + } else { |
| + UnchainContexts(current_context_level); |
| + } |
| + |
| + |
| AddReturnExit(node->token_pos(), return_value); |
| } |
| @@ -1444,6 +1448,55 @@ AssertAssignableInstr* EffectGraphVisitor::BuildAssertAssignable( |
| } |
| +void EffectGraphVisitor::BuildAwaitJump( |
| + const LocalVariable& continuation_result, |
| + const LocalVariable& old_ctx, |
| + const intptr_t old_ctx_level, |
| + JoinEntryInstr* target) { |
| + // Building a jump consists of the following actions: |
| + // * Record the current continuation result in a temporary. |
| + // * Restore the old context. |
| + // * Overwrite the old context's continuation result with the temporary. |
| + // * Append a Goto to the target's join. |
| + ASSERT(continuation_result.is_captured()); |
| + ASSERT(old_ctx.is_captured()); |
| + // Before resoring the continuation context we need to temporary save the |
| + // current continuation result. |
| + Value* continuation_result_value = Bind(BuildLoadLocal(continuation_result)); |
| + Do(BuildStoreExprTemp(continuation_result_value)); |
| + |
| + // Restore the saved continuation context. |
| + BuildRestoreContext(old_ctx); |
| + |
| + // Pass over the continuation result. |
| + Value* saved_continuation_result = Bind(BuildLoadExprTemp()); |
| + // FGB is at top context level, but the await target has possibly been |
|
srdjan
2014/08/20 17:50:06
ditto for FGB
Michael Lippautz (Google)
2014/08/20 20:56:06
Done.
|
| + // recorded in a nested context (old_ctx_level). We need to unroll manually |
| + // here. |
| + LocalVariable* tmp_var = EnterTempLocalScope(saved_continuation_result); |
| + intptr_t delta = old_ctx_level - continuation_result.owner()->context_level(); |
| + ASSERT(delta >= 0); |
| + Value* context = Bind(new(I) CurrentContextInstr()); |
| + while (delta-- > 0) { |
| + context = Bind(new(I) LoadFieldInstr( |
| + context, Context::parent_offset(), Type::ZoneHandle(I, Type::null()), |
| + Scanner::kNoSourcePos)); |
| + } |
| + Value* tmp_val = Bind(new(I) LoadLocalInstr(*tmp_var)); |
| + StoreInstanceFieldInstr* store = new(I) StoreInstanceFieldInstr( |
| + Context::variable_offset(continuation_result.index()), |
| + context, |
| + tmp_val, |
| + kEmitStoreBarrier, |
| + Scanner::kNoSourcePos); |
| + Do(store); |
| + Do(ExitTempLocalScope(tmp_var)); |
| + |
| + // Goto saved join. |
| + Goto(target); |
| +} |
| + |
| + |
| // Used for type casts and to test assignments. |
| Value* EffectGraphVisitor::BuildAssignableValue(intptr_t token_pos, |
| Value* value, |
| @@ -2120,6 +2173,51 @@ void EffectGraphVisitor::VisitAwaitNode(AwaitNode* node) { |
| } |
| +void EffectGraphVisitor::VisitAwaitMarkerNode(AwaitMarkerNode* node) { |
| + if (node->type() == AwaitMarkerNode::kNewContinuationState) { |
| + // We need to create a new await state which involves: |
| + // * Increase the jump counter. Sanity check against the list of targets. |
| + // * Save the current context for resuming. |
| + ASSERT(node->scope() != NULL); |
| + LocalVariable* jump_var = node->scope()->LookupVariable( |
| + Symbols::AwaitJumpVar(), false); |
| + LocalVariable* ctx_var = node->scope()->LookupVariable( |
| + Symbols::AwaitContextVar(), false); |
| + ASSERT(jump_var != NULL && jump_var->is_captured()); |
| + ASSERT(ctx_var != NULL && ctx_var->is_captured()); |
| + const int32_t jump_cnt = owner()->next_await_counter(); |
| + ASSERT(jump_cnt >= 0); |
| + // Sanity check that we always add a JoinEntryInstr before adding a new |
| + // state. |
| + ASSERT(jump_cnt == owner()->await_joins()->length()); |
| + // Store the counter in :await_jump_var. |
| + StoreLocalNode* store_jump_cnt = new(I) StoreLocalNode( |
|
Florian Schneider
2014/08/21 11:09:29
Why do you create an AST node first, only to visit
Michael Lippautz (Google)
2014/08/21 16:39:14
Yes and no :)
I inlined the IL instructions, but
|
| + Scanner::kNoSourcePos, |
| + jump_var, |
| + new(I) LiteralNode( |
| + Scanner::kNoSourcePos, Smi::ZoneHandle(I, Smi::New(jump_cnt)))); |
| + EffectGraphVisitor for_effect(owner()); |
| + store_jump_cnt->Visit(&for_effect); |
| + Append(for_effect); |
| + // Save the current context for resuming. |
| + BuildSaveContext(*ctx_var); |
| + owner()->await_levels()->Add(owner()->context_level()); |
| + return; |
| + } else if (node->type() == AwaitMarkerNode::kTargetForContinuation) { |
|
Florian Schneider
2014/08/21 11:09:29
No need for "else if" if you return inside the if-
Michael Lippautz (Google)
2014/08/21 16:39:14
Done.
|
| + // We need to create a new await target which involves: |
| + // * Append a join that is also added to the list that will later result in |
| + // a preamble. |
| + JoinEntryInstr* const join = new(I) JoinEntryInstr( |
| + owner()->AllocateBlockId(), owner()->try_index()); |
| + owner()->await_joins()->Add(join); |
| + Goto(join); |
| + exit_ = join; |
| + return; |
| + } |
| + UNREACHABLE(); |
| +} |
| + |
| + |
| intptr_t EffectGraphVisitor::GetCurrentTempLocalIndex() const { |
| return kFirstLocalSlotFromFp |
| - owner()->num_stack_locals() |
| @@ -3626,6 +3724,22 @@ void EffectGraphVisitor::VisitSequenceNode(SequenceNode* node) { |
| } |
| } |
| + // Continuation part: |
| + // If this node sequence is the body of an async closure leave room for a |
| + // preamble. The preamble is generated after visiting the body. |
| + GotoInstr* preamble_start = NULL; |
| + if (node == owner()->parsed_function()->node_sequence() && |
|
srdjan
2014/08/20 17:50:06
Add parentheses.
Michael Lippautz (Google)
2014/08/20 20:56:06
Done.
|
| + owner()->parsed_function()->function().is_async_closure()) { |
| + JoinEntryInstr* preamble_end = new(I) JoinEntryInstr( |
| + owner()->AllocateBlockId(), owner()->try_index()); |
| + ASSERT(exit() != NULL); |
| + exit()->Goto(preamble_end); |
| + ASSERT(exit()->next()->IsGoto()); |
| + preamble_start = exit()->next()->AsGoto(); |
| + ASSERT(preamble_start->IsGoto()); |
| + exit_ = preamble_end; |
| + } |
| + |
| intptr_t i = 0; |
| while (is_open() && (i < node->length())) { |
| EffectGraphVisitor for_effect(owner()); |
| @@ -3637,6 +3751,68 @@ void EffectGraphVisitor::VisitSequenceNode(SequenceNode* node) { |
| } |
| } |
| + // Continuation part: |
| + // After generating the CFG for the body we can create the preamble because we |
| + // know exactly how many continuation states we need. |
| + if (node == owner()->parsed_function()->node_sequence() && |
|
srdjan
2014/08/20 17:50:06
ditto
Michael Lippautz (Google)
2014/08/20 20:56:06
Done.
|
| + owner()->parsed_function()->function().is_async_closure()) { |
| + ASSERT(preamble_start != NULL); |
| + // We are at the top level. Fetch the corresponding scope. |
| + LocalScope* top_scope = node->scope(); |
| + LocalVariable* jump_var = top_scope->LookupVariable( |
| + Symbols::AwaitJumpVar(), false); |
| + LocalVariable* ctx_var = top_scope->LookupVariable( |
| + Symbols::AwaitContextVar(), false); |
| + ASSERT(jump_var != NULL && jump_var->is_captured()); |
| + ASSERT(ctx_var != NULL && ctx_var->is_captured()); |
| + |
| + Instruction* saved_entry = entry_; |
| + Instruction* saved_exit = exit_; |
| + entry_ = NULL; |
| + exit_ = NULL; |
| + |
| + LoadLocalNode* load_jump_cnt = new(I) LoadLocalNode( |
| + Scanner::kNoSourcePos, jump_var); |
| + ComparisonNode* check_jump_cnt; |
| + const int32_t num_await_states = owner()->await_joins()->length(); |
| + for (int32_t i = 0; i < num_await_states; i++) { |
| + check_jump_cnt = new(I) ComparisonNode( |
| + Scanner::kNoSourcePos, |
| + Token::kEQ, |
| + load_jump_cnt, |
| + new(I) LiteralNode( |
| + Scanner::kNoSourcePos, Smi::ZoneHandle(I, Smi::New(i)))); |
| + TestGraphVisitor for_test(owner(), Scanner::kNoSourcePos); |
| + check_jump_cnt->Visit(&for_test); |
| + EffectGraphVisitor for_true(owner()); |
| + EffectGraphVisitor for_false(owner()); |
| + |
| + LocalVariable* continuation_result_var = |
| + top_scope->LookupVariable(Symbols::AsyncOperationParam(), false); |
| + ASSERT(continuation_result_var != NULL); |
| + for_true.BuildAwaitJump(*continuation_result_var, |
| + *ctx_var, |
| + (*owner()->await_levels())[i], |
| + (*owner()->await_joins())[i]); |
| + Join(for_test, for_true, for_false); |
| + |
| + if (i == 0) { |
| + // Link up the preamble start. |
| + preamble_start->previous()->set_next(for_test.entry()); |
| + } |
| + if (i == (num_await_states - 1)) { |
| + // Link up preamble end. |
| + if (exit_ == NULL) { |
| + exit_ = preamble_start; |
| + } else { |
| + exit_->LinkTo(preamble_start); |
| + } |
| + } |
| + } |
| + entry_ = saved_entry; |
| + exit_ = saved_exit; |
| + } |
| + |
| if (is_open()) { |
| if (MustSaveRestoreContext(node)) { |
| BuildRestoreContext( |