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)
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)
{
//
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;
#region Properties
- public LocalVariable HoistedReturn {
- get {
- return hoisted_return;
- }
- }
+ public Expression HoistedReturnValue { get; set; }
public TypeSpec ReturnType {
get {
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;
};
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)) {
ConstructorScope = 1 << 3,
- AsyncBody = 1 << 4
+ AsyncBody = 1 << 4,
+
+ HoistedReturnResult = 1 << 5
}
// utility helper for CheckExpr, UnCheckExpr, Checked and Unchecked statements
// 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;
#region Properties
- public Label BodyEnd {
- get {
- return iterator_body_end;
- }
- }
+ public Label BodyEnd { get; set; }
public LocalBuilder CurrentPC
{
// 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);
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);
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)
} else {
label.AddGotoReference (rc, true);
}
-
- try_finally = null;
} else {
label.AddGotoReference (rc, false);
}
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)
{
public void EmitAddressOf (EmitContext ec)
{
+ // TODO: Need something better for temporary variables
+ if ((flags & Flags.CompilerGenerated) != 0)
+ CreateBuilder (ec);
+
ec.Emit (OpCodes.Ldloca, builder);
}
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)
{
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);
}
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)
--- /dev/null
+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
</type>\r
<type name="Tester+<NewInitTestGen>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 <>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+<YieldValue>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+<TestFinallyWithReturn>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+<TestFinallyWithReturnNoValue>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+<TestFinallyWithGoto>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+<TestFinallyWithGotoAndReturn>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