summaryrefslogtreecommitdiff
path: root/ICSharpCode.Decompiler/ILAst/ILInlining.cs
diff options
context:
space:
mode:
Diffstat (limited to 'ICSharpCode.Decompiler/ILAst/ILInlining.cs')
-rw-r--r--ICSharpCode.Decompiler/ILAst/ILInlining.cs524
1 files changed, 524 insertions, 0 deletions
diff --git a/ICSharpCode.Decompiler/ILAst/ILInlining.cs b/ICSharpCode.Decompiler/ILAst/ILInlining.cs
new file mode 100644
index 00000000..11140ef0
--- /dev/null
+++ b/ICSharpCode.Decompiler/ILAst/ILInlining.cs
@@ -0,0 +1,524 @@
+// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using Mono.Cecil;
+
+namespace ICSharpCode.Decompiler.ILAst
+{
+ /// <summary>
+ /// Performs inlining transformations.
+ /// </summary>
+ public class ILInlining
+ {
+ readonly ILBlock method;
+ internal Dictionary<ILVariable, int> numStloc = new Dictionary<ILVariable, int>();
+ internal Dictionary<ILVariable, int> numLdloc = new Dictionary<ILVariable, int>();
+ internal Dictionary<ILVariable, int> numLdloca = new Dictionary<ILVariable, int>();
+
+ public ILInlining(ILBlock method)
+ {
+ this.method = method;
+ AnalyzeMethod();
+ }
+
+ void AnalyzeMethod()
+ {
+ numStloc.Clear();
+ numLdloc.Clear();
+ numLdloca.Clear();
+
+ // Analyse the whole method
+ AnalyzeNode(method);
+ }
+
+ /// <summary>
+ /// For each variable reference, adds <paramref name="direction"/> to the num* dicts.
+ /// Direction will be 1 for analysis, and -1 when removing a node from analysis.
+ /// </summary>
+ void AnalyzeNode(ILNode node, int direction = 1)
+ {
+ ILExpression expr = node as ILExpression;
+ if (expr != null) {
+ ILVariable locVar = expr.Operand as ILVariable;
+ if (locVar != null) {
+ if (expr.Code == ILCode.Stloc) {
+ numStloc[locVar] = numStloc.GetOrDefault(locVar) + direction;
+ } else if (expr.Code == ILCode.Ldloc) {
+ numLdloc[locVar] = numLdloc.GetOrDefault(locVar) + direction;
+ } else if (expr.Code == ILCode.Ldloca) {
+ numLdloca[locVar] = numLdloca.GetOrDefault(locVar) + direction;
+ } else {
+ throw new NotSupportedException(expr.Code.ToString());
+ }
+ }
+ foreach (ILExpression child in expr.Arguments)
+ AnalyzeNode(child, direction);
+ } else {
+ var catchBlock = node as ILTryCatchBlock.CatchBlock;
+ if (catchBlock != null && catchBlock.ExceptionVariable != null) {
+ numStloc[catchBlock.ExceptionVariable] = numStloc.GetOrDefault(catchBlock.ExceptionVariable) + direction;
+ }
+
+ foreach (ILNode child in node.GetChildren())
+ AnalyzeNode(child, direction);
+ }
+ }
+
+ public bool InlineAllVariables()
+ {
+ bool modified = false;
+ ILInlining i = new ILInlining(method);
+ foreach (ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>())
+ modified |= i.InlineAllInBlock(block);
+ return modified;
+ }
+
+ public bool InlineAllInBlock(ILBlock block)
+ {
+ bool modified = false;
+ List<ILNode> body = block.Body;
+ if (block is ILTryCatchBlock.CatchBlock && body.Count > 1) {
+ ILVariable v = ((ILTryCatchBlock.CatchBlock)block).ExceptionVariable;
+ if (v != null && v.IsGenerated) {
+ if (numLdloca.GetOrDefault(v) == 0 && numStloc.GetOrDefault(v) == 1 && numLdloc.GetOrDefault(v) == 1) {
+ ILVariable v2;
+ ILExpression ldException;
+ if (body[0].Match(ILCode.Stloc, out v2, out ldException) && ldException.MatchLdloc(v)) {
+ body.RemoveAt(0);
+ ((ILTryCatchBlock.CatchBlock)block).ExceptionVariable = v2;
+ modified = true;
+ }
+ }
+ }
+ }
+ for(int i = 0; i < body.Count - 1;) {
+ ILVariable locVar;
+ ILExpression expr;
+ if (body[i].Match(ILCode.Stloc, out locVar, out expr) && InlineOneIfPossible(block.Body, i, aggressive: false)) {
+ modified = true;
+ i = Math.Max(0, i - 1); // Go back one step
+ } else {
+ i++;
+ }
+ }
+ foreach(ILBasicBlock bb in body.OfType<ILBasicBlock>()) {
+ modified |= InlineAllInBasicBlock(bb);
+ }
+ return modified;
+ }
+
+ public bool InlineAllInBasicBlock(ILBasicBlock bb)
+ {
+ bool modified = false;
+ List<ILNode> body = bb.Body;
+ for(int i = 0; i < body.Count;) {
+ ILVariable locVar;
+ ILExpression expr;
+ if (body[i].Match(ILCode.Stloc, out locVar, out expr) && InlineOneIfPossible(bb.Body, i, aggressive: false)) {
+ modified = true;
+ i = Math.Max(0, i - 1); // Go back one step
+ } else {
+ i++;
+ }
+ }
+ return modified;
+ }
+
+ /// <summary>
+ /// Inlines instructions before pos into block.Body[pos].
+ /// </summary>
+ /// <returns>The number of instructions that were inlined.</returns>
+ public int InlineInto(List<ILNode> body, int pos, bool aggressive)
+ {
+ if (pos >= body.Count)
+ return 0;
+ int count = 0;
+ while (--pos >= 0) {
+ ILExpression expr = body[pos] as ILExpression;
+ if (expr == null || expr.Code != ILCode.Stloc)
+ break;
+ if (InlineOneIfPossible(body, pos, aggressive))
+ count++;
+ else
+ break;
+ }
+ return count;
+ }
+
+ /// <summary>
+ /// Aggressively inlines the stloc instruction at block.Body[pos] into the next instruction, if possible.
+ /// If inlining was possible; we will continue to inline (non-aggressively) into the the combined instruction.
+ /// </summary>
+ /// <remarks>
+ /// After the operation, pos will point to the new combined instruction.
+ /// </remarks>
+ public bool InlineIfPossible(List<ILNode> body, ref int pos)
+ {
+ if (InlineOneIfPossible(body, pos, true)) {
+ pos -= InlineInto(body, pos, false);
+ return true;
+ }
+ return false;
+ }
+
+ /// <summary>
+ /// Inlines the stloc instruction at block.Body[pos] into the next instruction, if possible.
+ /// </summary>
+ public bool InlineOneIfPossible(List<ILNode> body, int pos, bool aggressive)
+ {
+ ILVariable v;
+ ILExpression inlinedExpression;
+ if (body[pos].Match(ILCode.Stloc, out v, out inlinedExpression) && !v.IsPinned) {
+ if (InlineIfPossible(v, inlinedExpression, body.ElementAtOrDefault(pos+1), aggressive)) {
+ // Assign the ranges of the stloc instruction:
+ inlinedExpression.ILRanges.AddRange(((ILExpression)body[pos]).ILRanges);
+ // Remove the stloc instruction:
+ body.RemoveAt(pos);
+ return true;
+ } else if (numLdloc.GetOrDefault(v) == 0 && numLdloca.GetOrDefault(v) == 0) {
+ // The variable is never loaded
+ if (inlinedExpression.HasNoSideEffects()) {
+ // Remove completely
+ AnalyzeNode(body[pos], -1);
+ body.RemoveAt(pos);
+ return true;
+ } else if (inlinedExpression.CanBeExpressionStatement() && v.IsGenerated) {
+ // Assign the ranges of the stloc instruction:
+ inlinedExpression.ILRanges.AddRange(((ILExpression)body[pos]).ILRanges);
+ // Remove the stloc, but keep the inner expression
+ body[pos] = inlinedExpression;
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /// <summary>
+ /// Inlines 'expr' into 'next', if possible.
+ /// </summary>
+ bool InlineIfPossible(ILVariable v, ILExpression inlinedExpression, ILNode next, bool aggressive)
+ {
+ // ensure the variable is accessed only a single time
+ if (numStloc.GetOrDefault(v) != 1)
+ return false;
+ int ldloc = numLdloc.GetOrDefault(v);
+ if (ldloc > 1 || ldloc + numLdloca.GetOrDefault(v) != 1)
+ return false;
+
+ if (next is ILCondition)
+ next = ((ILCondition)next).Condition;
+ else if (next is ILWhileLoop)
+ next = ((ILWhileLoop)next).Condition;
+
+ ILExpression parent;
+ int pos;
+ if (FindLoadInNext(next as ILExpression, v, inlinedExpression, out parent, out pos) == true) {
+ if (ldloc == 0) {
+ if (!IsGeneratedValueTypeTemporary((ILExpression)next, parent, pos, v, inlinedExpression))
+ return false;
+ } else {
+ if (!aggressive && !v.IsGenerated && !NonAggressiveInlineInto((ILExpression)next, parent, inlinedExpression))
+ return false;
+ }
+
+ // Assign the ranges of the ldloc instruction:
+ inlinedExpression.ILRanges.AddRange(parent.Arguments[pos].ILRanges);
+
+ if (ldloc == 0) {
+ // it was an ldloca instruction, so we need to use the pseudo-opcode 'addressof' so that the types
+ // comes out correctly
+ parent.Arguments[pos] = new ILExpression(ILCode.AddressOf, null, inlinedExpression);
+ } else {
+ parent.Arguments[pos] = inlinedExpression;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /// <summary>
+ /// Is this a temporary variable generated by the C# compiler for instance method calls on value type values
+ /// </summary>
+ /// <param name="next">The next top-level expression</param>
+ /// <param name="parent">The direct parent of the load within 'next'</param>
+ /// <param name="pos">Index of the load within 'parent'</param>
+ /// <param name="v">The variable being inlined.</param>
+ /// <param name="inlinedExpression">The expression being inlined</param>
+ bool IsGeneratedValueTypeTemporary(ILExpression next, ILExpression parent, int pos, ILVariable v, ILExpression inlinedExpression)
+ {
+ if (pos == 0 && v.Type != null && v.Type.IsValueType) {
+ // Inlining a value type variable is allowed only if the resulting code will maintain the semantics
+ // that the method is operating on a copy.
+ // Thus, we have to disallow inlining of other locals, fields, array elements, dereferenced pointers
+ switch (inlinedExpression.Code) {
+ case ILCode.Ldloc:
+ case ILCode.Stloc:
+ case ILCode.CompoundAssignment:
+ case ILCode.Ldelem_Any:
+ case ILCode.Ldelem_I:
+ case ILCode.Ldelem_I1:
+ case ILCode.Ldelem_I2:
+ case ILCode.Ldelem_I4:
+ case ILCode.Ldelem_I8:
+ case ILCode.Ldelem_R4:
+ case ILCode.Ldelem_R8:
+ case ILCode.Ldelem_Ref:
+ case ILCode.Ldelem_U1:
+ case ILCode.Ldelem_U2:
+ case ILCode.Ldelem_U4:
+ case ILCode.Ldobj:
+ case ILCode.Ldind_Ref:
+ return false;
+ case ILCode.Ldfld:
+ case ILCode.Stfld:
+ case ILCode.Ldsfld:
+ case ILCode.Stsfld:
+ // allow inlining field access only if it's a readonly field
+ FieldDefinition f = ((FieldReference)inlinedExpression.Operand).Resolve();
+ if (!(f != null && f.IsInitOnly))
+ return false;
+ break;
+ case ILCode.Call:
+ case ILCode.CallGetter:
+ // inlining runs both before and after IntroducePropertyAccessInstructions,
+ // so we have to handle both 'call' and 'callgetter'
+ MethodReference mr = (MethodReference)inlinedExpression.Operand;
+ // ensure that it's not an multi-dimensional array getter
+ if (mr.DeclaringType is ArrayType)
+ return false;
+ goto case ILCode.Callvirt;
+ case ILCode.Callvirt:
+ case ILCode.CallvirtGetter:
+ // don't inline foreach loop variables:
+ mr = (MethodReference)inlinedExpression.Operand;
+ if (mr.Name == "get_Current" && mr.HasThis)
+ return false;
+ break;
+ case ILCode.Castclass:
+ case ILCode.Unbox_Any:
+ // These are valid, but might occur as part of a foreach loop variable.
+ ILExpression arg = inlinedExpression.Arguments[0];
+ if (arg.Code == ILCode.CallGetter || arg.Code == ILCode.CallvirtGetter || arg.Code == ILCode.Call || arg.Code == ILCode.Callvirt) {
+ mr = (MethodReference)arg.Operand;
+ if (mr.Name == "get_Current" && mr.HasThis)
+ return false; // looks like a foreach loop variable, so don't inline it
+ }
+ break;
+ }
+
+ // inline the compiler-generated variable that are used when accessing a member on a value type:
+ switch (parent.Code) {
+ case ILCode.Call:
+ case ILCode.CallGetter:
+ case ILCode.CallSetter:
+ case ILCode.Callvirt:
+ case ILCode.CallvirtGetter:
+ case ILCode.CallvirtSetter:
+ MethodReference mr = (MethodReference)parent.Operand;
+ return mr.HasThis;
+ case ILCode.Stfld:
+ case ILCode.Ldfld:
+ case ILCode.Ldflda:
+ case ILCode.Await:
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /// <summary>
+ /// Determines whether a variable should be inlined in non-aggressive mode, even though it is not a generated variable.
+ /// </summary>
+ /// <param name="next">The next top-level expression</param>
+ /// <param name="parent">The direct parent of the load within 'next'</param>
+ /// <param name="inlinedExpression">The expression being inlined</param>
+ bool NonAggressiveInlineInto(ILExpression next, ILExpression parent, ILExpression inlinedExpression)
+ {
+ if (inlinedExpression.Code == ILCode.DefaultValue)
+ return true;
+
+ switch (next.Code) {
+ case ILCode.Ret:
+ case ILCode.Brtrue:
+ return parent == next;
+ case ILCode.Switch:
+ return parent == next || (parent.Code == ILCode.Sub && parent == next.Arguments[0]);
+ default:
+ return false;
+ }
+ }
+
+ /// <summary>
+ /// Gets whether 'expressionBeingMoved' can be inlined into 'expr'.
+ /// </summary>
+ public bool CanInlineInto(ILExpression expr, ILVariable v, ILExpression expressionBeingMoved)
+ {
+ ILExpression parent;
+ int pos;
+ return FindLoadInNext(expr, v, expressionBeingMoved, out parent, out pos) == true;
+ }
+
+ /// <summary>
+ /// Finds the position to inline to.
+ /// </summary>
+ /// <returns>true = found; false = cannot continue search; null = not found</returns>
+ bool? FindLoadInNext(ILExpression expr, ILVariable v, ILExpression expressionBeingMoved, out ILExpression parent, out int pos)
+ {
+ parent = null;
+ pos = 0;
+ if (expr == null)
+ return false;
+ for (int i = 0; i < expr.Arguments.Count; i++) {
+ // Stop when seeing an opcode that does not guarantee that its operands will be evaluated.
+ // Inlining in that case might result in the inlined expresion not being evaluted.
+ if (i == 1 && (expr.Code == ILCode.LogicAnd || expr.Code == ILCode.LogicOr || expr.Code == ILCode.TernaryOp || expr.Code == ILCode.NullCoalescing))
+ return false;
+
+ ILExpression arg = expr.Arguments[i];
+
+ if ((arg.Code == ILCode.Ldloc || arg.Code == ILCode.Ldloca) && arg.Operand == v) {
+ parent = expr;
+ pos = i;
+ return true;
+ }
+ bool? r = FindLoadInNext(arg, v, expressionBeingMoved, out parent, out pos);
+ if (r != null)
+ return r;
+ }
+ if (IsSafeForInlineOver(expr, expressionBeingMoved))
+ return null; // continue searching
+ else
+ return false; // abort, inlining not possible
+ }
+
+ /// <summary>
+ /// Determines whether it is safe to move 'expressionBeingMoved' past 'expr'
+ /// </summary>
+ bool IsSafeForInlineOver(ILExpression expr, ILExpression expressionBeingMoved)
+ {
+ 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<ILExpression>()) {
+ if (potentialStore.Code == ILCode.Stloc && potentialStore.Operand == loadedVar)
+ return false;
+ }
+ // the expression is loading a non-forbidden variable
+ return true;
+ case ILCode.Ldloca:
+ case ILCode.Ldflda:
+ case ILCode.Ldsflda:
+ case ILCode.Ldelema:
+ case ILCode.AddressOf:
+ case ILCode.ValueOf:
+ case ILCode.NullableOf:
+ // address-loading instructions are safe if their arguments are safe
+ foreach (ILExpression arg in expr.Arguments) {
+ if (!IsSafeForInlineOver(arg, expressionBeingMoved))
+ return false;
+ }
+ return true;
+ default:
+ // instructions with no side-effects are safe (except for Ldloc and Ldloca which are handled separately)
+ return expr.HasNoSideEffects();
+ }
+ }
+
+ /// <summary>
+ /// 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 address-loading instructions to local variables
+ /// </summary>
+ public void CopyPropagation()
+ {
+ foreach (ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) {
+ for (int i = 0; i < block.Body.Count; i++) {
+ ILVariable v;
+ ILExpression copiedExpr;
+ if (block.Body[i].Match(ILCode.Stloc, out v, out copiedExpr)
+ && !v.IsParameter && numStloc.GetOrDefault(v) == 1 && numLdloca.GetOrDefault(v) == 0
+ && CanPerformCopyPropagation(copiedExpr, v))
+ {
+ // un-inline the arguments of the ldArg instruction
+ ILVariable[] uninlinedArgs = new ILVariable[copiedExpr.Arguments.Count];
+ for (int j = 0; j < uninlinedArgs.Length; j++) {
+ uninlinedArgs[j] = new ILVariable { IsGenerated = true, Name = v.Name + "_cp_" + j };
+ block.Body.Insert(i++, new ILExpression(ILCode.Stloc, uninlinedArgs[j], copiedExpr.Arguments[j]));
+ }
+
+ // perform copy propagation:
+ foreach (var expr in method.GetSelfAndChildrenRecursive<ILExpression>()) {
+ if (expr.Code == ILCode.Ldloc && expr.Operand == v) {
+ expr.Code = copiedExpr.Code;
+ expr.Operand = copiedExpr.Operand;
+ for (int j = 0; j < uninlinedArgs.Length; j++) {
+ expr.Arguments.Add(new ILExpression(ILCode.Ldloc, uninlinedArgs[j]));
+ }
+ }
+ }
+
+ block.Body.RemoveAt(i);
+ if (uninlinedArgs.Length > 0) {
+ // if we un-inlined stuff; we need to update the usage counters
+ AnalyzeMethod();
+ }
+ InlineInto(block.Body, i, aggressive: false); // maybe inlining gets possible after the removal of block.Body[i]
+ i -= uninlinedArgs.Length + 1;
+ }
+ }
+ }
+ }
+
+ bool CanPerformCopyPropagation(ILExpression expr, ILVariable copyVariable)
+ {
+ switch (expr.Code) {
+ case ILCode.Ldloca:
+ case ILCode.Ldelema:
+ case ILCode.Ldflda:
+ case ILCode.Ldsflda:
+ // All address-loading instructions always return the same value for a given operand/argument combination,
+ // so they can be safely copied.
+ return true;
+ case ILCode.Ldloc:
+ ILVariable v = (ILVariable)expr.Operand;
+ if (v.IsParameter) {
+ // Parameters can be copied only if they aren't assigned to (directly or indirectly via ldarga)
+ return numLdloca.GetOrDefault(v) == 0 && numStloc.GetOrDefault(v) == 0;
+ } else {
+ // Variables are be copied only if both they and the target copy variable are generated,
+ // and if the variable has only a single assignment
+ return v.IsGenerated && copyVariable.IsGenerated && numLdloca.GetOrDefault(v) == 0 && numStloc.GetOrDefault(v) == 1;
+ }
+ default:
+ return false;
+ }
+ }
+ }
+}