[mcs] Codegen of try-catch-finally with await inside both try and catch. Fixes #39866
authorMarek Safar <marek.safar@gmail.com>
Sat, 9 Apr 2016 07:44:25 +0000 (09:44 +0200)
committerMarek Safar <marek.safar@gmail.com>
Sat, 9 Apr 2016 07:47:25 +0000 (09:47 +0200)
mcs/mcs/context.cs
mcs/mcs/iterators.cs
mcs/mcs/statement.cs
mcs/tests/test-async-85.cs [new file with mode: 0644]
mcs/tests/ver-il-net_4_x.xml

index 7b5ffadf328884fdeaf5bc532468b005be5f98f2..b2399c8bd89d45c71aee3882c56bb09c8fddd2e1 100644 (file)
@@ -116,6 +116,8 @@ namespace Mono.CSharp
 
                public ExceptionStatement CurrentTryBlock { get; set; }
 
+               public TryCatch CurrentTryCatch { get; set; }
+
                public LoopStatement EnclosingLoop { get; set; }
 
                public LoopStatement EnclosingLoopOrSwitch { get; set; }
index df43ff38e8e09f0387a6810ad441a6d56c5d8457..47919ec8ce2b5fda133bddbb93509096ba9ef671 100644 (file)
@@ -30,6 +30,7 @@ namespace Mono.CSharp
                protected T machine_initializer;
                int resume_pc;
                ExceptionStatement inside_try_block;
+               TryCatch inside_catch_block;
 
                protected YieldStatement (Expression expr, Location l)
                {
@@ -69,6 +70,7 @@ namespace Mono.CSharp
 
                        machine_initializer = bc.CurrentAnonymousMethod as T;
                        inside_try_block = bc.CurrentTryBlock;
+                       inside_catch_block = bc.CurrentTryCatch;
                        return true;
                }
 
@@ -80,7 +82,7 @@ namespace Mono.CSharp
                        if (inside_try_block == null) {
                                resume_pc = machine_initializer.AddResumePoint (this);
                        } else {
-                               resume_pc = inside_try_block.AddResumePoint (this, resume_pc, machine_initializer);
+                               resume_pc = inside_try_block.AddResumePoint (this, resume_pc, machine_initializer, inside_catch_block);
                                unwind_protect = true;
                                inside_try_block = null;
                        }
