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");
68 rc.Report.Error (4004, loc,
69 "The `await' operator cannot be used in an unsafe context");
72 var bc = (BlockContext) rc;
74 stmt = new AwaitStatement (expr, loc);
75 if (!stmt.Resolve (bc))
78 type = stmt.ResultType;
79 eclass = ExprClass.Variable;
83 public override void Emit (EmitContext ec)
85 stmt.EmitPrologue (ec);
87 using (ec.With (BuilderContext.Options.OmitDebugInfo, true)) {
92 public override Expression EmitToField (EmitContext ec)
94 stmt.EmitPrologue (ec);
95 return stmt.GetResultExpression (ec);
98 public void EmitAssign (EmitContext ec, FieldExpr field)
100 stmt.EmitPrologue (ec);
101 field.InstanceExpression.Emit (ec);
105 public override void EmitStatement (EmitContext ec)
107 stmt.EmitStatement (ec);
110 public override object Accept (StructuralVisitor visitor)
112 return visitor.Visit (this);
116 class AwaitStatement : YieldStatement<AsyncInitializer>
118 sealed class AwaitableMemberAccess : MemberAccess
120 public AwaitableMemberAccess (Expression expr)
121 : base (expr, "GetAwaiter")
125 protected override void Error_TypeDoesNotContainDefinition (ResolveContext rc, TypeSpec type, string name)
127 Error_OperatorCannotBeApplied (rc, type);
130 protected override void Error_OperatorCannotBeApplied (ResolveContext rc, TypeSpec type)
132 var invocation = LeftExpression as Invocation;
133 if (invocation != null && invocation.MethodGroup != null && (invocation.MethodGroup.BestCandidate.Modifiers & Modifiers.ASYNC) != 0) {
134 rc.Report.Error (4008, loc, "Cannot await void method `{0}'. Consider changing method return type to `Task'",
135 invocation.GetSignatureForError ());
137 rc.Report.Error (4001, loc, "Cannot await `{0}' expression", type.GetSignatureForError ());
142 sealed class GetResultInvocation : Invocation
144 public GetResultInvocation (MethodGroupExpr mge, Arguments arguments)
145 : base (null, arguments)
148 type = mg.BestCandidateReturnType;
151 public override Expression EmitToField (EmitContext ec)
158 PropertySpec is_completed;
159 MethodSpec on_completed;
160 MethodSpec get_result;
162 TypeSpec result_type;
164 public AwaitStatement (Expression expr, Location loc)
173 return is_completed == null;
177 public TypeSpec ResultType {
185 protected override void DoEmit (EmitContext ec)
187 GetResultExpression (ec).Emit (ec);
190 public Expression GetResultExpression (EmitContext ec)
192 var fe_awaiter = new FieldExpr (awaiter, loc);
193 fe_awaiter.InstanceExpression = new CompilerGeneratedThis (ec.CurrentType, loc);
196 // result = awaiter.GetResult ();
199 var rc = new ResolveContext (ec.MemberContext);
200 return new Invocation (new MemberAccess (fe_awaiter, "GetResult"), new Arguments (0)).Resolve (rc);
202 var mg_result = MethodGroupExpr.CreatePredefined (get_result, fe_awaiter.Type, loc);
203 mg_result.InstanceExpression = fe_awaiter;
205 return new GetResultInvocation (mg_result, new Arguments (0));
209 public void EmitPrologue (EmitContext ec)
211 var fe_awaiter = new FieldExpr (awaiter, loc);
212 fe_awaiter.InstanceExpression = new CompilerGeneratedThis (ec.CurrentType, loc);
215 // awaiter = expr.GetAwaiter ();
217 fe_awaiter.EmitAssign (ec, expr, false, false);
219 Label skip_continuation = ec.DefineLabel ();
221 Expression completed_expr;
223 var rc = new ResolveContext (ec.MemberContext);
225 Arguments dargs = new Arguments (1);
226 dargs.Add (new Argument (fe_awaiter));
227 completed_expr = new DynamicMemberBinder ("IsCompleted", dargs, loc).Resolve (rc);
229 var pe = PropertyExpr.CreatePredefined (is_completed, loc);
230 pe.InstanceExpression = fe_awaiter;
234 completed_expr.EmitBranchable (ec, skip_continuation, true);
239 // The stack has to be empty before calling await continuation. We handle this
240 // by lifting values which would be left on stack into class fields. The process
241 // is quite complicated and quite hard to test because any expression can possibly
242 // leave a value on the stack.
244 // Following assert fails when some of expression called before is missing EmitToField
245 // or parent expression fails to find await in children expressions
247 ec.AssertEmptyStack ();
249 var storey = (AsyncTaskStorey) machine_initializer.Storey;
250 var cont_field = storey.EmitContinuationInitialization (ec);
252 var args = new Arguments (1);
253 args.Add (new Argument (cont_field));
256 var rc = new ResolveContext (ec.MemberContext);
257 var mg_expr = new Invocation (new MemberAccess (fe_awaiter, "OnCompleted"), args).Resolve (rc);
259 ExpressionStatement es = (ExpressionStatement) mg_expr;
260 es.EmitStatement (ec);
262 var mg_completed = MethodGroupExpr.CreatePredefined (on_completed, fe_awaiter.Type, loc);
263 mg_completed.InstanceExpression = fe_awaiter;
266 // awaiter.OnCompleted (continuation);
268 mg_completed.EmitCall (ec, args);
272 machine_initializer.EmitLeave (ec, unwind_protect);
274 ec.MarkLabel (resume_point);
275 ec.MarkLabel (skip_continuation);
278 public void EmitStatement (EmitContext ec)
283 if (ResultType.Kind != MemberKind.Void) {
284 var storey = (AsyncTaskStorey) machine_initializer.Storey;
286 if (storey.HoistedReturn != null)
287 storey.HoistedReturn.EmitAssign (ec);
289 ec.Emit (OpCodes.Pop);
293 void Error_WrongAwaiterPattern (ResolveContext rc, TypeSpec awaiter)
295 rc.Report.Error (4011, loc, "The awaiter type `{0}' must have suitable IsCompleted, OnCompleted, and GetResult members",
296 awaiter.GetSignatureForError ());
299 public override bool Resolve (BlockContext bc)
301 if (bc.CurrentBlock is Linq.QueryBlock) {
302 bc.Report.Error (1995, loc,
303 "The `await' operator may only be used in a query expression within the first collection expression of the initial `from' clause or within the collection expression of a `join' clause");
307 if (!base.Resolve (bc))
310 Arguments args = new Arguments (0);
315 // The await expression is of dynamic type
317 if (type.BuiltinType == BuiltinTypeSpec.Type.Dynamic) {
320 awaiter = ((AsyncTaskStorey) machine_initializer.Storey).AddAwaiter (type, loc);
322 expr = new Invocation (new MemberAccess (expr, "GetAwaiter"), args).Resolve (bc);
327 // Check whether the expression is awaitable
329 Expression ama = new AwaitableMemberAccess (expr).Resolve (bc);
333 var errors_printer = new SessionReportPrinter ();
334 var old = bc.Report.SetPrinter (errors_printer);
335 ama = new Invocation (ama, args).Resolve (bc);
336 bc.Report.SetPrinter (old);
338 if (errors_printer.ErrorsCount > 0 || !MemberAccess.IsValidDotExpression (ama.Type)) {
339 bc.Report.Error (1986, expr.Location,
340 "The `await' operand type `{0}' must have suitable GetAwaiter method",
341 expr.Type.GetSignatureForError ());
346 var awaiter_type = ama.Type;
347 awaiter = ((AsyncTaskStorey) machine_initializer.Storey).AddAwaiter (awaiter_type, loc);
352 // Predefined: bool IsCompleted { get; }
354 is_completed = MemberCache.FindMember (awaiter_type, MemberFilter.Property ("IsCompleted", bc.Module.Compiler.BuiltinTypes.Bool),
355 BindingRestriction.InstanceOnly) as PropertySpec;
357 if (is_completed == null || !is_completed.HasGet) {
358 Error_WrongAwaiterPattern (bc, awaiter_type);
363 // Predefined: OnCompleted (Action)
365 if (bc.Module.PredefinedTypes.Action.Define ()) {
366 on_completed = MemberCache.FindMember (awaiter_type, MemberFilter.Method ("OnCompleted", 0,
367 ParametersCompiled.CreateFullyResolved (bc.Module.PredefinedTypes.Action.TypeSpec), bc.Module.Compiler.BuiltinTypes.Void),
368 BindingRestriction.InstanceOnly) as MethodSpec;
370 if (on_completed == null) {
371 Error_WrongAwaiterPattern (bc, awaiter_type);
377 // Predefined: GetResult ()
379 // The method return type is also result type of await expression
381 get_result = MemberCache.FindMember (awaiter_type, MemberFilter.Method ("GetResult", 0,
382 ParametersCompiled.EmptyReadOnlyParameters, null),
383 BindingRestriction.InstanceOnly) as MethodSpec;
385 if (get_result == null) {
386 Error_WrongAwaiterPattern (bc, awaiter_type);
390 result_type = get_result.ReturnType;
396 public class AsyncInitializer : StateMachineInitializer
398 TypeInferenceContext return_inference;
400 public AsyncInitializer (ParametersBlock block, TypeDefinition host, TypeSpec returnType)
401 : base (block, host, returnType)
407 public override string ContainerType {
409 return "async state machine block";
413 public override bool IsIterator {
419 public Block OriginalBlock {
425 public TypeInferenceContext ReturnTypeInference {
427 return return_inference;
433 public static void Create (IMemberContext context, ParametersBlock block, ParametersCompiled parameters, TypeDefinition host, TypeSpec returnType, Location loc)
435 for (int i = 0; i < parameters.Count; i++) {
436 Parameter p = parameters[i];
437 Parameter.Modifier mod = p.ModFlags;
438 if ((mod & Parameter.Modifier.RefOutMask) != 0) {
439 host.Compiler.Report.Error (1988, p.Location,
440 "Async methods cannot have ref or out parameters");
444 if (p is ArglistParameter) {
445 host.Compiler.Report.Error (4006, p.Location,
446 "__arglist is not allowed in parameter list of async methods");
450 if (parameters.Types[i].IsPointer) {
451 host.Compiler.Report.Error (4005, p.Location,
452 "Async methods cannot have unsafe parameters");
457 if (!block.HasAwait) {
458 host.Compiler.Report.Warning (1998, 1, loc,
459 "Async block lacks `await' operator and will run synchronously");
462 block.WrapIntoAsyncTask (context, host, returnType);
465 protected override BlockContext CreateBlockContext (ResolveContext rc)
467 var ctx = base.CreateBlockContext (rc);
468 var lambda = rc.CurrentAnonymousMethod as LambdaMethod;
470 return_inference = lambda.ReturnTypeInference;
472 ctx.StartFlowBranching (this, rc.CurrentBranching);
476 public override Expression CreateExpressionTree (ResolveContext ec)
478 return base.CreateExpressionTree (ec);
481 public override void Emit (EmitContext ec)
483 throw new NotImplementedException ();
486 protected override void EmitMoveNextEpilogue (EmitContext ec)
488 var storey = (AsyncTaskStorey) Storey;
489 storey.EmitSetResult (ec);
492 public override void EmitStatement (EmitContext ec)
494 var storey = (AsyncTaskStorey) Storey;
495 storey.Instance.Emit (ec);
497 var move_next_entry = storey.StateMachineMethod.Spec;
498 if (storey.MemberName.Arity > 0) {
499 move_next_entry = MemberCache.GetMember (storey.Instance.Type, move_next_entry);
502 ec.Emit (OpCodes.Call, move_next_entry);
505 // Emits return <async-storey-instance>.$builder.Task;
507 if (storey.Task != null) {
508 var builder_field = storey.Builder.Spec;
509 var task_get = storey.Task.Get;
511 if (storey.MemberName.Arity > 0) {
512 builder_field = MemberCache.GetMember (storey.Instance.Type, builder_field);
513 task_get = MemberCache.GetMember (builder_field.MemberType, task_get);
516 var pe_task = new PropertyExpr (storey.Task, loc) {
517 InstanceExpression = new FieldExpr (builder_field, loc) {
518 InstanceExpression = storey.Instance
526 ec.Emit (OpCodes.Ret);
530 class AsyncTaskStorey : StateMachine
533 Field builder, continuation;
534 readonly TypeSpec return_type;
535 MethodSpec set_result;
536 MethodSpec set_exception;
538 LocalVariable hoisted_return;
540 Dictionary<TypeSpec, List<StackField>> stack_fields;
543 public AsyncTaskStorey (IMemberContext context, AsyncInitializer initializer, TypeSpec type)
544 : base (initializer.OriginalBlock, initializer.Host, context.CurrentMemberDefinition as MemberBase, context.CurrentTypeParameters, "async")
551 public Field Builder {
557 public LocalVariable HoistedReturn {
559 return hoisted_return;
563 public TypeSpec ReturnType {
569 public PropertySpec Task {
577 public Field AddAwaiter (TypeSpec type, Location loc)
579 return AddCapturedVariable ("$awaiter" + awaiters++.ToString ("X"), type);
582 public StackField AddCapturedLocalVariable (TypeSpec type)
585 type = mutator.Mutate (type);
587 List<StackField> existing_fields = null;
588 if (stack_fields == null) {
589 stack_fields = new Dictionary<TypeSpec, List<StackField>> ();
590 } else if (stack_fields.TryGetValue (type, out existing_fields)) {
591 foreach (var f in existing_fields) {
593 f.CanBeReused = false;
599 const Modifiers mod = Modifiers.COMPILER_GENERATED | Modifiers.PRIVATE;
600 var field = new StackField (this, new TypeExpression (type, Location), mod, new MemberName ("<s>$" + locals_captured++.ToString ("X"), Location));
605 if (existing_fields == null) {
606 existing_fields = new List<StackField> ();
607 stack_fields.Add (type, existing_fields);
610 existing_fields.Add (field);
615 protected override bool DoDefineMembers ()
617 action = Module.PredefinedTypes.Action.Resolve ();
619 PredefinedType builder_type;
620 PredefinedMember<MethodSpec> bf;
621 PredefinedMember<MethodSpec> sr;
622 PredefinedMember<MethodSpec> se;
623 bool has_task_return_type = false;
624 var pred_members = Module.PredefinedMembers;
626 if (return_type.Kind == MemberKind.Void) {
627 builder_type = Module.PredefinedTypes.AsyncVoidMethodBuilder;
628 bf = pred_members.AsyncVoidMethodBuilderCreate;
629 sr = pred_members.AsyncVoidMethodBuilderSetResult;
630 se = pred_members.AsyncVoidMethodBuilderSetException;
631 } else if (return_type == Module.PredefinedTypes.Task.TypeSpec) {
632 builder_type = Module.PredefinedTypes.AsyncTaskMethodBuilder;
633 bf = pred_members.AsyncTaskMethodBuilderCreate;
634 sr = pred_members.AsyncTaskMethodBuilderSetResult;
635 se = pred_members.AsyncTaskMethodBuilderSetException;
636 task = pred_members.AsyncTaskMethodBuilderTask.Get ();
638 builder_type = Module.PredefinedTypes.AsyncTaskMethodBuilderGeneric;
639 bf = pred_members.AsyncTaskMethodBuilderGenericCreate;
640 sr = pred_members.AsyncTaskMethodBuilderGenericSetResult;
641 se = pred_members.AsyncTaskMethodBuilderGenericSetException;
642 task = pred_members.AsyncTaskMethodBuilderGenericTask.Get ();
643 has_task_return_type = true;
646 set_result = sr.Get ();
647 set_exception = se.Get ();
648 var builder_factory = bf.Get ();
649 if (!builder_type.Define () || set_result == null || builder_factory == null || set_exception == null) {
650 Report.Error (1993, Location,
651 "Cannot find compiler required types for asynchronous functions support. Are you targeting the wrong framework version?");
652 return base.DoDefineMembers ();
655 var bt = builder_type.TypeSpec;
658 // Inflate generic Task types
660 if (has_task_return_type) {
661 var task_return_type = return_type.TypeArguments;
663 task_return_type = mutator.Mutate (task_return_type);
665 bt = bt.MakeGenericType (Module, task_return_type);
666 builder_factory = MemberCache.GetMember<MethodSpec> (bt, builder_factory);
667 set_result = MemberCache.GetMember<MethodSpec> (bt, set_result);
668 set_exception = MemberCache.GetMember<MethodSpec> (bt, set_exception);
671 task = MemberCache.GetMember<PropertySpec> (bt, task);
674 builder = AddCompilerGeneratedField ("$builder", new TypeExpression (bt, Location));
676 var ctor = DefineDefaultConstructor (false);
678 if (!base.DoDefineMembers ())
681 Block block = ctor.Block;
683 var mg = MethodGroupExpr.CreatePredefined (builder_factory, bt, Location);
685 new StatementExpression (new SimpleAssign (
686 new FieldExpr (builder, Location),
687 new Invocation (mg, new Arguments (0)),
690 if (has_task_return_type) {
691 hoisted_return = LocalVariable.CreateCompilerGenerated (bt.TypeArguments[0], block, Location);
697 public Expression EmitContinuationInitialization (EmitContext ec)
700 // When more than 1 awaiter has been used in the block we
701 // introduce class scope field to cache continuation delegate
704 if (continuation == null) {
705 continuation = AddCompilerGeneratedField ("$continuation", new TypeExpression (action, Location), true);
706 continuation.Define ();
709 var fexpr = new FieldExpr (continuation, Location);
710 fexpr.InstanceExpression = new CompilerGeneratedThis (CurrentType, Location);
713 // if ($continuation == null)
714 // $continuation = new Action (MoveNext);
718 var skip_cont_init = ec.DefineLabel ();
719 ec.Emit (OpCodes.Brtrue_S, skip_cont_init);
723 ec.Emit (OpCodes.Stfld, continuation.Spec);
724 ec.MarkLabel (skip_cont_init);
730 // Otherwise simply use temporary local variable
732 var field = LocalVariable.CreateCompilerGenerated (action, OriginalSourceBlock, Location);
734 field.EmitAssign (ec);
735 return new LocalVariableReference (field, Location);
738 void EmitActionLoad (EmitContext ec)
741 ec.Emit (OpCodes.Ldftn, StateMachineMethod.Spec);
742 ec.Emit (OpCodes.Newobj, (MethodSpec) MemberCache.FindMember (action, MemberFilter.Constructor (null), BindingRestriction.DeclaredOnly));
745 public void EmitSetException (EmitContext ec, LocalVariableReference exceptionVariable)
748 // $builder.SetException (Exception)
750 var mg = MethodGroupExpr.CreatePredefined (set_exception, set_exception.DeclaringType, Location);
751 mg.InstanceExpression = new FieldExpr (Builder, Location) {
752 InstanceExpression = new CompilerGeneratedThis (ec.CurrentType, Location)
755 Arguments args = new Arguments (1);
756 args.Add (new Argument (exceptionVariable));
758 mg.EmitCall (ec, args);
761 public void EmitSetResult (EmitContext ec)
764 // $builder.SetResult ();
765 // $builder.SetResult<return-type> (value);
767 var mg = MethodGroupExpr.CreatePredefined (set_result, set_result.DeclaringType, Location);
768 mg.InstanceExpression = new FieldExpr (Builder, Location) {
769 InstanceExpression = new CompilerGeneratedThis (ec.CurrentType, Location)
773 if (hoisted_return == null) {
774 args = new Arguments (0);
776 args = new Arguments (1);
777 args.Add (new Argument (new LocalVariableReference (hoisted_return, Location)));
780 mg.EmitCall (ec, args);
784 class StackField : Field
786 public StackField (TypeDefinition parent, FullNamedExpression type, Modifiers mod, MemberName name)
787 : base (parent, type, mod, name, null)
791 public bool CanBeReused { get; set; }
794 class StackFieldExpr : FieldExpr
796 public StackFieldExpr (Field field)
797 : base (field, Location.Null)
801 public override void Emit (EmitContext ec)
805 var field = (StackField) spec.MemberDefinition;
806 field.CanBeReused = true;