From 7d6122cfafa23173a051c7786e1c93e392407999 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 3 Sep 2017 23:54:54 +0200 Subject: [PATCH] [async] Decompile await operator. --- .../CSharp/CSharpDecompiler.cs | 4 +- .../CSharp/ExpressionBuilder.cs | 8 + .../ICSharpCode.Decompiler.csproj | 1 + .../IL/ControlFlow/AsyncAwaitDecompiler.cs | 218 ++++++++++++++++-- ICSharpCode.Decompiler/IL/Instructions.cs | 6 +- ICSharpCode.Decompiler/IL/Instructions.tt | 3 +- .../IL/Instructions/Await.cs | 15 ++ .../IL/Instructions/ILVariableCollection.cs | 1 + .../IL/Transforms/ExpressionTransforms.cs | 32 +-- .../IL/Transforms/ILInlining.cs | 3 +- .../IL/Transforms/RemoveDeadVariableInit.cs | 4 +- 11 files changed, 260 insertions(+), 35 deletions(-) create mode 100644 ICSharpCode.Decompiler/IL/Instructions/Await.cs diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 4ffa93594..4fb107ddb 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -188,8 +188,8 @@ namespace ICSharpCode.Decompiler.CSharp return true; if (settings.YieldReturn && YieldReturnDecompiler.IsCompilerGeneratorEnumerator(type)) return true; -// if (settings.AsyncAwait && AsyncDecompiler.IsCompilerGeneratedStateMachine(type)) -// return true; + if (settings.AsyncAwait && AsyncAwaitDecompiler.IsCompilerGeneratedStateMachine(type)) + return true; } else if (type.IsCompilerGenerated()) { // if (type.Name.StartsWith("", StringComparison.Ordinal)) // return true; diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 2e111be2a..4a515817c 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -1730,6 +1730,14 @@ namespace ICSharpCode.Decompiler.CSharp .WithRR(new ByReferenceResolveResult(value.ResolveResult, false)); } + protected internal override TranslatedExpression VisitAwait(Await inst, TranslationContext context) + { + var value = Translate(inst.Value); + return new UnaryOperatorExpression(UnaryOperatorType.Await, value.Expression) + .WithILInstruction(inst) + .WithRR(new ResolveResult(inst?.GetResultMethod.ReturnType ?? SpecialType.UnknownType)); + } + protected internal override TranslatedExpression VisitInvalidBranch(InvalidBranch inst, TranslationContext context) { string message = "Error"; diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 3d5f7d33e..820ea3a98 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -261,6 +261,7 @@ + diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs b/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs index 3260678fa..1b0f3799e 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs @@ -1,4 +1,5 @@ -using ICSharpCode.Decompiler.IL.Transforms; +using ICSharpCode.Decompiler.CSharp; +using ICSharpCode.Decompiler.IL.Transforms; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.Util; using System; @@ -15,18 +16,17 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow /// class AsyncAwaitDecompiler : IILTransform { - /* - public static bool IsCompilerGeneratedStateMachine(TypeDefinition type) + public static bool IsCompilerGeneratedStateMachine(Mono.Cecil.TypeDefinition type) { if (!(type.DeclaringType != null && type.IsCompilerGenerated())) return false; - foreach (TypeReference i in type.Interfaces) { - if (i.Namespace == "System.Runtime.CompilerServices" && i.Name == "IAsyncStateMachine") + foreach (var i in type.Interfaces) { + var iface = i.InterfaceType; + if (iface.Namespace == "System.Runtime.CompilerServices" && iface.Name == "IAsyncStateMachine") return true; } return false; } - */ enum AsyncMethodType { @@ -55,26 +55,42 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow Block setResultAndExitBlock; // block that is jumped to for return statements int finalState; // final state after the setResultAndExitBlock ILVariable resultVar; // the variable that gets returned by the setResultAndExitBlock - ILVariable doFinallyBodies; + // These fields are set by AnalyzeStateMachine(): + + // For each block containing an 'await', stores the awaiter variable, and the field storing the awaiter + // across the yield point. + Dictionary awaitBlocks = new Dictionary(); + public void Run(ILFunction function, ILTransformContext context) { if (!context.Settings.AsyncAwait) return; // abort if async/await decompilation is disabled this.context = context; fieldToParameterMap.Clear(); + awaitBlocks.Clear(); if (!MatchTaskCreationPattern(function)) return; try { AnalyzeMoveNext(); ValidateCatchBlock(); - InlineBodyOfMoveNext(function); - AnalyzeStateMachine(function); - FinalizeInlineMoveNext(function); } catch (SymbolicAnalysisFailedException) { return; } + + InlineBodyOfMoveNext(function); + AnalyzeStateMachine(function); + DetectAwaitPattern(function); + + context.Step("Translate fields to local accesses", function); + YieldReturnDecompiler.TranslateFieldsToLocalAccess(function, function, fieldToParameterMap); + + FinalizeInlineMoveNext(function); + + // Re-run control flow simplification over the newly constructed set of gotos, + // and inlining because TranslateFieldsToLocalAccess() might have opened up new inlining opportunities. + function.RunTransforms(CSharpDecompiler.EarlyILTransforms(), context); } #region MatchTaskCreationPattern @@ -396,6 +412,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow } function.Variables.AddRange(function.Descendants.OfType().Select(inst => inst.Variable).Distinct()); function.Variables.RemoveDead(); + function.Variables.AddRange(fieldToParameterMap.Values); } void FinalizeInlineMoveNext(ILFunction function) @@ -412,6 +429,8 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow } #endregion + #region AnalyzeStateMachine + /// /// Analyze the the state machine; and replace 'leave IL_0000' with await+jump to block that gets /// entered on the next MoveNext() call. @@ -427,16 +446,18 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow sra.AssignStateRanges(container, LongSet.Universe); foreach (var block in container.Blocks) { + context.CancellationToken.ThrowIfCancellationRequested(); if (block.Instructions.Last().MatchLeave((BlockContainer)moveNextFunction.Body)) { // This is likely an 'await' block - if (AnalyzeAwaitBlock(block, out var awaiter, out var awaiterField, out var state)) { - block.Instructions.Add(new Await(new LdLoca(awaiter))); + if (AnalyzeAwaitBlock(block, out var awaiterVar, out var awaiterField, out var state)) { + block.Instructions.Add(new Await(new LdLoca(awaiterVar))); Block targetBlock = sra.FindBlock(container, state); if (targetBlock != null) { block.Instructions.Add(new Branch(targetBlock)); } else { block.Instructions.Add(new InvalidBranch("Could not find block for state " + state)); } + awaitBlocks.Add(block, (awaiterVar, awaiterField)); } } } @@ -458,7 +479,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow awaiter = null; awaiterField = null; state = 0; - context.CancellationToken.ThrowIfCancellationRequested(); int pos = block.Instructions.Count - 2; if (doFinallyBodies != null && block.Instructions[pos] is StLoc storeDoFinallyBodies) { if (!(storeDoFinallyBodies.Variable.Kind == VariableKind.Local @@ -525,7 +545,9 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow // delete preceding dead stores: while (pos > 0 && block.Instructions[pos - 1] is StLoc stloc2 && stloc2.Variable.IsSingleDefinition && stloc2.Variable.LoadCount == 0 - && stloc2.Variable.Kind == VariableKind.StackSlot) { + && stloc2.Variable.Kind == VariableKind.StackSlot + && SemanticHelper.IsPure(stloc2.Value.Flags)) + { pos--; } block.Instructions.RemoveRange(pos, block.Instructions.Count - pos); @@ -541,5 +563,173 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow } return inst; } + #endregion + + #region DetectAwaitPattern + void DetectAwaitPattern(ILFunction function) + { + context.StepStartGroup("DetectAwaitPattern", function); + foreach (var container in function.Descendants.OfType()) { + foreach (var block in container.Blocks) { + context.CancellationToken.ThrowIfCancellationRequested(); + DetectAwaitPattern(block); + } + container.SortBlocks(deleteUnreachableBlocks: true); + } + context.StepEndGroup(keepIfEmpty: true); + } + + void DetectAwaitPattern(Block block) + { + // block: + // stloc awaiterVar(callvirt GetAwaiter(...)) + // if (call get_IsCompleted(ldloca awaiterVar)) br completedBlock + // br awaitBlock + // awaitBlock: + // .. + // br resumeBlock + // resumeBlock: + // .. + // br completedBlock + if (block.Instructions.Count < 3) + return; + // stloc awaiterVar(callvirt GetAwaiter(...)) + if (!(block.Instructions[block.Instructions.Count - 3] is StLoc stLocAwaiter)) + return; + ILVariable awaiterVar = stLocAwaiter.Variable; + if (!(stLocAwaiter.Value is CallInstruction getAwaiterCall)) + return; + if (!(getAwaiterCall.Method.Name == "GetAwaiter" && (!getAwaiterCall.Method.IsStatic || getAwaiterCall.Method.IsExtensionMethod))) + return; + if (getAwaiterCall.Arguments.Count != 1) + return; + // if (call get_IsCompleted(ldloca awaiterVar)) br completedBlock + if (!block.Instructions[block.Instructions.Count - 2].MatchIfInstruction(out var condition, out var trueInst)) + return; + if (!MatchCall(condition, "get_IsCompleted", out var isCompletedArgs) || isCompletedArgs.Count != 1) + return; + if (!isCompletedArgs[0].MatchLdLocRef(awaiterVar)) + return; + if (!trueInst.MatchBranch(out var completedBlock)) + return; + // br awaitBlock + if (!block.Instructions.Last().MatchBranch(out var awaitBlock)) + return; + // Check awaitBlock and resumeBlock: + if (!awaitBlocks.TryGetValue(awaitBlock, out var awaitBlockData)) + return; + if (awaitBlockData.awaiterVar != awaiterVar) + return; + if (!CheckAwaitBlock(awaitBlock, out var resumeBlock)) + return; + if (!CheckResumeBlock(resumeBlock, awaiterVar, awaitBlockData.awaiterField, completedBlock)) + return; + // Check completedBlock: + ILInstruction getResultCall; + if (completedBlock.Instructions[0] is StLoc resultStore) { + getResultCall = resultStore.Value; + } else { + getResultCall = completedBlock.Instructions[0]; + resultStore = null; + } + if (!MatchCall(getResultCall, "GetResult", out var getResultArgs) || getResultArgs.Count != 1) + return; + if (!getResultArgs[0].MatchLdLocRef(awaiterVar)) + return; + // All checks successful, let's transform. + context.Step("Transform await pattern", block); + block.Instructions.RemoveAt(block.Instructions.Count - 3); // remove getAwaiter call + block.Instructions.RemoveAt(block.Instructions.Count - 2); // remove if (isCompleted) + ((Branch)block.Instructions.Last()).TargetBlock = completedBlock; // instead, directly jump to completed block + Await awaitInst = new Await(getAwaiterCall.Arguments.Single()); + awaitInst.GetResultMethod = ((CallInstruction)getResultCall).Method; + awaitInst.GetAwaiterMethod = getAwaiterCall.Method; + if (resultStore != null) { + resultStore.Value = awaitInst; + } else { + completedBlock.Instructions[0] = awaitInst; + } + + // Remove useless reset of awaiterVar. + if (completedBlock.Instructions[1] is StObj stobj) { + if (stobj.Target.MatchLdLoca(awaiterVar) && stobj.Value.OpCode == OpCode.DefaultValue) { + completedBlock.Instructions.RemoveAt(1); + } + } + } + + bool CheckAwaitBlock(Block block, out Block resumeBlock) + { + // awaitBlock: + // await(ldloca V_2) + // br resumeBlock + resumeBlock = null; + if (block.Instructions.Count != 2) + return false; + if (block.Instructions[0].OpCode != OpCode.Await) + return false; + if (!block.Instructions[1].MatchBranch(out resumeBlock)) + return false; + return true; + } + + bool CheckResumeBlock(Block block, ILVariable awaiterVar, IField awaiterField, Block completedBlock) + { + int pos = 0; + // stloc awaiterVar(ldfld awaiterField(ldloc this)) + if (!block.Instructions[pos].MatchStLoc(awaiterVar, out var value)) + return false; + if (!value.MatchLdFld(out var target, out var field)) + return false; + if (!target.MatchLdThis()) + return false; + if (field.MemberDefinition != awaiterField) + return false; + pos++; + + // stfld awaiterField(ldloc this, default.value) + if (block.Instructions[pos].MatchStFld(out target, out field, out value) + && target.MatchLdThis() + && field.MemberDefinition == awaiterField + && value.OpCode == OpCode.DefaultValue) + { + pos++; + } + + // stloc S_27(ldloc this) + // stloc S_28(ldc.i4 -1) + // stloc cachedStateVar(ldloc S_28) + // stfld <>1__state(ldloc S_27, ldloc S_28) + ILVariable thisVar = null; + if (block.Instructions[pos] is StLoc stlocThis && stlocThis.Value.MatchLdThis() && stlocThis.Variable.Kind == VariableKind.StackSlot) { + thisVar = stlocThis.Variable; + pos++; + } + ILVariable m1Var = null; + if (block.Instructions[pos] is StLoc stlocM1 && stlocM1.Value.MatchLdcI4(initialState) && stlocM1.Variable.Kind == VariableKind.StackSlot) { + m1Var = stlocM1.Variable; + pos++; + } + if (block.Instructions[pos] is StLoc stlocCachedState) { + if (stlocCachedState.Variable.Kind == VariableKind.Local && stlocCachedState.Variable.Index == cachedStateVar.Index) { + if (stlocCachedState.Value.MatchLdLoc(m1Var) || stlocCachedState.Value.MatchLdcI4(initialState)) + pos++; + } + } + if (block.Instructions[pos].MatchStFld(out target, out field, out value)) { + if (!(target.MatchLdThis() || target.MatchLdLoc(thisVar))) + return false; + if (field.MemberDefinition != stateField) + return false; + if (!(value.MatchLdcI4(initialState) || value.MatchLdLoc(m1Var))) + return false; + pos++; + } else { + return false; + } + + return block.Instructions[pos].MatchBranch(completedBlock); + } + #endregion } } diff --git a/ICSharpCode.Decompiler/IL/Instructions.cs b/ICSharpCode.Decompiler/IL/Instructions.cs index 59e5d6dea..1bce7d2e7 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.cs +++ b/ICSharpCode.Decompiler/IL/Instructions.cs @@ -727,6 +727,7 @@ namespace ICSharpCode.Decompiler.IL { base.CheckInvariant(phase); Debug.Assert(phase <= ILPhase.InILReader || this.IsDescendantOf(variable.Function)); + Debug.Assert(phase <= ILPhase.InILReader || variable.Function.Variables[variable.IndexInFunction] == variable); } public static readonly SlotInfo InitSlot = new SlotInfo("Init", canInlineInto: true); ILInstruction init; @@ -1790,6 +1791,7 @@ namespace ICSharpCode.Decompiler.IL { base.CheckInvariant(phase); Debug.Assert(phase <= ILPhase.InILReader || this.IsDescendantOf(variable.Function)); + Debug.Assert(phase <= ILPhase.InILReader || variable.Function.Variables[variable.IndexInFunction] == variable); } public override StackType ResultType { get { return variable.StackType; } } protected override InstructionFlags ComputeFlags() @@ -1873,6 +1875,7 @@ namespace ICSharpCode.Decompiler.IL { base.CheckInvariant(phase); Debug.Assert(phase <= ILPhase.InILReader || this.IsDescendantOf(variable.Function)); + Debug.Assert(phase <= ILPhase.InILReader || variable.Function.Variables[variable.IndexInFunction] == variable); } public override void WriteTo(ITextOutput output) { @@ -1946,6 +1949,7 @@ namespace ICSharpCode.Decompiler.IL { base.CheckInvariant(phase); Debug.Assert(phase <= ILPhase.InILReader || this.IsDescendantOf(variable.Function)); + Debug.Assert(phase <= ILPhase.InILReader || variable.Function.Variables[variable.IndexInFunction] == variable); } public static readonly SlotInfo ValueSlot = new SlotInfo("Value", canInlineInto: true); ILInstruction value; @@ -4053,7 +4057,7 @@ namespace ICSharpCode.Decompiler.IL clone.Value = this.value.Clone(); return clone; } - public override StackType ResultType { get { return StackType.Void; } } + public override StackType ResultType { get { return GetResultMethod?.ReturnType.GetStackType() ?? StackType.Unknown; } } protected override InstructionFlags ComputeFlags() { return InstructionFlags.SideEffect | value.Flags; diff --git a/ICSharpCode.Decompiler/IL/Instructions.tt b/ICSharpCode.Decompiler/IL/Instructions.tt index 8fab367ca..8bc633ec2 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.tt +++ b/ICSharpCode.Decompiler/IL/Instructions.tt @@ -225,7 +225,7 @@ // note: "yield break" is always represented using a "leave" instruction new OpCode("await", "C# await operator.", SideEffect, // other code can run with arbitrary side effects while we're waiting - CustomArguments("value"), ResultType("Void")), + CustomArguments("value"), ResultType("GetResultMethod?.ReturnType.GetStackType() ?? StackType.Unknown")), // patterns new OpCode("AnyNode", "Matches any node", Pattern, CustomArguments(), CustomConstructor), @@ -869,6 +869,7 @@ protected override void Disconnected() { base.CheckInvariant(phase); Debug.Assert(phase <= ILPhase.InILReader || this.IsDescendantOf(variable.Function)); + Debug.Assert(phase <= ILPhase.InILReader || variable.Function.Variables[variable.IndexInFunction] == variable); }"); } }; diff --git a/ICSharpCode.Decompiler/IL/Instructions/Await.cs b/ICSharpCode.Decompiler/IL/Instructions/Await.cs new file mode 100644 index 000000000..0872e6ae7 --- /dev/null +++ b/ICSharpCode.Decompiler/IL/Instructions/Await.cs @@ -0,0 +1,15 @@ +using ICSharpCode.Decompiler.TypeSystem; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ICSharpCode.Decompiler.IL +{ + partial class Await + { + public IMethod GetAwaiterMethod; + public IMethod GetResultMethod; + } +} diff --git a/ICSharpCode.Decompiler/IL/Instructions/ILVariableCollection.cs b/ICSharpCode.Decompiler/IL/Instructions/ILVariableCollection.cs index 061f826e3..5698040b0 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/ILVariableCollection.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/ILVariableCollection.cs @@ -88,6 +88,7 @@ namespace ICSharpCode.Decompiler.IL void RemoveAt(int index) { + list[index].Function = null; // swap-remove index list[index] = list[list.Count - 1]; list[index].IndexInFunction = index; diff --git a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs index 769ad2fe4..f2d90c92f 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs @@ -216,27 +216,33 @@ namespace ICSharpCode.Decompiler.IL.Transforms protected internal override void VisitStObj(StObj inst) { base.VisitStObj(inst); - ILVariable v; - if (inst.Target.MatchLdLoca(out v) - && TypeUtils.IsCompatibleTypeForMemoryAccess(new ByReferenceType(v.Type), inst.Type) - && inst.UnalignedPrefix == 0 - && !inst.IsVolatile) - { - context.Step("stobj(ldloca(v), ...) => stloc(v, ...)", inst); - inst.ReplaceWith(new StLoc(v, inst.Value)); + if (StObjToStLoc(inst, context)) { return; } - - ILInstruction target; - IType t; - BinaryNumericInstruction binary = inst.Value as BinaryNumericInstruction; - if (binary != null && binary.Left.MatchLdObj(out target, out t) && inst.Target.Match(target).Success) { + + if (inst.Value is BinaryNumericInstruction binary + && binary.Left.MatchLdObj(out ILInstruction target, out IType t) + && inst.Target.Match(target).Success) + { context.Step("compound assignment", inst); // stobj(target, binary.op(ldobj(target), ...)) // => compound.op(target, ...) inst.ReplaceWith(new CompoundAssignmentInstruction(binary.Operator, binary.Left, binary.Right, t, binary.CheckForOverflow, binary.Sign, CompoundAssignmentType.EvaluatesToNewValue)); } } + + internal static bool StObjToStLoc(StObj inst, ILTransformContext context) + { + if (inst.Target.MatchLdLoca(out ILVariable v) + && TypeUtils.IsCompatibleTypeForMemoryAccess(new ByReferenceType(v.Type), inst.Type) + && inst.UnalignedPrefix == 0 + && !inst.IsVolatile) { + context.Step("stobj(ldloca(v), ...) => stloc(v, ...)", inst); + inst.ReplaceWith(new StLoc(v, inst.Value)); + return true; + } + return false; + } protected internal override void VisitIfInstruction(IfInstruction inst) { diff --git a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs index 5c2fc5121..86469b622 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs @@ -261,10 +261,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms // decide based on the source expression being inlined switch (inlinedExpression.OpCode) { case OpCode.DefaultValue: - return true; case OpCode.StObj: - return true; case OpCode.CompoundAssignmentInstruction: + case OpCode.Await: return true; case OpCode.LdLoc: if (v.StateMachineField == null && ((LdLoc)inlinedExpression).Variable.StateMachineField != null) { diff --git a/ICSharpCode.Decompiler/IL/Transforms/RemoveDeadVariableInit.cs b/ICSharpCode.Decompiler/IL/Transforms/RemoveDeadVariableInit.cs index b3787c008..b8dc74578 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/RemoveDeadVariableInit.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/RemoveDeadVariableInit.cs @@ -37,8 +37,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms v.HasInitialValue = false; } } - if (function.IsIterator) { - // In yield return, the C# compiler tends to store null/default(T) to variables + if (function.IsIterator || function.IsAsync) { + // In yield return + async, the C# compiler tends to store null/default(T) to variables // when the variable goes out of scope. Remove such useless stores. foreach (var v in function.Variables) { if (v.Kind == VariableKind.Local && v.StoreCount == 1 && v.LoadCount == 0 && v.AddressCount == 0) {