index 9c84398ee3e4d52c01e8a3d25fd83adff75e96e3..d0341cb3b633045c48889bfa3a3f31789df36ff0 100644 (file)
@@ -5877,7 +5877,7 @@ namespace Mono.CSharp {
                protected override bool DoFlowAnalysis (FlowAnalysisContext fc)
                {
                        var res = stmt.FlowAnalysis (fc);
-                       parent = null;
+                       parent_try_block = null;
                        return res;
                }
 
@@ -5897,14 +5897,18 @@ namespace Mono.CSharp {
                {
                        bool ok;
 
-                       parent = bc.CurrentTryBlock;
+                       parent_try_block = bc.CurrentTryBlock;
                        bc.CurrentTryBlock = this;
 
-                       using (bc.Set (ResolveContext.Options.TryScope)) {
+                       if (stmt is TryCatch) {
                                ok = stmt.Resolve (bc);
+                       } else {
+                               using (bc.Set (ResolveContext.Options.TryScope)) {
+                                       ok = stmt.Resolve (bc);
+                               }
                        }
 
-                       bc.CurrentTryBlock = parent;
+                       bc.CurrentTryBlock = parent_try_block;
 
                        //
                        // Finally block inside iterator is called from MoveNext and
@@ -5930,18 +5934,14 @@ namespace Mono.CSharp {
        {
                protected List<ResumableStatement> resume_points;
                protected int first_resume_pc;
-               protected ExceptionStatement parent;
+               protected ExceptionStatement parent_try_block;
+               protected int first_catch_resume_pc = -1;
 
                protected ExceptionStatement (Location loc)
                {
                        this.loc = loc;
                }
 
-               protected virtual void EmitBeginException (EmitContext ec)
-               {
-                       ec.BeginExceptionBlock ();
-               }
-
                protected virtual void EmitTryBodyPrepare (EmitContext ec)
                {
                        StateMachineInitializer state_machine = null;
@@ -5952,10 +5952,37 @@ namespace Mono.CSharp {
                                ec.Emit (OpCodes.Stloc, state_machine.CurrentPC);
                        }
 
-                       EmitBeginException (ec);
+                       //
+                       // The resume points in catch section when this is try-catch-finally
+                       //
+                       if (IsRewrittenTryCatchFinally ()) {
+                               ec.BeginExceptionBlock ();
+
+                               if (first_catch_resume_pc >= 0) {
 
-                       if (resume_points != null) {
-                               ec.MarkLabel (resume_point);
+                                       ec.MarkLabel (resume_point);
+
+                                       // For normal control flow, we want to fall-through the Switch
+                                       // So, we use CurrentPC rather than the $PC field, and initialize it to an outside value above
+                                       ec.Emit (OpCodes.Ldloc, state_machine.CurrentPC);
+                                       ec.EmitInt (first_resume_pc + first_catch_resume_pc);
+                                       ec.Emit (OpCodes.Sub);
+
+                                       var labels = new Label [resume_points.Count - first_catch_resume_pc];
+                                       for (int i = 0; i < labels.Length; ++i)
+                                               labels [i] = resume_points [i + first_catch_resume_pc].PrepareForEmit (ec);
+                                       ec.Emit (OpCodes.Switch, labels);
+                               }
+                       }
+
+                       ec.BeginExceptionBlock ();
+
+                       //
+                       // The resume points for try section
+                       //
+                       if (resume_points != null && first_catch_resume_pc != 0) {
+                               if (first_catch_resume_pc < 0)
+                                       ec.MarkLabel (resume_point);
 
                                // For normal control flow, we want to fall-through the Switch
                                // So, we use CurrentPC rather than the $PC field, and initialize it to an outside value above
@@ -5963,21 +5990,30 @@ namespace Mono.CSharp {
                                ec.EmitInt (first_resume_pc);
                                ec.Emit (OpCodes.Sub);
 
-                               Label[] labels = new Label[resume_points.Count];
-                               for (int i = 0; i < resume_points.Count; ++i)
+                               var labels = new Label[resume_points.Count - System.Math.Max (first_catch_resume_pc, 0)];
+                               for (int i = 0; i < labels.Length; ++i)
                                        labels[i] = resume_points[i].PrepareForEmit (ec);
                                ec.Emit (OpCodes.Switch, labels);
                        }
                }
 
-               public virtual int AddResumePoint (ResumableStatement stmt, int pc, StateMachineInitializer stateMachine)
+               bool IsRewrittenTryCatchFinally ()
                {
-                       if (parent != null) {
-                               // TODO: MOVE to virtual TryCatch
-                               var tc = this as TryCatch;
-                               var s = tc != null && tc.IsTryCatchFinally ? stmt : this;
+                       var tf = this as TryFinally;
+                       if (tf == null)
+                               return false;
+
+                       var tc = tf.Statement as TryCatch;
+                       if (tc == null)
+                               return false;
 
-                               pc = parent.AddResumePoint (s, pc, stateMachine);
+                       return tf.FinallyBlock.HasAwait || tc.HasClauseWithAwait;
+               }
+
+               public int AddResumePoint (ResumableStatement stmt, int pc, StateMachineInitializer stateMachine, TryCatch catchBlock)
+               {
+                       if (parent_try_block != null) {
+                               pc = parent_try_block.AddResumePoint (this, pc, stateMachine, catchBlock);
                        } else {
                                pc = stateMachine.AddResumePoint (this);
                        }
@@ -5990,6 +6026,11 @@ namespace Mono.CSharp {
                        if (pc != first_resume_pc + resume_points.Count)
                                throw new InternalErrorException ("missed an intervening AddResumePoint?");
 
+                       var tf = this as TryFinally;
+                       if (tf != null && tf.Statement == catchBlock && first_catch_resume_pc < 0) {
+                               first_catch_resume_pc = resume_points.Count;
+                       }
+
                        resume_points.Add (stmt);
                        return pc;
                }
@@ -6936,14 +6977,6 @@ namespace Mono.CSharp {
                        return ok;
                }
 
-               protected override void EmitBeginException (EmitContext ec)
-               {
-                       if (fini.HasAwait && stmt is TryCatch)
-                               ec.BeginExceptionBlock ();
-
-                       base.EmitBeginException (ec);
-               }
-
                protected override void EmitTryBody (EmitContext ec)
                {
                        if (fini.HasAwait) {
@@ -6953,7 +6986,7 @@ namespace Mono.CSharp {
                                ec.TryFinallyUnwind.Add (this);
                                stmt.Emit (ec);
 
-                               if (stmt is TryCatch)
+                               if (first_catch_resume_pc < 0 && stmt is TryCatch)
                                        ec.EndExceptionBlock ();
 
                                ec.TryFinallyUnwind.Remove (this);
@@ -7193,6 +7226,12 @@ namespace Mono.CSharp {
                        }
                }
 
+               public bool HasClauseWithAwait {
+                       get {
+                               return catch_sm != null;
+                       }
+               }
+
                public bool IsTryCatchFinally {
                        get {
                                return inside_try_finally;
@@ -7204,7 +7243,8 @@ namespace Mono.CSharp {
                        bool ok;
 
                        using (bc.Set (ResolveContext.Options.TryScope)) {
-                               parent = bc.CurrentTryBlock;
+
+                               parent_try_block = bc.CurrentTryBlock;
 
                                if (IsTryCatchFinally) {
                                        ok = Block.Resolve (bc);
@@ -7212,11 +7252,14 @@ namespace Mono.CSharp {
                                        using (bc.Set (ResolveContext.Options.TryWithCatchScope)) {
                                                bc.CurrentTryBlock = this;
                                                ok = Block.Resolve (bc);
-                                               bc.CurrentTryBlock = parent;
+                                               bc.CurrentTryBlock = parent_try_block;
                                        }
                                }
                        }
 
+                       var prev_catch = bc.CurrentTryCatch;
+                       bc.CurrentTryCatch = this;
+
                        for (int i = 0; i < clauses.Count; ++i) {
                                var c = clauses[i];
 
@@ -7275,6 +7318,8 @@ namespace Mono.CSharp {
                                }
                        }
 
+                       bc.CurrentTryCatch = prev_catch;
+
                        return base.Resolve (bc) && ok;
                }
 
@@ -7307,10 +7352,12 @@ namespace Mono.CSharp {
                                }
                        }
 
-                       if (!inside_try_finally)
+                       if (state_variable == null) {
+                               if (!inside_try_finally)
+                                       ec.EndExceptionBlock ();
+                       } else {
                                ec.EndExceptionBlock ();
 
-                       if (state_variable != null) {
                                ec.Emit (OpCodes.Ldloc, state_variable);
 
                                var labels = new Label [catch_sm.Count + 1];
@@ -7362,7 +7409,7 @@ namespace Mono.CSharp {
                        }
 
                        fc.DefiniteAssignment = try_fc ?? start_fc;
-                       parent = null;
+                       parent_try_block = null;
                        return res;
                }
 
diff --git a/mcs/tests/test-async-85.cs b/mcs/tests/test-async-85.cs
new file mode 100644 (file)
index 0000000..36c7a1e
--- /dev/null
@@ -0,0 +1,74 @@
+using System;
+using System.Threading.Tasks;
+
+class Program
+{
+       static int count;
+
+       static int Main ()
+       {
+               Test (false).Wait ();
+               Console.WriteLine (count);
+               if (count != 110011)
+                       return 1;
+
+               count = 0;
+               Test (true).Wait ();
+               Console.WriteLine (count);
+               if (count != 111101)
+                       return 2;
+
+               count = 0;
+               Test2 (false).Wait ();
+               Console.WriteLine (count);
+               if (count != 11)
+                       return 3;
+
+               count = 0;
+               Test2 (true).Wait ();
+               Console.WriteLine (count);
+               if (count != 1101)
+                       return 4;
+
+               return 0;
+       }
+
+       static async Task Test (bool throwTest)
+       {
+               try {
+                       count += 1;
+                       await Task.Delay (10);
+
+                       if (throwTest)
+                               throw new ApplicationException ();
+
+                       count += 10;
+               } catch (ApplicationException) {
+                       count += 100;
+                       await Task.Delay (10);
+                       count += 1000;
+               } finally {
+                       count += 10000;
+                       await Task.Delay (10);
+                       count += 100000;
+               }
+       }
+
+       static async Task Test2 (bool throwTest)
+       {
+               try {
+                       count += 1;
+                       await Task.Delay (10);
+
+                       if (throwTest)
+                               throw new ApplicationException ();
+
+                       count += 10;
+               } catch (ApplicationException) {
+                       count += 100;
+                       await Task.Delay (10);
+                       count += 1000;
+               } finally {
+               }
+       }       
+}
index 576a15ddb738d3d6612dffbf80fa5a75ce29346a..b7054e4b28943dcc026469b86d3f13d8af453e54 100644 (file)
       </method>
     </type>
   </test>
+  <test name="test-async-85.cs">
+    <type name="Program">
+      <method name="Int32 Main()" attrs="145">
+        <size>197</size>
+      </method>
+      <method name="System.Threading.Tasks.Task Test(Boolean)" attrs="145">
+        <size>41</size>
+      </method>
+      <method name="System.Threading.Tasks.Task Test2(Boolean)" attrs="145">
+        <size>41</size>
+      </method>
+      <method name="Void .ctor()" attrs="6278">
+        <size>7</size>
+      </method>
+    </type>
+    <type name="Program+&lt;Test&gt;c__async0">
+      <method name="Void MoveNext()" attrs="486">
+        <size>543</size>
+      </method>
+      <method name="Void SetStateMachine(System.Runtime.CompilerServices.IAsyncStateMachine)" attrs="486">
+        <size>13</size>
+      </method>
+    </type>
+    <type name="Program+&lt;Test2&gt;c__async1">
+      <method name="Void MoveNext()" attrs="486">
+        <size>398</size>
+      </method>
+      <method name="Void SetStateMachine(System.Runtime.CompilerServices.IAsyncStateMachine)" attrs="486">
+        <size>13</size>
+      </method>
+    </type>
+  </test>
   <test name="test-cls-00.cs">
     <type name="CLSCLass_6">
       <method name="Void add_Disposed(Delegate)" attrs="2182">