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 Type {
182 public TypeSpec ResultType {
190 protected override void DoEmit (EmitContext ec)
192 GetResultExpression (ec).Emit (ec);
195 public Expression GetResultExpression (EmitContext ec)
197 var fe_awaiter = new FieldExpr (awaiter, loc);
198 fe_awaiter.InstanceExpression = new CompilerGeneratedThis (ec.CurrentType, loc);
201 // result = awaiter.GetResult ();
204 var rc = new ResolveContext (ec.MemberContext);
205 return new Invocation (new MemberAccess (fe_awaiter, "GetResult"), new Arguments (0)).Resolve (rc);
207 var mg_result = MethodGroupExpr.CreatePredefined (get_result, fe_awaiter.Type, loc);
208 mg_result.InstanceExpression = fe_awaiter;
210 return new GetResultInvocation (mg_result, new Arguments (0));
214 public void EmitPrologue (EmitContext ec)
216 var fe_awaiter = new FieldExpr (awaiter, loc);
217 fe_awaiter.InstanceExpression = new CompilerGeneratedThis (ec.CurrentType, loc);
220 // awaiter = expr.GetAwaiter ();
222 fe_awaiter.EmitAssign (ec, expr, false, false);
224 Label skip_continuation = ec.DefineLabel ();
226 Expression completed_expr;
228 var rc = new ResolveContext (ec.MemberContext);
230 Arguments dargs = new Arguments (1);
231 dargs.Add (new Argument (fe_awaiter));
232 completed_expr = new DynamicMemberBinder ("IsCompleted", dargs, loc).Resolve (rc);
234 var pe = PropertyExpr.CreatePredefined (is_completed, loc);
235 pe.InstanceExpression = fe_awaiter;
239 completed_expr.EmitBranchable (ec, skip_continuation, true);
244 // The stack has to be empty before calling await continuation. We handle this
245 // by lifting values which would be left on stack into class fields. The process
246 // is quite complicated and quite hard to test because any expression can possibly
247 // leave a value on the stack.
249 // Following assert fails when some of expression called before is missing EmitToField
250 // or parent expression fails to find await in children expressions
252 ec.AssertEmptyStack ();
254 var storey = (AsyncTaskStorey) machine_initializer.Storey;
255 var cont_field = storey.EmitContinuationInitialization (ec);
257 var args = new Arguments (1);
258 args.Add (new Argument (cont_field));
261 var rc = new ResolveContext (ec.MemberContext);
262 var mg_expr = new Invocation (new MemberAccess (fe_awaiter, "OnCompleted"), args).Resolve (rc);
264 ExpressionStatement es = (ExpressionStatement) mg_expr;
265 es.EmitStatement (ec);
267 var mg_completed = MethodGroupExpr.CreatePredefined (on_completed, fe_awaiter.Type, loc);
268 mg_completed.InstanceExpression = fe_awaiter;
271 // awaiter.OnCompleted (continuation);
273 mg_completed.EmitCall (ec, args);
277 machine_initializer.EmitLeave (ec, unwind_protect);
279 ec.MarkLabel (resume_point);
280 ec.MarkLabel (skip_continuation);
283 public void EmitStatement (EmitContext ec)
288 if (ResultType.Kind != MemberKind.Void) {
289 var storey = (AsyncTaskStorey) machine_initializer.Storey;
291 if (storey.HoistedReturn != null)
292 storey.HoistedReturn.EmitAssign (ec);
294 ec.Emit (OpCodes.Pop);
298 void Error_WrongAwaiterPattern (ResolveContext rc, TypeSpec awaiter)
300 rc.Report.Error (4011, loc, "The awaiter type `{0}' must have suitable IsCompleted, OnCompleted, and GetResult members",
301 awaiter.GetSignatureForError ());
304 public override bool Resolve (BlockContext bc)
306 if (!base.Resolve (bc))
309 Arguments args = new Arguments (0);
314 // The await expression is of dynamic type
316 if (type.BuiltinType == BuiltinTypeSpec.Type.Dynamic) {
319 awaiter = ((AsyncTaskStorey) machine_initializer.Storey).AddAwaiter (type, loc);
321 expr = new Invocation (new MemberAccess (expr, "GetAwaiter"), args).Resolve (bc);
326 // Check whether the expression is awaitable
328 Expression ama = new AwaitableMemberAccess (expr).Resolve (bc);
332 var errors_printer = new SessionReportPrinter ();
333 var old = bc.Report.SetPrinter (errors_printer);
334 ama = new Invocation (ama, args).Resolve (bc);
335 bc.Report.SetPrinter (old);
337 if (errors_printer.ErrorsCount > 0 || !MemberAccess.IsValidDotExpression (ama.Type)) {
338 bc.Report.Error (1986, expr.Location,
339 "The `await' operand type `{0}' must have suitable GetAwaiter method",
340 expr.Type.GetSignatureForError ());
345 var awaiter_type = ama.Type;
346 awaiter = ((AsyncTaskStorey) machine_initializer.Storey).AddAwaiter (awaiter_type, loc);
351 // Predefined: bool IsCompleted { get; }
353 is_completed = MemberCache.FindMember (awaiter_type, MemberFilter.Property ("IsCompleted", bc.Module.Compiler.BuiltinTypes.Bool),
354 BindingRestriction.InstanceOnly) as PropertySpec;
356 if (is_completed == null || !is_completed.HasGet) {
357 Error_WrongAwaiterPattern (bc, awaiter_type);
362 // Predefined: OnCompleted (Action)
364 if (bc.Module.PredefinedTypes.Action.Define ()) {
365 on_completed = MemberCache.FindMember (awaiter_type, MemberFilter.Method ("OnCompleted", 0,
366 ParametersCompiled.CreateFullyResolved (bc.Module.PredefinedTypes.Action.TypeSpec), bc.Module.Compiler.BuiltinTypes.Void),
367 BindingRestriction.InstanceOnly) as MethodSpec;
369 if (on_completed == null) {
370 Error_WrongAwaiterPattern (bc, awaiter_type);
376 // Predefined: GetResult ()
378 // The method return type is also result type of await expression
380 get_result = MemberCache.FindMember (awaiter_type, MemberFilter.Method ("GetResult", 0,
381 ParametersCompiled.EmptyReadOnlyParameters, null),
382 BindingRestriction.InstanceOnly) as MethodSpec;
384 if (get_result == null) {
385 Error_WrongAwaiterPattern (bc, awaiter_type);
389 result_type = get_result.ReturnType;
395 public class AsyncInitializer : StateMachineInitializer
397 TypeInferenceContext return_inference;
399 public AsyncInitializer (ParametersBlock block, TypeDefinition host, TypeSpec returnType)
400 : base (block, host, returnType)
406 public override string ContainerType {
408 return "async state machine block";
412 public override bool IsIterator {
418 public Block OriginalBlock {
424 public TypeInferenceContext ReturnTypeInference {
426 return return_inference;
432 public static void Create (IMemberContext context, ParametersBlock block, ParametersCompiled parameters, TypeDefinition host, TypeSpec returnType, Location loc)
434 for (int i = 0; i < parameters.Count; i++) {
435 Parameter p = parameters[i];
436 Parameter.Modifier mod = p.ModFlags;
437 if ((mod & Parameter.Modifier.ISBYREF) != 0) {
438 host.Compiler.Report.Error (1988, p.Location,
439 "Async methods cannot have ref or out parameters");
443 if (p is ArglistParameter) {
444 host.Compiler.Report.Error (4006, p.Location,
445 "__arglist is not allowed in parameter list of async methods");
449 if (parameters.Types[i].IsPointer) {
450 host.Compiler.Report.Error (4005, p.Location,
451 "Async methods cannot have unsafe parameters");
456 if (!block.HasAwait) {
457 host.Compiler.Report.Warning (1998, 1, loc,
458 "Async block lacks `await' operator and will run synchronously");
461 block.WrapIntoAsyncTask (context, host, returnType);
464 protected override BlockContext CreateBlockContext (ResolveContext rc)
466 var ctx = base.CreateBlockContext (rc);
467 var lambda = rc.CurrentAnonymousMethod as LambdaMethod;
469 return_inference = lambda.ReturnTypeInference;
471 ctx.StartFlowBranching (this, rc.CurrentBranching);
475 public override Expression CreateExpressionTree (ResolveContext ec)
477 return base.CreateExpressionTree (ec);
480 public override void Emit (EmitContext ec)
482 throw new NotImplementedException ();
485 protected override void EmitMoveNextEpilogue (EmitContext ec)
487 var storey = (AsyncTaskStorey) Storey;
488 storey.EmitSetResult (ec);
491 public override void EmitStatement (EmitContext ec)
493 var storey = (AsyncTaskStorey) Storey;
494 storey.Instance.Emit (ec);
496 var move_next_entry = storey.StateMachineMethod.Spec;
497 if (storey.MemberName.Arity > 0) {
498 move_next_entry = MemberCache.GetMember (storey.Instance.Type, move_next_entry);
501 ec.Emit (OpCodes.Call, move_next_entry);
504 // Emits return <async-storey-instance>.$builder.Task;
506 if (storey.Task != null) {
507 var builder_field = storey.Builder.Spec;
508 var task_get = storey.Task.Get;
510 if (storey.MemberName.Arity > 0) {
511 builder_field = MemberCache.GetMember (storey.Instance.Type, builder_field);
512 task_get = MemberCache.GetMember (builder_field.MemberType, task_get);
515 var pe_task = new PropertyExpr (storey.Task, loc) {
516 InstanceExpression = new FieldExpr (builder_field, loc) {
517 InstanceExpression = storey.Instance
525 ec.Emit (OpCodes.Ret);
529 class AsyncTaskStorey : StateMachine
532 Field builder, continuation;
533 readonly TypeSpec return_type;
534 MethodSpec set_result;
535 MethodSpec set_exception;
537 LocalVariable hoisted_return;
539 Dictionary<TypeSpec, List<StackField>> stack_fields;
542 public AsyncTaskStorey (IMemberContext context, AsyncInitializer initializer, TypeSpec type)
543 : base (initializer.OriginalBlock, initializer.Host, context.CurrentMemberDefinition as MemberBase, context.CurrentTypeParameters, "async")
550 public Field Builder {
556 public LocalVariable HoistedReturn {
558 return hoisted_return;
562 public TypeSpec ReturnType {
568 public PropertySpec Task {
576 public Field AddAwaiter (TypeSpec type, Location loc)
578 return AddCapturedVariable ("$awaiter" + awaiters++.ToString ("X"), type);
581 public StackField AddCapturedLocalVariable (TypeSpec type)
584 type = mutator.Mutate (type);
586 List<StackField> existing_fields = null;
587 if (stack_fields == null) {
588 stack_fields = new Dictionary<TypeSpec, List<StackField>> ();
589 } else if (stack_fields.TryGetValue (type, out existing_fields)) {
590 foreach (var f in existing_fields) {
592 f.CanBeReused = false;
598 const Modifiers mod = Modifiers.COMPILER_GENERATED | Modifiers.PRIVATE;
599 var field = new StackField (this, new TypeExpression (type, Location), mod, new MemberName ("<s>$" + locals_captured++.ToString ("X"), Location));
604 if (existing_fields == null) {
605 existing_fields = new List<StackField> ();
606 stack_fields.Add (type, existing_fields);
609 existing_fields.Add (field);
614 protected override bool DoDefineMembers ()
616 action = Module.PredefinedTypes.Action.Resolve ();
618 PredefinedType builder_type;
619 PredefinedMember<MethodSpec> bf;
620 PredefinedMember<MethodSpec> sr;
621 PredefinedMember<MethodSpec> se;
622 bool has_task_return_type = false;
623 var pred_members = Module.PredefinedMembers;
625 if (return_type.Kind == MemberKind.Void) {
626 builder_type = Module.PredefinedTypes.AsyncVoidMethodBuilder;
627 bf = pred_members.AsyncVoidMethodBuilderCreate;
628 sr = pred_members.AsyncVoidMethodBuilderSetResult;
629 se = pred_members.AsyncVoidMethodBuilderSetException;
630 } else if (return_type == Module.PredefinedTypes.Task.TypeSpec) {
631 builder_type = Module.PredefinedTypes.AsyncTaskMethodBuilder;
632 bf = pred_members.AsyncTaskMethodBuilderCreate;
633 sr = pred_members.AsyncTaskMethodBuilderSetResult;
634 se = pred_members.AsyncTaskMethodBuilderSetException;
635 task = pred_members.AsyncTaskMethodBuilderTask.Get ();
637 builder_type = Module.PredefinedTypes.AsyncTaskMethodBuilderGeneric;
638 bf = pred_members.AsyncTaskMethodBuilderGenericCreate;
639 sr = pred_members.AsyncTaskMethodBuilderGenericSetResult;
640 se = pred_members.AsyncTaskMethodBuilderGenericSetException;
641 task = pred_members.AsyncTaskMethodBuilderGenericTask.Get ();
642 has_task_return_type = true;
645 set_result = sr.Get ();
646 set_exception = se.Get ();
647 var builder_factory = bf.Get ();
648 if (!builder_type.Define () || set_result == null || builder_factory == null || set_exception == null) {
649 Report.Error (1993, Location,
650 "Cannot find compiler required types for asynchronous functions support. Are you targeting the wrong framework version?");
651 return base.DoDefineMembers ();
654 var bt = builder_type.TypeSpec;
657 // Inflate generic Task types
659 if (has_task_return_type) {
660 var task_return_type = return_type.TypeArguments;
662 task_return_type = mutator.Mutate (task_return_type);
664 bt = bt.MakeGenericType (Module, task_return_type);
665 builder_factory = MemberCache.GetMember<MethodSpec> (bt, builder_factory);
666 set_result = MemberCache.GetMember<MethodSpec> (bt, set_result);
667 set_exception = MemberCache.GetMember<MethodSpec> (bt, set_exception);
670 task = MemberCache.GetMember<PropertySpec> (bt, task);
673 builder = AddCompilerGeneratedField ("$builder", new TypeExpression (bt, Location));
675 var ctor = DefineDefaultConstructor (false);
677 if (!base.DoDefineMembers ())
680 Block block = ctor.Block;
682 var mg = MethodGroupExpr.CreatePredefined (builder_factory, bt, Location);
684 new StatementExpression (new SimpleAssign (
685 new FieldExpr (builder, Location),
686 new Invocation (mg, new Arguments (0)),
689 if (has_task_return_type) {
690 hoisted_return = LocalVariable.CreateCompilerGenerated (bt.TypeArguments[0], block, Location);
696 public Expression EmitContinuationInitialization (EmitContext ec)
699 // When more than 1 awaiter has been used in the block we
700 // introduce class scope field to cache continuation delegate
703 if (continuation == null) {
704 continuation = AddCompilerGeneratedField ("$continuation", new TypeExpression (action, Location), true);
705 continuation.Define ();
708 var fexpr = new FieldExpr (continuation, Location);
709 fexpr.InstanceExpression = new CompilerGeneratedThis (CurrentType, Location);
712 // if ($continuation == null)
713 // $continuation = new Action (MoveNext);
717 var skip_cont_init = ec.DefineLabel ();
718 ec.Emit (OpCodes.Brtrue_S, skip_cont_init);
722 ec.Emit (OpCodes.Stfld, continuation.Spec);
723 ec.MarkLabel (skip_cont_init);
729 // Otherwise simply use temporary local variable
731 var field = LocalVariable.CreateCompilerGenerated (action, OriginalSourceBlock, Location);
733 field.EmitAssign (ec);
734 return new LocalVariableReference (field, Location);
737 void EmitActionLoad (EmitContext ec)
740 ec.Emit (OpCodes.Ldftn, StateMachineMethod.Spec);
741 ec.Emit (OpCodes.Newobj, (MethodSpec) MemberCache.FindMember (action, MemberFilter.Constructor (null), BindingRestriction.DeclaredOnly));
744 public void EmitSetException (EmitContext ec, LocalVariableReference exceptionVariable)
747 // $builder.SetException (Exception)
749 var mg = MethodGroupExpr.CreatePredefined (set_exception, set_exception.DeclaringType, Location);
750 mg.InstanceExpression = new FieldExpr (Builder, Location) {
751 InstanceExpression = new CompilerGeneratedThis (ec.CurrentType, Location)
754 Arguments args = new Arguments (1);
755 args.Add (new Argument (exceptionVariable));
757 mg.EmitCall (ec, args);
760 public void EmitSetResult (EmitContext ec)
763 // $builder.SetResult ();
764 // $builder.SetResult<return-type> (value);
766 var mg = MethodGroupExpr.CreatePredefined (set_result, set_result.DeclaringType, Location);
767 mg.InstanceExpression = new FieldExpr (Builder, Location) {
768 InstanceExpression = new CompilerGeneratedThis (ec.CurrentType, Location)
772 if (hoisted_return == null) {
773 args = new Arguments (0);
775 args = new Arguments (1);
776 args.Add (new Argument (new LocalVariableReference (hoisted_return, Location)));
779 mg.EmitCall (ec, args);
783 class StackField : Field
785 public StackField (TypeDefinition parent, FullNamedExpression type, Modifiers mod, MemberName name)
786 : base (parent, type, mod, name, null)
790 public bool CanBeReused { get; set; }
793 class StackFieldExpr : FieldExpr
795 public StackFieldExpr (Field field)
796 : base (field, Location.Null)
800 public override void Emit (EmitContext ec)
804 var field = (StackField) spec.MemberDefinition;
805 field.CanBeReused = true;