diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index ac3e8c51a..610011534 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -81,6 +81,7 @@ + diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs index 711a307dc..8af4d4a13 100644 --- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs @@ -307,6 +307,12 @@ namespace ICSharpCode.Decompiler.Tests Run(cscOptions: cscOptions); } + [Test] + public void AsyncStreams([ValueSource(nameof(dotnetCoreOnlyOptions))] CompilerOptions cscOptions) + { + RunForLibrary(cscOptions: cscOptions); + } + [Test] public void AsyncUsing([ValueSource(nameof(dotnetCoreOnlyOptions))] CompilerOptions cscOptions) { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncStreams.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncStreams.cs new file mode 100644 index 000000000..58f8a40fc --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncStreams.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty +{ + public class AsyncStreams + { + public static async IAsyncEnumerable CountTo(int until) + { + for (int i = 0; i < until; i++) { + yield return i; + await Task.Delay(10); + } + } + + public static async IAsyncEnumerable AlwaysThrow() + { + throw null; + yield break; + } + + public static async IAsyncEnumerator InfiniteLoop() + { + while (true) { + } + yield break; + } + + public static async IAsyncEnumerable InfiniteLoopWithAwait() + { + while (true) { + await Task.Delay(10); + } + yield break; + } + } +} diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 3702f04d2..d1c3ee95d 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -1343,7 +1343,11 @@ namespace ICSharpCode.Decompiler.CSharp if (localSettings.DecompileMemberBodies && !body.Descendants.Any(d => d is YieldReturnStatement || d is YieldBreakStatement)) { body.Add(new YieldBreakStatement()); } - RemoveAttribute(entityDecl, KnownAttribute.IteratorStateMachine); + if (function.IsAsync) { + RemoveAttribute(entityDecl, KnownAttribute.AsyncIteratorStateMachine); + } else { + RemoveAttribute(entityDecl, KnownAttribute.IteratorStateMachine); + } if (function.StateMachineCompiledWithMono) { RemoveAttribute(entityDecl, KnownAttribute.DebuggerHidden); } diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs b/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs index 7dfc1048a..def38d2ff 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs @@ -174,9 +174,14 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow CopyPropagation.Propagate(stloc, context); } new RemoveDeadVariableInit().Run(function, context); - // Run inlining, but don't remove dead variables (they might get revived by TranslateFieldsToLocalAccess) foreach (var block in function.Descendants.OfType()) { + // Run inlining, but don't remove dead variables (they might get revived by TranslateFieldsToLocalAccess) ILInlining.InlineAllInBlock(function, block, context); + if (IsAsyncEnumerator) { + // Remove lone 'ldc.i4', those are sometimes left over after C# compiler + // optimizes out stores to the state variable. + block.Instructions.RemoveAll(inst => inst.OpCode == OpCode.LdcI4); + } } context.StepEndGroup(); } diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/ControlFlowSimplification.cs b/ICSharpCode.Decompiler/IL/ControlFlow/ControlFlowSimplification.cs index 9fd519461..1f8233b6b 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/ControlFlowSimplification.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/ControlFlowSimplification.cs @@ -46,7 +46,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow context.CancellationToken.ThrowIfCancellationRequested(); RemoveNopInstructions(block); - RemoveDeadStackStores(block, aggressive: context.Settings.RemoveDeadStores); + RemoveDeadStackStores(block, context); InlineVariableInReturnBlock(block, context); // 1st pass SimplifySwitchInstruction before SimplifyBranchChains() @@ -70,13 +70,15 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow block.Instructions.RemoveAll(inst => inst.OpCode == OpCode.Nop); } - private void RemoveDeadStackStores(Block block, bool aggressive) + private static void RemoveDeadStackStores(Block block, ILTransformContext context) { + bool aggressive = context.Settings.RemoveDeadStores; // Previously copy propagation did this; // ideally the ILReader would already do this, // for now do this here (even though it's not control-flow related). for (int i = block.Instructions.Count - 1; i >= 0; i--) { if (block.Instructions[i] is StLoc stloc && stloc.Variable.IsSingleDefinition && stloc.Variable.LoadCount == 0 && stloc.Variable.Kind == VariableKind.StackSlot) { + context.Step($"Remove dead stack store {stloc.Variable.Name}", stloc); if (aggressive ? SemanticHelper.IsPure(stloc.Value.Flags) : IsSimple(stloc.Value)) { Debug.Assert(SemanticHelper.IsPure(stloc.Value.Flags)); block.Instructions.RemoveAt(i++); diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs index 766cdc025..872f31af0 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs @@ -64,6 +64,7 @@ namespace ICSharpCode.Decompiler.TypeSystem IteratorStateMachine, AsyncStateMachine, AsyncMethodBuilder, + AsyncIteratorStateMachine, // Field attributes: FieldOffset, @@ -131,6 +132,7 @@ namespace ICSharpCode.Decompiler.TypeSystem new TopLevelTypeName("System.Runtime.CompilerServices", nameof(IteratorStateMachineAttribute)), new TopLevelTypeName("System.Runtime.CompilerServices", nameof(AsyncStateMachineAttribute)), new TopLevelTypeName("System.Runtime.CompilerServices", "AsyncMethodBuilderAttribute"), + new TopLevelTypeName("System.Runtime.CompilerServices", "AsyncIteratorStateMachineAttribute"), // Field attributes: new TopLevelTypeName("System.Runtime.InteropServices", nameof(FieldOffsetAttribute)), new TopLevelTypeName("System", nameof(NonSerializedAttribute)),