using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using ICSharpCode.Decompiler.FlowAnalysis; using Mono.Cecil; using Mono.Cecil.Cil; using Mono.CSharp; namespace ICSharpCode.Decompiler.ILAst { public enum ILAstOptimizationStep { RemoveRedundantCode, ReduceBranchInstructionSet, InlineVariables, CopyPropagation, YieldReturn, SplitToMovableBlocks, TypeInference, SimplifyShortCircuit, SimplifyTernaryOperator, SimplifyNullCoalescing, JoinBasicBlocks, TransformDecimalCtorToConstant, SimplifyLdObjAndStObj, TransformArrayInitializers, TransformCollectionInitializers, MakeAssignmentExpression, InlineVariables2, FindLoops, FindConditions, FlattenNestedMovableBlocks, RemoveRedundantCode2, GotoRemoval, DuplicateReturns, ReduceIfNesting, InlineVariables3, CachedDelegateInitialization, IntroduceFixedStatements, RecombineVariables, TypeInference2, RemoveRedundantCode3, None } public partial class ILAstOptimizer { int nextLabelIndex = 0; DecompilerContext context; TypeSystem typeSystem; ILBlock method; public void Optimize(DecompilerContext context, ILBlock method, ILAstOptimizationStep abortBeforeStep = ILAstOptimizationStep.None) { this.context = context; this.typeSystem = context.CurrentMethod.Module.TypeSystem; this.method = method; if (abortBeforeStep == ILAstOptimizationStep.RemoveRedundantCode) return; RemoveRedundantCode(method); if (abortBeforeStep == ILAstOptimizationStep.ReduceBranchInstructionSet) return; foreach(ILBlock block in method.GetSelfAndChildrenRecursive()) { 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); if (abortBeforeStep == ILAstOptimizationStep.SplitToMovableBlocks) return; foreach(ILBlock block in method.GetSelfAndChildrenRecursive()) { SplitToBasicBlocks(block); } if (abortBeforeStep == ILAstOptimizationStep.TypeInference) return; // Types are needed for the ternary operator optimization TypeAnalysis.Run(context, method); foreach(ILBlock block in method.GetSelfAndChildrenRecursive()) { bool modified; do { modified = false; if (abortBeforeStep == ILAstOptimizationStep.SimplifyShortCircuit) return; modified |= block.RunOptimization(new SimpleControlFlow(context, method).SimplifyShortCircuit); if (abortBeforeStep == ILAstOptimizationStep.SimplifyTernaryOperator) return; modified |= block.RunOptimization(new SimpleControlFlow(context, method).SimplifyTernaryOperator); if (abortBeforeStep == ILAstOptimizationStep.SimplifyNullCoalescing) return; modified |= block.RunOptimization(new SimpleControlFlow(context, method).SimplifyNullCoalescing); if (abortBeforeStep == ILAstOptimizationStep.JoinBasicBlocks) return; modified |= block.RunOptimization(new SimpleControlFlow(context, method).JoinBasicBlocks); if (abortBeforeStep == ILAstOptimizationStep.TransformDecimalCtorToConstant) return; modified |= block.RunOptimization(TransformDecimalCtorToConstant); modified |= block.RunOptimization(SimplifyLdcI4ConvI8); if (abortBeforeStep == ILAstOptimizationStep.SimplifyLdObjAndStObj) return; modified |= block.RunOptimization(SimplifyLdObjAndStObj); if (abortBeforeStep == ILAstOptimizationStep.TransformArrayInitializers) return; modified |= block.RunOptimization(Initializers.TransformArrayInitializers); if (abortBeforeStep == ILAstOptimizationStep.TransformCollectionInitializers) return; modified |= block.RunOptimization(Initializers.TransformCollectionInitializers); if (abortBeforeStep == ILAstOptimizationStep.MakeAssignmentExpression) return; modified |= block.RunOptimization(MakeAssignmentExpression); modified |= block.RunOptimization(MakeCompoundAssignments); if (abortBeforeStep == ILAstOptimizationStep.InlineVariables2) return; modified |= new ILInlining(method).InlineAllInBlock(block); new ILInlining(method).CopyPropagation(); } while(modified); } if (abortBeforeStep == ILAstOptimizationStep.FindLoops) return; foreach(ILBlock block in method.GetSelfAndChildrenRecursive()) { new LoopsAndConditions(context).FindLoops(block); } if (abortBeforeStep == ILAstOptimizationStep.FindConditions) return; foreach(ILBlock block in method.GetSelfAndChildrenRecursive()) { new LoopsAndConditions(context).FindConditions(block); } if (abortBeforeStep == ILAstOptimizationStep.FlattenNestedMovableBlocks) return; FlattenBasicBlocks(method); if (abortBeforeStep == ILAstOptimizationStep.RemoveRedundantCode2) return; RemoveRedundantCode(method); if (abortBeforeStep == ILAstOptimizationStep.GotoRemoval) return; new GotoRemoval().RemoveGotos(method); if (abortBeforeStep == ILAstOptimizationStep.DuplicateReturns) return; DuplicateReturnStatements(method); if (abortBeforeStep == ILAstOptimizationStep.ReduceIfNesting) return; ReduceIfNesting(method); if (abortBeforeStep == ILAstOptimizationStep.InlineVariables3) return; // 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.CachedDelegateInitialization) return; foreach(ILBlock block in method.GetSelfAndChildrenRecursive()) { for (int i = 0; i < block.Body.Count; i++) { // TODO: Move before loops CachedDelegateInitialization(block, ref i); } } if (abortBeforeStep == ILAstOptimizationStep.IntroduceFixedStatements) return; foreach(ILBlock block in method.GetSelfAndChildrenRecursive()) { for (int i = block.Body.Count - 1; i >= 0; i--) { // TODO: Move before loops if (i < block.Body.Count) IntroduceFixedStatements(block.Body, i); } } foreach(ILBlock block in method.GetSelfAndChildrenRecursive()) { for (int i = block.Body.Count - 1; i >= 0; i--) { // TODO: Move before loops if (i < block.Body.Count) IntroduceFixedStatements(block.Body, i); } } if (abortBeforeStep == ILAstOptimizationStep.RecombineVariables) return; RecombineVariables(method); if (abortBeforeStep == ILAstOptimizationStep.TypeInference2) return; TypeAnalysis.Reset(method); TypeAnalysis.Run(context, method); if (abortBeforeStep == ILAstOptimizationStep.RemoveRedundantCode3) return; GotoRemoval.RemoveRedundantCode(method); // ReportUnassignedILRanges(method); } /// /// Removes redundatant Br, Nop, Dup, Pop /// /// void RemoveRedundantCode(ILBlock method) { Dictionary labelRefCount = new Dictionary(); foreach (ILLabel target in method.GetSelfAndChildrenRecursive(e => e.IsBranch()).SelectMany(e => e.GetBranchTargets())) { labelRefCount[target] = labelRefCount.GetOrDefault(target) + 1; } foreach(ILBlock block in method.GetSelfAndChildrenRecursive()) { List body = block.Body; List newBody = new List(body.Count); for (int i = 0; i < body.Count; i++) { ILLabel target; ILExpression popExpr; if (body[i].Match(ILCode.Br, out target) && i+1 < body.Count && body[i+1] == target) { // Ignore the branch if (labelRefCount[target] == 1) i++; // Ignore the label as well } else if (body[i].Match(ILCode.Nop)){ // Ignore nop } else if (body[i].Match(ILCode.Pop, out popExpr)) { ILVariable v; if (!popExpr.Match(ILCode.Ldloc, out v)) throw new Exception("Pop should have just ldloc at this stage"); // Best effort to move the ILRange to previous statement ILVariable prevVar; ILExpression prevExpr; if (i - 1 >= 0 && body[i - 1].Match(ILCode.Stloc, out prevVar, out prevExpr) && prevVar == v) prevExpr.ILRanges.AddRange(((ILExpression)body[i]).ILRanges); // Ignore pop } else { newBody.Add(body[i]); } } block.Body = newBody; } // 'dup' removal foreach (ILExpression expr in method.GetSelfAndChildrenRecursive()) { for (int i = 0; i < expr.Arguments.Count; i++) { ILExpression child; if (expr.Arguments[i].Match(ILCode.Dup, out child)) { child.ILRanges.AddRange(expr.Arguments[i].ILRanges); expr.Arguments[i] = child; } } } } /// /// Reduces the branch codes to just br and brtrue. /// Moves ILRanges to the branch argument /// void ReduceBranchInstructionSet(ILBlock block) { for (int i = 0; i < block.Body.Count; i++) { ILExpression expr = block.Body[i] as ILExpression; if (expr != null && expr.Prefixes == null) { switch(expr.Code) { case ILCode.Switch: case ILCode.Brtrue: expr.Arguments.Single().ILRanges.AddRange(expr.ILRanges); expr.ILRanges.Clear(); continue; case ILCode.__Brfalse: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, expr.Arguments.Single())); break; case ILCode.__Beq: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Ceq, null, expr.Arguments)); break; case ILCode.__Bne_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Ceq, null, expr.Arguments))); break; case ILCode.__Bgt: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Cgt, null, expr.Arguments)); break; case ILCode.__Bgt_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Cgt_Un, null, expr.Arguments)); break; case ILCode.__Ble: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Cgt, null, expr.Arguments))); break; case ILCode.__Ble_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Cgt_Un, null, expr.Arguments))); break; case ILCode.__Blt: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Clt, null, expr.Arguments)); break; case ILCode.__Blt_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Clt_Un, null, expr.Arguments)); break; case ILCode.__Bge: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Clt, null, expr.Arguments))); break; case ILCode.__Bge_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Clt_Un, null, expr.Arguments))); break; default: continue; } ((ILExpression)block.Body[i]).Arguments.Single().ILRanges.AddRange(expr.ILRanges); } } } /// /// Group input into a set of blocks that can be later arbitraliby schufled. /// The method adds necessary branches to make control flow between blocks /// explicit and thus order independent. /// void SplitToBasicBlocks(ILBlock block) { List basicBlocks = new List(); ILLabel entryLabel = block.Body.FirstOrDefault() as ILLabel ?? new ILLabel() { Name = "Block_" + (nextLabelIndex++) }; ILBasicBlock basicBlock = new ILBasicBlock(); basicBlocks.Add(basicBlock); basicBlock.Body.Add(entryLabel); block.EntryGoto = new ILExpression(ILCode.Br, entryLabel); if (block.Body.Count > 0) { if (block.Body[0] != entryLabel) basicBlock.Body.Add(block.Body[0]); for (int i = 1; i < block.Body.Count; i++) { ILNode lastNode = block.Body[i - 1]; ILNode currNode = block.Body[i]; // Start a new basic block if necessary if (currNode is ILLabel || currNode is ILTryCatchBlock || // Counts as label lastNode.IsConditionalControlFlow() || lastNode.IsUnconditionalControlFlow()) { // Try to reuse the label ILLabel label = currNode is ILLabel ? ((ILLabel)currNode) : new ILLabel() { Name = "Block_" + (nextLabelIndex++) }; // Terminate the last block if (!lastNode.IsUnconditionalControlFlow()) { // Explicit branch from one block to other basicBlock.Body.Add(new ILExpression(ILCode.Br, label)); } // Start the new block basicBlock = new ILBasicBlock(); basicBlocks.Add(basicBlock); basicBlock.Body.Add(label); // Add the node to the basic block if (currNode != label) basicBlock.Body.Add(currNode); } else { basicBlock.Body.Add(currNode); } } } block.Body = basicBlocks; return; } void DuplicateReturnStatements(ILBlock method) { Dictionary nextSibling = new Dictionary(); // Build navigation data foreach(ILBlock block in method.GetSelfAndChildrenRecursive()) { for (int i = 0; i < block.Body.Count - 1; i++) { ILLabel curr = block.Body[i] as ILLabel; if (curr != null) { nextSibling[curr] = block.Body[i + 1]; } } } // Duplicate returns foreach(ILBlock block in method.GetSelfAndChildrenRecursive()) { for (int i = 0; i < block.Body.Count; i++) { ILLabel targetLabel; if (block.Body[i].Match(ILCode.Br, out targetLabel) || block.Body[i].Match(ILCode.Leave, out targetLabel)) { // Skip extra labels while(nextSibling.ContainsKey(targetLabel) && nextSibling[targetLabel] is ILLabel) { targetLabel = (ILLabel)nextSibling[targetLabel]; } // Inline return statement ILNode target; List retArgs; if (nextSibling.TryGetValue(targetLabel, out target)) { if (target.Match(ILCode.Ret, out retArgs)) { ILVariable locVar; object constValue; if (retArgs.Count == 0) { block.Body[i] = new ILExpression(ILCode.Ret, null); } else if (retArgs.Single().Match(ILCode.Ldloc, out locVar)) { block.Body[i] = new ILExpression(ILCode.Ret, null, new ILExpression(ILCode.Ldloc, locVar)); } else if (retArgs.Single().Match(ILCode.Ldc_I4, out constValue)) { block.Body[i] = new ILExpression(ILCode.Ret, null, new ILExpression(ILCode.Ldc_I4, constValue)); } } } else { if (method.Body.Count > 0 && method.Body.Last() == targetLabel) { // It exits the main method - so it is same as return; block.Body[i] = new ILExpression(ILCode.Ret, null); } } } } } } /// /// Flattens all nested basic blocks, except the the top level 'node' argument /// void FlattenBasicBlocks(ILNode node) { ILBlock block = node as ILBlock; if (block != null) { List flatBody = new List(); foreach (ILNode child in block.GetChildren()) { FlattenBasicBlocks(child); ILBasicBlock childAsBB = child as ILBasicBlock; if (childAsBB != null) { if (!(childAsBB.Body.FirstOrDefault() is ILLabel)) throw new Exception("Basic block has to start with a label. \n" + childAsBB.ToString()); if (childAsBB.Body.LastOrDefault() is ILExpression && !childAsBB.Body.LastOrDefault().IsUnconditionalControlFlow()) throw new Exception("Basci block has to end with unconditional control flow. \n" + childAsBB.ToString()); flatBody.AddRange(childAsBB.GetChildren()); } else { flatBody.Add(child); } } block.EntryGoto = null; block.Body = flatBody; } else if (node is ILExpression) { // Optimization - no need to check expressions } else if (node != null) { // Recursively find all ILBlocks foreach(ILNode child in node.GetChildren()) { FlattenBasicBlocks(child); } } } /// /// Reduce the nesting of conditions. /// It should be done on flat data that already had most gotos removed /// void ReduceIfNesting(ILNode node) { ILBlock block = node as ILBlock; if (block != null) { for (int i = 0; i < block.Body.Count; i++) { ILCondition cond = block.Body[i] as ILCondition; if (cond != null) { bool trueExits = cond.TrueBlock.Body.LastOrDefault().IsUnconditionalControlFlow(); bool falseExits = cond.FalseBlock.Body.LastOrDefault().IsUnconditionalControlFlow(); if (trueExits) { // Move the false block after the condition block.Body.InsertRange(i + 1, cond.FalseBlock.GetChildren()); cond.FalseBlock = new ILBlock(); } else if (falseExits) { // Move the true block after the condition block.Body.InsertRange(i + 1, cond.TrueBlock.GetChildren()); cond.TrueBlock = new ILBlock(); } // Eliminate empty true block if (!cond.TrueBlock.GetChildren().Any() && cond.FalseBlock.GetChildren().Any()) { // Swap bodies ILBlock tmp = cond.TrueBlock; cond.TrueBlock = cond.FalseBlock; cond.FalseBlock = tmp; cond.Condition = new ILExpression(ILCode.LogicNot, null, cond.Condition); } } } } // We are changing the number of blocks so we use plain old recursion to get all blocks foreach(ILNode child in node.GetChildren()) { if (child != null && !(child is ILExpression)) ReduceIfNesting(child); } } void RecombineVariables(ILBlock method) { // Recombine variables that were split when the ILAst was created // This ensures that a single IL variable is a single C# variable (gets assigned only one name) // The DeclareVariables transformation might then split up the C# variable again if it is used indendently in two separate scopes. Dictionary dict = new Dictionary(); foreach (ILExpression expr in method.GetSelfAndChildrenRecursive()) { ILVariable v = expr.Operand as ILVariable; if (v != null && v.OriginalVariable != null) { ILVariable combinedVariable; if (!dict.TryGetValue(v.OriginalVariable, out combinedVariable)) { dict.Add(v.OriginalVariable, v); combinedVariable = v; } expr.Operand = combinedVariable; } } } void ReportUnassignedILRanges(ILBlock method) { var unassigned = ILRange.Invert(method.GetSelfAndChildrenRecursive().SelectMany(e => e.ILRanges), context.CurrentMethod.Body.CodeSize).ToList(); if (unassigned.Count > 0) Debug.WriteLine(string.Format("Unassigned ILRanges for {0}.{1}: {2}", this.context.CurrentMethod.DeclaringType.Name, this.context.CurrentMethod.Name, string.Join(", ", unassigned.Select(r => r.ToString())))); } } public static class ILAstOptimizerExtensionMethods { /// /// Perform one pass of a given optimization on this block. /// This block must consist of only basicblocks. /// public static bool RunOptimization(this ILBlock block, Func, ILBasicBlock, int, bool> optimization) { bool modified = false; List body = block.Body; for (int i = body.Count - 1; i >= 0; i--) { if (i < body.Count && optimization(body, (ILBasicBlock)body[i], i)) { modified = true; } } return modified; } public static bool RunOptimization(this ILBlock block, Func, ILExpression, int, bool> optimization) { bool modified = false; foreach (ILBasicBlock bb in block.Body) { for (int i = bb.Body.Count - 1; i >= 0; i--) { ILExpression expr = bb.Body.ElementAtOrDefault(i) as ILExpression; if (expr != null && optimization(bb.Body, expr, i)) { modified = true; } } } return modified; } public static bool IsConditionalControlFlow(this ILNode node) { ILExpression expr = node as ILExpression; return expr != null && expr.Code.IsConditionalControlFlow(); } public static bool IsUnconditionalControlFlow(this ILNode node) { ILExpression expr = node as ILExpression; return expr != null && expr.Code.IsUnconditionalControlFlow(); } /// /// The expression has no effect on the program and can be removed /// if its return value is not needed. /// public static bool HasNoSideEffects(this ILExpression expr) { // Remember that if expression can throw an exception, it is a side effect switch(expr.Code) { case ILCode.Ldloc: case ILCode.Ldloca: case ILCode.Ldstr: case ILCode.Ldnull: case ILCode.Ldc_I4: case ILCode.Ldc_I8: case ILCode.Ldc_R4: case ILCode.Ldc_R8: return true; default: return false; } } public static bool IsStoreToArray(this ILCode code) { switch (code) { case ILCode.Stelem_Any: case ILCode.Stelem_I: case ILCode.Stelem_I1: case ILCode.Stelem_I2: case ILCode.Stelem_I4: case ILCode.Stelem_I8: case ILCode.Stelem_R4: case ILCode.Stelem_R8: case ILCode.Stelem_Ref: return true; default: return false; } } /// /// Can the expression be used as a statement in C#? /// public static bool CanBeExpressionStatement(this ILExpression expr) { switch(expr.Code) { case ILCode.Call: case ILCode.Callvirt: // property getters can't be expression statements, but all other method calls can be MethodReference mr = (MethodReference)expr.Operand; return !mr.Name.StartsWith("get_", StringComparison.Ordinal); case ILCode.Newobj: case ILCode.Newarr: case ILCode.Stloc: return true; default: return false; } } public static void RemoveTail(this List body, params ILCode[] codes) { for (int i = 0; i < codes.Length; i++) { if (((ILExpression)body[body.Count - codes.Length + i]).Code != codes[i]) throw new Exception("Tailing code does not match expected."); } body.RemoveRange(body.Count - codes.Length, codes.Length); } public static V GetOrDefault(this Dictionary dict, K key) { V ret; dict.TryGetValue(key, out ret); return ret; } public static void RemoveOrThrow(this ICollection collection, T item) { if (!collection.Remove(item)) throw new Exception("The item was not found in the collection"); } public static void RemoveOrThrow(this Dictionary collection, K key) { if (!collection.Remove(key)) throw new Exception("The key was not found in the dictionary"); } } }