2 // async.cs: Asynchronous functions
5 // Marek Safar (marek.safar@gmail.com)
7 // Dual licensed under the terms of the MIT X11 or GNU GPL
9 // Copyright 2011 Novell, Inc.
10 // Copyright 2011 Xamarin Inc.
14 using System.Collections.Generic;
16 using System.Collections;
19 using IKVM.Reflection.Emit;
21 using System.Reflection.Emit;
26 public class Await : ExpressionStatement
31 public Await (Expression expr, Location loc)
37 public Expression Expr {
43 protected override void CloneTo (CloneContext clonectx, Expression target)
45 var t = (Await) target;
47 t.expr = expr.Clone (clonectx);
50 public override Expression CreateExpressionTree (ResolveContext ec)
52 throw new NotImplementedException ("ET");
55 public override bool ContainsEmitWithAwait ()
60 protected override Expression DoResolve (ResolveContext rc)
62 if (rc.HasSet (ResolveContext.Options.LockScope)) {
63 rc.Report.Error (1996, loc,
64 "The `await' operator cannot be used in the body of a lock statement");
67 if (rc.HasSet (ResolveContext.Options.ExpressionTreeConversion)) {
68 rc.Report.Error (1989, loc, "An expression tree cannot contain an await operator");
73 rc.Report.Error (4004, loc,
74 "The `await' operator cannot be used in an unsafe context");
77 var bc = (BlockContext) rc;
79 stmt = new AwaitStatement (expr, loc);
80 if (!stmt.Resolve (bc))
83 type = stmt.ResultType;
84 eclass = ExprClass.Variable;
88 public override void Emit (EmitContext ec)
90 stmt.EmitPrologue (ec);
92 using (ec.With (BuilderContext.Options.OmitDebugInfo, true)) {
97 public override Expression EmitToField (EmitContext ec)
99 stmt.EmitPrologue (ec);
100 return stmt.GetResultExpression (ec);
103 public void EmitAssign (EmitContext ec, FieldExpr field)
105 stmt.EmitPrologue (ec);
106 field.InstanceExpression.Emit (ec);
110 public override void EmitStatement (EmitContext ec)
112 stmt.EmitStatement (ec);
115 public override object Accept (StructuralVisitor visitor)
117 return visitor.Visit (this);
121 class AwaitStatement : YieldStatement<AsyncInitializer>
123 sealed class AwaitableMemberAccess : MemberAccess
125 public AwaitableMemberAccess (Expression expr)
126 : base (expr, "GetAwaiter")
130 protected override void Error_TypeDoesNotContainDefinition (ResolveContext rc, TypeSpec type, string name)
132 Error_OperatorCannotBeApplied (rc, type);
135 protected override void Error_OperatorCannotBeApplied (ResolveContext rc, TypeSpec type)
137 rc.Report.Error (4001, loc, "Cannot await `{0}' expression", type.GetSignatureForError ());
141 sealed class GetResultInvocation : Invocation
143 public GetResultInvocation (MethodGroupExpr mge, Arguments arguments)
144 : base (null, arguments)
147 type = mg.BestCandidateReturnType;
150 public override Expression EmitToField (EmitContext ec)
157 PropertySpec is_completed;
158 MethodSpec on_completed;
159 MethodSpec get_result;
161 TypeSpec result_type;
163 public AwaitStatement (Expression expr, Location loc)
172 return is_completed == null;
176 public TypeSpec ResultType {
184 protected override void DoEmit (EmitContext ec)
186 GetResultExpression (ec).Emit (ec);
189 public Expression GetResultExpression (EmitContext ec)
191 var fe_awaiter = new FieldExpr (awaiter, loc);
192 fe_awaiter.InstanceExpression = new CompilerGeneratedThis (ec.CurrentType, loc);
195 // result = awaiter.GetResult ();
198 var rc = new ResolveContext (ec.MemberContext);
199 return new Invocation (new MemberAccess (fe_awaiter, "GetResult"), new Arguments (0)).Resolve (rc);
201 var mg_result = MethodGroupExpr.CreatePredefined (get_result, fe_awaiter.Type, loc);
202 mg_result.InstanceExpression = fe_awaiter;
204 return new GetResultInvocation (mg_result, new Arguments (0));
208 public void EmitPrologue (EmitContext ec)
210 var fe_awaiter = new FieldExpr (awaiter, loc);
211 fe_awaiter.InstanceExpression = new CompilerGeneratedThis (ec.CurrentType, loc);
214 // awaiter = expr.GetAwaiter ();
216 fe_awaiter.EmitAssign (ec, expr, false, false);
218 Label skip_continuation = ec.DefineLabel ();
220 Expression completed_expr;
222 var rc = new ResolveContext (ec.MemberContext);
224 Arguments dargs = new Arguments (1);
225 dargs.Add (new Argument (fe_awaiter));
226 completed_expr = new DynamicMemberBinder ("IsCompleted", dargs, loc).Resolve (rc);
228 var pe = PropertyExpr.CreatePredefined (is_completed, loc);
229 pe.InstanceExpression = fe_awaiter;
233 completed_expr.EmitBranchable (ec, skip_continuation, true);
238 // The stack has to be empty before calling await continuation. We handle this
239 // by lifting values which would be left on stack into class fields. The process
240 // is quite complicated and quite hard to test because any expression can possibly
241 // leave a value on the stack.
243 // Following assert fails when some of expression called before is missing EmitToField
244 // or parent expression fails to find await in children expressions
246 ec.AssertEmptyStack ();
248 var storey = (AsyncTaskStorey) machine_initializer.Storey;
249 var cont_field = storey.EmitContinuationInitialization (ec);
251 var args = new Arguments (1);
252 args.Add (new Argument (cont_field));
255 var rc = new ResolveContext (ec.MemberContext);
256 var mg_expr = new Invocation (new MemberAccess (fe_awaiter, "OnCompleted"), args).Resolve (rc);
258 ExpressionStatement es = (ExpressionStatement) mg_expr;
259 es.EmitStatement (ec);
261 var mg_completed = MethodGroupExpr.CreatePredefined (on_completed, fe_awaiter.Type, loc);
262 mg_completed.InstanceExpression = fe_awaiter;
265 // awaiter.OnCompleted (continuation);
267 mg_completed.EmitCall (ec, args);
271 machine_initializer.EmitLeave (ec, unwind_protect);
273 ec.MarkLabel (resume_point);
274 ec.MarkLabel (skip_continuation);
277 public void EmitStatement (EmitContext ec)
282 if (ResultType.Kind != MemberKind.Void) {
283 var storey = (AsyncTaskStorey) machine_initializer.Storey;
285 if (storey.HoistedReturn != null)
286 storey.HoistedReturn.EmitAssign (ec);
288 ec.Emit (OpCodes.Pop);
292 void Error_WrongAwaiterPattern (ResolveContext rc, TypeSpec awaiter)
294 rc.Report.Error (4011, loc, "The awaiter type `{0}' must have suitable IsCompleted, OnCompleted, and GetResult members",
295 awaiter.GetSignatureForError ());
298 public override bool Resolve (BlockContext bc)
300 if (!base.Resolve (bc))
303 Arguments args = new Arguments (0);
308 // The await expression is of dynamic type
310 if (type.BuiltinType == BuiltinTypeSpec.Type.Dynamic) {
313 awaiter = ((AsyncTaskStorey) machine_initializer.Storey).AddAwaiter (type, loc);
315 expr = new Invocation (new MemberAccess (expr, "GetAwaiter"), args).Resolve (bc);
320 // Check whether the expression is awaitable
322 Expression ama = new AwaitableMemberAccess (expr).Resolve (bc);
326 var errors_printer = new SessionReportPrinter ();
327 var old = bc.Report.SetPrinter (errors_printer);
328 ama = new Invocation (ama, args).Resolve (bc);
329 bc.Report.SetPrinter (old);
331 if (errors_printer.ErrorsCount > 0 || !MemberAccess.IsValidDotExpression (ama.Type)) {
332 bc.Report.Error (1986, expr.Location,
333 "The `await' operand type `{0}' must have suitable GetAwaiter method",
334 expr.Type.GetSignatureForError ());
339 var awaiter_type = ama.Type;
340 awaiter = ((AsyncTaskStorey) machine_initializer.Storey).AddAwaiter (awaiter_type, loc);
345 // Predefined: bool IsCompleted { get; }
347 is_completed = MemberCache.FindMember (awaiter_type, MemberFilter.Property ("IsCompleted", bc.Module.Compiler.BuiltinTypes.Bool),
348 BindingRestriction.InstanceOnly) as PropertySpec;
350 if (is_completed == null || !is_completed.HasGet) {
351 Error_WrongAwaiterPattern (bc, awaiter_type);
356 // Predefined: OnCompleted (Action)
358 if (bc.Module.PredefinedTypes.Action.Define ()) {
359 on_completed = MemberCache.FindMember (awaiter_type, MemberFilter.Method ("OnCompleted", 0,
360 ParametersCompiled.CreateFullyResolved (bc.Module.PredefinedTypes.Action.TypeSpec), bc.Module.Compiler.BuiltinTypes.Void),
361 BindingRestriction.InstanceOnly) as MethodSpec;
363 if (on_completed == null) {
364 Error_WrongAwaiterPattern (bc, awaiter_type);
370 // Predefined: GetResult ()
372 // The method return type is also result type of await expression
374 get_result = MemberCache.FindMember (awaiter_type, MemberFilter.Method ("GetResult", 0,
375 ParametersCompiled.EmptyReadOnlyParameters, null),
376 BindingRestriction.InstanceOnly) as MethodSpec;
378 if (get_result == null) {
379 Error_WrongAwaiterPattern (bc, awaiter_type);
383 result_type = get_result.ReturnType;
389 public class AsyncInitializer : StateMachineInitializer
391 TypeInferenceContext return_inference;
393 public AsyncInitializer (ParametersBlock block, TypeDefinition host, TypeSpec returnType)
394 : base (block, host, returnType)
400 public override string ContainerType {
402 return "async state machine block";
406 public override bool IsIterator {
412 public Block OriginalBlock {
418 public TypeInferenceContext ReturnTypeInference {
420 return return_inference;
426 public static void Create (IMemberContext context, ParametersBlock block, ParametersCompiled parameters, TypeDefinition host, TypeSpec returnType, Location loc)
428 for (int i = 0; i < parameters.Count; i++) {
429 Parameter p = parameters[i];
430 Parameter.Modifier mod = p.ModFlags;
431 if ((mod & Parameter.Modifier.ISBYREF) != 0) {
432 host.Compiler.Report.Error (1988, p.Location,
433 "Async methods cannot have ref or out parameters");
437 if (p is ArglistParameter) {
438 host.Compiler.Report.Error (4006, p.Location,
439 "__arglist is not allowed in parameter list of async methods");
443 if (parameters.Types[i].IsPointer) {
444 host.Compiler.Report.Error (4005, p.Location,
445 "Async methods cannot have unsafe parameters");
450 if (!block.HasAwait) {
451 host.Compiler.Report.Warning (1998, 1, loc,
452 "Async block lacks `await' operator and will run synchronously");
455 block.WrapIntoAsyncTask (context, host, returnType);
458 protected override BlockContext CreateBlockContext (ResolveContext rc)
460 var ctx = base.CreateBlockContext (rc);
461 var lambda = rc.CurrentAnonymousMethod as LambdaMethod;
463 return_inference = lambda.ReturnTypeInference;
465 ctx.StartFlowBranching (this, rc.CurrentBranching);
469 public override Expression CreateExpressionTree (ResolveContext ec)
471 return base.CreateExpressionTree (ec);
474 public override void Emit (EmitContext ec)
476 throw new NotImplementedException ();
479 protected override void EmitMoveNextEpilogue (EmitContext ec)
481 var storey = (AsyncTaskStorey) Storey;
482 storey.EmitSetResult (ec);
485 public override void EmitStatement (EmitContext ec)
487 var storey = (AsyncTaskStorey) Storey;
488 storey.Instance.Emit (ec);
490 var move_next_entry = storey.StateMachineMethod.Spec;
491 if (storey.MemberName.Arity > 0) {
492 move_next_entry = MemberCache.GetMember (storey.Instance.Type, move_next_entry);
495 ec.Emit (OpCodes.Call, move_next_entry);
498 // Emits return <async-storey-instance>.$builder.Task;
500 if (storey.Task != null) {
501 var builder_field = storey.Builder.Spec;
502 var task_get = storey.Task.Get;
504 if (storey.MemberName.Arity > 0) {
505 builder_field = MemberCache.GetMember (storey.Instance.Type, builder_field);
506 task_get = MemberCache.GetMember (builder_field.MemberType, task_get);
509 var pe_task = new PropertyExpr (storey.Task, loc) {
510 InstanceExpression = new FieldExpr (builder_field, loc) {
511 InstanceExpression = storey.Instance
519 ec.Emit (OpCodes.Ret);
523 class AsyncTaskStorey : StateMachine
526 Field builder, continuation;
527 readonly TypeSpec return_type;
528 MethodSpec set_result;
529 MethodSpec set_exception;
531 LocalVariable hoisted_return;
533 Dictionary<TypeSpec, List<StackField>> stack_fields;
536 public AsyncTaskStorey (IMemberContext context, AsyncInitializer initializer, TypeSpec type)
537 : base (initializer.OriginalBlock, initializer.Host, context.CurrentMemberDefinition as MemberBase, context.CurrentTypeParameters, "async")
544 public Field Builder {
550 public LocalVariable HoistedReturn {
552 return hoisted_return;
556 public TypeSpec ReturnType {
562 public PropertySpec Task {
570 public Field AddAwaiter (TypeSpec type, Location loc)
572 return AddCapturedVariable ("$awaiter" + awaiters++.ToString ("X"), type);
575 public StackField AddCapturedLocalVariable (TypeSpec type)
578 type = mutator.Mutate (type);
580 List<StackField> existing_fields = null;
581 if (stack_fields == null) {
582 stack_fields = new Dictionary<TypeSpec, List<StackField>> ();
583 } else if (stack_fields.TryGetValue (type, out existing_fields)) {
584 foreach (var f in existing_fields) {
586 f.CanBeReused = false;
592 const Modifiers mod = Modifiers.COMPILER_GENERATED | Modifiers.PRIVATE;
593 var field = new StackField (this, new TypeExpression (type, Location), mod, new MemberName ("<s>$" + locals_captured++.ToString ("X"), Location));
598 if (existing_fields == null) {
599 existing_fields = new List<StackField> ();
600 stack_fields.Add (type, existing_fields);
603 existing_fields.Add (field);
608 protected override bool DoDefineMembers ()
610 action = Module.PredefinedTypes.Action.Resolve ();
612 PredefinedType builder_type;
613 PredefinedMember<MethodSpec> bf;
614 PredefinedMember<MethodSpec> sr;
615 PredefinedMember<MethodSpec> se;
616 bool has_task_return_type = false;
617 var pred_members = Module.PredefinedMembers;
619 if (return_type.Kind == MemberKind.Void) {
620 builder_type = Module.PredefinedTypes.AsyncVoidMethodBuilder;
621 bf = pred_members.AsyncVoidMethodBuilderCreate;
622 sr = pred_members.AsyncVoidMethodBuilderSetResult;
623 se = pred_members.AsyncVoidMethodBuilderSetException;
624 } else if (return_type == Module.PredefinedTypes.Task.TypeSpec) {
625 builder_type = Module.PredefinedTypes.AsyncTaskMethodBuilder;
626 bf = pred_members.AsyncTaskMethodBuilderCreate;
627 sr = pred_members.AsyncTaskMethodBuilderSetResult;
628 se = pred_members.AsyncTaskMethodBuilderSetException;
629 task = pred_members.AsyncTaskMethodBuilderTask.Get ();
631 builder_type = Module.PredefinedTypes.AsyncTaskMethodBuilderGeneric;
632 bf = pred_members.AsyncTaskMethodBuilderGenericCreate;
633 sr = pred_members.AsyncTaskMethodBuilderGenericSetResult;
634 se = pred_members.AsyncTaskMethodBuilderGenericSetException;
635 task = pred_members.AsyncTaskMethodBuilderGenericTask.Get ();
636 has_task_return_type = true;
639 set_result = sr.Get ();
640 set_exception = se.Get ();
641 var builder_factory = bf.Get ();
642 if (!builder_type.Define () || set_result == null || builder_factory == null || set_exception == null) {
643 Report.Error (1993, Location,
644 "Cannot find compiler required types for asynchronous functions support. Are you targeting the wrong framework version?");
645 return base.DoDefineMembers ();
648 var bt = builder_type.TypeSpec;
651 // Inflate generic Task types
653 if (has_task_return_type) {
654 var task_return_type = return_type.TypeArguments;
656 task_return_type = mutator.Mutate (task_return_type);
658 bt = bt.MakeGenericType (Module, task_return_type);
659 builder_factory = MemberCache.GetMember<MethodSpec> (bt, builder_factory);
660 set_result = MemberCache.GetMember<MethodSpec> (bt, set_result);
661 set_exception = MemberCache.GetMember<MethodSpec> (bt, set_exception);
664 task = MemberCache.GetMember<PropertySpec> (bt, task);
667 builder = AddCompilerGeneratedField ("$builder", new TypeExpression (bt, Location));
669 var ctor = DefineDefaultConstructor (false);
671 if (!base.DoDefineMembers ())
674 Block block = ctor.Block;
676 var mg = MethodGroupExpr.CreatePredefined (builder_factory, bt, Location);
678 new StatementExpression (new SimpleAssign (
679 new FieldExpr (builder, Location),
680 new Invocation (mg, new Arguments (0)),
683 if (has_task_return_type) {
684 hoisted_return = LocalVariable.CreateCompilerGenerated (bt.TypeArguments[0], block, Location);
690 public Expression EmitContinuationInitialization (EmitContext ec)
693 // When more than 1 awaiter has been used in the block we
694 // introduce class scope field to cache continuation delegate
697 if (continuation == null) {
698 continuation = AddCompilerGeneratedField ("$continuation", new TypeExpression (action, Location), true);
699 continuation.Define ();
702 var fexpr = new FieldExpr (continuation, Location);
703 fexpr.InstanceExpression = new CompilerGeneratedThis (CurrentType, Location);
706 // if ($continuation == null)
707 // $continuation = new Action (MoveNext);
711 var skip_cont_init = ec.DefineLabel ();
712 ec.Emit (OpCodes.Brtrue_S, skip_cont_init);
716 ec.Emit (OpCodes.Stfld, continuation.Spec);
717 ec.MarkLabel (skip_cont_init);
723 // Otherwise simply use temporary local variable
725 var field = LocalVariable.CreateCompilerGenerated (action, OriginalSourceBlock, Location);
727 field.EmitAssign (ec);
728 return new LocalVariableReference (field, Location);
731 void EmitActionLoad (EmitContext ec)
734 ec.Emit (OpCodes.Ldftn, StateMachineMethod.Spec);
735 ec.Emit (OpCodes.Newobj, (MethodSpec) MemberCache.FindMember (action, MemberFilter.Constructor (null), BindingRestriction.DeclaredOnly));
738 public void EmitSetException (EmitContext ec, LocalVariableReference exceptionVariable)
741 // $builder.SetException (Exception)
743 var mg = MethodGroupExpr.CreatePredefined (set_exception, set_exception.DeclaringType, Location);
744 mg.InstanceExpression = new FieldExpr (Builder, Location) {
745 InstanceExpression = new CompilerGeneratedThis (ec.CurrentType, Location)
748 Arguments args = new Arguments (1);
749 args.Add (new Argument (exceptionVariable));
751 mg.EmitCall (ec, args);
754 public void EmitSetResult (EmitContext ec)
757 // $builder.SetResult ();
758 // $builder.SetResult<return-type> (value);
760 var mg = MethodGroupExpr.CreatePredefined (set_result, set_result.DeclaringType, Location);
761 mg.InstanceExpression = new FieldExpr (Builder, Location) {
762 InstanceExpression = new CompilerGeneratedThis (ec.CurrentType, Location)
766 if (hoisted_return == null) {
767 args = new Arguments (0);
769 args = new Arguments (1);
770 args.Add (new Argument (new LocalVariableReference (hoisted_return, Location)));
773 mg.EmitCall (ec, args);
777 class StackField : Field
779 public StackField (TypeDefinition parent, FullNamedExpression type, Modifiers mod, MemberName name)
780 : base (parent, type, mod, name, null)
784 public bool CanBeReused { get; set; }
787 class StackFieldExpr : FieldExpr
789 public StackFieldExpr (Field field)
790 : base (field, Location.Null)
794 public override void Emit (EmitContext ec)
798 var field = (StackField) spec.MemberDefinition;
799 field.CanBeReused = true;