From 7c5ded93f0998bbd08f3cea278d626114ef82736 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 6 Oct 2017 08:25:00 +0200 Subject: [PATCH 1/7] Move F# Tests to ILPrettyTestRunner --- .../FSharpPatterns/FSharpPatternTests.cs | 30 ------ .../FSharpPatterns/TestHelpers.cs | 87 ---------------- .../FSharpPatterns/ToolLocator.cs | 99 ------------------- .../ICSharpCode.Decompiler.Tests.csproj | 5 + .../ILPretty}/FSharpUsing.fs | 0 .../ILPretty/FSharpUsing_Debug.cs} | 0 .../ILPretty/FSharpUsing_Debug.il} | 0 .../ILPretty/FSharpUsing_Release.cs} | 0 .../ILPretty/FSharpUsing_Release.il} | 0 9 files changed, 5 insertions(+), 216 deletions(-) delete mode 100644 ICSharpCode.Decompiler.Tests/FSharpPatterns/FSharpPatternTests.cs delete mode 100644 ICSharpCode.Decompiler.Tests/FSharpPatterns/TestHelpers.cs delete mode 100644 ICSharpCode.Decompiler.Tests/FSharpPatterns/ToolLocator.cs rename ICSharpCode.Decompiler.Tests/{FSharpPatterns => TestCases/ILPretty}/FSharpUsing.fs (100%) rename ICSharpCode.Decompiler.Tests/{FSharpPatterns/FSharpUsing.fs.Debug.cs => TestCases/ILPretty/FSharpUsing_Debug.cs} (100%) rename ICSharpCode.Decompiler.Tests/{FSharpPatterns/FSharpUsing.fs.Debug.il => TestCases/ILPretty/FSharpUsing_Debug.il} (100%) rename ICSharpCode.Decompiler.Tests/{FSharpPatterns/FSharpUsing.fs.Release.cs => TestCases/ILPretty/FSharpUsing_Release.cs} (100%) rename ICSharpCode.Decompiler.Tests/{FSharpPatterns/FSharpUsing.fs.Release.il => TestCases/ILPretty/FSharpUsing_Release.il} (100%) diff --git a/ICSharpCode.Decompiler.Tests/FSharpPatterns/FSharpPatternTests.cs b/ICSharpCode.Decompiler.Tests/FSharpPatterns/FSharpPatternTests.cs deleted file mode 100644 index 7980d6579..000000000 --- a/ICSharpCode.Decompiler.Tests/FSharpPatterns/FSharpPatternTests.cs +++ /dev/null @@ -1,30 +0,0 @@ -using NUnit.Framework; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; - -namespace ICSharpCode.Decompiler.Tests.FSharpPatterns -{ - [TestFixture] - public class FSharpPatternTests - { - [Test] - public void FSharpUsingDecompilesToCSharpUsing_Debug() - { - var ilCode = TestHelpers.FuzzyReadResource("FSharpUsing.fs.Debug.il"); - var csharpCode = TestHelpers.FuzzyReadResource("FSharpUsing.fs.Debug.cs"); - TestHelpers.RunIL(ilCode, csharpCode); - } - - [Test] - public void FSharpUsingDecompilesToCSharpUsing_Release() - { - var ilCode = TestHelpers.FuzzyReadResource("FSharpUsing.fs.Release.il"); - var csharpCode = TestHelpers.FuzzyReadResource("FSharpUsing.fs.Release.cs"); - TestHelpers.RunIL(ilCode, csharpCode); - } - } -} diff --git a/ICSharpCode.Decompiler.Tests/FSharpPatterns/TestHelpers.cs b/ICSharpCode.Decompiler.Tests/FSharpPatterns/TestHelpers.cs deleted file mode 100644 index dc6cd0bc4..000000000 --- a/ICSharpCode.Decompiler.Tests/FSharpPatterns/TestHelpers.cs +++ /dev/null @@ -1,87 +0,0 @@ -using ICSharpCode.Decompiler.Ast; -using ICSharpCode.Decompiler.Tests.Helpers; -using ICSharpCode.NRefactory.CSharp; -using Mono.Cecil; -using NUnit.Framework; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; - -namespace ICSharpCode.Decompiler.Tests.FSharpPatterns -{ - public class TestHelpers - { - public static string FuzzyReadResource(string resourceName) - { - var asm = Assembly.GetExecutingAssembly(); - var allResources = asm.GetManifestResourceNames(); - var fullResourceName = allResources.Single(r => r.EndsWith(resourceName, StringComparison.OrdinalIgnoreCase)); - return new StreamReader(asm.GetManifestResourceStream(fullResourceName)).ReadToEnd(); - } - - static Lazy ilasm = new Lazy(() => ToolLocator.FindTool("ilasm.exe")); - static Lazy ildasm = new Lazy(() => ToolLocator.FindTool("ildasm.exe")); - - public static string CompileIL(string source) - { - if (ilasm.Value == null) - Assert.NotNull(ilasm.Value, "Could not find ILASM.exe"); - var tmp = Path.GetTempFileName(); - File.Delete(tmp); - var sourceFile = Path.ChangeExtension(tmp, ".il"); - File.WriteAllText(sourceFile, source); - var asmFile = Path.ChangeExtension(sourceFile, ".dll"); - - var args = string.Format("{0} /dll /debug /output:{1}", sourceFile, asmFile); - using (var proc = Process.Start(new ProcessStartInfo(ilasm.Value, args) { UseShellExecute = false, })) - { - proc.WaitForExit(); - Assert.AreEqual(0, proc.ExitCode); - } - - File.Delete(sourceFile); - Assert.True(File.Exists(asmFile), "Assembly File does not exist"); - return asmFile; - } - - public static void RunIL(string ilCode, string expectedCSharpCode) - { - var asmFilePath = CompileIL(ilCode); - CompareAssemblyAgainstCSharp(expectedCSharpCode, asmFilePath); - } - - private static void CompareAssemblyAgainstCSharp(string expectedCSharpCode, string asmFilePath) - { - var module = ModuleDefinition.ReadModule(asmFilePath); - try - { - try { module.ReadSymbols(); } catch { } - AstBuilder decompiler = new AstBuilder(new DecompilerContext(module)); - decompiler.AddAssembly(module); - new Helpers.RemoveCompilerAttribute().Run(decompiler.SyntaxTree); - StringWriter output = new StringWriter(); - - // the F# assembly contains a namespace `` where the part after tmp is randomly generated. - // remove this from the ast to simplify the diff - var startupCodeNode = decompiler.SyntaxTree.Children.OfType().SingleOrDefault(d => d.Name.StartsWith(" Path.Combine(dir, fileName)).FirstOrDefault(File.Exists); - } - - private static IEnumerable FindPathForWindowsSdk() - { - string[] windowsSdkPaths = new[] - { - @"Microsoft SDKs\Windows\v8.0A\bin\NETFX 4.0 Tools\", - @"Microsoft SDKs\Windows\v8.0A\bin\", - @"Microsoft SDKs\Windows\v8.0\bin\NETFX 4.0 Tools\", - @"Microsoft SDKs\Windows\v8.0\bin\", - @"Microsoft SDKs\Windows\v7.1A\bin\NETFX 4.0 Tools\", - @"Microsoft SDKs\Windows\v7.1A\bin\", - @"Microsoft SDKs\Windows\v7.0A\bin\NETFX 4.0 Tools\", - @"Microsoft SDKs\Windows\v7.0A\bin\", - @"Microsoft SDKs\Windows\v6.1A\bin\", - @"Microsoft SDKs\Windows\v6.0A\bin\", - @"Microsoft SDKs\Windows\v6.0\bin\", - @"Microsoft.NET\FrameworkSDK\bin" - }; - - foreach (var possiblePath in windowsSdkPaths) - { - string fullPath = string.Empty; - - // Check alternate program file paths as well as 64-bit versions. - if (Environment.Is64BitProcess) - { - fullPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), possiblePath, "x64"); - if (Directory.Exists(fullPath)) - { - yield return fullPath; - } - - fullPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), possiblePath, "x64"); - if (Directory.Exists(fullPath)) - { - yield return fullPath; - } - } - - fullPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), possiblePath); - if (Directory.Exists(fullPath)) - { - yield return fullPath; - } - - fullPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), possiblePath); - if (Directory.Exists(fullPath)) - { - yield return fullPath; - } - } - } - - private static IEnumerable FindPathForDotNetFramework() - { - string[] frameworkPaths = new[] - { - @"Microsoft.NET\Framework\v4.0.30319", - @"Microsoft.NET\Framework\v2.0.50727" - }; - - foreach (var possiblePath in frameworkPaths) - { - string fullPath = string.Empty; - - if (Environment.Is64BitProcess) - { - fullPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), possiblePath.Replace(@"\Framework\", @"\Framework64\")); - if (Directory.Exists(fullPath)) - { - yield return fullPath; - } - } - - fullPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), possiblePath); - if (Directory.Exists(fullPath)) - { - yield return fullPath; - } - } - } - } -} diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index a46ed0bcf..eec8a43b6 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -44,12 +44,17 @@ + + + + + diff --git a/ICSharpCode.Decompiler.Tests/FSharpPatterns/FSharpUsing.fs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing.fs similarity index 100% rename from ICSharpCode.Decompiler.Tests/FSharpPatterns/FSharpUsing.fs rename to ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing.fs diff --git a/ICSharpCode.Decompiler.Tests/FSharpPatterns/FSharpUsing.fs.Debug.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Debug.cs similarity index 100% rename from ICSharpCode.Decompiler.Tests/FSharpPatterns/FSharpUsing.fs.Debug.cs rename to ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Debug.cs diff --git a/ICSharpCode.Decompiler.Tests/FSharpPatterns/FSharpUsing.fs.Debug.il b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Debug.il similarity index 100% rename from ICSharpCode.Decompiler.Tests/FSharpPatterns/FSharpUsing.fs.Debug.il rename to ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Debug.il diff --git a/ICSharpCode.Decompiler.Tests/FSharpPatterns/FSharpUsing.fs.Release.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Release.cs similarity index 100% rename from ICSharpCode.Decompiler.Tests/FSharpPatterns/FSharpUsing.fs.Release.cs rename to ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Release.cs diff --git a/ICSharpCode.Decompiler.Tests/FSharpPatterns/FSharpUsing.fs.Release.il b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Release.il similarity index 100% rename from ICSharpCode.Decompiler.Tests/FSharpPatterns/FSharpUsing.fs.Release.il rename to ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Release.il From 2d2ca893e9d89ce016f63e8bba6ba9cb5958d6da Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 6 Oct 2017 09:13:52 +0200 Subject: [PATCH 2/7] ConditionDetection: Remove empty else-branches. --- .../ICSharpCode.Decompiler.csproj | 1 + .../IL/ControlFlow/ConditionDetection.cs | 17 ++++++++++++---- .../Instructions/ILInstructionExtensions.cs | 20 +++++++++++++++++++ 3 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 ICSharpCode.Decompiler/IL/Instructions/ILInstructionExtensions.cs diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 581380e6d..b9320c5b4 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -271,6 +271,7 @@ + diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs b/ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs index 268b9a429..d93a5b58a 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs @@ -176,10 +176,19 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow if (DetectExitPoints.CompatibleExitInstruction(trueExitInst, falseExitInst)) { // if (...) { ...; goto exitPoint; } goto nextBlock; nextBlock: ...; goto exitPoint; // -> if (...) { ... } else { ... } goto exitPoint; - context.Step("Inline block as else-branch", ifInst); - targetBlock.Instructions.RemoveAt(targetBlock.Instructions.Count - 1); - targetBlock.Remove(); - ifInst.FalseInst = targetBlock; + + // the else block is not empty or nop-only: + if (!targetBlock.IsNopBlock(ignoreExitPoint: falseExitInst)) { + context.Step("Inline block as else-branch", ifInst); + targetBlock.Instructions.RemoveAt(targetBlock.Instructions.Count - 1); + targetBlock.Remove(); + ifInst.FalseInst = targetBlock; + } else { + // the else block is empty or nop-only and can be safely removed: + context.Step("Remove empty else-branch", ifInst); + targetBlock.Instructions.RemoveAt(targetBlock.Instructions.Count - 1); + targetBlock.Remove(); + } exitInst = block.Instructions[block.Instructions.Count - 1] = falseExitInst; Block trueBlock = ifInst.TrueInst as Block; if (trueBlock != null) { diff --git a/ICSharpCode.Decompiler/IL/Instructions/ILInstructionExtensions.cs b/ICSharpCode.Decompiler/IL/Instructions/ILInstructionExtensions.cs new file mode 100644 index 000000000..c363dd1de --- /dev/null +++ b/ICSharpCode.Decompiler/IL/Instructions/ILInstructionExtensions.cs @@ -0,0 +1,20 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Text; + +namespace ICSharpCode.Decompiler.IL +{ + public static class ILInstructionExtensions + { + /// + /// Determines whether a block only consists of nop instructions or is empty. + /// + public static bool IsNopBlock(this Block block, ILInstruction ignoreExitPoint = null) + { + if (block == null) + throw new ArgumentNullException(nameof(block)); + return block.Children.Count(i => !(i is Nop) && i != ignoreExitPoint) == 0; + } + } +} From aa0e37923657456e83474215b00653195f744c80 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 6 Oct 2017 13:33:46 +0200 Subject: [PATCH 3/7] Add tests to ILPrettyTestRunner --- .../ILPrettyTestRunner.cs | 12 +++++++ .../TestCases/ILPretty/FSharpUsing_Debug.cs | 32 ++++++++----------- .../TestCases/ILPretty/FSharpUsing_Release.cs | 32 ++++++++----------- 3 files changed, 38 insertions(+), 38 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs index 7d2f9ed47..97f7fa0e4 100644 --- a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs @@ -42,6 +42,18 @@ namespace ICSharpCode.Decompiler.Tests Run(); } + [Test] + public void FSharpUsing_Debug() + { + Run(); + } + + [Test] + public void FSharpUsing_Release() + { + Run(); + } + void Run([CallerMemberName] string testName = null) { var ilFile = Path.Combine(TestCasePath, testName + ".il"); diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Debug.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Debug.cs index 3c62c0cac..d9ca8bff4 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Debug.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Debug.cs @@ -5,18 +5,16 @@ public static class FSharpUsingPatterns { public static void sample1() { - using (FileStream fs = File.Create("x.txt")) - { - fs.WriteByte((byte)1); + using (FileStream fileStream = File.Create("x.txt")) { + fileStream.WriteByte((byte)1); } } public static void sample2() { Console.WriteLine("some text"); - using (FileStream fs = File.Create("x.txt")) - { - fs.WriteByte((byte)2); + using (FileStream fileStream = File.Create("x.txt")) { + fileStream.WriteByte((byte)2); Console.WriteLine("some text"); } } @@ -24,9 +22,8 @@ public static class FSharpUsingPatterns public static void sample3() { Console.WriteLine("some text"); - using (FileStream fs = File.Create("x.txt")) - { - fs.WriteByte((byte)3); + using (FileStream fileStream = File.Create("x.txt")) { + fileStream.WriteByte((byte)3); } Console.WriteLine("some text"); } @@ -35,9 +32,8 @@ public static class FSharpUsingPatterns { Console.WriteLine("some text"); int num; - using (FileStream fs = File.OpenRead("x.txt")) - { - num = fs.ReadByte(); + using (FileStream fileStream = File.OpenRead("x.txt")) { + num = fileStream.ReadByte(); } int firstByte = num; Console.WriteLine("read:" + firstByte.ToString()); @@ -47,16 +43,14 @@ public static class FSharpUsingPatterns { Console.WriteLine("some text"); int num; - using (FileStream fs = File.OpenRead("x.txt")) - { - num = fs.ReadByte(); + using (FileStream fileStream = File.OpenRead("x.txt")) { + num = fileStream.ReadByte(); } int firstByte = num; int num3; - using (FileStream fs = File.OpenRead("x.txt")) - { - int num2 = fs.ReadByte(); - num3 = fs.ReadByte(); + using (FileStream fileStream = File.OpenRead("x.txt")) { + int num2 = fileStream.ReadByte(); + num3 = fileStream.ReadByte(); } int secondByte = num3; Console.WriteLine("read: {0}, {1}", firstByte, secondByte); diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Release.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Release.cs index c9d091847..6396bc6d5 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Release.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Release.cs @@ -5,18 +5,16 @@ public static class FSharpUsingPatterns { public static void sample1() { - using (FileStream fs = File.Create("x.txt")) - { - fs.WriteByte(1); + using (FileStream fileStream = File.Create("x.txt")) { + fileStream.WriteByte(1); } } public static void sample2() { Console.WriteLine("some text"); - using (FileStream fs = File.Create("x.txt")) - { - fs.WriteByte(2); + using (FileStream fileStream = File.Create("x.txt")) { + fileStream.WriteByte(2); Console.WriteLine("some text"); } } @@ -24,9 +22,8 @@ public static class FSharpUsingPatterns public static void sample3() { Console.WriteLine("some text"); - using (FileStream fs = File.Create("x.txt")) - { - fs.WriteByte(3); + using (FileStream fileStream = File.Create("x.txt")) { + fileStream.WriteByte(3); } Console.WriteLine("some text"); } @@ -35,9 +32,8 @@ public static class FSharpUsingPatterns { Console.WriteLine("some text"); int num; - using (FileStream fs = File.OpenRead("x.txt")) - { - num = fs.ReadByte(); + using (FileStream fileStream = File.OpenRead("x.txt")) { + num = fileStream.ReadByte(); } int firstByte = num; Console.WriteLine("read:" + firstByte.ToString()); @@ -47,16 +43,14 @@ public static class FSharpUsingPatterns { Console.WriteLine("some text"); int secondByte; - using (FileStream fs = File.OpenRead("x.txt")) - { - secondByte = fs.ReadByte(); + using (FileStream fileStream = File.OpenRead("x.txt")) { + secondByte = fileStream.ReadByte(); } int firstByte = secondByte; int num2; - using (FileStream fs = File.OpenRead("x.txt")) - { - int num = fs.ReadByte(); - num2 = fs.ReadByte(); + using (FileStream fileStream = File.OpenRead("x.txt")) { + int num = fileStream.ReadByte(); + num2 = fileStream.ReadByte(); } secondByte = num2; Console.WriteLine("read: {0}, {1}", firstByte, secondByte); From 9e2f98cb8d75253fe9dd480fa88359618a89b2fc Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 28 Oct 2017 18:28:03 +0200 Subject: [PATCH 4/7] Improve RemoveDeadVariableInit and CopyPropagation --- .../IL/Transforms/CopyPropagation.cs | 2 +- .../IL/Transforms/RemoveDeadVariableInit.cs | 28 +++++++++++++++---- 2 files changed, 23 insertions(+), 7 deletions(-) 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); } } } From 97eac21021cab2923e1d6abe2f7a753acee241b2 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 28 Oct 2017 18:28:30 +0200 Subject: [PATCH 5/7] Update F# using pretty tests --- .../TestCases/ILPretty/FSharpUsing_Debug.cs | 18 +++++++-------- .../TestCases/ILPretty/FSharpUsing_Release.cs | 22 +++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Debug.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Debug.cs index d9ca8bff4..972149116 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Debug.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Debug.cs @@ -31,28 +31,28 @@ public static class FSharpUsingPatterns public static void sample4() { Console.WriteLine("some text"); - int num; + int num = default(int); using (FileStream fileStream = File.OpenRead("x.txt")) { num = fileStream.ReadByte(); } - int firstByte = num; - Console.WriteLine("read:" + firstByte.ToString()); + int num2 = num; + Console.WriteLine("read:" + num2.ToString()); } public static void sample5() { Console.WriteLine("some text"); - int num; + int num = default(int); using (FileStream fileStream = File.OpenRead("x.txt")) { num = fileStream.ReadByte(); } - int firstByte = num; - int num3; + int num2 = num; + int num3 = default(int); using (FileStream fileStream = File.OpenRead("x.txt")) { - int num2 = fileStream.ReadByte(); + fileStream.ReadByte(); num3 = fileStream.ReadByte(); } - int secondByte = num3; - Console.WriteLine("read: {0}, {1}", firstByte, secondByte); + int num4 = num3; + Console.WriteLine("read: {0}, {1}", num2, num4); } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Release.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Release.cs index 6396bc6d5..9585b51a1 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Release.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Release.cs @@ -31,28 +31,28 @@ public static class FSharpUsingPatterns public static void sample4() { Console.WriteLine("some text"); - int num; + int num = default(int); using (FileStream fileStream = File.OpenRead("x.txt")) { num = fileStream.ReadByte(); } - int firstByte = num; - Console.WriteLine("read:" + firstByte.ToString()); + int num2 = num; + Console.WriteLine("read:" + num2.ToString()); } public static void sample5() { Console.WriteLine("some text"); - int secondByte; + int num = default(int); using (FileStream fileStream = File.OpenRead("x.txt")) { - secondByte = fileStream.ReadByte(); + num = fileStream.ReadByte(); } - int firstByte = secondByte; - int num2; + int num2 = num; + int num3 = default(int); using (FileStream fileStream = File.OpenRead("x.txt")) { - int num = fileStream.ReadByte(); - num2 = fileStream.ReadByte(); + fileStream.ReadByte(); + num3 = fileStream.ReadByte(); } - secondByte = num2; - Console.WriteLine("read: {0}, {1}", firstByte, secondByte); + num = num3; + Console.WriteLine("read: {0}, {1}", num2, num); } } From aa87169331ce21892f3a33c9c23e44a4ab655583 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 28 Oct 2017 18:50:18 +0200 Subject: [PATCH 6/7] Clean up RemoveDeadVariableInit --- .../IL/Transforms/RemoveDeadVariableInit.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ICSharpCode.Decompiler/IL/Transforms/RemoveDeadVariableInit.cs b/ICSharpCode.Decompiler/IL/Transforms/RemoveDeadVariableInit.cs index b12f102a7..e5d1831bf 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/RemoveDeadVariableInit.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/RemoveDeadVariableInit.cs @@ -44,7 +44,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms // 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)); + var variableQueue = new Queue(function.Variables); while (variableQueue.Count > 0) { var v = variableQueue.Dequeue(); if (v.Kind != VariableKind.Local && v.Kind != VariableKind.StackSlot) @@ -58,7 +58,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms } else { stloc.ReplaceWith(stloc.Value); } - if (stloc.Value is LdLoc ldloc && (ldloc.Variable.Kind == VariableKind.Local || ldloc.Variable.Kind == VariableKind.StackSlot)) { + if (stloc.Value is LdLoc ldloc) { variableQueue.Enqueue(ldloc.Variable); } } From f8b27066a182837a1bdc17298d85a61c48e4a9bb Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 29 Oct 2017 02:32:58 +0200 Subject: [PATCH 7/7] Remove ILInstructionExtensions.cs --- .../ICSharpCode.Decompiler.csproj | 1 - .../IL/ControlFlow/ConditionDetection.cs | 2 +- .../Instructions/ILInstructionExtensions.cs | 20 ------------------- 3 files changed, 1 insertion(+), 22 deletions(-) delete mode 100644 ICSharpCode.Decompiler/IL/Instructions/ILInstructionExtensions.cs diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 1e02da7aa..d94c974fc 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -275,7 +275,6 @@ - diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs b/ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs index 97f763426..657a5f7a0 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs @@ -174,7 +174,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow // -> if (...) { ... } else { ... } goto exitPoint; // the else block is not empty or nop-only: - if (!targetBlock.IsNopBlock(ignoreExitPoint: falseExitInst)) { + if (targetBlock.Children.Any(inst => !(inst is Nop) && inst != falseExitInst)) { context.Step("Inline block as else-branch", ifInst); targetBlock.Instructions.RemoveAt(targetBlock.Instructions.Count - 1); targetBlock.Remove(); diff --git a/ICSharpCode.Decompiler/IL/Instructions/ILInstructionExtensions.cs b/ICSharpCode.Decompiler/IL/Instructions/ILInstructionExtensions.cs deleted file mode 100644 index c363dd1de..000000000 --- a/ICSharpCode.Decompiler/IL/Instructions/ILInstructionExtensions.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Linq; -using System.Collections.Generic; -using System.Text; - -namespace ICSharpCode.Decompiler.IL -{ - public static class ILInstructionExtensions - { - /// - /// Determines whether a block only consists of nop instructions or is empty. - /// - public static bool IsNopBlock(this Block block, ILInstruction ignoreExitPoint = null) - { - if (block == null) - throw new ArgumentNullException(nameof(block)); - return block.Children.Count(i => !(i is Nop) && i != ignoreExitPoint) == 0; - } - } -}