Index: runtime/vm/flow_graph_builder.cc |
diff --git a/runtime/vm/flow_graph_builder.cc b/runtime/vm/flow_graph_builder.cc |
index dfdec06e3ed8bcbed4058c2280507412c95d1944..40cdf4cdd57b522dd747ff1c23f3edf4030509e1 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 |
@@ -1056,7 +1050,8 @@ void EffectGraphVisitor::VisitReturnNode(ReturnNode* node) { |
// |
// We distinguish those kinds of nodes via is_regular_return(). |
// |
- if (function.is_async_closure() && node->is_regular_return()) { |
+ if (function.is_async_closure() && |
+ (node->return_type() == ReturnNode::kRegular)) { |
// Temporary store the computed return value. |
Do(BuildStoreExprTemp(return_value)); |
@@ -1083,6 +1078,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 +1449,58 @@ AssertAssignableInstr* EffectGraphVisitor::BuildAssertAssignable( |
} |
+void EffectGraphVisitor::BuildAwaitJump(LocalScope* lookup_scope, |
+ 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. |
+ LocalVariable* old_ctx = lookup_scope->LookupVariable( |
+ Symbols::AwaitContextVar(), false); |
+ LocalVariable* continuation_result = lookup_scope->LookupVariable( |
+ Symbols::AsyncOperationParam(), false); |
+ ASSERT((continuation_result != NULL) && continuation_result->is_captured()); |
+ ASSERT((old_ctx != NULL) && old_ctx->is_captured()); |
+ // Before restoring 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()); |
+ // FlowGraphBuilder is at top context level, but the await target has possibly |
+ // been 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 +2177,47 @@ void EffectGraphVisitor::VisitAwaitNode(AwaitNode* node) { |
} |
+void EffectGraphVisitor::VisitAwaitMarkerNode(AwaitMarkerNode* node) { |
+ if (node->marker_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 intptr_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. |
+ Value* jump_val = Bind(new (I) ConstantInstr( |
+ Smi::ZoneHandle(I, Smi::New(jump_cnt)))); |
+ Do(BuildStoreLocal(*jump_var, jump_val)); |
+ // Save the current context for resuming. |
+ BuildSaveContext(*ctx_var); |
+ owner()->await_levels()->Add(owner()->context_level()); |
+ return; |
+ } |
+ if (node->marker_type() == AwaitMarkerNode::kTargetForContinuation) { |
+ // 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() |
@@ -3638,6 +3736,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()) && |
+ (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()); |
@@ -3649,6 +3763,62 @@ 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()) && |
+ (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); |
+ ASSERT(jump_var != NULL && jump_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 intptr_t num_await_states = owner()->await_joins()->length(); |
+ for (intptr_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()); |
+ |
+ for_true.BuildAwaitJump(top_scope, |
+ (*owner()->await_levels())[i], |
+ (*owner()->await_joins())[i]); |
+ Join(for_test, for_true, for_false); |
+ |
+ if (i == 0) { |
+ // Manually link up the preamble start. |
+ preamble_start->previous()->set_next(for_test.entry()); |
+ for_test.entry()->set_previous(preamble_start->previous()); |
+ } |
+ 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( |