[mcs] Implements finally execution for await with try block exit.
authorMarek Safar <marek.safar@gmail.com>
Tue, 17 Jun 2014 14:43:29 +0000 (16:43 +0200)
committerMarek Safar <marek.safar@gmail.com>
Tue, 17 Jun 2014 14:45:08 +0000 (16:45 +0200)
mcs/mcs/async.cs
mcs/mcs/context.cs
mcs/mcs/iterators.cs
mcs/mcs/statement.cs
mcs/tests/test-async-69.cs [new file with mode: 0644]
mcs/tests/ver-il-net_4_5.xml

index 5d8f65053bf7f2010a2d56ee3e555f3900e39474..6cd75bc9af503de909d7d9c17c0a90e6a05e5cf6 100644 (file)
@@ -421,6 +421,8 @@ namespace Mono.CSharp
        public class AsyncInitializer : StateMachineInitializer
        {
                TypeInferenceContext return_inference;
+               List<Label> redirected_jumps;
+               FieldExpr HoistedReturnState;
 
                public AsyncInitializer (ParametersBlock block, TypeDefinition host, TypeSpec returnType)
                        : base (block, host, returnType)
@@ -483,6 +485,57 @@ namespace Mono.CSharp
                        ec.Emit (OpCodes.Ret);
                }
 
