From 5f02ceed86a38342b5d5d0b0231b4ebd76b9e522 Mon Sep 17 00:00:00 2001 From: "mlippautz@google.com" Date: Mon, 18 Aug 2014 18:41:12 +0000 Subject: [PATCH] Introduce await. This CL adds basic infrastructure needed for awaitable expressions. Implementation of continuations is not part of this CL. Expressions containing ``await'' are transformed into a series of operations on intermediates, effectively getting rid of the temporary expression stack. Currently only expressions evaluating to an actual value (read: non-future) are supported. Also, not all kinds of statements support awaitable expressions yet. Missing: * Capturing all (needed) variables * Continuations (connecting a preamble with await statements; re-adding the closure to the run queue) BUG= R=hausner@google.com Review URL: https://codereview.chromium.org//447003003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@39345 260f80e4-7a28-3924-810f-c04153c831b5 --- runtime/vm/ast.h | 21 ++ runtime/vm/ast_printer.cc | 5 + runtime/vm/ast_transformer.cc | 508 +++++++++++++++++++++++++++++++ runtime/vm/ast_transformer.h | 84 +++++ runtime/vm/flow_graph_builder.cc | 6 + runtime/vm/parser.cc | 75 ++++- runtime/vm/parser.h | 22 ++ runtime/vm/symbols.h | 2 + runtime/vm/vm_sources.gypi | 2 + tests/language/await_test.dart | 132 ++++++++ tests/language/language.status | 6 +- 11 files changed, 858 insertions(+), 5 deletions(-) create mode 100644 runtime/vm/ast_transformer.cc create mode 100644 runtime/vm/ast_transformer.h create mode 100644 tests/language/await_test.dart diff --git a/runtime/vm/ast.h b/runtime/vm/ast.h index 26a6be23579..c88dc69d472 100644 --- a/runtime/vm/ast.h +++ b/runtime/vm/ast.h @@ -16,6 +16,7 @@ namespace dart { #define FOR_EACH_NODE(V) \ + V(Await) \ V(Return) \ V(Literal) \ V(Type) \ @@ -146,6 +147,26 @@ class AstNode : public ZoneAllocated { }; +class AwaitNode : public AstNode { + public: + AwaitNode(intptr_t token_pos, AstNode* expr) + : AstNode(token_pos), expr_(expr) { } + + void VisitChildren(AstNodeVisitor* visitor) const { + expr_->Visit(visitor); + } + + AstNode* expr() const { return expr_; } + + DECLARE_COMMON_NODE_FUNCTIONS(AwaitNode); + + private: + AstNode* expr_; + + DISALLOW_COPY_AND_ASSIGN(AwaitNode); +}; + + class SequenceNode : public AstNode { public: SequenceNode(intptr_t token_pos, LocalScope* scope) diff --git a/runtime/vm/ast_printer.cc b/runtime/vm/ast_printer.cc index 1c76c593820..fc5e3a1df2e 100644 --- a/runtime/vm/ast_printer.cc +++ b/runtime/vm/ast_printer.cc @@ -152,6 +152,11 @@ void AstPrinter::VisitAssignableNode(AssignableNode* node) { } +void AstPrinter::VisitAwaitNode(AwaitNode* node) { + VisitGenericAstNode(node); +} + + void AstPrinter::VisitPrimaryNode(PrimaryNode* node) { OS::Print("*****%s***** \"%s\")", node->PrettyName(), diff --git a/runtime/vm/ast_transformer.cc b/runtime/vm/ast_transformer.cc new file mode 100644 index 00000000000..2a9ae4a9efa --- /dev/null +++ b/runtime/vm/ast_transformer.cc @@ -0,0 +1,508 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +#include "vm/ast_transformer.h" + +#include "vm/parser.h" + +namespace dart { + +// Quick access to the locally defined isolate() method. +#define I (isolate()) + +// Nodes that are unreachable from already parsed expressions. +#define FOR_EACH_UNREACHABLE_NODE(V) \ + V(Case) \ + V(CatchClause) \ + V(CloneContext) \ + V(ClosureCall) \ + V(DoWhile) \ + V(If) \ + V(InlinedFinally) \ + V(For) \ + V(Jump) \ + V(LoadInstanceField) \ + V(NativeBody) \ + V(Primary) \ + V(Return) \ + V(Sequence) \ + V(StoreInstanceField) \ + V(Switch) \ + V(TryCatch) \ + V(While) + +#define DEFINE_UNREACHABLE(BaseName) \ +void AwaitTransformer::Visit##BaseName##Node(BaseName##Node* node) { \ + UNREACHABLE(); \ +} + +FOR_EACH_UNREACHABLE_NODE(DEFINE_UNREACHABLE) +#undef DEFINE_UNREACHABLE + + +AstNode* AwaitTransformer::Transform(AstNode* expr) { + expr->Visit(this); + return result_; +} + + +LocalVariable* AwaitTransformer::EnsureCurrentTempVar() { + const char* await_temp_prefix = ":await_temp_var_"; + const String& cnt_str = String::ZoneHandle(I, + String::NewFormatted( + "%s%" Pd "", await_temp_prefix, temp_cnt_)); + const String& symbol = String::ZoneHandle(I, Symbols::New(cnt_str)); + ASSERT(!symbol.IsNull()); + LocalVariable* await_tmp = + parsed_function_->await_temps_scope()->LookupVariable(symbol, false); + if (await_tmp == NULL) { + await_tmp = new(I) LocalVariable( + Scanner::kNoSourcePos, + symbol, + Type::ZoneHandle(I, Type::DynamicType())); + parsed_function_->await_temps_scope()->AddVariable(await_tmp); + } + return await_tmp; +} + + +LocalVariable* AwaitTransformer::AddToPreambleNewTempVar(AstNode* node) { + LocalVariable* tmp_var = EnsureCurrentTempVar(); + preamble_->Add(new(I) StoreLocalNode(Scanner::kNoSourcePos, tmp_var, node)); + NextTempVar(); + return tmp_var; +} + + +void AwaitTransformer::VisitLiteralNode(LiteralNode* node) { + result_ = node; +} + + +void AwaitTransformer::VisitTypeNode(TypeNode* node) { + result_ = new(I) TypeNode(node->token_pos(), node->type()); +} + + + +void AwaitTransformer::VisitAwaitNode(AwaitNode* node) { + // Await transformation: + // + // :await_temp_var_X = ; + // :result_param = :await_temp_var_X; + // if (:result_param is Future) { + // // :result_param.then(:async_op); + // } + // :await_temp_var_(X+1) = :result_param; + + LocalVariable* async_op = preamble_->scope()->LookupVariable( + Symbols::AsyncOperation(), false); + ASSERT(async_op != NULL); + LocalVariable* result_param = preamble_->scope()->LookupVariable( + Symbols::AsyncOperationParam(), false); + ASSERT(result_param != NULL); + + node->expr()->Visit(this); + preamble_->Add(new(I) StoreLocalNode(Scanner::kNoSourcePos, + result_param, + result_)); + LoadLocalNode* load_result_param = new(I) LoadLocalNode( + Scanner::kNoSourcePos, result_param); + SequenceNode* is_future_branch = new(I) SequenceNode( + Scanner::kNoSourcePos, preamble_->scope()); + ArgumentListNode* args = new(I) ArgumentListNode(Scanner::kNoSourcePos); + args->Add(new(I) LoadLocalNode(Scanner::kNoSourcePos, async_op)); + // TODO(mlippautz): Once continuations are supported, just call .then(). + // is_future_branch->Add(new(I) InstanceCallNode( + // Scanner::kNoSourcePos, load_result_param, Symbols::FutureThen(), args)); + // + // For now, throw an exception. + const String& exception = String::ZoneHandle( + I, String::New("awaitable futures not yet supported", Heap::kOld)); + is_future_branch->Add(new(I) ThrowNode( + Scanner::kNoSourcePos, + new(I) LiteralNode( + Scanner::kNoSourcePos, + String::ZoneHandle(I, Symbols::New(exception))), + NULL)); + const Class& cls = Class::ZoneHandle( + I, library_.LookupClass(Symbols::Future())); + const AbstractType& future_type = AbstractType::ZoneHandle(I, + cls.RareType()); + ASSERT(!future_type.IsNull()); + TypeNode* future_type_node = new(I) TypeNode( + Scanner::kNoSourcePos, future_type); + IfNode* is_future_if = new(I) IfNode( + Scanner::kNoSourcePos, + new(I) ComparisonNode(Scanner::kNoSourcePos, + Token::kIS, + load_result_param, + future_type_node), + is_future_branch, + NULL); + preamble_->Add(is_future_if); + + // TODO(mlippautz): Join for await needs to happen here. + + LocalVariable* result = AddToPreambleNewTempVar(new(I) LoadLocalNode( + Scanner::kNoSourcePos, result_param)); + result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); +} + + +// Transforms boolean expressions into a sequence of evaluatons that only lazily +// evaluate subexpressions. +// +// Example: +// +// (a || b) only evaluates b if a is false +// +// Transformation (roughly): +// +// t_1 = a; +// if (!t_1) { +// t_2 = b; +// } +// t_3 = t_1 || t_2; // Compiler takes care that lazy evaluation takes place +// on this level. +AstNode* AwaitTransformer::LazyTransform(const Token::Kind logical_op, + AstNode* new_left, + AstNode* right) { + ASSERT(logical_op == Token::kAND || logical_op == Token::kOR); + AstNode* result = NULL; + const Token::Kind compare_logical_op = (logical_op == Token::kAND) ? + Token::kEQ : Token::kNE; + SequenceNode* eval = new(I) SequenceNode( + Scanner::kNoSourcePos, preamble_->scope()); + SequenceNode* saved_preamble = preamble_; + preamble_ = eval; + result = Transform(right); + preamble_ = saved_preamble; + IfNode* right_body = new(I) IfNode( + Scanner::kNoSourcePos, + new(I) ComparisonNode( + Scanner::kNoSourcePos, + compare_logical_op, + new_left, + new(I) LiteralNode(Scanner::kNoSourcePos, Bool::True())), + eval, + NULL); + preamble_->Add(right_body); + return result; +} + + +void AwaitTransformer::VisitBinaryOpNode(BinaryOpNode* node) { + node->left()->Visit(this); + AstNode* new_left = result_; + AstNode* new_right = NULL; + // Preserve lazy evaluaton. + if ((node->kind() == Token::kAND) || (node->kind() == Token::kOR)) { + new_right = LazyTransform(node->kind(), new_left, node->right()); + } else { + new_right = Transform(node->right()); + } + LocalVariable* result = AddToPreambleNewTempVar( + new(I) BinaryOpNode(node->token_pos(), + node->kind(), + new_left, + new_right)); + result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); +} + + +void AwaitTransformer::VisitBinaryOpWithMask32Node( + BinaryOpWithMask32Node* node) { + node->left()->Visit(this); + AstNode* new_left = result_; + AstNode* new_right = NULL; + // Preserve lazy evaluaton. + if ((node->kind() == Token::kAND) || (node->kind() == Token::kOR)) { + new_right = LazyTransform(node->kind(), new_left, node->right()); + } else { + new_right = Transform(node->right()); + } + LocalVariable* result = AddToPreambleNewTempVar( + new(I) BinaryOpWithMask32Node(node->token_pos(), + node->kind(), + new_left, + new_right, + node->mask32())); + result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); +} + + +void AwaitTransformer::VisitComparisonNode(ComparisonNode* node) { + AstNode* new_left = Transform(node->left()); + AstNode* new_right = Transform(node->right()); + LocalVariable* result = AddToPreambleNewTempVar( + new(I) ComparisonNode(node->token_pos(), + node->kind(), + new_left, + new_right)); + result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); +} + + +void AwaitTransformer::VisitUnaryOpNode(UnaryOpNode* node) { + AstNode* new_operand = Transform(node->operand()); + LocalVariable* result = AddToPreambleNewTempVar( + new(I) UnaryOpNode(node->token_pos(), node->kind(), new_operand)); + result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); +} + + +// ::= () ? : +// +void AwaitTransformer::VisitConditionalExprNode(ConditionalExprNode* node) { + AstNode* new_condition = Transform(node->condition()); + SequenceNode* new_true = new(I) SequenceNode( + Scanner::kNoSourcePos, preamble_->scope()); + SequenceNode* saved_preamble = preamble_; + preamble_ = new_true; + AstNode* new_true_result = Transform(node->true_expr()); + SequenceNode* new_false = new(I) SequenceNode( + Scanner::kNoSourcePos, preamble_->scope()); + preamble_ = new_false; + AstNode* new_false_result = Transform(node->false_expr()); + preamble_ = saved_preamble; + IfNode* new_if = new(I) IfNode(Scanner::kNoSourcePos, + new_condition, + new_true, + new_false); + preamble_->Add(new_if); + LocalVariable* result = AddToPreambleNewTempVar( + new(I) ConditionalExprNode(Scanner::kNoSourcePos, + new_condition, + new_true_result, + new_false_result)); + result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); +} + + +void AwaitTransformer::VisitArgumentListNode(ArgumentListNode* node) { + ArgumentListNode* new_args = new(I) ArgumentListNode(node->token_pos()); + for (intptr_t i = 0; i < node->length(); i++) { + new_args->Add(Transform(node->NodeAt(i))); + } + result_ = new_args; +} + + +void AwaitTransformer::VisitArrayNode(ArrayNode* node) { + GrowableArray new_elements; + for (intptr_t i = 0; i < node->length(); i++) { + new_elements.Add(Transform(node->ElementAt(i))); + } + result_ = new(I) ArrayNode(node->token_pos(), node->type(), new_elements); +} + + +void AwaitTransformer::VisitStringInterpolateNode(StringInterpolateNode* node) { + ArrayNode* new_value = Transform(node->value())->AsArrayNode(); + LocalVariable* result = AddToPreambleNewTempVar( + new(I) StringInterpolateNode(node->token_pos(), + new_value)); + result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); +} + + +void AwaitTransformer::VisitClosureNode(ClosureNode* node) { + AstNode* new_receiver = node->receiver(); + if (new_receiver != NULL) { + new_receiver = Transform(new_receiver); + } + LocalVariable* result = AddToPreambleNewTempVar( + new(I) ClosureNode(node->token_pos(), + node->function(), + new_receiver, + node->scope())); + result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); +} + + +void AwaitTransformer::VisitInstanceCallNode(InstanceCallNode* node) { + AstNode* new_receiver = Transform(node->receiver()); + ArgumentListNode* new_args = + Transform(node->arguments())->AsArgumentListNode(); + LocalVariable* result = AddToPreambleNewTempVar( + new(I) InstanceCallNode(node->token_pos(), + new_receiver, + node->function_name(), + new_args)); + result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); +} + + +void AwaitTransformer::VisitStaticCallNode(StaticCallNode* node) { + ArgumentListNode* new_args = + Transform(node->arguments())->AsArgumentListNode(); + LocalVariable* result = AddToPreambleNewTempVar( + new(I) StaticCallNode(node->token_pos(), + node->function(), + new_args)); + result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); +} + + +void AwaitTransformer::VisitConstructorCallNode(ConstructorCallNode* node) { + ArgumentListNode* new_args = + Transform(node->arguments())->AsArgumentListNode(); + LocalVariable* result = AddToPreambleNewTempVar( + new(I) ConstructorCallNode(node->token_pos(), + node->type_arguments(), + node->constructor(), + new_args)); + result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); +} + + +void AwaitTransformer::VisitInstanceGetterNode(InstanceGetterNode* node) { + AstNode* new_receiver = Transform(node->receiver()); + LocalVariable* result = AddToPreambleNewTempVar( + new(I) InstanceGetterNode(node->token_pos(), + new_receiver, + node->field_name())); + result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); +} + + +void AwaitTransformer::VisitInstanceSetterNode(InstanceSetterNode* node) { + AstNode* new_receiver = node->receiver(); + if (new_receiver != NULL) { + new_receiver = Transform(new_receiver); + } + AstNode* new_value = Transform(node->value()); + LocalVariable* result = AddToPreambleNewTempVar( + new(I) InstanceSetterNode(node->token_pos(), + new_receiver, + node->field_name(), + new_value)); + result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); +} + + +void AwaitTransformer::VisitStaticGetterNode(StaticGetterNode* node) { + AstNode* new_receiver = node->receiver(); + if (new_receiver != NULL) { + new_receiver = Transform(new_receiver); + } + LocalVariable* result = AddToPreambleNewTempVar( + new(I) StaticGetterNode(node->token_pos(), + new_receiver, + node->is_super_getter(), + node->cls(), + node->field_name())); + result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); +} + + +void AwaitTransformer::VisitStaticSetterNode(StaticSetterNode* node) { + AstNode* new_receiver = node->receiver(); + if (new_receiver != NULL) { + new_receiver = Transform(new_receiver); + } + AstNode* new_value = Transform(node->value()); + LocalVariable* result = AddToPreambleNewTempVar( + new(I) StaticSetterNode(node->token_pos(), + new_receiver, + node->cls(), + node->field_name(), + new_value)); + result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); +} + + +void AwaitTransformer::VisitLoadLocalNode(LoadLocalNode* node) { + LocalVariable* result = AddToPreambleNewTempVar( + new(I) LoadLocalNode(node->token_pos(), &node->local())); + result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); +} + + +void AwaitTransformer::VisitStoreLocalNode(StoreLocalNode* node) { + AstNode* new_value = Transform(node->value()); + LocalVariable* result = AddToPreambleNewTempVar( + new(I) StoreLocalNode(node->token_pos(), + &node->local(), + new_value)); + result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); +} + + +void AwaitTransformer::VisitLoadStaticFieldNode(LoadStaticFieldNode* node) { + LocalVariable* result = AddToPreambleNewTempVar( + new(I) LoadStaticFieldNode(node->token_pos(), + node->field())); + result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); +} + + +void AwaitTransformer::VisitStoreStaticFieldNode(StoreStaticFieldNode* node) { + AstNode* new_value = Transform(node->value()); + LocalVariable* result = AddToPreambleNewTempVar( + new(I) StoreStaticFieldNode(node->token_pos(), + node->field(), + new_value)); + result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); +} + + +void AwaitTransformer::VisitLoadIndexedNode(LoadIndexedNode* node) { + AstNode* new_array = Transform(node->array()); + AstNode* new_index = Transform(node->index_expr()); + LocalVariable* result = AddToPreambleNewTempVar( + new(I) LoadIndexedNode(node->token_pos(), + new_array, + new_index, + node->super_class())); + result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); +} + + +void AwaitTransformer::VisitStoreIndexedNode(StoreIndexedNode* node) { + AstNode* new_array = Transform(node->array()); + AstNode* new_index = Transform(node->index_expr()); + AstNode* new_value = Transform(node->value()); + LocalVariable* result = AddToPreambleNewTempVar( + new(I) StoreIndexedNode(node->token_pos(), + new_array, + new_index, + new_value, + node->super_class())); + result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); +} + + +void AwaitTransformer::VisitAssignableNode(AssignableNode* node) { + AstNode* new_expr = Transform(node->expr()); + LocalVariable* result = AddToPreambleNewTempVar( + new(I) AssignableNode(node->token_pos(), + new_expr, + node->type(), + node->dst_name())); + result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); +} + + +void AwaitTransformer::VisitLetNode(LetNode* node) { + // TODO(mlippautz): Check initializers and their temps. + LetNode* result = new(I) LetNode(node->token_pos()); + for (intptr_t i = 0; i < node->nodes().length(); i++) { + result->AddNode(Transform(node->nodes()[i])); + } + result_ = result; +} + + +void AwaitTransformer::VisitThrowNode(ThrowNode* node) { + // TODO(mlippautz): Check if relevant. + AstNode* new_exception = Transform(node->exception()); + AstNode* new_stacktrace = Transform(node->stacktrace()); + result_ = new(I) ThrowNode(node->token_pos(), + new_exception, + new_stacktrace); +} + +} // namespace dart diff --git a/runtime/vm/ast_transformer.h b/runtime/vm/ast_transformer.h new file mode 100644 index 00000000000..d0d4c8262f2 --- /dev/null +++ b/runtime/vm/ast_transformer.h @@ -0,0 +1,84 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +#ifndef VM_AST_TRANSFORMER_H_ +#define VM_AST_TRANSFORMER_H_ + +#include "platform/assert.h" +#include "vm/ast.h" + +namespace dart { + +class ParsedFunction; + +// Translate an AstNode containing an expression (that itself contains one or +// more awaits) into a sequential representation where subexpressions are +// evaluated sequentially into intermediates. Those intermediates are stored +// within a context. +// +// This allows a function to be suspended and resumed from within evaluating an +// expression. The evaluation is split among a so-called preamble and the +// evaluation of the resulting expression (which is only a single load). +// +// Example (minimalistic): +// +// var a = (await bar()) + foo(); +// +// This translates to a premable similar to: +// +// var t_1, t_2, t_3, t_4; // All stored in a context. +// t_1 = bar(); +// :result_param = t_1; +// +// t_2 = :result_param; +// t_3 = foo(); +// t_4 = t_2.operator+(t_3); +// +// and a resulting expression of a load of t_4. +// +class AwaitTransformer : public AstNodeVisitor { + public: + AwaitTransformer(SequenceNode* preamble, + const Library& library, + ParsedFunction* const parsed_function) + : preamble_(preamble), + temp_cnt_(0), + library_(library), + parsed_function_(parsed_function), + isolate_(Isolate::Current()) {} + +#define DECLARE_VISIT(BaseName) \ + virtual void Visit##BaseName##Node(BaseName##Node* node); + + FOR_EACH_NODE(DECLARE_VISIT) +#undef DECLARE_VISIT + + AstNode* Transform(AstNode* expr); + + private: + LocalVariable* EnsureCurrentTempVar(); + LocalVariable* AddToPreambleNewTempVar(AstNode* node); + ArgumentListNode* TransformArguments(ArgumentListNode* node); + AstNode* LazyTransform(const Token::Kind kind, + AstNode* new_left, + AstNode* right); + + void NextTempVar() { temp_cnt_++; } + + Isolate* isolate() const { return isolate_; } + + SequenceNode* preamble_; + int temp_cnt_; + AstNode* result_; + const Library& library_; + ParsedFunction* const parsed_function_; + + Isolate* isolate_; + + DISALLOW_COPY_AND_ASSIGN(AwaitTransformer); +}; + +} // namespace dart + +#endif // VM_AST_TRANSFORMER_H_ diff --git a/runtime/vm/flow_graph_builder.cc b/runtime/vm/flow_graph_builder.cc index 3a3f08706c8..321369b7d22 100644 --- a/runtime/vm/flow_graph_builder.cc +++ b/runtime/vm/flow_graph_builder.cc @@ -2114,6 +2114,12 @@ void EffectGraphVisitor::VisitArgumentListNode(ArgumentListNode* node) { } +void EffectGraphVisitor::VisitAwaitNode(AwaitNode* node) { + // Await nodes are temporary during parsing. + UNREACHABLE(); +} + + intptr_t EffectGraphVisitor::GetCurrentTempLocalIndex() const { return kFirstLocalSlotFromFp - owner()->num_stack_locals() diff --git a/runtime/vm/parser.cc b/runtime/vm/parser.cc index f87763f6ab7..28fc9b486dd 100644 --- a/runtime/vm/parser.cc +++ b/runtime/vm/parser.cc @@ -6,6 +6,7 @@ #include "lib/invocation_mirror.h" #include "platform/utils.h" +#include "vm/ast_transformer.h" #include "vm/bootstrap.h" #include "vm/class_finalizer.h" #include "vm/compiler.h" @@ -2988,13 +2989,25 @@ SequenceNode* Parser::ParseFunc(const Function& func, // we are compiling a getter this will at most populate the receiver. AddFormalParamsToScope(¶ms, current_block_->scope); } else if (func.is_async_closure()) { + // Async closures have one optional parameter for continuation results. + ParamDesc result_param; + result_param.name = &Symbols::AsyncOperationParam(); + result_param.default_value = &Object::null_instance(); + result_param.type = &Type::ZoneHandle(I, Type::DynamicType()); + params.parameters->Add(result_param); + params.num_optional_parameters++; + params.has_optional_positional_parameters = true; + SetupDefaultsForOptionalParams(¶ms, default_parameter_values); AddFormalParamsToScope(¶ms, current_block_->scope); ASSERT(AbstractType::Handle(I, func.result_type()).IsResolved()); ASSERT(func.NumParameters() == params.parameters->length()); if (!Function::Handle(func.parent_function()).IsGetterFunction()) { // Parse away any formal parameters, as they are accessed as as context // variables. - ParseFormalParameterList(allow_explicit_default_values, false, ¶ms); + ParamList parse_away; + ParseFormalParameterList(allow_explicit_default_values, + false, + &parse_away); } } else { ParseFormalParameterList(allow_explicit_default_values, false, ¶ms); @@ -3044,6 +3057,8 @@ SequenceNode* Parser::ParseFunc(const Function& func, Function& async_closure = Function::ZoneHandle(I); if (func.IsAsyncFunction() && !func.is_async_closure()) { async_closure = OpenAsyncFunction(formal_params_pos); + } else if (func.is_async_closure()) { + OpenAsyncClosure(); } intptr_t end_token_pos = 0; @@ -5526,7 +5541,15 @@ void Parser::OpenFunctionBlock(const Function& func) { } +void Parser::OpenAsyncClosure() { + TRACE_PARSER("OpenAsyncClosure"); + parsed_function()->set_await_temps_scope(current_block_->scope); + // TODO(mlippautz): Set up explicit jump table for await continuations. +} + + RawFunction* Parser::OpenAsyncFunction(intptr_t formal_param_pos) { + TRACE_PARSER("OpenAsyncFunction"); // Create the closure containing the old body of this function. Class& sig_cls = Class::ZoneHandle(I); Type& sig_type = Type::ZoneHandle(I); @@ -5537,6 +5560,13 @@ RawFunction* Parser::OpenAsyncFunction(intptr_t formal_param_pos) { formal_param_pos, &Symbols::ClosureParameter(), &Type::ZoneHandle(I, Type::DynamicType())); + ParamDesc result_param; + result_param.name = &Symbols::AsyncOperationParam(); + result_param.default_value = &Object::null_instance(); + result_param.type = &Type::ZoneHandle(I, Type::DynamicType()); + closure_params.parameters->Add(result_param); + closure_params.has_optional_positional_parameters = true; + closure_params.num_optional_parameters++; closure = Function::NewClosureFunction( Symbols::AnonymousClosure(), innermost_function(), @@ -5580,6 +5610,7 @@ SequenceNode* Parser::CloseBlock() { SequenceNode* Parser::CloseAsyncFunction(const Function& closure, SequenceNode* closure_body) { + TRACE_PARSER("CloseAsyncFunction"); ASSERT(!closure.IsNull()); ASSERT(closure_body != NULL); // The block for the async closure body has already been closed. Close the @@ -5676,6 +5707,7 @@ SequenceNode* Parser::CloseAsyncFunction(const Function& closure, void Parser::CloseAsyncClosure(SequenceNode* body) { + TRACE_PARSER("CloseAsyncClosure"); // We need a temporary expression to store intermediate return values. parsed_function()->EnsureExpressionTemp(); } @@ -5878,7 +5910,7 @@ AstNode* Parser::ParseVariableDeclaration(const AbstractType& type, // Variable initialization. const intptr_t assign_pos = TokenPos(); ConsumeToken(); - AstNode* expr = ParseExpr(is_const, kConsumeCascades); + AstNode* expr = ParseAwaitableExpr(is_const, kConsumeCascades); initialization = new(I) StoreLocalNode( assign_pos, variable, expr); if (is_const) { @@ -7751,7 +7783,7 @@ AstNode* Parser::ParseStatement() { new(I) LoadLocalNode(statement_pos, excp_var), new(I) LoadLocalNode(statement_pos, trace_var)); } else { - statement = ParseExpr(kAllowConst, kConsumeCascades); + statement = ParseAwaitableExpr(kAllowConst, kConsumeCascades); ExpectSemicolon(); } return statement; @@ -8407,6 +8439,31 @@ static AstNode* LiteralIfStaticConst(Isolate* iso, AstNode* expr) { } +AstNode* Parser::ParseAwaitableExpr(bool require_compiletime_const, + bool consume_cascades) { + TRACE_PARSER("ParseAwaitableExpr"); + parsed_function()->reset_have_seen_await(); + AstNode* expr = ParseExpr(require_compiletime_const, consume_cascades); + if (parsed_function()->have_seen_await()) { + if (!current_block_->scope->LookupVariable( + Symbols::AsyncOperation(), true)) { + // Async operations are always encapsulated into a local function. We only + // need to transform the expression when generating code for this inner + // function. + return expr; + } + SequenceNode* intermediates_block = new(I) SequenceNode( + Scanner::kNoSourcePos, current_block_->scope); + AwaitTransformer at(intermediates_block, library_, parsed_function()); + AstNode* result = at.Transform(expr); + current_block_->statements->Add(intermediates_block); + parsed_function()->reset_have_seen_await(); + return result; + } + return expr; +} + + AstNode* Parser::ParseExpr(bool require_compiletime_const, bool consume_cascades) { TRACE_PARSER("ParseExpr"); @@ -10775,6 +10832,18 @@ AstNode* Parser::ParsePrimary() { OpenBlock(); primary = ParseFunctionStatement(true); CloseBlock(); + } else if (IsLiteral("await") && + (parsed_function()->function().IsAsyncFunction() || + parsed_function()->function().is_async_closure())) { + // The body of an async function is parsed multiple times. The first time + // when setting up an AsyncFunction() for generating relevant scope + // information. The second time the body is parsed for actually generating + // code. + TRACE_PARSER("ParseAwaitExpr"); + ConsumeToken(); + parsed_function()->record_await(); + primary = new(I) AwaitNode( + TokenPos(), ParseExpr(kAllowConst, kConsumeCascades)); } else if (IsIdentifier()) { intptr_t qual_ident_pos = TokenPos(); const LibraryPrefix& prefix = LibraryPrefix::ZoneHandle(I, ParsePrefix()); diff --git a/runtime/vm/parser.h b/runtime/vm/parser.h index 2c5acf3e4c6..81aff04f9c5 100644 --- a/runtime/vm/parser.h +++ b/runtime/vm/parser.h @@ -48,11 +48,13 @@ class ParsedFunction : public ZoneAllocated { saved_entry_context_var_(NULL), expression_temp_var_(NULL), finally_return_temp_var_(NULL), + await_temps_scope_(NULL), deferred_prefixes_(new ZoneGrowableArray()), first_parameter_index_(0), first_stack_local_index_(0), num_copied_params_(0), num_stack_locals_(0), + have_seen_await_expr_(false), isolate_(isolate) { ASSERT(function.IsZoneHandle()); } @@ -138,6 +140,21 @@ class ParsedFunction : public ZoneAllocated { void AllocateVariables(); + void set_await_temps_scope(LocalScope* scope) { + ASSERT(await_temps_scope_ == NULL); + await_temps_scope_ = scope; + } + LocalScope* await_temps_scope() const { + ASSERT(await_temps_scope_ != NULL); + return await_temps_scope_; + } + + void record_await() { + have_seen_await_expr_ = true; + } + void reset_have_seen_await() { have_seen_await_expr_ = false; } + bool have_seen_await() const { return have_seen_await_expr_; } + Isolate* isolate() const { return isolate_; } private: @@ -150,12 +167,14 @@ class ParsedFunction : public ZoneAllocated { LocalVariable* saved_entry_context_var_; LocalVariable* expression_temp_var_; LocalVariable* finally_return_temp_var_; + LocalScope* await_temps_scope_; ZoneGrowableArray* deferred_prefixes_; int first_parameter_index_; int first_stack_local_index_; int num_copied_params_; int num_stack_locals_; + bool have_seen_await_expr_; Isolate* isolate_; @@ -502,6 +521,7 @@ class Parser : public ValueObject { void OpenBlock(); void OpenLoopBlock(); void OpenFunctionBlock(const Function& func); + void OpenAsyncClosure(); RawFunction* OpenAsyncFunction(intptr_t formal_param_pos); SequenceNode* CloseBlock(); SequenceNode* CloseAsyncFunction(const Function& closure, @@ -586,6 +606,8 @@ class Parser : public ValueObject { static const bool kAllowConst = false; static const bool kConsumeCascades = true; static const bool kNoCascades = false; + AstNode* ParseAwaitableExpr(bool require_compiletime_const, + bool consume_cascades); AstNode* ParseExpr(bool require_compiletime_const, bool consume_cascades); AstNode* ParseExprList(); AstNode* ParseConditionalExpr(); diff --git a/runtime/vm/symbols.h b/runtime/vm/symbols.h index f061c3c731f..14bbf0f0909 100644 --- a/runtime/vm/symbols.h +++ b/runtime/vm/symbols.h @@ -71,8 +71,10 @@ class ObjectPointerVisitor; V(Async, "async") \ V(AsyncCompleter, ":async_completer") \ V(AsyncOperation, ":async_op") \ + V(AsyncOperationParam, ":async_result") \ V(Future, "Future") \ V(FutureConstructor, "Future.") \ + V(FutureThen, "then") \ V(Completer, "Completer") \ V(CompleterComplete, "complete") \ V(CompleterConstructor, "Completer.") \ diff --git a/runtime/vm/vm_sources.gypi b/runtime/vm/vm_sources.gypi index 3173a1b09d5..4083cfbb842 100644 --- a/runtime/vm/vm_sources.gypi +++ b/runtime/vm/vm_sources.gypi @@ -34,6 +34,8 @@ 'ast_printer.h', 'ast_printer_test.cc', 'ast_test.cc', + 'ast_transformer.cc', + 'ast_transformer.h', 'atomic.h', 'atomic_android.cc', 'atomic_linux.cc', diff --git a/tests/language/await_test.dart b/tests/language/await_test.dart new file mode 100644 index 00000000000..ef2b56ffdc0 --- /dev/null +++ b/tests/language/await_test.dart @@ -0,0 +1,132 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// VMOptions=--enable_async --optimization-counter-threshold=10 + +import 'package:expect/expect.dart'; + +import 'dart:async'; + +int globalVariable = 1; +int topLevelFoo(int param) => 1; +int get topLevelGetter => globalVariable; +int set topLevelSetter(val) { + globalVariable = val; +} + +class C { + static int staticField = 1; + static int get staticGetter => staticField; + static int set staticSetter(val) { + staticField = val; + } + static int staticFoo(int param) => param; + + int field = 1; + int get getter => field; + int set setter(val) { + field = val; + } + int foo(int param) => param; +} + +dummy() => 1; + +staticMembers() async { + var a = C.staticField + await dummy(); + Expect.equals(a, 2); + var f = (C.staticField = 1) + await dummy(); + Expect.equals(f, 2); + var b = C.staticGetter + await dummy(); + Expect.equals(b, 2); + var c = (C.staticSetter = 1) + await dummy(); + Expect.equals(c, 2); + var d = C.staticFoo(2) + await dummy(); + Expect.equals(d, 3); + var e = C.staticField + + C.staticGetter + + (C.staticSetter = 1) + + C.staticFoo(1) + + await dummy(); + Expect.equals(e, 5); +} + +topLevelMembers() async { + var a = globalVariable + await dummy(); + Expect.equals(a, 2); + var b = topLevelGetter + await dummy(); + Expect.equals(b, 2); + var c = (topLevelSetter = 1) + await dummy(); + Expect.equals(c, 2); + var d = topLevelFoo(1) + await dummy(); + Expect.equals(d, 2); + var e = globalVariable + + topLevelGetter + + (topLevelSetter = 1) + + topLevelFoo(1) + + await dummy(); + Expect.equals(e, 5); +} + +instanceMembers() async { + var inst = new C(); + var a = inst.field + await dummy(); + Expect.equals(a, 2); + var b = inst.getter + await dummy(); + Expect.equals(b, 2); + var c = (inst.setter = 1) + await dummy(); + Expect.equals(c, 2); + var d = inst.foo(1) + await dummy(); + Expect.equals(d, 2); + var e = inst.field + + inst.getter + + (inst.setter = 1) + + inst.foo(1) + + await dummy(); + Expect.equals(e, 5); +} + +await() => 4; +nonAsyncFunction() => await(); + +others() async { + var a = "${globalVariable} ${await dummy()} " + await "someString"; + Expect.equals(a, "1 1 someString"); + try { + var c = new C(); + var d = c.nooooo() + await bar(); + } catch (e) {} + var cnt = 2; + var b = [1,2,3]; + b[cnt] = await dummy(); + Expect.equals(b[cnt], 1); + var e = b[0] + await dummy(); + Expect.equals(e, 2); + Expect.equals(nonAsyncFunction(), 4); +} + +conditionals() async { + var a = false; + var b = true; + var c = (a || b) || await dummy(); + Expect.isTrue(c); + var d = (a || b) ? a : await dummy(); + Expect.isFalse(d); + var e = (a is int) ? await dummy() : 2; + Expect.equals(e, 2); + try { + var f = (a is intt) ? await dummy() : 2; + } catch(e) {} +} + +main() { + for (int i = 0; i < 10; i++) { + staticMembers(); + topLevelMembers(); + instanceMembers(); + conditionals(); + others(); + } +} + diff --git a/tests/language/language.status b/tests/language/language.status index 497900c08a3..09939e5b367 100644 --- a/tests/language/language.status +++ b/tests/language/language.status @@ -32,14 +32,16 @@ deferred_constraints_constants_old_syntax_test/constructor1: Fail, Ok deferred_constraints_constants_old_syntax_test/constructor2: Fail, Ok [ $runtime != vm ] -# Async tests are currently only supported by the vm. +# Async/await is currently only supported by the vm. async_test/*: Skip async_control_structures_test: Skip +await_test: Skip [ $compiler == dart2dart] -# Async tests are not yet supported in dart2dart. +# Async/await not yet supported in dart2dart. async_test/*: Skip async_control_structures_test: Skip +await_test: Skip deferred_load_library_wrong_args_test/none: Fail # Issue 17523 deferred_load_inval_code_test: Fail # Issue 17523 deferred_not_loaded_check_test: Fail # Issue 17523