diff --git a/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs b/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs index 037a41355..331402d6f 100644 --- a/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs +++ b/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs @@ -416,6 +416,31 @@ namespace ICSharpCode.Decompiler.ILAst } } + // Try to use single temporary variable insted of several if possilbe (especially useful for dup) + foreach(ByteCode byteCode in body) { + if (byteCode.StoreTo != null && byteCode.StoreTo.Count > 1) { + var locVars = byteCode.StoreTo; + // For each of the variables, find the location where it is loaded - there should be preciesly one + var loadedBy = locVars.Select(argVar => body.SelectMany(bc => bc.StackBefore).Where(s => s.LoadFrom == argVar).Single()).ToList(); + // We now know that all the variables have a single load, + // Let's make sure that they have also a single store - us + if (loadedBy.All(slot => slot.PushedBy.Length == 1 && slot.PushedBy[0] == byteCode)) { + // Great - we can reduce everything into single variable + ILVariable tmpVar = new ILVariable() { Name = string.Format("expr_{0:X2}", byteCode.Offset), IsGenerated = true }; + byteCode.StoreTo = new List() { tmpVar }; + foreach(ByteCode bc in body) { + for (int i = 0; i < bc.StackBefore.Count; i++) { + // Is it one of the variable to be merged? + if (locVars.Contains(bc.StackBefore[i].LoadFrom)) { + // Replace with the new temp variable + bc.StackBefore[i] = new StackSlot(bc.StackBefore[i].PushedBy, tmpVar); + } + } + } + } + } + } + // Split and convert the normal local variables ConvertLocalVariables(body); diff --git a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs index a58f7b1aa..95efd0ef8 100644 --- a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs +++ b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs @@ -12,8 +12,9 @@ namespace ICSharpCode.Decompiler.ILAst public enum ILAstOptimizationStep { SimpleGotoAndNopRemoval, - InlineVariables, ReduceBranchInstructionSet, + InlineVariables, + CopyPropagation, YieldReturn, SplitToMovableBlocks, PeepholeOptimizations, @@ -40,14 +41,20 @@ namespace ICSharpCode.Decompiler.ILAst if (abortBeforeStep == ILAstOptimizationStep.SimpleGotoAndNopRemoval) return; SimpleGotoAndNopRemoval(method); - if (abortBeforeStep == ILAstOptimizationStep.InlineVariables) return; - // Works better after simple goto removal because of the following debug pattern: stloc X; br Next; Next:; ldloc X - ILInlining.InlineAllVariables(method); - if (abortBeforeStep == ILAstOptimizationStep.ReduceBranchInstructionSet) return; foreach(ILBlock block in method.GetSelfAndChildrenRecursive().ToList()) { ReduceBranchInstructionSet(block); } + // ReduceBranchInstructionSet runs before inlining because the non-aggressive inlining heuristic + // looks at which type of instruction consumes the inlined variable. + + if (abortBeforeStep == ILAstOptimizationStep.InlineVariables) return; + // Works better after simple goto removal because of the following debug pattern: stloc X; br Next; Next:; ldloc X + ILInlining inlining1 = new ILInlining(method); + inlining1.InlineAllVariables(); + + if (abortBeforeStep == ILAstOptimizationStep.CopyPropagation) return; + inlining1.CopyPropagation(); if (abortBeforeStep == ILAstOptimizationStep.YieldReturn) return; YieldReturnDecompiler.Run(context, method); @@ -98,7 +105,9 @@ namespace ICSharpCode.Decompiler.ILAst FlattenIfStatements(method); if (abortBeforeStep == ILAstOptimizationStep.InlineVariables2) return; - ILInlining.InlineAllVariables(method); + // The 2nd inlining pass is necessary because DuplicateReturns and the introduction of ternary operators + // open up additional inlining possibilities. + new ILInlining(method).InlineAllVariables(); if (abortBeforeStep == ILAstOptimizationStep.PeepholeTransforms) return; PeepholeTransforms.Run(context, method); diff --git a/ICSharpCode.Decompiler/ILAst/ILInlining.cs b/ICSharpCode.Decompiler/ILAst/ILInlining.cs index 073c01087..bc2430dfd 100644 --- a/ICSharpCode.Decompiler/ILAst/ILInlining.cs +++ b/ICSharpCode.Decompiler/ILAst/ILInlining.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using Mono.Cecil; namespace ICSharpCode.Decompiler.ILAst { @@ -13,19 +14,17 @@ namespace ICSharpCode.Decompiler.ILAst /// public class ILInlining { - public static void InlineAllVariables(ILBlock method) - { - ILInlining i = new ILInlining(method); - foreach (ILBlock block in method.GetSelfAndChildrenRecursive()) - i.InlineAllInBlock(block); - } - + readonly ILBlock method; Dictionary numStloc = new Dictionary(); Dictionary numLdloc = new Dictionary(); Dictionary numLdloca = new Dictionary(); + Dictionary numStarg = new Dictionary(); + Dictionary numLdarga = new Dictionary(); + public ILInlining(ILBlock method) { + this.method = method; // Analyse the whole method foreach(ILExpression expr in method.GetSelfAndChildrenRecursive()) { ILVariable locVar = expr.Operand as ILVariable; @@ -39,10 +38,25 @@ namespace ICSharpCode.Decompiler.ILAst } else { throw new NotSupportedException(expr.Code.ToString()); } + } else { + ParameterDefinition pd = expr.Operand as ParameterDefinition; + if (pd != null) { + if (expr.Code == ILCode.Starg) + numStarg[pd] = numStarg.GetOrDefault(pd) + 1; + else if (expr.Code == ILCode.Ldarga) + numLdarga[pd] = numLdarga.GetOrDefault(pd) + 1; + } } } } + public void InlineAllVariables() + { + ILInlining i = new ILInlining(method); + foreach (ILBlock block in method.GetSelfAndChildrenRecursive()) + i.InlineAllInBlock(block); + } + public void InlineAllInBlock(ILBlock block) { List body = block.Body; @@ -50,14 +64,7 @@ namespace ICSharpCode.Decompiler.ILAst ILExpression nextExpr = body[i + 1] as ILExpression; ILVariable locVar; ILExpression expr; - ILExpression ldParent; - int ldPos; - if (body[i].Match(ILCode.Stloc, out locVar, out expr) && InlineIfPossible(block, i)) { - - - // We are moving the expression evaluation past the other aguments. - // It is ok to pass ldloc because the expression can not contain stloc and thus the ldloc will still return the same value - + if (body[i].Match(ILCode.Stloc, out locVar, out expr) && InlineIfPossible(block, i, aggressive: false)) { i = Math.Max(0, i - 1); // Go back one step } else { i++; @@ -71,6 +78,8 @@ namespace ICSharpCode.Decompiler.ILAst /// The number of instructions that were inlined. public int InlineInto(ILBlock block, int pos) { + if (pos >= block.Body.Count) + return 0; int count = 0; while (--pos >= 0) { ILExpression expr = block.Body[pos] as ILExpression; @@ -87,12 +96,12 @@ namespace ICSharpCode.Decompiler.ILAst /// /// Inlines the stloc instruction at block.Body[pos] into the next instruction, if possible. /// - public bool InlineIfPossible(ILBlock block, int pos) + public bool InlineIfPossible(ILBlock block, int pos, bool aggressive = true) { ILVariable v; ILExpression inlinedExpression; if (block.Body[pos].Match(ILCode.Stloc, out v, out inlinedExpression) - && InlineIfPossible(v, inlinedExpression, block.Body.ElementAtOrDefault(pos+1))) + && InlineIfPossible(v, inlinedExpression, block.Body.ElementAtOrDefault(pos+1), aggressive)) { // Assign the ranges of the stloc instruction: inlinedExpression.ILRanges.AddRange(((ILExpression)block.Body[pos]).ILRanges); @@ -106,19 +115,17 @@ namespace ICSharpCode.Decompiler.ILAst /// /// Inlines 'expr' into 'next', if possible. /// - bool InlineIfPossible(ILVariable v, ILExpression inlinedExpression, ILNode next) + bool InlineIfPossible(ILVariable v, ILExpression inlinedExpression, ILNode next, bool aggressive) { // ensure the variable is accessed only a single time if (!(numStloc.GetOrDefault(v) == 1 && numLdloc.GetOrDefault(v) == 1 && numLdloca.GetOrDefault(v) == 0)) return false; - HashSet forbiddenVariables = new HashSet(); - foreach (ILExpression potentialStore in inlinedExpression.GetSelfAndChildrenRecursive()) { - if (potentialStore.Code == ILCode.Stloc) - forbiddenVariables.Add((ILVariable)potentialStore.Operand); - } ILExpression parent; int pos; - if (FindLoadInNext(next as ILExpression, v, forbiddenVariables, out parent, out pos) == true) { + if (FindLoadInNext(next as ILExpression, v, inlinedExpression, out parent, out pos) == true) { + if (!aggressive && !v.IsGenerated && !NonAggressiveInlineInto((ILExpression)next, parent)) + return false; + // Assign the ranges of the ldloc instruction: inlinedExpression.ILRanges.AddRange(parent.Arguments[pos].ILRanges); @@ -129,11 +136,25 @@ namespace ICSharpCode.Decompiler.ILAst return false; } + bool NonAggressiveInlineInto(ILExpression next, ILExpression parent) + { + switch (next.Code) { + case ILCode.Ret: + return parent.Code == ILCode.Ret; + case ILCode.Brtrue: + return parent.Code == ILCode.Brtrue; + case ILCode.Switch: + return parent.Code == ILCode.Switch || parent.Code == ILCode.Sub; + default: + return false; + } + } + /// /// Finds the position to inline to. /// /// true = found; false = cannot continue search; null = not found - bool? FindLoadInNext(ILExpression expr, ILVariable v, HashSet forbiddenVariables, out ILExpression parent, out int pos) + bool? FindLoadInNext(ILExpression expr, ILVariable v, ILExpression expressionBeingMoved, out ILExpression parent, out int pos) { parent = null; pos = 0; @@ -152,20 +173,92 @@ namespace ICSharpCode.Decompiler.ILAst pos = i; return true; } - bool? r = FindLoadInNext(arg, v, forbiddenVariables, out parent, out pos); + bool? r = FindLoadInNext(arg, v, expressionBeingMoved, out parent, out pos); if (r != null) return r; } - if (expr.Code == ILCode.Ldloc) { - ILVariable loadedVar = (ILVariable)expr.Operand; - if (!forbiddenVariables.Contains(loadedVar) && numLdloca.GetOrDefault(loadedVar) == 0) { + switch (expr.Code) { + case ILCode.Ldloc: + ILVariable loadedVar = (ILVariable)expr.Operand; + if (numLdloca.GetOrDefault(loadedVar) != 0) { + // abort, inlining is not possible + return false; + } + foreach (ILExpression potentialStore in expressionBeingMoved.GetSelfAndChildrenRecursive()) { + if (potentialStore.Code == ILCode.Stloc && potentialStore.Operand == loadedVar) + return false; + } // the expression is loading a non-forbidden variable: // we're allowed to continue searching return null; + case ILCode.Ldarg: + // Also try moving over ldarg instructions - this is necessary because an earlier copy propagation + // step might have introduced ldarg in place of an ldloc that would be skipped. + ParameterDefinition loadedParam = (ParameterDefinition)expr.Operand; + if (numLdarga.GetOrDefault(loadedParam) != 0) + return false; + foreach (ILExpression potentialStore in expressionBeingMoved.GetSelfAndChildrenRecursive()) { + if (potentialStore.Code == ILCode.Starg && potentialStore.Operand == loadedParam) + return false; + } + return null; + case ILCode.Ldloca: + case ILCode.Ldarga: + // Continue searching: + // It is always safe to move code past an instruction that loads a constant. + return null; + default: + // abort, inlining is not possible + return false; + } + } + + /// + /// Runs a very simple form of copy propagation. + /// Copy propagation is used in two cases: + /// 1) assignments from arguments to local variables + /// If the target variable is assigned to only once (so always is that argument) and the argument is never changed (no ldarga/starg), + /// then we can replace the variable with the argument. + /// 2) assignments of 'ldloca/ldarga' to local variables + /// + public void CopyPropagation() + { + foreach (ILBlock block in method.GetSelfAndChildrenRecursive()) { + for (int i = block.Body.Count - 1; i >= 0; i--) { + ILVariable v; + ILExpression ldArg; + if (block.Body[i].Match(ILCode.Stloc, out v, out ldArg) + && numStloc.GetOrDefault(v) == 1 && numLdloca.GetOrDefault(v) == 0 + && CanPerformCopyPropagation(ldArg)) + { + // perform copy propagation: + foreach (var expr in method.GetSelfAndChildrenRecursive()) { + if (expr.Code == ILCode.Ldloc && expr.Operand == v) { + expr.Code = ldArg.Code; + expr.Operand = ldArg.Operand; + } + } + + block.Body.RemoveAt(i); + InlineInto(block, i); // maybe inlining gets possible after the removal of block.Body[i] + } } } - // otherwise: abort, inlining is not possible - return false; + } + + bool CanPerformCopyPropagation(ILExpression ldArg) + { + switch (ldArg.Code) { + case ILCode.Ldloca: + case ILCode.Ldarga: + return true; // ldloca/ldarga always return the same value for a given operand, so they can be safely copied + case ILCode.Ldarg: + // arguments can be copied only if they aren't assigned to (directly or indirectly via ldarga) + ParameterDefinition pd = (ParameterDefinition)ldArg.Operand; + return numLdarga.GetOrDefault(pd) == 0 && numStarg.GetOrDefault(pd) == 0; + default: + return false; + } } } }