+               //
+               // Emits state table of jumps outside of try block and reload of return
+               // value when try block returns value
+               //
+               public void EmitRedirectedJumpsTable (EmitContext ec, StackFieldExpr returnResult, Label localReturn)
+               {
+                       if (redirected_jumps == null)
+                               return;
+
+                       int ret_index = redirected_jumps.IndexOf (localReturn);
+                       if (ret_index >= 0) {
+                               redirected_jumps [ret_index] = ec.DefineLabel ();
+                       }
+
+                       HoistedReturnState.Emit (ec);
+                       ec.Emit (OpCodes.Switch, redirected_jumps.ToArray ());
+
+                       if (ret_index >= 0) {
+                               ec.MarkLabel (redirected_jumps [ret_index]);
+                               var s = (AsyncTaskStorey)storey;
+                               ((IAssignMethod)s.HoistedReturnValue).EmitAssign (ec, returnResult, false, false);
+
+                               ec.Emit (OpCodes.Leave, BodyEnd);
+                       }
+
+                       // Mark fallthrough label
+                       ec.MarkLabel (redirected_jumps [0]);
+               }
+
+               public void EmitRedirectedJump (EmitContext ec, Label label)
+               {
+                       if (redirected_jumps == null) {
+                               redirected_jumps = new List<Label> ();
+
+                               // Add fallthrough label
+                               redirected_jumps.Add (ec.DefineLabel ());
+                               HoistedReturnState = ec.GetTemporaryField (ec.Module.Compiler.BuiltinTypes.Int);
+                       }
+
+                       int index = redirected_jumps.IndexOf (label);
+                       if (index < 0) {
+                               redirected_jumps.Add (label);
+                               index = redirected_jumps.Count - 1;
+                       }
+
+                       //
+                       // Indicates we have return value captured
+                       //
+                       HoistedReturnState.EmitAssign (ec, new IntConstant (HoistedReturnState.Type, index, Location.Null), false, false);
+               }
+
                public override void MarkReachable (Reachability rc)
                {
                        //
@@ -501,7 +554,6 @@ namespace Mono.CSharp
                MethodSpec builder_factory;
                MethodSpec builder_start;
                PropertySpec task;
-               LocalVariable hoisted_return;
                int locals_captured;
                Dictionary<TypeSpec, List<Field>> stack_fields;
                Dictionary<TypeSpec, List<Field>> awaiter_fields;
@@ -515,11 +567,7 @@ namespace Mono.CSharp
 
                #region Properties
 
-               public LocalVariable HoistedReturn {
-                       get {
-                               return hoisted_return;
-                       }
-               }
+               public Expression HoistedReturnValue { get; set; }
 
                public TypeSpec ReturnType {
                        get {
@@ -709,7 +757,7 @@ namespace Mono.CSharp
                        set_state_machine.Block.AddStatement (new StatementExpression (new Invocation (mg, args)));
 
                        if (has_task_return_type) {
-                               hoisted_return = LocalVariable.CreateCompilerGenerated (bt.TypeArguments[0], StateMachineMethod.Block, Location);
+                               HoistedReturnValue = TemporaryVariableReference.Create (bt.TypeArguments [0], StateMachineMethod.Block, Location);
                        }
 
                        return true;
@@ -896,11 +944,11 @@ namespace Mono.CSharp
                        };
 
                        Arguments args;
-                       if (hoisted_return == null) {
+                       if (HoistedReturnValue == null) {
                                args = new Arguments (0);
                        } else {
                                args = new Arguments (1);
-                               args.Add (new Argument (new LocalVariableReference (hoisted_return, Location)));
+                               args.Add (new Argument (HoistedReturnValue));
                        }
 
                        using (ec.With (BuilderContext.Options.OmitDebugInfo, true)) {
index 8cbaeff0c5464d7956eebd1f5039ffa632e52e83..95ccc943b810ba71ff56195c66ce682861586fc7 100644 (file)
@@ -664,7 +664,9 @@ namespace Mono.CSharp
 
                        ConstructorScope = 1 << 3,
 
-                       AsyncBody = 1 << 4
+                       AsyncBody = 1 << 4,
+
+                       HoistedReturnResult = 1 << 5
                }
 
                // utility helper for CheckExpr, UnCheckExpr, Checked and Unchecked statements
index f10773009de7d715ecdd03b75f701cf871573441..8b45f88a911a054c4156b8d13f545f430d2e707a 100644 (file)
@@ -724,7 +724,6 @@ namespace Mono.CSharp
                // The state as we generate the machine
                //
                Label move_next_ok;
-               Label iterator_body_end;
                protected Label move_next_error;
                LocalBuilder skip_finally;
                protected LocalBuilder current_pc;
@@ -738,11 +737,7 @@ namespace Mono.CSharp
 
                #region Properties
 
-               public Label BodyEnd {
-                       get {
-                               return iterator_body_end;
-                       }
-               }
+               public Label BodyEnd { get; set; }
 
                public LocalBuilder CurrentPC
                {
@@ -830,11 +825,11 @@ namespace Mono.CSharp
                        // We only care if the PC is zero (start executing) or non-zero (don't do anything)
                        ec.Emit (OpCodes.Brtrue, move_next_error);
 
-                       iterator_body_end = ec.DefineLabel ();
+                       BodyEnd = ec.DefineLabel ();
 
                        block.EmitEmbedded (ec);
 
-                       ec.MarkLabel (iterator_body_end);
+                       ec.MarkLabel (BodyEnd);
 
                        EmitMoveNextEpilogue (ec);
 
@@ -893,11 +888,11 @@ namespace Mono.CSharp
 
                        ec.MarkLabel (labels[0]);
 
-                       iterator_body_end = ec.DefineLabel ();
+                       BodyEnd = ec.DefineLabel ();
 
                        block.EmitEmbedded (ec);
 
-                       ec.MarkLabel (iterator_body_end);
+                       ec.MarkLabel (BodyEnd);
 
                        if (async_init != null) {
                                var catch_value = LocalVariable.CreateCompilerGenerated (ec.Module.Compiler.BuiltinTypes.Exception, block, Location);
index a09c4acd939b99b5262234bdac40c46019aa09ce..4076eb05e8e4257e13380cf709b298615dc86d04 100644 (file)
@@ -1284,23 +1284,39 @@ namespace Mono.CSharp {
                protected override void DoEmit (EmitContext ec)
                {
                        if (expr != null) {
-                               expr.Emit (ec);
 
                                var async_body = ec.CurrentAnonymousMethod as AsyncInitializer;
                                if (async_body != null) {
-                                       var async_return = ((AsyncTaskStorey) async_body.Storey).HoistedReturn;
+                                       var storey = (AsyncTaskStorey)async_body.Storey;
 
+                                       //
                                        // It's null for await without async
-                                       if (async_return != null) {
-                                               async_return.EmitAssign (ec);
+                                       //
+                                       if (storey.HoistedReturnValue != null) {
+                                               //
+                                               // Special case hoisted return value (happens in try/finally scenario)
+                                               //
+                                               if (ec.HasSet (BuilderContext.Options.HoistedReturnResult)) {
+
+                                                       if (storey.HoistedReturnValue is VariableReference) {
+                                                               storey.HoistedReturnValue = ec.GetTemporaryField (storey.HoistedReturnValue.Type);
+                                                       }
 
+                                                       async_body.EmitRedirectedJump (ec, async_body.BodyEnd);
+                                               }
+
+                                               var async_return = (IAssignMethod)storey.HoistedReturnValue;
+                                               async_return.EmitAssign (ec, expr, false, false);
                                                ec.EmitEpilogue ();
+                                       } else {
+                                               expr.Emit (ec);
                                        }
 
                                        ec.Emit (OpCodes.Leave, async_body.BodyEnd);
                                        return;
                                }
 
+                               expr.Emit (ec);
                                ec.EmitEpilogue ();
 
                                if (unwind_protect || ec.EmitAccurateDebugInfo)
@@ -1421,8 +1437,6 @@ namespace Mono.CSharp {
                                } else {
                                        label.AddGotoReference (rc, true);
                                }
-
-                               try_finally = null;
                        } else {
                                label.AddGotoReference (rc, false);
                        }
@@ -1441,8 +1455,28 @@ namespace Mono.CSharp {
                                throw new InternalErrorException ("goto emitted before target resolved");
 
                        Label l = label.LabelTarget (ec);
+
+                       if (try_finally != null && ec.HasSet (BuilderContext.Options.HoistedReturnResult) && IsLeavingFinally (label.Block)) {
+                               var async_body = (AsyncInitializer) ec.CurrentAnonymousMethod;
+                               async_body.EmitRedirectedJump (ec, l);
+                               l = async_body.BodyEnd;
+                       }
+
                        ec.Emit (unwind_protect ? OpCodes.Leave : OpCodes.Br, l);
                }
+
+               bool IsLeavingFinally (Block labelBlock)
+               {
+                       var b = try_finally.Statement as Block;
+                       while (b != null) {
+                               if (b == labelBlock)
+                                       return true;
+
+                               b = b.Parent;
+                       }
+
+                       return false;
+               }
                
                public override object Accept (StructuralVisitor visitor)
                {
@@ -2522,6 +2556,10 @@ namespace Mono.CSharp {
 
                public void EmitAddressOf (EmitContext ec)
                {
+                       // TODO: Need something better for temporary variables
+                       if ((flags & Flags.CompilerGenerated) != 0)
+                               CreateBuilder (ec);
+
                        ec.Emit (OpCodes.Ldloca, builder);
                }
 
@@ -6569,6 +6607,10 @@ namespace Mono.CSharp {
                ExplicitBlock fini;
                List<DefiniteAssignmentBitSet> try_exit_dat;
 
+               // TODO: Should be on stack only
+               Label prev_body_end;
+               Expression prev_HoistedReturn;
+
                public TryFinally (Statement stmt, ExplicitBlock fini, Location loc)
                         : base (stmt, loc)
                {
@@ -6603,6 +6645,19 @@ namespace Mono.CSharp {
 
                protected override void EmitTryBody (EmitContext ec)
                {
+                       if (fini.HasAwait) {
+                               prev_HoistedReturn = ec.AsyncTaskStorey.HoistedReturnValue;
+                               var ai = (AsyncInitializer)ec.CurrentAnonymousMethod;
+                               prev_body_end = ai.BodyEnd;
+                               ai.BodyEnd = ec.DefineLabel ();
+
+                               using (ec.With (BuilderContext.Options.HoistedReturnResult, true)) {
+                                       stmt.Emit (ec);
+                               }
+
+                               return;
+                       }
+
                        stmt.Emit (ec);
                }
 
@@ -6616,52 +6671,60 @@ namespace Mono.CSharp {
 
                public override void EmitFinallyBody (EmitContext ec)
                {
-                       StackFieldExpr exception_field;
+                       if (!fini.HasAwait) {
+                               fini.Emit (ec);
+                               return;
+                       }
 
-                       if (fini.HasAwait) {
-                               //
-                               // Emits catch block like
-                               //
-                               // catch (object temp) {
-                               //      this.exception_field = temp;
-                               // }
-                               //
-                               var type = ec.BuiltinTypes.Object;
-                               ec.BeginCatchBlock (type);
+                       //
+                       // Emits catch block like
+                       //
+                       // catch (object temp) {
+                       //      this.exception_field = temp;
+                       // }
+                       //
+                       var type = ec.BuiltinTypes.Object;
+                       ec.BeginCatchBlock (type);
 
-                               var temp = ec.GetTemporaryLocal (type);
-                               ec.Emit (OpCodes.Stloc, temp);
+                       var temp = ec.GetTemporaryLocal (type);
+                       ec.Emit (OpCodes.Stloc, temp);
 
-                               exception_field = ec.GetTemporaryField (type);
-                               ec.EmitThis ();
-                               ec.Emit (OpCodes.Ldloc, temp);
-                               exception_field.EmitAssignFromStack (ec);
+                       var exception_field = ec.GetTemporaryField (type);
+                       ec.EmitThis ();
+                       ec.Emit (OpCodes.Ldloc, temp);
+                       exception_field.EmitAssignFromStack (ec);
 
-                               ec.EndExceptionBlock ();
+                       ec.EndExceptionBlock ();
 
-                               ec.FreeTemporaryLocal (temp, type);
-                       } else {
-                               exception_field = null;
-                       }
+                       ec.FreeTemporaryLocal (temp, type);
 
-                       fini.Emit (ec);
+                       var ai = (AsyncInitializer)ec.CurrentAnonymousMethod;
+                       ec.MarkLabel (ai.BodyEnd);
+                       var local_return = ai.BodyEnd;
+                       ai.BodyEnd = prev_body_end;
+                       var fin_hoisted_return = ec.AsyncTaskStorey.HoistedReturnValue as StackFieldExpr;
+                       ec.AsyncTaskStorey.HoistedReturnValue = prev_HoistedReturn;
 
-                       if (exception_field != null) {
-                               //
-                               // Emits exception rethrow
-                               //
-                               // if (this.exception_field != null)
-                               //      throw this.exception_field;
-                               //
-                               exception_field.Emit (ec);
-                               var skip_throw = ec.DefineLabel ();
-                               ec.Emit (OpCodes.Brfalse_S, skip_throw);
-                               exception_field.Emit (ec);
-                               ec.Emit (OpCodes.Throw);
-                               ec.MarkLabel (skip_throw);
+                       fini.Emit (ec);
 
-                               exception_field.IsAvailableForReuse = true;
-                       }
+                       //
+                       // Emits exception rethrow
+                       //
+                       // if (this.exception_field != null)
+                       //      throw this.exception_field;
+                       //
+                       exception_field.Emit (ec);
+                       var skip_throw = ec.DefineLabel ();
+                       ec.Emit (OpCodes.Brfalse_S, skip_throw);
+                       exception_field.Emit (ec);
+                       ec.Emit (OpCodes.Throw);
+                       ec.MarkLabel (skip_throw);
+
+                       exception_field.IsAvailableForReuse = true;
+
+                       ai.EmitRedirectedJumpsTable (ec, fin_hoisted_return, local_return);
+                       if (fin_hoisted_return != null)
+                               fin_hoisted_return.PrepareCleanup (ec);
                }
 
                protected override bool DoFlowAnalysis (FlowAnalysisContext fc)
diff --git a/mcs/tests/test-async-69.cs b/mcs/tests/test-async-69.cs
new file mode 100644 (file)
index 0000000..af133f9
--- /dev/null
@@ -0,0 +1,151 @@
+using System;
+using System.Threading.Tasks;
+
+class Test
+{
+       static bool fin;
+
+       static async Task<int> YieldValue (int a)
+       {
+               await Task.Yield ();
+               return a;
+       }
+
+       static async Task<int> TestFinallyWithReturn (int value)
+       {
+               fin = false;
+               try {
+                       if (value > 4)
+                               return 5;
+
+                       value += 10;
+                       Console.WriteLine ("try");
+               } finally {
+                       fin = true;
+                       Console.WriteLine ("finally");
+                       value += await YieldValue (100);
+               }
+
+               value += 1000;
+               Console.WriteLine ("over");
+
+               return value;
+       }
+
+       static async Task TestFinallyWithReturnNoValue (int value)
+       {
+               fin = false;
+               try {
+                       if (value > 4)
+                               return;
+
+                       value += 10;
+                       Console.WriteLine ("try");
+               } finally {
+                       fin = true;
+                       Console.WriteLine ("finally");
+                       value += await YieldValue (100);
+               }
+
+               value += 1000;
+               Console.WriteLine ("over");
+       }
+
+       static async Task<int> TestFinallyWithGoto (int value)
+       {
+               fin = false;
+               try {
+                       if (value > 4)
+                               goto L;
+
+                       value += 10;
+                       Console.WriteLine ("try");
+               } finally {
+                       fin = true;
+                       Console.WriteLine ("finally");
+                       value += await YieldValue (100);
+               }
+               value += 1000;
+L:
+               Console.WriteLine ("over");
+               return value;
+       }
+
+        static async Task<int> TestFinallyWithGotoAndReturn (int value)
+       {
+               fin = false;
+               try {
+                       if (value > 4)
+                               goto L;
+
+                       value += 10;
+                       Console.WriteLine ("try");
+                       if (value > 12)
+                               return 9;
+               } finally {
+                       fin = true;
+                       Console.WriteLine ("finally");
+                       value += await YieldValue (100);
+               }
+               value += 1000;
+L:
+               Console.WriteLine ("over");
+               return value;
+       }
+
+       public static int Main ()
+       {
+               if (TestFinallyWithReturn (9).Result != 5)
+                       return 1;
+
+               if (!fin)
+                       return 2;
+
+               if (TestFinallyWithReturn (1).Result != 1111)
+                       return 3;
+
+               if (!fin)
+                       return 4;
+
+               TestFinallyWithReturnNoValue (9).Wait ();
+               if (!fin)
+                       return 5;
+
+               TestFinallyWithReturnNoValue (1).Wait ();
+               if (!fin)
+                       return 6;
+
+               if (TestFinallyWithGoto (9).Result != 109)
+                       return 7;
+
+               if (!fin)
+                       return 8;
+
+               if (TestFinallyWithGoto (1).Result != 1111)
+                       return 9;
+
+               if (!fin)
+                       return 10;
+
+               if (TestFinallyWithGotoAndReturn (9).Result != 109)
+                       return 11;
+
+               if (!fin)
+                       return 12;
+
+               if (TestFinallyWithGotoAndReturn (1).Result != 1111)
+                       return 13;
+
+               if (!fin)
+                       return 14;
+
+               if (TestFinallyWithGotoAndReturn (3).Result != 9)
+                       return 15;
+
+               if (!fin)
+                       return 16;
+
+               Console.WriteLine ("ok");
+               return 0;
+       }
+}
\ No newline at end of file
index 97366458344bab69fb156aa464139cdb0f3a0b16..fbf8ee7af10f2a35932f48a2b69ad9d5c6e41c38 100644 (file)
     </type>\r
     <type name="Tester+&lt;NewInitTestGen&gt;c__async0`1[T]">\r
       <method name="Void MoveNext()" attrs="486">\r
-        <size>298</size>\r
+        <size>295</size>\r
       </method>\r
       <method name="Int32 &lt;&gt;m__0()" attrs="145">\r
         <size>10</size>\r
       </method>\r
     </type>\r
   </test>\r
+  <test name="test-async-69.cs">\r
+    <type name="Test">\r
+      <method name="System.Threading.Tasks.Task`1[System.Int32] YieldValue(Int32)" attrs="145">\r
+        <size>41</size>\r
+      </method>\r
+      <method name="System.Threading.Tasks.Task`1[System.Int32] TestFinallyWithReturn(Int32)" attrs="145">\r
+        <size>41</size>\r
+      </method>\r
+      <method name="System.Threading.Tasks.Task TestFinallyWithReturnNoValue(Int32)" attrs="145">\r
+        <size>41</size>\r
+      </method>\r
+      <method name="System.Threading.Tasks.Task`1[System.Int32] TestFinallyWithGoto(Int32)" attrs="145">\r
+        <size>41</size>\r
+      </method>\r
+      <method name="System.Threading.Tasks.Task`1[System.Int32] TestFinallyWithGotoAndReturn(Int32)" attrs="145">\r
+        <size>41</size>\r
+      </method>\r
+      <method name="Int32 Main()" attrs="150">\r
+        <size>390</size>\r
+      </method>\r
+      <method name="Void .ctor()" attrs="6278">\r
+        <size>7</size>\r
+      </method>\r
+    </type>\r
+    <type name="Test+&lt;YieldValue&gt;c__async0">\r
+      <method name="Void MoveNext()" attrs="486">\r
+        <size>172</size>\r
+      </method>\r
+      <method name="Void SetStateMachine(System.Runtime.CompilerServices.IAsyncStateMachine)" attrs="486">\r
+        <size>13</size>\r
+      </method>\r
+    </type>\r
+    <type name="Test+&lt;TestFinallyWithReturn&gt;c__async1">\r
+      <method name="Void MoveNext()" attrs="486">\r
+        <size>381</size>\r
+      </method>\r
+      <method name="Void SetStateMachine(System.Runtime.CompilerServices.IAsyncStateMachine)" attrs="486">\r
+        <size>13</size>\r
+      </method>\r
+    </type>\r
+    <type name="Test+&lt;TestFinallyWithReturnNoValue&gt;c__async2">\r
+      <method name="Void MoveNext()" attrs="486">\r
+        <size>321</size>\r
+      </method>\r
+      <method name="Void SetStateMachine(System.Runtime.CompilerServices.IAsyncStateMachine)" attrs="486">\r
+        <size>13</size>\r
+      </method>\r
+    </type>\r
+    <type name="Test+&lt;TestFinallyWithGoto&gt;c__async3">\r
+      <method name="Void MoveNext()" attrs="486">\r
+        <size>362</size>\r
+      </method>\r
+      <method name="Void SetStateMachine(System.Runtime.CompilerServices.IAsyncStateMachine)" attrs="486">\r
+        <size>13</size>\r
+      </method>\r
+    </type>\r
+    <type name="Test+&lt;TestFinallyWithGotoAndReturn&gt;c__async4">\r
+      <method name="Void MoveNext()" attrs="486">\r
+        <size>411</size>\r
+      </method>\r
+      <method name="Void SetStateMachine(System.Runtime.CompilerServices.IAsyncStateMachine)" attrs="486">\r
+        <size>13</size>\r
+      </method>\r
+    </type>\r
+  </test>\r
   <test name="test-cls-00.cs">\r
     <type name="CLSCLass_6">\r
       <method name="Void add_Disposed(Delegate)" attrs="2182">\r