diff --git a/ICSharpCode.Decompiler/IL/Transforms/CopyPropagation.cs b/ICSharpCode.Decompiler/IL/Transforms/CopyPropagation.cs index 1ef40d116..071f59d63 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/CopyPropagation.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/CopyPropagation.cs @@ -49,7 +49,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (block.Instructions[i].MatchStLoc(out v, out copiedExpr)) { if (v.IsSingleDefinition && v.LoadCount == 0 && v.Kind == VariableKind.StackSlot) { // dead store to stack - if (copiedExpr.Flags == InstructionFlags.None) { + if (SemanticHelper.IsPure(copiedExpr.Flags)) { // no-op -> delete context.Step("remove dead store to stack: no-op -> delete", block.Instructions[i]); block.Instructions.RemoveAt(i--); diff --git a/ICSharpCode.Decompiler/IL/Transforms/RemoveDeadVariableInit.cs b/ICSharpCode.Decompiler/IL/Transforms/RemoveDeadVariableInit.cs index b8dc74578..b12f102a7 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/RemoveDeadVariableInit.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/RemoveDeadVariableInit.cs @@ -16,6 +16,8 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using System.Collections.Generic; +using System.Linq; using ICSharpCode.Decompiler.FlowAnalysis; namespace ICSharpCode.Decompiler.IL.Transforms @@ -37,13 +39,27 @@ namespace ICSharpCode.Decompiler.IL.Transforms v.HasInitialValue = false; } } - 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) { - if (v.StoreInstructions[0] is StLoc stloc && (stloc.Value.MatchLdNull() || stloc.Value is DefaultValue) && stloc.Parent is Block block) { + // Remove dead stores to variables that are never read from. + // If the stored value has some side-effect, the value is unwrapped. + // This is necessary to remove useless stores generated by some compilers, e.g., the F# compiler. + // In yield return + async, the C# compiler tends to store null/default(T) to variables + // when the variable goes out of scope. + var variableQueue = new Queue(function.Variables.Where(v => v.Kind == VariableKind.Local || v.Kind == VariableKind.StackSlot)); + while (variableQueue.Count > 0) { + var v = variableQueue.Dequeue(); + if (v.Kind != VariableKind.Local && v.Kind != VariableKind.StackSlot) + continue; + if (v.LoadCount != 0 || v.AddressCount != 0) + continue; + foreach (var stloc in v.StoreInstructions.OfType().ToArray()) { + if (stloc.Parent is Block block) { + if (SemanticHelper.IsPure(stloc.Value.Flags)) { block.Instructions.Remove(stloc); + } else { + stloc.ReplaceWith(stloc.Value); + } + if (stloc.Value is LdLoc ldloc && (ldloc.Variable.Kind == VariableKind.Local || ldloc.Variable.Kind == VariableKind.StackSlot)) { + variableQueue.Enqueue(ldloc.Variable); } } }