| // 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/object_store.h" |
| #include "vm/parser.h" |
| #include "vm/thread.h" |
| |
| namespace dart { |
| |
| // Quick access to the current zone. |
| #define Z (thread()->zone()) |
| |
| // Nodes that are unreachable from already parsed expressions. |
| #define FOR_EACH_UNREACHABLE_NODE(V) \ |
| V(AwaitMarker) \ |
| V(Case) \ |
| V(CatchClause) \ |
| V(CloneContext) \ |
| V(ClosureCall) \ |
| V(DoWhile) \ |
| V(If) \ |
| V(InitStaticField) \ |
| V(InlinedFinally) \ |
| V(For) \ |
| V(Jump) \ |
| V(Stop) \ |
| 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 |
| |
| AwaitTransformer::AwaitTransformer(SequenceNode* preamble, |
| LocalScope* async_temp_scope) |
| : preamble_(preamble), |
| temp_cnt_(0), |
| async_temp_scope_(async_temp_scope), |
| thread_(Thread::Current()) { |
| ASSERT(async_temp_scope_ != NULL); |
| } |
| |
| |
| AstNode* AwaitTransformer::Transform(AstNode* expr) { |
| expr->Visit(this); |
| return result_; |
| } |
| |
| |
| LocalVariable* AwaitTransformer::EnsureCurrentTempVar() { |
| String& symbol = |
| String::ZoneHandle(Z, Symbols::NewFormatted("%d", temp_cnt_)); |
| symbol = Symbols::FromConcat(Symbols::AwaitTempVarPrefix(), symbol); |
| ASSERT(!symbol.IsNull()); |
| // Look up the variable in the scope used for async temp variables. |
| LocalVariable* await_tmp = async_temp_scope_->LocalLookupVariable(symbol); |
| if (await_tmp == NULL) { |
| // We need a new temp variable; add it to the function's top scope. |
| await_tmp = new (Z) LocalVariable( |
| Scanner::kNoSourcePos, symbol, Type::ZoneHandle(Type::DynamicType())); |
| async_temp_scope_->AddVariable(await_tmp); |
| // After adding it to the top scope, we can look it up from the preamble. |
| // The following call includes an ASSERT check. |
| await_tmp = GetVariableInScope(preamble_->scope(), symbol); |
| } |
| return await_tmp; |
| } |
| |
| |
| LocalVariable* AwaitTransformer::GetVariableInScope(LocalScope* scope, |
| const String& symbol) { |
| LocalVariable* var = scope->LookupVariable(symbol, false); |
| ASSERT(var != NULL); |
| return var; |
| } |
| |
| |
| LocalVariable* AwaitTransformer::AddToPreambleNewTempVar(AstNode* node) { |
| LocalVariable* tmp_var = EnsureCurrentTempVar(); |
| preamble_->Add(new(Z) StoreLocalNode(Scanner::kNoSourcePos, tmp_var, node)); |
| NextTempVar(); |
| return tmp_var; |
| } |
| |
| |
| void AwaitTransformer::VisitLiteralNode(LiteralNode* node) { |
| result_ = node; |
| } |
| |
| |
| void AwaitTransformer::VisitTypeNode(TypeNode* node) { |
| result_ = new(Z) TypeNode(node->token_pos(), node->type()); |
| } |
| |
| |
| void AwaitTransformer::VisitAwaitNode(AwaitNode* node) { |
| // Await transformation: |
| // |
| // :await_temp_var_X = <expr>; |
| // AwaitMarker(kNewContinuationState); |
| // :result_param = _awaitHelper( |
| // :await_temp_var_X, :async_then_callback, :async_catch_error_callback); |
| // return; // (return_type() == kContinuationTarget) |
| // |
| // :saved_try_ctx_var = :await_saved_try_ctx_var_y; |
| // :await_temp_var_(X+1) = :result_param; |
| |
| LocalVariable* async_op = GetVariableInScope( |
| preamble_->scope(), Symbols::AsyncOperation()); |
| LocalVariable* async_then_callback = GetVariableInScope( |
| preamble_->scope(), Symbols::AsyncThenCallback()); |
| LocalVariable* async_catch_error_callback = GetVariableInScope( |
| preamble_->scope(), Symbols::AsyncCatchErrorCallback()); |
| LocalVariable* result_param = GetVariableInScope( |
| preamble_->scope(), Symbols::AsyncOperationParam()); |
| LocalVariable* error_param = GetVariableInScope( |
| preamble_->scope(), Symbols::AsyncOperationErrorParam()); |
| LocalVariable* stack_trace_param = GetVariableInScope( |
| preamble_->scope(), Symbols::AsyncOperationStackTraceParam()); |
| |
| AstNode* transformed_expr = Transform(node->expr()); |
| LocalVariable* await_temp = AddToPreambleNewTempVar(transformed_expr); |
| |
| AwaitMarkerNode* await_marker = |
| new (Z) AwaitMarkerNode(async_temp_scope_, node->scope()); |
| preamble_->Add(await_marker); |
| |
| // :result_param = _awaitHelper( |
| // :await_temp, :async_then_callback, :async_catch_error_callback) |
| const Library& async_lib = Library::Handle(Library::AsyncLibrary()); |
| const Function& async_await_helper = Function::ZoneHandle( |
| Z, async_lib.LookupFunctionAllowPrivate(Symbols::AsyncAwaitHelper())); |
| ASSERT(!async_await_helper.IsNull()); |
| ArgumentListNode* async_await_helper_args = new (Z) ArgumentListNode( |
| Scanner::kNoSourcePos); |
| async_await_helper_args->Add( |
| new(Z) LoadLocalNode(Scanner::kNoSourcePos, await_temp)); |
| async_await_helper_args->Add( |
| new(Z) LoadLocalNode(Scanner::kNoSourcePos, async_then_callback)); |
| async_await_helper_args->Add( |
| new(Z) LoadLocalNode(Scanner::kNoSourcePos, async_catch_error_callback)); |
| StaticCallNode* await_helper_call = new (Z) StaticCallNode( |
| node->token_pos(), |
| async_await_helper, |
| async_await_helper_args); |
| |
| preamble_->Add(new(Z) StoreLocalNode( |
| Scanner::kNoSourcePos, result_param, await_helper_call)); |
| |
| ReturnNode* continuation_return = new(Z) ReturnNode(Scanner::kNoSourcePos); |
| continuation_return->set_return_type(ReturnNode::kContinuationTarget); |
| preamble_->Add(continuation_return); |
| |
| // If this expression is part of a try block, also append the code for |
| // restoring the saved try context that lives on the stack and possibly the |
| // saved try context of the outer try block. |
| if (node->saved_try_ctx() != NULL) { |
| preamble_->Add(new (Z) StoreLocalNode( |
| Scanner::kNoSourcePos, |
| node->saved_try_ctx(), |
| new (Z) LoadLocalNode(Scanner::kNoSourcePos, |
| node->async_saved_try_ctx()))); |
| if (node->outer_saved_try_ctx() != NULL) { |
| preamble_->Add(new (Z) StoreLocalNode( |
| Scanner::kNoSourcePos, |
| node->outer_saved_try_ctx(), |
| new (Z) LoadLocalNode(Scanner::kNoSourcePos, |
| node->outer_async_saved_try_ctx()))); |
| } |
| } else { |
| ASSERT(node->outer_saved_try_ctx() == NULL); |
| } |
| |
| // Load the async_op variable. It is unused, but the observatory uses it |
| // to determine if a breakpoint is inside an asynchronous function. |
| LoadLocalNode* load_async_op = new (Z) LoadLocalNode( |
| Scanner::kNoSourcePos, async_op); |
| preamble_->Add(load_async_op); |
| |
| LoadLocalNode* load_error_param = new (Z) LoadLocalNode( |
| Scanner::kNoSourcePos, error_param); |
| LoadLocalNode* load_stack_trace_param = new (Z) LoadLocalNode( |
| Scanner::kNoSourcePos, stack_trace_param); |
| SequenceNode* error_ne_null_branch = new (Z) SequenceNode( |
| Scanner::kNoSourcePos, ChainNewScope(preamble_->scope())); |
| error_ne_null_branch->Add(new (Z) ThrowNode( |
| Scanner::kNoSourcePos, |
| load_error_param, |
| load_stack_trace_param)); |
| preamble_->Add(new (Z) IfNode( |
| Scanner::kNoSourcePos, |
| new (Z) ComparisonNode( |
| Scanner::kNoSourcePos, |
| Token::kNE, |
| load_error_param, |
| new (Z) LiteralNode(Scanner::kNoSourcePos, |
| Object::null_instance())), |
| error_ne_null_branch, |
| NULL)); |
| |
| LocalVariable* result = AddToPreambleNewTempVar(new(Z) LoadLocalNode( |
| Scanner::kNoSourcePos, result_param)); |
| result_ = new(Z) 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 (Z) SequenceNode( |
| Scanner::kNoSourcePos, ChainNewScope(preamble_->scope())); |
| SequenceNode* saved_preamble = preamble_; |
| preamble_ = eval; |
| result = Transform(right); |
| preamble_ = saved_preamble; |
| IfNode* right_body = new(Z) IfNode( |
| Scanner::kNoSourcePos, |
| new(Z) ComparisonNode( |
| Scanner::kNoSourcePos, |
| compare_logical_op, |
| new_left, |
| new(Z) LiteralNode(Scanner::kNoSourcePos, Bool::True())), |
| eval, |
| NULL); |
| preamble_->Add(right_body); |
| return result; |
| } |
| |
| |
| LocalScope* AwaitTransformer::ChainNewScope(LocalScope* parent) { |
| return new (Z) LocalScope( |
| parent, parent->function_level(), parent->loop_level()); |
| } |
| |
| |
| void AwaitTransformer::VisitBinaryOpNode(BinaryOpNode* node) { |
| AstNode* new_left = Transform(node->left()); |
| 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(Z) BinaryOpNode(node->token_pos(), |
| node->kind(), |
| new_left, |
| new_right)); |
| result_ = new(Z) LoadLocalNode(Scanner::kNoSourcePos, result); |
| } |
| |
| |
| void AwaitTransformer::VisitBinaryOpWithMask32Node( |
| BinaryOpWithMask32Node* node) { |
| ASSERT((node->kind() != Token::kAND) && (node->kind() != Token::kOR)); |
| AstNode* new_left = Transform(node->left()); |
| AstNode* new_right = Transform(node->right()); |
| LocalVariable* result = AddToPreambleNewTempVar( |
| new(Z) BinaryOpWithMask32Node(node->token_pos(), |
| node->kind(), |
| new_left, |
| new_right, |
| node->mask32())); |
| result_ = new(Z) 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(Z) ComparisonNode(node->token_pos(), |
| node->kind(), |
| new_left, |
| new_right)); |
| result_ = new(Z) LoadLocalNode(Scanner::kNoSourcePos, result); |
| } |
| |
| |
| void AwaitTransformer::VisitUnaryOpNode(UnaryOpNode* node) { |
| AstNode* new_operand = Transform(node->operand()); |
| LocalVariable* result = AddToPreambleNewTempVar( |
| new(Z) UnaryOpNode(node->token_pos(), node->kind(), new_operand)); |
| result_ = new(Z) LoadLocalNode(Scanner::kNoSourcePos, result); |
| } |
| |
| |
| // ::= (<condition>) ? <true-branch> : <false-branch> |
| // |
| void AwaitTransformer::VisitConditionalExprNode(ConditionalExprNode* node) { |
| AstNode* new_condition = Transform(node->condition()); |
| SequenceNode* new_true = new (Z) SequenceNode( |
| Scanner::kNoSourcePos, ChainNewScope(preamble_->scope())); |
| SequenceNode* saved_preamble = preamble_; |
| preamble_ = new_true; |
| AstNode* new_true_result = Transform(node->true_expr()); |
| SequenceNode* new_false = new (Z) SequenceNode( |
| Scanner::kNoSourcePos, ChainNewScope(preamble_->scope())); |
| preamble_ = new_false; |
| AstNode* new_false_result = Transform(node->false_expr()); |
| preamble_ = saved_preamble; |
| IfNode* new_if = new(Z) IfNode(Scanner::kNoSourcePos, |
| new_condition, |
| new_true, |
| new_false); |
| preamble_->Add(new_if); |
| LocalVariable* result = AddToPreambleNewTempVar( |
| new(Z) ConditionalExprNode(Scanner::kNoSourcePos, |
| new_condition, |
| new_true_result, |
| new_false_result)); |
| result_ = new(Z) LoadLocalNode(Scanner::kNoSourcePos, result); |
| } |
| |
| |
| void AwaitTransformer::VisitArgumentListNode(ArgumentListNode* node) { |
| ArgumentListNode* new_args = new(Z) ArgumentListNode(node->token_pos()); |
| for (intptr_t i = 0; i < node->length(); i++) { |
| new_args->Add(Transform(node->NodeAt(i))); |
| } |
| new_args->set_names(node->names()); |
| result_ = new_args; |
| } |
| |
| |
| void AwaitTransformer::VisitArrayNode(ArrayNode* node) { |
| GrowableArray<AstNode*> new_elements; |
| for (intptr_t i = 0; i < node->length(); i++) { |
| new_elements.Add(Transform(node->ElementAt(i))); |
| } |
| result_ = new(Z) ArrayNode(node->token_pos(), node->type(), new_elements); |
| } |
| |
| |
| void AwaitTransformer::VisitStringInterpolateNode(StringInterpolateNode* node) { |
| ArrayNode* new_value = Transform(node->value())->AsArrayNode(); |
| LocalVariable* result = AddToPreambleNewTempVar( |
| new(Z) StringInterpolateNode(node->token_pos(), |
| new_value)); |
| result_ = new(Z) 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(Z) ClosureNode(node->token_pos(), |
| node->function(), |
| new_receiver, |
| node->scope())); |
| result_ = new(Z) 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(Z) InstanceCallNode(node->token_pos(), |
| new_receiver, |
| node->function_name(), |
| new_args, |
| node->is_conditional())); |
| result_ = new(Z) LoadLocalNode(Scanner::kNoSourcePos, result); |
| } |
| |
| |
| void AwaitTransformer::VisitStaticCallNode(StaticCallNode* node) { |
| ArgumentListNode* new_args = |
| Transform(node->arguments())->AsArgumentListNode(); |
| LocalVariable* result = AddToPreambleNewTempVar( |
| new(Z) StaticCallNode(node->token_pos(), |
| node->function(), |
| new_args)); |
| result_ = new(Z) LoadLocalNode(Scanner::kNoSourcePos, result); |
| } |
| |
| |
| void AwaitTransformer::VisitConstructorCallNode(ConstructorCallNode* node) { |
| ArgumentListNode* new_args = |
| Transform(node->arguments())->AsArgumentListNode(); |
| LocalVariable* result = AddToPreambleNewTempVar( |
| new(Z) ConstructorCallNode(node->token_pos(), |
| node->type_arguments(), |
| node->constructor(), |
| new_args)); |
| result_ = new(Z) LoadLocalNode(Scanner::kNoSourcePos, result); |
| } |
| |
| |
| void AwaitTransformer::VisitInstanceGetterNode(InstanceGetterNode* node) { |
| AstNode* new_receiver = Transform(node->receiver()); |
| LocalVariable* result = AddToPreambleNewTempVar( |
| new(Z) InstanceGetterNode(node->token_pos(), |
| new_receiver, |
| node->field_name(), |
| node->is_conditional())); |
| result_ = new(Z) 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(Z) InstanceSetterNode(node->token_pos(), |
| new_receiver, |
| node->field_name(), |
| new_value, |
| node->is_conditional())); |
| result_ = new(Z) LoadLocalNode(Scanner::kNoSourcePos, result); |
| } |
| |
| |
| void AwaitTransformer::VisitStaticGetterNode(StaticGetterNode* node) { |
| AstNode* new_receiver = node->receiver(); |
| if (new_receiver != NULL) { |
| new_receiver = Transform(new_receiver); |
| } |
| StaticGetterNode* new_getter = |
| new(Z) StaticGetterNode(node->token_pos(), |
| new_receiver, |
| node->cls(), |
| node->field_name()); |
| new_getter->set_owner(node->owner()); |
| LocalVariable* result = AddToPreambleNewTempVar(new_getter); |
| result_ = new(Z) 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()); |
| StaticSetterNode* new_setter = |
| node->function().IsNull() |
| ? new(Z) StaticSetterNode(node->token_pos(), |
| new_receiver, |
| node->cls(), |
| node->field_name(), |
| new_value) |
| : new(Z) StaticSetterNode(node->token_pos(), |
| new_receiver, |
| node->field_name(), |
| node->function(), |
| new_value); |
| |
| LocalVariable* result = AddToPreambleNewTempVar(new_setter); |
| result_ = new(Z) LoadLocalNode(Scanner::kNoSourcePos, result); |
| } |
| |
| |
| void AwaitTransformer::VisitLoadLocalNode(LoadLocalNode* node) { |
| result_ = node; |
| } |
| |
| |
| void AwaitTransformer::VisitStoreLocalNode(StoreLocalNode* node) { |
| AstNode* new_value = Transform(node->value()); |
| result_ = new(Z) StoreLocalNode(node->token_pos(), &node->local(), new_value); |
| } |
| |
| |
| void AwaitTransformer::VisitLoadStaticFieldNode(LoadStaticFieldNode* node) { |
| result_ = node; |
| } |
| |
| |
| void AwaitTransformer::VisitStoreStaticFieldNode(StoreStaticFieldNode* node) { |
| AstNode* new_value = Transform(node->value()); |
| result_ = new(Z) StoreStaticFieldNode(node->token_pos(), |
| node->field(), |
| new_value); |
| } |
| |
| |
| void AwaitTransformer::VisitLoadIndexedNode(LoadIndexedNode* node) { |
| AstNode* new_array = Transform(node->array()); |
| AstNode* new_index = Transform(node->index_expr()); |
| LocalVariable* result = AddToPreambleNewTempVar( |
| new(Z) LoadIndexedNode(node->token_pos(), |
| new_array, |
| new_index, |
| node->super_class())); |
| result_ = new(Z) 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(Z) StoreIndexedNode(node->token_pos(), |
| new_array, |
| new_index, |
| new_value, |
| node->super_class())); |
| result_ = new(Z) LoadLocalNode(Scanner::kNoSourcePos, result); |
| } |
| |
| |
| void AwaitTransformer::VisitAssignableNode(AssignableNode* node) { |
| AstNode* new_expr = Transform(node->expr()); |
| LocalVariable* result = AddToPreambleNewTempVar( |
| new(Z) AssignableNode(node->token_pos(), |
| new_expr, |
| node->type(), |
| node->dst_name())); |
| result_ = new(Z) LoadLocalNode(Scanner::kNoSourcePos, result); |
| } |
| |
| |
| void AwaitTransformer::VisitLetNode(LetNode* node) { |
| // Add all the initializer nodes to the preamble and the |
| // temporary variables to the scope for async temporary variables. |
| // The temporary variables will be captured as a side effect of being |
| // added to a scope, and the subsequent nodes that are added to the |
| // preample can access them. |
| for (intptr_t i = 0; i < node->num_temps(); i++) { |
| async_temp_scope_->AddVariable(node->TempAt(i)); |
| AstNode* new_init_val = Transform(node->InitializerAt(i)); |
| preamble_->Add(new(Z) StoreLocalNode(node->token_pos(), |
| node->TempAt(i), |
| new_init_val)); |
| } |
| |
| // Add all expressions but the last to the preamble. We must do |
| // this because subexpressions of the awaitable expression we |
| // are currently transforming may depend on each other, |
| // e.g. await foo(a++, a++). Thus we must preserve the order of the |
| // transformed subexpressions. |
| for (intptr_t i = 0; i < node->nodes().length() - 1; i++) { |
| preamble_->Add(Transform(node->nodes()[i])); |
| } |
| |
| // The last expression in the let node is the value of the node. |
| // The result of the transformed let node is this expression. |
| ASSERT(node->nodes().length() > 0); |
| const intptr_t last_node_index = node->nodes().length() - 1; |
| result_ = Transform(node->nodes()[last_node_index]); |
| } |
| |
| |
| void AwaitTransformer::VisitThrowNode(ThrowNode* node) { |
| AstNode* new_exception = Transform(node->exception()); |
| result_ = new(Z) ThrowNode(node->token_pos(), |
| new_exception, |
| node->stacktrace()); |
| } |
| |
| } // namespace dart |