From 85198271c9b376fc1a7bf2daa9a744cf9a8f6210 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 12 Sep 2026 16:10:59 +0200 Subject: [PATCH] Fix #3320: detect dynamic await in pre-Roslyn state machines The C# 5 compiler loads the awaiter into a fresh local before every dynamic call site and before the ICriticalNotifyCompletion type test, and the early non-aggressive inlining refuses to fold those copies. Every await matcher identifies an await by the identity of its awaiter variable, so each copy hid the awaiter from the matcher and the whole await stayed undetected. Each such copy has one store and one load, and after the dynamic call sites are collapsed that load sits in the next instruction, which is the case ILInlining.InlineOne already handles, including the check that the source is not overwritten first. Folding is restricted to the IsCompleted and GetResult call sites and the completion-interface type tests, so dynamic calls in user code keep their locals: a blanket fold retypes unrelated locals of the surrounding method (an enum local decompiled as int plus a cast). Two smaller divergences from the Roslyn shape sat behind that one: the merge block of the AwaitOnCompleted/AwaitUnsafeOnCompleted diamond clears doFinallyBodies before its leave, and the awaiter is restored from its object-typed field with unbox.any rather than castclass. The ILPretty fixture is the state machine from the issue's assembly with its external types stubbed out, so the fix stays guarded where the legacy compiler is unavailable. The Pretty fixture for dynamic await now runs the pre-Roslyn configurations as well, which is the end-to-end guard on Windows; its expected output records that the compiler copies an awaited dynamic value into a local first and emits no debug name for it. The dead stores an optimized build leaves before a try block containing an await are not specific to dynamic - a plain await on a Task produces the same three - so they sit behind an EXPECTED_OUTPUT-only block. Checked against real legacy csc output (/o- and /o+): dynamic await in plain code, loops, try/catch, try/finally and using. Assisted-by: Claude:claude-opus-5[1m]:Claude Code --- .../ILPrettyTestRunner.cs | 6 + .../PrettyTestRunner.cs | 2 +- .../TestCases/ILPretty/Issue3320.cs | 22 + .../TestCases/ILPretty/Issue3320.il | 508 ++++++++++++++++++ .../TestCases/Pretty/DynamicAwait.cs | 29 +- .../IL/ControlFlow/AsyncAwaitDecompiler.cs | 73 ++- 6 files changed, 624 insertions(+), 16 deletions(-) create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3320.cs create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3320.il diff --git a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs index 44d08a2bf..e8dc84535 100644 --- a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs @@ -51,6 +51,12 @@ namespace ICSharpCode.Decompiler.Tests } } + [Test] + public async Task Issue3320() + { + await Run(); + } + [Test] public async Task ConditionalChain() { diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs index faa5a7456..86c86cbb7 100644 --- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs @@ -525,7 +525,7 @@ namespace ICSharpCode.Decompiler.Tests } [Test] - public async Task DynamicAwait([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions) + public async Task DynamicAwait([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) { await RunForLibrary(cscOptions: cscOptions); } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3320.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3320.cs new file mode 100644 index 000000000..73e1095c1 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3320.cs @@ -0,0 +1,22 @@ +using System.Threading.Tasks; + +public interface IDapperAsyncImplementor +{ + Task GetAsync(IDbConnection connection, object id, IDbTransaction transaction, int? commandTimeout) where T : class; +} +public interface IDbConnection +{ +} +public interface IDbTransaction +{ +} +public static class Issue3320 +{ + public static IDapperAsyncImplementor Instance => null; + + public static async Task GetAsync(this IDbConnection connection, dynamic id, IDbTransaction transaction = null, int? commandTimeout = null) where T : class + { + dynamic async = Instance.GetAsync(connection, id, transaction, commandTimeout); + return await async; + } +} diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3320.il b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3320.il new file mode 100644 index 000000000..5527fea61 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3320.il @@ -0,0 +1,508 @@ +.assembly extern mscorlib +{ + .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) + .ver 4:0:0:0 +} +.assembly extern System.Core +{ + .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) + .ver 4:0:0:0 +} +.assembly extern Microsoft.CSharp +{ + .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) + .ver 4:0:0:0 +} +.assembly Issue3320 +{ + .hash algorithm 0x00008004 + .ver 1:0:0:0 +} +.module Issue3320.dll +.imagebase 0x00400000 +.file alignment 0x00000200 +.stackreserve 0x00100000 +.subsystem 0x0003 // WINDOWS_CUI +.corflags 0x00000001 // ILONLY + +.class interface public abstract auto ansi IDbConnection +{ +} + +.class interface public abstract auto ansi IDbTransaction +{ +} + +.class interface public abstract auto ansi IDapperAsyncImplementor +{ + .method public hidebysig newslot abstract virtual + instance class [mscorlib]System.Threading.Tasks.Task`1 GetAsync ( + class IDbConnection connection, + object id, + class IDbTransaction transaction, + valuetype [mscorlib]System.Nullable`1 commandTimeout + ) cil managed + { + } +} + +.class public abstract auto ansi sealed beforefieldinit Issue3320 + extends [mscorlib]System.Object +{ + .method public hidebysig specialname static + class IDapperAsyncImplementor get_Instance () cil managed + { + .maxstack 8 + + IL_0000: ldnull + IL_0001: ret + } + + .property class IDapperAsyncImplementor Instance() + { + .get class IDapperAsyncImplementor Issue3320::get_Instance() + } + + .class nested private auto ansi abstract sealed beforefieldinit 'o__SiteContainer6`1' + extends [mscorlib]System.Object + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( + 01 00 00 00 + ) + // Fields + .field public static class [System.Core]System.Runtime.CompilerServices.CallSite`1> '<>p__Site7' + .field public static class [System.Core]System.Runtime.CompilerServices.CallSite`1, object>> '<>p__Site8' + .field public static class [System.Core]System.Runtime.CompilerServices.CallSite`1> '<>p__Site9' + .field public static class [System.Core]System.Runtime.CompilerServices.CallSite`1> '<>p__Sitea' + .field public static class [System.Core]System.Runtime.CompilerServices.CallSite`1> '<>p__Siteb' + .field public static class [System.Core]System.Runtime.CompilerServices.CallSite`1> '<>p__Sitec' + + } // end of class o__SiteContainer6`1 + + .class nested private auto ansi sealed beforefieldinit 'd__d`1' + extends [mscorlib]System.ValueType + implements [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( + 01 00 00 00 + ) + // Fields + .field public int32 '<>1__state' + .field public valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 '<>t__builder' + .field public class IDbConnection connection + .field public object id + .field public class IDbTransaction transaction + .field public valuetype [mscorlib]System.Nullable`1 commandTimeout + .field private object '<>u__$awaitere' + .field private object '<>t__stack' + + // Methods + .method private final hidebysig newslot virtual + instance void MoveNext () cil managed + { + .override method instance void [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine::MoveNext() + // Method begins at RVA 0x7188 + // Header size: 12 + // Code size: 788 (0x314) + .maxstack 11 + .locals init ( + [0] bool, + [1] !T, + [2] class [mscorlib]System.Exception, + [3] int32, + [4] class [mscorlib]System.Type[], + [5] class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo[], + [6] object, + [7] class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo[], + [8] object, + [9] object, + [10] class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo[], + [11] class [mscorlib]System.Tuple`2, class [System.Core]System.Runtime.CompilerServices.CallSite`1>>, + [12] object, + [13] class [mscorlib]System.Runtime.CompilerServices.ICriticalNotifyCompletion, + [14] object, + [15] class [mscorlib]System.Runtime.CompilerServices.INotifyCompletion, + [16] class [mscorlib]System.Tuple`2, class [System.Core]System.Runtime.CompilerServices.CallSite`1>>, + [17] object, + [18] class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo[] + ) + + .try + { + IL_0000: ldc.i4.1 + IL_0001: stloc.0 + IL_0002: ldarg.0 + IL_0003: ldfld int32 valuetype Issue3320/'d__d`1'::'<>1__state' + IL_0008: stloc.3 + IL_0009: ldloc.3 + IL_000a: ldc.i4.0 + IL_000b: beq IL_024a + + IL_0010: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1> class Issue3320/'o__SiteContainer6`1'::'<>p__Site7' + IL_0015: brtrue.s IL_003b + + IL_0017: ldc.i4.0 + IL_0018: ldtoken !T + IL_001d: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) + IL_0022: ldtoken Issue3320 + IL_0027: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) + IL_002c: call class [System.Core]System.Runtime.CompilerServices.CallSiteBinder [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.Binder::Convert(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, class [mscorlib]System.Type, class [mscorlib]System.Type) + IL_0031: call class [System.Core]System.Runtime.CompilerServices.CallSite`1 class [System.Core]System.Runtime.CompilerServices.CallSite`1>::Create(class [System.Core]System.Runtime.CompilerServices.CallSiteBinder) + IL_0036: stsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1> class Issue3320/'o__SiteContainer6`1'::'<>p__Site7' + + IL_003b: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1> class Issue3320/'o__SiteContainer6`1'::'<>p__Site7' + IL_0040: ldfld !0 class [System.Core]System.Runtime.CompilerServices.CallSite`1>::Target + IL_0045: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1> class Issue3320/'o__SiteContainer6`1'::'<>p__Site7' + IL_004a: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1, object>> class Issue3320/'o__SiteContainer6`1'::'<>p__Site8' + IL_004f: brtrue.s IL_00c9 + + IL_0051: ldc.i4.0 + IL_0052: ldstr "GetAsync" + IL_0057: ldc.i4.1 + IL_0058: newarr [mscorlib]System.Type + IL_005d: stloc.s 4 + IL_005f: ldloc.s 4 + IL_0061: ldc.i4.0 + IL_0062: ldtoken !T + IL_0067: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) + IL_006c: stelem.ref + IL_006d: ldloc.s 4 + IL_006f: ldtoken Issue3320 + IL_0074: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) + IL_0079: ldc.i4.5 + IL_007a: newarr [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo + IL_007f: stloc.s 5 + IL_0081: ldloc.s 5 + IL_0083: ldc.i4.0 + IL_0084: ldc.i4.1 + IL_0085: ldnull + IL_0086: call class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string) + IL_008b: stelem.ref + IL_008c: ldloc.s 5 + IL_008e: ldc.i4.1 + IL_008f: ldc.i4.1 + IL_0090: ldnull + IL_0091: call class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string) + IL_0096: stelem.ref + IL_0097: ldloc.s 5 + IL_0099: ldc.i4.2 + IL_009a: ldc.i4.0 + IL_009b: ldnull + IL_009c: call class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string) + IL_00a1: stelem.ref + IL_00a2: ldloc.s 5 + IL_00a4: ldc.i4.3 + IL_00a5: ldc.i4.1 + IL_00a6: ldnull + IL_00a7: call class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string) + IL_00ac: stelem.ref + IL_00ad: ldloc.s 5 + IL_00af: ldc.i4.4 + IL_00b0: ldc.i4.1 + IL_00b1: ldnull + IL_00b2: call class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string) + IL_00b7: stelem.ref + IL_00b8: ldloc.s 5 + IL_00ba: call class [System.Core]System.Runtime.CompilerServices.CallSiteBinder [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.Binder::InvokeMember(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, string, class [mscorlib]System.Collections.Generic.IEnumerable`1, class [mscorlib]System.Type, class [mscorlib]System.Collections.Generic.IEnumerable`1) + IL_00bf: call class [System.Core]System.Runtime.CompilerServices.CallSite`1 class [System.Core]System.Runtime.CompilerServices.CallSite`1, object>>::Create(class [System.Core]System.Runtime.CompilerServices.CallSiteBinder) + IL_00c4: stsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1, object>> class Issue3320/'o__SiteContainer6`1'::'<>p__Site8' + + IL_00c9: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1, object>> class Issue3320/'o__SiteContainer6`1'::'<>p__Site8' + IL_00ce: ldfld !0 class [System.Core]System.Runtime.CompilerServices.CallSite`1, object>>::Target + IL_00d3: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1, object>> class Issue3320/'o__SiteContainer6`1'::'<>p__Site8' + IL_00d8: call class IDapperAsyncImplementor Issue3320::get_Instance() + IL_00dd: ldarg.0 + IL_00de: ldfld class IDbConnection valuetype Issue3320/'d__d`1'::connection + IL_00e3: ldarg.0 + IL_00e4: ldfld object valuetype Issue3320/'d__d`1'::id + IL_00e9: ldarg.0 + IL_00ea: ldfld class IDbTransaction valuetype Issue3320/'d__d`1'::transaction + IL_00ef: ldarg.0 + IL_00f0: ldfld valuetype [mscorlib]System.Nullable`1 valuetype Issue3320/'d__d`1'::commandTimeout + IL_00f5: callvirt instance !6 class [mscorlib]System.Func`7, object>::Invoke(!0, !1, !2, !3, !4, !5) + IL_00fa: stloc.s 6 + IL_00fc: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1> class Issue3320/'o__SiteContainer6`1'::'<>p__Site9' + IL_0101: brtrue.s IL_0138 + + IL_0103: ldc.i4.0 + IL_0104: ldstr "GetAwaiter" + IL_0109: ldnull + IL_010a: ldtoken Issue3320 + IL_010f: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) + IL_0114: ldc.i4.1 + IL_0115: newarr [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo + IL_011a: stloc.s 7 + IL_011c: ldloc.s 7 + IL_011e: ldc.i4.0 + IL_011f: ldc.i4.0 + IL_0120: ldnull + IL_0121: call class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string) + IL_0126: stelem.ref + IL_0127: ldloc.s 7 + IL_0129: call class [System.Core]System.Runtime.CompilerServices.CallSiteBinder [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.Binder::InvokeMember(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, string, class [mscorlib]System.Collections.Generic.IEnumerable`1, class [mscorlib]System.Type, class [mscorlib]System.Collections.Generic.IEnumerable`1) + IL_012e: call class [System.Core]System.Runtime.CompilerServices.CallSite`1 class [System.Core]System.Runtime.CompilerServices.CallSite`1>::Create(class [System.Core]System.Runtime.CompilerServices.CallSiteBinder) + IL_0133: stsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1> class Issue3320/'o__SiteContainer6`1'::'<>p__Site9' + + IL_0138: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1> class Issue3320/'o__SiteContainer6`1'::'<>p__Site9' + IL_013d: ldfld !0 class [System.Core]System.Runtime.CompilerServices.CallSite`1>::Target + IL_0142: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1> class Issue3320/'o__SiteContainer6`1'::'<>p__Site9' + IL_0147: ldloc.s 6 + IL_0149: callvirt instance !2 class [mscorlib]System.Func`3::Invoke(!0, !1) + IL_014e: stloc.s 8 + IL_0150: ldloc.s 8 + IL_0152: stloc.s 9 + IL_0154: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1> class Issue3320/'o__SiteContainer6`1'::'<>p__Sitea' + IL_0159: brtrue.s IL_017f + + IL_015b: ldc.i4.0 + IL_015c: ldtoken [mscorlib]System.Boolean + IL_0161: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) + IL_0166: ldtoken Issue3320 + IL_016b: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) + IL_0170: call class [System.Core]System.Runtime.CompilerServices.CallSiteBinder [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.Binder::Convert(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, class [mscorlib]System.Type, class [mscorlib]System.Type) + IL_0175: call class [System.Core]System.Runtime.CompilerServices.CallSite`1 class [System.Core]System.Runtime.CompilerServices.CallSite`1>::Create(class [System.Core]System.Runtime.CompilerServices.CallSiteBinder) + IL_017a: stsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1> class Issue3320/'o__SiteContainer6`1'::'<>p__Sitea' + + IL_017f: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1> class Issue3320/'o__SiteContainer6`1'::'<>p__Sitea' + IL_0184: ldfld !0 class [System.Core]System.Runtime.CompilerServices.CallSite`1>::Target + IL_0189: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1> class Issue3320/'o__SiteContainer6`1'::'<>p__Sitea' + IL_018e: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1> class Issue3320/'o__SiteContainer6`1'::'<>p__Siteb' + IL_0193: brtrue.s IL_01c9 + + IL_0195: ldc.i4.0 + IL_0196: ldstr "IsCompleted" + IL_019b: ldtoken Issue3320 + IL_01a0: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) + IL_01a5: ldc.i4.1 + IL_01a6: newarr [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo + IL_01ab: stloc.s 10 + IL_01ad: ldloc.s 10 + IL_01af: ldc.i4.0 + IL_01b0: ldc.i4.0 + IL_01b1: ldnull + IL_01b2: call class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string) + IL_01b7: stelem.ref + IL_01b8: ldloc.s 10 + IL_01ba: call class [System.Core]System.Runtime.CompilerServices.CallSiteBinder [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.Binder::GetMember(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, string, class [mscorlib]System.Type, class [mscorlib]System.Collections.Generic.IEnumerable`1) + IL_01bf: call class [System.Core]System.Runtime.CompilerServices.CallSite`1 class [System.Core]System.Runtime.CompilerServices.CallSite`1>::Create(class [System.Core]System.Runtime.CompilerServices.CallSiteBinder) + IL_01c4: stsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1> class Issue3320/'o__SiteContainer6`1'::'<>p__Siteb' + + IL_01c9: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1> class Issue3320/'o__SiteContainer6`1'::'<>p__Siteb' + IL_01ce: ldfld !0 class [System.Core]System.Runtime.CompilerServices.CallSite`1>::Target + IL_01d3: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1> class Issue3320/'o__SiteContainer6`1'::'<>p__Siteb' + IL_01d8: ldloc.s 9 + IL_01da: callvirt instance !2 class [mscorlib]System.Func`3::Invoke(!0, !1) + IL_01df: callvirt instance !2 class [mscorlib]System.Func`3::Invoke(!0, !1) + IL_01e4: brtrue IL_0287 + + IL_01e9: newobj instance void class [mscorlib]System.Tuple`2, class [System.Core]System.Runtime.CompilerServices.CallSite`1>>::.ctor(!0, !1) + IL_01ee: stloc.s 11 + IL_01f0: ldarg.0 + IL_01f1: ldloc.s 11 + IL_01f3: stfld object valuetype Issue3320/'d__d`1'::'<>t__stack' + IL_01f8: ldarg.0 + IL_01f9: ldc.i4.0 + IL_01fa: stfld int32 valuetype Issue3320/'d__d`1'::'<>1__state' + IL_01ff: ldarg.0 + IL_0200: ldloc.s 8 + IL_0202: stfld object valuetype Issue3320/'d__d`1'::'<>u__$awaitere' + IL_0207: ldloc.s 8 + IL_0209: stloc.s 12 + IL_020b: ldloc.s 12 + IL_020d: isinst [mscorlib]System.Runtime.CompilerServices.ICriticalNotifyCompletion + IL_0212: stloc.s 13 + IL_0214: ldloc.s 13 + IL_0216: brtrue.s IL_0235 + + IL_0218: ldloc.s 8 + IL_021a: stloc.s 14 + IL_021c: ldloc.s 14 + IL_021e: castclass [mscorlib]System.Runtime.CompilerServices.INotifyCompletion + IL_0223: stloc.s 15 + IL_0225: ldarg.0 + IL_0226: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 valuetype Issue3320/'d__d`1'::'<>t__builder' + IL_022b: ldloca.s 15 + IL_022d: ldarg.0 + IL_022e: call instance void valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::AwaitOnCompletedd__d`1'>(!!0&, !!1&) + IL_0233: br.s IL_0243 + + IL_0235: ldarg.0 + IL_0236: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 valuetype Issue3320/'d__d`1'::'<>t__builder' + IL_023b: ldloca.s 13 + IL_023d: ldarg.0 + IL_023e: call instance void valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::AwaitUnsafeOnCompletedd__d`1'>(!!0&, !!1&) + + IL_0243: ldc.i4.0 + IL_0244: stloc.0 + IL_0245: leave IL_0313 + + IL_024a: ldarg.0 + IL_024b: ldfld object valuetype Issue3320/'d__d`1'::'<>t__stack' + IL_0250: unbox.any class [mscorlib]System.Tuple`2, class [System.Core]System.Runtime.CompilerServices.CallSite`1>> + IL_0255: stloc.s 16 + IL_0257: ldloc.s 16 + IL_0259: callvirt instance !0 class [mscorlib]System.Tuple`2, class [System.Core]System.Runtime.CompilerServices.CallSite`1>>::get_Item1() + IL_025e: ldloc.s 16 + IL_0260: callvirt instance !1 class [mscorlib]System.Tuple`2, class [System.Core]System.Runtime.CompilerServices.CallSite`1>>::get_Item2() + IL_0265: ldarg.0 + IL_0266: ldnull + IL_0267: stfld object valuetype Issue3320/'d__d`1'::'<>t__stack' + IL_026c: ldarg.0 + IL_026d: ldfld object valuetype Issue3320/'d__d`1'::'<>u__$awaitere' + IL_0272: unbox.any [mscorlib]System.Object + IL_0277: stloc.s 8 + IL_0279: ldarg.0 + IL_027a: ldnull + IL_027b: stfld object valuetype Issue3320/'d__d`1'::'<>u__$awaitere' + IL_0280: ldarg.0 + IL_0281: ldc.i4.m1 + IL_0282: stfld int32 valuetype Issue3320/'d__d`1'::'<>1__state' + + IL_0287: ldloc.s 8 + IL_0289: stloc.s 17 + IL_028b: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1> class Issue3320/'o__SiteContainer6`1'::'<>p__Sitec' + IL_0290: brtrue.s IL_02c7 + + IL_0292: ldc.i4.0 + IL_0293: ldstr "GetResult" + IL_0298: ldnull + IL_0299: ldtoken Issue3320 + IL_029e: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) + IL_02a3: ldc.i4.1 + IL_02a4: newarr [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo + IL_02a9: stloc.s 18 + IL_02ab: ldloc.s 18 + IL_02ad: ldc.i4.0 + IL_02ae: ldc.i4.0 + IL_02af: ldnull + IL_02b0: call class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string) + IL_02b5: stelem.ref + IL_02b6: ldloc.s 18 + IL_02b8: call class [System.Core]System.Runtime.CompilerServices.CallSiteBinder [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.Binder::InvokeMember(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, string, class [mscorlib]System.Collections.Generic.IEnumerable`1, class [mscorlib]System.Type, class [mscorlib]System.Collections.Generic.IEnumerable`1) + IL_02bd: call class [System.Core]System.Runtime.CompilerServices.CallSite`1 class [System.Core]System.Runtime.CompilerServices.CallSite`1>::Create(class [System.Core]System.Runtime.CompilerServices.CallSiteBinder) + IL_02c2: stsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1> class Issue3320/'o__SiteContainer6`1'::'<>p__Sitec' + + IL_02c7: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1> class Issue3320/'o__SiteContainer6`1'::'<>p__Sitec' + IL_02cc: ldfld !0 class [System.Core]System.Runtime.CompilerServices.CallSite`1>::Target + IL_02d1: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1> class Issue3320/'o__SiteContainer6`1'::'<>p__Sitec' + IL_02d6: ldloc.s 17 + IL_02d8: callvirt instance !2 class [mscorlib]System.Func`3::Invoke(!0, !1) + IL_02dd: ldnull + IL_02de: stloc.s 8 + IL_02e0: callvirt instance !2 class [mscorlib]System.Func`3::Invoke(!0, !1) + IL_02e5: stloc.1 + IL_02e6: leave.s IL_02ff + } // end .try + catch [mscorlib]System.Exception + { + IL_02e8: stloc.2 + IL_02e9: ldarg.0 + IL_02ea: ldc.i4.s -2 + IL_02ec: stfld int32 valuetype Issue3320/'d__d`1'::'<>1__state' + IL_02f1: ldarg.0 + IL_02f2: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 valuetype Issue3320/'d__d`1'::'<>t__builder' + IL_02f7: ldloc.2 + IL_02f8: call instance void valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::SetException(class [mscorlib]System.Exception) + IL_02fd: leave.s IL_0313 + } // end handler + + IL_02ff: ldarg.0 + IL_0300: ldc.i4.s -2 + IL_0302: stfld int32 valuetype Issue3320/'d__d`1'::'<>1__state' + IL_0307: ldarg.0 + IL_0308: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 valuetype Issue3320/'d__d`1'::'<>t__builder' + IL_030d: ldloc.1 + IL_030e: call instance void valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::SetResult(!0) + + IL_0313: ret + } // end of method 'd__d`1'::MoveNext + + .method private final hidebysig newslot virtual + instance void SetStateMachine ( + class [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine param0 + ) cil managed + { + .custom instance void [mscorlib]System.Diagnostics.DebuggerHiddenAttribute::.ctor() = ( + 01 00 00 00 + ) + .override method instance void [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine::SetStateMachine(class [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine) + // Method begins at RVA 0x74c4 + // Header size: 1 + // Code size: 13 (0xd) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 valuetype Issue3320/'d__d`1'::'<>t__builder' + IL_0006: ldarg.1 + IL_0007: call instance void valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::SetStateMachine(class [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine) + IL_000c: ret + } // end of method 'd__d`1'::SetStateMachine + + } // end of class d__d`1 + + + .method public hidebysig static + class [mscorlib]System.Threading.Tasks.Task`1 GetAsync ( + class IDbConnection connection, + object id, + [opt] class IDbTransaction transaction, + [opt] valuetype [mscorlib]System.Nullable`1 commandTimeout + ) cil managed + { + .custom instance void [mscorlib]System.Diagnostics.DebuggerStepThroughAttribute::.ctor() = ( + 01 00 00 00 + ) + .custom instance void [mscorlib]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [mscorlib]System.Type) = ( + 01 00 37 44 61 70 70 65 72 45 78 74 65 6e 73 69 + 6f 6e 73 2e 44 61 70 70 65 72 41 73 79 6e 63 45 + 78 74 65 6e 73 69 6f 6e 73 2b 3c 47 65 74 41 73 + 79 6e 63 3e 64 5f 5f 64 60 31 00 00 + ) + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( + 01 00 00 00 + ) + .param [2] + .custom instance void [System.Core]System.Runtime.CompilerServices.DynamicAttribute::.ctor() = ( + 01 00 00 00 + ) + .param [3] = nullref + .param [4] = nullref + // Method begins at RVA 0x74d4 + // Header size: 12 + // Code size: 82 (0x52) + .maxstack 2 + .locals init ( + [0] valuetype Issue3320/'d__d`1', + [1] valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 + ) + + IL_0000: ldloca.s 0 + IL_0002: ldarg.0 + IL_0003: stfld class IDbConnection valuetype Issue3320/'d__d`1'::connection + IL_0008: ldloca.s 0 + IL_000a: ldarg.1 + IL_000b: stfld object valuetype Issue3320/'d__d`1'::id + IL_0010: ldloca.s 0 + IL_0012: ldarg.2 + IL_0013: stfld class IDbTransaction valuetype Issue3320/'d__d`1'::transaction + IL_0018: ldloca.s 0 + IL_001a: ldarg.3 + IL_001b: stfld valuetype [mscorlib]System.Nullable`1 valuetype Issue3320/'d__d`1'::commandTimeout + IL_0020: ldloca.s 0 + IL_0022: call valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::Create() + IL_0027: stfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 valuetype Issue3320/'d__d`1'::'<>t__builder' + IL_002c: ldloca.s 0 + IL_002e: ldc.i4.m1 + IL_002f: stfld int32 valuetype Issue3320/'d__d`1'::'<>1__state' + IL_0034: ldloca.s 0 + IL_0036: ldfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 valuetype Issue3320/'d__d`1'::'<>t__builder' + IL_003b: stloc.1 + IL_003c: ldloca.s 1 + IL_003e: ldloca.s 0 + IL_0040: call instance void valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::Startd__d`1'>(!!0&) + IL_0045: ldloca.s 0 + IL_0047: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 valuetype Issue3320/'d__d`1'::'<>t__builder' + IL_004c: call instance class [mscorlib]System.Threading.Tasks.Task`1 valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::get_Task() + IL_0051: ret + } // end of method Issue3320::GetAsync +} diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicAwait.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicAwait.cs index ec020e2d4..9b7466c3c 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicAwait.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DynamicAwait.cs @@ -92,6 +92,14 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty private static async Task DynamicAwaitInTryCatch(dynamic d) { + // Optimized pre-Roslyn state machines keep the bookkeeping of an await inside a try + // block in locals that survive decompilation as dead stores; a plain await on a Task + // produces the same three stores. +#if EXPECTED_OUTPUT && OPT && LEGACY_CSC + int num = default(int); + int num2 = num; + int num3 = 0; +#endif try { d.Before = 1; @@ -151,7 +159,13 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty // awaited dynamic stored in a dynamic local, then used (#1388) private static async Task AwaitDynamicIntoUsedLocal() { + // The pre-Roslyn compiler copies the awaited value into its own local first. +#if LEGACY_CSC + dynamic dynamic = GetDynamic(); + dynamic val = await dynamic; +#else dynamic val = await GetDynamic(); +#endif val.UseMe(); return val; } @@ -159,7 +173,12 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty // dynamic-dispatched awaited call with a dynamic-converted result (#1928) private static async Task AwaitDynamicConvertResult(dynamic d) { +#if LEGACY_CSC + dynamic val = d.RunAsync(); + return (string)(await val); +#else return (string)(await d.RunAsync()); +#endif } // --- static-type call target spilled across an await --- @@ -182,13 +201,19 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty private static async Task AwaitDynamicMethodResult(dynamic d) { +#if LEGACY_CSC + dynamic val = d.RunAsync(); + await val; +#else await d.RunAsync(); +#endif } private static async Task AwaitDynamicLocalWithStatementBefore() { - // Optimized builds drop the local's debug name, so the decompiler regenerates one from the type. -#if OPT + // Optimized builds drop the local's debug name, so the decompiler regenerates one from + // the type; the pre-Roslyn compiler never emits the name in the first place. +#if OPT || LEGACY_CSC dynamic dynamic = GetDynamic(); Console.WriteLine("before await"); await dynamic; diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs b/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs index 3775d2eec..80e325b68 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs @@ -1344,6 +1344,8 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow smallestAwaiterVarIndex = int.MaxValue; foreach (var container in function.Descendants.OfType()) { + foreach (var block in container.Blocks) + InlineAwaiterCopies(block); // Fold the runtime ICriticalNotifyCompletion type-check that the compiler emits when the // awaiter's static type is not known to implement it (dynamic awaiters, some generic awaiters) // into the canonical single-call form that AnalyzeAwaitBlock recognizes. @@ -1629,7 +1631,10 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow if (!onCompletedBlock.Instructions[2].MatchBranch(out var mergeBlock2) || mergeBlock2 != mergeBlock) return false; - if (mergeBlock.Instructions.Count != 1 || !(mergeBlock.Instructions[0] is Leave mergeLeave)) + // The pre-Roslyn compiler clears doFinallyBodies right before leaving MoveNext; + // AnalyzeAwaitBlock expects that store directly in front of the leave. + int leavePos = MatchStoreDoFinallyBodies(mergeBlock.Instructions.FirstOrDefault()) ? 1 : 0; + if (mergeBlock.Instructions.Count != leavePos + 1 || !(mergeBlock.Instructions[leavePos] is Leave mergeLeave)) return false; // Rewrite `block` into the canonical single-call form. @@ -1641,12 +1646,57 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow block.Instructions.RemoveRange(count - 2, 2); // if + br block.Instructions.RemoveAt(isInstPos); // isinst store block.Instructions.Add(awaitCall); + if (leavePos > 0) + { + block.Instructions.Add(mergeBlock.Instructions[0].Clone()); + } var newLeave = (Leave)mergeLeave.Clone(); moveNextLeaves.Add(newLeave); block.Instructions.Add(newLeave); return true; } + /// + /// Matches the 'stloc doFinallyBodies(ldc.i4 0)' the pre-Roslyn compiler emits before leaving MoveNext. + /// + bool MatchStoreDoFinallyBodies(ILInstruction inst) + { + if (doFinallyBodies == null || inst == null) + return false; + return inst.MatchStLoc(out var v, out var value) + && v.Kind == VariableKind.Local + && v.Type.IsKnownType(KnownTypeCode.Boolean) + && v.Index == doFinallyBodies.Index + && value.MatchLdcI4(0); + } + + /// + /// The pre-Roslyn compiler copies the awaiter into a fresh local right before each dynamic call site + /// and each completion-interface type test on it. The await matchers identify an await by its awaiter + /// variable, so inline those copies. + /// + void InlineAwaiterCopies(Block block) + { + for (int i = block.Instructions.Count - 1; i >= 0; i--) + { + if (block.Instructions[i] is StLoc { Variable: { Kind: VariableKind.Local, IsSingleDefinition: true, LoadCount: 1 } copy, Value: LdLoc } store + && IsAwaiterAccess(copy.LoadInstructions[0].Parent)) + { + ILInlining.InlineOne(store, InliningOptions.Aggressive, context); + } + } + + static bool IsAwaiterAccess(ILInstruction inst) + { + if (inst is DynamicGetMemberInstruction { Name: "IsCompleted" } or DynamicInvokeMemberInstruction { Name: "GetResult" }) + return true; + if (!inst.MatchIsInst(out _, out var type) && !inst.MatchCastClass(out _, out type)) + return false; + return type.FullName is "System.Runtime.CompilerServices.ICriticalNotifyCompletion" + or "System.Runtime.CompilerServices.INotifyCompletion"; + } + } + bool AnalyzeAwaitBlock(Block block, out ILVariable awaiter, out IField awaiterField, out int state, out int yieldOffset) { awaiter = null; @@ -1654,15 +1704,9 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow state = 0; yieldOffset = -1; int pos = block.Instructions.Count - 2; - if (pos >= 0 && doFinallyBodies != null && block.Instructions[pos] is StLoc storeDoFinallyBodies) + if (pos >= 0 && doFinallyBodies != null && block.Instructions[pos] is StLoc) { - if (!(storeDoFinallyBodies.Variable.Kind == VariableKind.Local - && storeDoFinallyBodies.Variable.Type.IsKnownType(KnownTypeCode.Boolean) - && storeDoFinallyBodies.Variable.Index == doFinallyBodies.Index)) - { - return false; - } - if (!storeDoFinallyBodies.Value.MatchLdcI4(0)) + if (!MatchStoreDoFinallyBodies(block.Instructions[pos])) return false; pos--; } @@ -2008,6 +2052,8 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow { // keep pulling in trivial branch targets until this block can grow no further } + // Only now is each awaiter copy in the same block as the call site that uses it. + InlineAwaiterCopies(block); } } @@ -2085,11 +2131,12 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow // stloc awaiterVar(ldfld awaiterField(ldloc this)) if (!instr.MatchStLoc(awaiterVar, out var value)) return false; - if (value is CastClass cast && cast.Type.Equals(awaiterVar.Type)) + // If the awaiter is a reference type, it might get stored in a field of type `object` + // and cast back to the awaiter type in the resume block (pre-Roslyn: with unbox.any) + if ((value.MatchCastClass(out var castArg, out var castType) || value.MatchUnboxAny(out castArg, out castType)) + && castType.Equals(awaiterVar.Type)) { - // If the awaiter is a reference type, it might get stored in a field of type `object` - // and cast back to the awaiter type in the resume block - value = cast.Argument; + value = castArg; } if (!value.MatchLdFld(out var target, out var field)) return false;