mirror of
https://github.com/dart-lang/sdk
synced 2024-09-20 09:36:27 +00:00
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
This commit is contained in:
parent
bfa2868d07
commit
5f02ceed86
|
@ -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)
|
||||
|
|
|
@ -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(),
|
||||
|
|
508
runtime/vm/ast_transformer.cc
Normal file
508
runtime/vm/ast_transformer.cc
Normal file
|
@ -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 = <expr>;
|
||||
// :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);
|
||||
}
|
||||
|
||||
|
||||
// ::= (<condition>) ? <true-branch> : <false-branch>
|
||||
//
|
||||
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<AstNode*> 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
|
84
runtime/vm/ast_transformer.h
Normal file
84
runtime/vm/ast_transformer.h
Normal file
|
@ -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;
|
||||
// <continuation logic>
|
||||
// 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_
|
|
@ -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()
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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<const LibraryPrefix*>()),
|
||||
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<const LibraryPrefix*>* 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();
|
||||
|
|
|
@ -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.") \
|
||||
|
|
|
@ -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',
|
||||
|
|
132
tests/language/await_test.dart
Normal file
132
tests/language/await_test.dart
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue