diff options
author | Jason Smith <jason.smith@xamarin.com> | 2016-03-22 13:02:25 -0700 |
---|---|---|
committer | Jason Smith <jason.smith@xamarin.com> | 2016-03-22 16:13:41 -0700 |
commit | 17fdde66d94155fc62a034fa6658995bef6fd6e5 (patch) | |
tree | b5e5073a2a7b15cdbe826faa5c763e270a505729 /ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs | |
download | xamarin-forms-17fdde66d94155fc62a034fa6658995bef6fd6e5.tar.gz xamarin-forms-17fdde66d94155fc62a034fa6658995bef6fd6e5.tar.bz2 xamarin-forms-17fdde66d94155fc62a034fa6658995bef6fd6e5.zip |
Initial import
Diffstat (limited to 'ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs')
-rw-r--r-- | ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs | 1103 |
1 files changed, 1103 insertions, 0 deletions
diff --git a/ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs b/ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs new file mode 100644 index 00000000..f514aa45 --- /dev/null +++ b/ICSharpCode.Decompiler/ILAst/PeepholeTransform.cs @@ -0,0 +1,1103 @@ +// 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 ICSharpCode.NRefactory.Utils; +using Mono.Cecil; + +namespace ICSharpCode.Decompiler.ILAst +{ + public partial class ILAstOptimizer + { + #region TypeConversionSimplifications + static bool TypeConversionSimplifications(List<ILNode> body, ILExpression expr, int pos) + { + bool modified = false; + modified |= TransformDecimalCtorToConstant(expr); + modified |= SimplifyLdcI4ConvI8(expr); + modified |= RemoveConvIFromArrayCreation(expr); + foreach(ILExpression arg in expr.Arguments) { + modified |= TypeConversionSimplifications(null, arg, -1); + } + return modified; + } + + static bool TransformDecimalCtorToConstant(ILExpression expr) + { + MethodReference r; + List<ILExpression> args; + if (expr.Match(ILCode.Newobj, out r, out args) && + r.DeclaringType.Namespace == "System" && + r.DeclaringType.Name == "Decimal") + { + if (args.Count == 1) { + int val; + if (args[0].Match(ILCode.Ldc_I4, out val)) { + expr.Code = ILCode.Ldc_Decimal; + expr.Operand = new decimal(val); + expr.InferredType = r.DeclaringType; + expr.Arguments.Clear(); + return true; + } + } else if (args.Count == 5) { + int lo, mid, hi, isNegative, scale; + if (expr.Arguments[0].Match(ILCode.Ldc_I4, out lo) && + expr.Arguments[1].Match(ILCode.Ldc_I4, out mid) && + expr.Arguments[2].Match(ILCode.Ldc_I4, out hi) && + expr.Arguments[3].Match(ILCode.Ldc_I4, out isNegative) && + expr.Arguments[4].Match(ILCode.Ldc_I4, out scale)) + { + expr.Code = ILCode.Ldc_Decimal; + expr.Operand = new decimal(lo, mid, hi, isNegative != 0, (byte)scale); + expr.InferredType = r.DeclaringType; + expr.Arguments.Clear(); + return true; + } + } + } + return false; + } + + static bool SimplifyLdcI4ConvI8(ILExpression expr) + { + ILExpression ldc; + int val; + if (expr.Match(ILCode.Conv_I8, out ldc) && ldc.Match(ILCode.Ldc_I4, out val)) { + expr.Code = ILCode.Ldc_I8; + expr.Operand = (long)val; + expr.Arguments.Clear(); + return true; + } + return false; + } + + static bool RemoveConvIFromArrayCreation(ILExpression expr) + { + TypeReference typeRef; + ILExpression length; + ILExpression input; + if (expr.Match(ILCode.Newarr, out typeRef, out length)) { + if (length.Match(ILCode.Conv_Ovf_I, out input) || length.Match(ILCode.Conv_I, out input) + || length.Match(ILCode.Conv_Ovf_I_Un, out input) || length.Match(ILCode.Conv_U, out input)) + { + expr.Arguments[0] = input; + return true; + } + } + return false; + } + #endregion + + #region SimplifyLdObjAndStObj + static bool SimplifyLdObjAndStObj(List<ILNode> body, ILExpression expr, int pos) + { + bool modified = false; + expr = SimplifyLdObjAndStObj(expr, ref modified); + if (modified && body != null) + body[pos] = expr; + for (int i = 0; i < expr.Arguments.Count; i++) { + expr.Arguments[i] = SimplifyLdObjAndStObj(expr.Arguments[i], ref modified); + modified |= SimplifyLdObjAndStObj(null, expr.Arguments[i], -1); + } + return modified; + } + + static ILExpression SimplifyLdObjAndStObj(ILExpression expr, ref bool modified) + { + if (expr.Code == ILCode.Initobj) { + expr.Code = ILCode.Stobj; + expr.Arguments.Add(new ILExpression(ILCode.DefaultValue, expr.Operand)); + modified = true; + } else if (expr.Code == ILCode.Cpobj) { + expr.Code = ILCode.Stobj; + expr.Arguments[1] = new ILExpression(ILCode.Ldobj, expr.Operand, expr.Arguments[1]); + modified = true; + } + ILExpression arg, arg2; + TypeReference type; + ILCode? newCode = null; + if (expr.Match(ILCode.Stobj, out type, out arg, out arg2)) { + switch (arg.Code) { + case ILCode.Ldelema: newCode = ILCode.Stelem_Any; break; + case ILCode.Ldloca: newCode = ILCode.Stloc; break; + case ILCode.Ldflda: newCode = ILCode.Stfld; break; + case ILCode.Ldsflda: newCode = ILCode.Stsfld; break; + } + } else if (expr.Match(ILCode.Ldobj, out type, out arg)) { + switch (arg.Code) { + case ILCode.Ldelema: newCode = ILCode.Ldelem_Any; break; + case ILCode.Ldloca: newCode = ILCode.Ldloc; break; + case ILCode.Ldflda: newCode = ILCode.Ldfld; break; + case ILCode.Ldsflda: newCode = ILCode.Ldsfld; break; + } + } + if (newCode != null) { + arg.Code = newCode.Value; + if (expr.Code == ILCode.Stobj) { + arg.InferredType = expr.InferredType; + arg.ExpectedType = expr.ExpectedType; + arg.Arguments.Add(arg2); + } + arg.ILRanges.AddRange(expr.ILRanges); + modified = true; + return arg; + } else { + return expr; + } + } + #endregion + + #region CachedDelegateInitialization + void CachedDelegateInitializationWithField(ILBlock block, ref int i) + { + // if (logicnot(ldsfld(field))) { + // stsfld(field, newobj(Action::.ctor, ldnull(), ldftn(method))) + // } else { + // } + // ...(..., ldsfld(field), ...) + + ILCondition c = block.Body[i] as ILCondition; + if (c == null || c.Condition == null && c.TrueBlock == null || c.FalseBlock == null) + return; + if (!(c.TrueBlock.Body.Count == 1 && c.FalseBlock.Body.Count == 0)) + return; + if (!c.Condition.Match(ILCode.LogicNot)) + return; + ILExpression condition = c.Condition.Arguments.Single() as ILExpression; + if (condition == null || condition.Code != ILCode.Ldsfld) + return; + FieldDefinition field = ((FieldReference)condition.Operand).ResolveWithinSameModule(); // field is defined in current assembly + if (field == null || !field.IsCompilerGeneratedOrIsInCompilerGeneratedClass()) + return; + ILExpression stsfld = c.TrueBlock.Body[0] as ILExpression; + if (!(stsfld != null && stsfld.Code == ILCode.Stsfld && ((FieldReference)stsfld.Operand).ResolveWithinSameModule() == field)) + return; + ILExpression newObj = stsfld.Arguments[0]; + if (!(newObj.Code == ILCode.Newobj && newObj.Arguments.Count == 2)) + return; + if (newObj.Arguments[0].Code != ILCode.Ldnull) + return; + if (newObj.Arguments[1].Code != ILCode.Ldftn) + return; + MethodDefinition anonymousMethod = ((MethodReference)newObj.Arguments[1].Operand).ResolveWithinSameModule(); // method is defined in current assembly + if (!Ast.Transforms.DelegateConstruction.IsAnonymousMethod(context, anonymousMethod)) + return; + + ILNode followingNode = block.Body.ElementAtOrDefault(i + 1); + if (followingNode != null && followingNode.GetSelfAndChildrenRecursive<ILExpression>().Count( + e => e.Code == ILCode.Ldsfld && ((FieldReference)e.Operand).ResolveWithinSameModule() == field) == 1) + { + foreach (ILExpression parent in followingNode.GetSelfAndChildrenRecursive<ILExpression>()) { + for (int j = 0; j < parent.Arguments.Count; j++) { + if (parent.Arguments[j].Code == ILCode.Ldsfld && ((FieldReference)parent.Arguments[j].Operand).ResolveWithinSameModule() == field) { + parent.Arguments[j] = newObj; + block.Body.RemoveAt(i); + i -= new ILInlining(method).InlineInto(block.Body, i, aggressive: false); + return; + } + } + } + } + } + + void CachedDelegateInitializationWithLocal(ILBlock block, ref int i) + { + // if (logicnot(ldloc(v))) { + // stloc(v, newobj(Action::.ctor, ldloc(displayClass), ldftn(method))) + // } else { + // } + // ...(..., ldloc(v), ...) + + ILCondition c = block.Body[i] as ILCondition; + if (c == null || c.Condition == null && c.TrueBlock == null || c.FalseBlock == null) + return; + if (!(c.TrueBlock.Body.Count == 1 && c.FalseBlock.Body.Count == 0)) + return; + if (!c.Condition.Match(ILCode.LogicNot)) + return; + ILExpression condition = c.Condition.Arguments.Single() as ILExpression; + if (condition == null || condition.Code != ILCode.Ldloc) + return; + ILVariable v = (ILVariable)condition.Operand; + ILExpression stloc = c.TrueBlock.Body[0] as ILExpression; + if (!(stloc != null && stloc.Code == ILCode.Stloc && (ILVariable)stloc.Operand == v)) + return; + ILExpression newObj = stloc.Arguments[0]; + if (!(newObj.Code == ILCode.Newobj && newObj.Arguments.Count == 2)) + return; + if (newObj.Arguments[0].Code != ILCode.Ldloc) + return; + if (newObj.Arguments[1].Code != ILCode.Ldftn) + return; + MethodDefinition anonymousMethod = ((MethodReference)newObj.Arguments[1].Operand).ResolveWithinSameModule(); // method is defined in current assembly + if (!Ast.Transforms.DelegateConstruction.IsAnonymousMethod(context, anonymousMethod)) + return; + + ILNode followingNode = block.Body.ElementAtOrDefault(i + 1); + if (followingNode != null && followingNode.GetSelfAndChildrenRecursive<ILExpression>().Count( + e => e.Code == ILCode.Ldloc && (ILVariable)e.Operand == v) == 1) + { + ILInlining inlining = new ILInlining(method); + if (!(inlining.numLdloc.GetOrDefault(v) == 2 && inlining.numStloc.GetOrDefault(v) == 2 && inlining.numLdloca.GetOrDefault(v) == 0)) + return; + + // Find the store instruction that initializes the local to null: + foreach (ILBlock storeBlock in method.GetSelfAndChildrenRecursive<ILBlock>()) { + for (int j = 0; j < storeBlock.Body.Count; j++) { + ILVariable storedVar; + ILExpression storedExpr; + if (storeBlock.Body[j].Match(ILCode.Stloc, out storedVar, out storedExpr) && storedVar == v && storedExpr.Match(ILCode.Ldnull)) { + // Remove the instruction + storeBlock.Body.RemoveAt(j); + if (storeBlock == block && j < i) + i--; + break; + } + } + } + + block.Body[i] = stloc; // remove the 'if (v==null)' + inlining = new ILInlining(method); + inlining.InlineIfPossible(block.Body, ref i); + } + } + #endregion + + #region MakeAssignmentExpression + bool MakeAssignmentExpression(List<ILNode> body, ILExpression expr, int pos) + { + // exprVar = ... + // stloc(v, exprVar) + // -> + // exprVar = stloc(v, ...)) + ILVariable exprVar; + ILExpression initializer; + if (!(expr.Match(ILCode.Stloc, out exprVar, out initializer) && exprVar.IsGenerated)) + return false; + ILExpression nextExpr = body.ElementAtOrDefault(pos + 1) as ILExpression; + ILVariable v; + ILExpression stLocArg; + if (nextExpr.Match(ILCode.Stloc, out v, out stLocArg) && stLocArg.MatchLdloc(exprVar)) { + ILExpression store2 = body.ElementAtOrDefault(pos + 2) as ILExpression; + if (StoreCanBeConvertedToAssignment(store2, exprVar)) { + // expr_44 = ... + // stloc(v1, expr_44) + // anystore(v2, expr_44) + // -> + // stloc(v1, anystore(v2, ...)) + ILInlining inlining = new ILInlining(method); + if (inlining.numLdloc.GetOrDefault(exprVar) == 2 && inlining.numStloc.GetOrDefault(exprVar) == 1) { + body.RemoveAt(pos + 2); // remove store2 + body.RemoveAt(pos); // remove expr = ... + nextExpr.Arguments[0] = store2; + store2.Arguments[store2.Arguments.Count - 1] = initializer; + + inlining.InlineIfPossible(body, ref pos); + + return true; + } + } + + body.RemoveAt(pos + 1); // remove stloc + nextExpr.Arguments[0] = initializer; + ((ILExpression)body[pos]).Arguments[0] = nextExpr; + return true; + } else if ((nextExpr.Code == ILCode.Stsfld || nextExpr.Code == ILCode.CallSetter || nextExpr.Code == ILCode.CallvirtSetter) && nextExpr.Arguments.Count == 1) { + // exprVar = ... + // stsfld(fld, exprVar) + // -> + // exprVar = stsfld(fld, ...)) + if (nextExpr.Arguments[0].MatchLdloc(exprVar)) { + body.RemoveAt(pos + 1); // remove stsfld + nextExpr.Arguments[0] = initializer; + ((ILExpression)body[pos]).Arguments[0] = nextExpr; + return true; + } + } + return false; + } + + bool StoreCanBeConvertedToAssignment(ILExpression store, ILVariable exprVar) + { + if (store == null) + return false; + switch (store.Code) { + case ILCode.Stloc: + case ILCode.Stfld: + case ILCode.Stsfld: + case ILCode.Stobj: + case ILCode.CallSetter: + case ILCode.CallvirtSetter: + break; + default: + if (!store.Code.IsStoreToArray()) + return false; + break; + } + return store.Arguments.Last().Code == ILCode.Ldloc && store.Arguments.Last().Operand == exprVar; + } + #endregion + + #region MakeCompoundAssignments + bool MakeCompoundAssignments(List<ILNode> body, ILExpression expr, int pos) + { + bool modified = false; + modified |= MakeCompoundAssignment(expr); + // Static fields and local variables are not handled here - those are expressions without side effects + // and get handled by ReplaceMethodCallsWithOperators + // (which does a reversible transform to the short operator form, as the introduction of checked/unchecked might have to revert to the long form). + foreach (ILExpression arg in expr.Arguments) { + modified |= MakeCompoundAssignments(null, arg, -1); + } + if (modified && body != null) + new ILInlining(method).InlineInto(body, pos, aggressive: false); + return modified; + } + + bool MakeCompoundAssignment(ILExpression expr) + { + // stelem.any(T, ldloc(array), ldloc(pos), <OP>(ldelem.any(T, ldloc(array), ldloc(pos)), <RIGHT>)) + // or + // stobj(T, ldloc(ptr), <OP>(ldobj(T, ldloc(ptr)), <RIGHT>)) + ILCode expectedLdelemCode; + switch (expr.Code) { + case ILCode.Stelem_Any: + expectedLdelemCode = ILCode.Ldelem_Any; + break; + case ILCode.Stfld: + expectedLdelemCode = ILCode.Ldfld; + break; + case ILCode.Stobj: + expectedLdelemCode = ILCode.Ldobj; + break; + case ILCode.CallSetter: + expectedLdelemCode = ILCode.CallGetter; + break; + case ILCode.CallvirtSetter: + expectedLdelemCode = ILCode.CallvirtGetter; + break; + default: + return false; + } + + // all arguments except the last (so either array+pos, or ptr): + bool hasGeneratedVar = false; + for (int i = 0; i < expr.Arguments.Count - 1; i++) { + ILVariable inputVar; + if (!expr.Arguments[i].Match(ILCode.Ldloc, out inputVar)) + return false; + hasGeneratedVar |= inputVar.IsGenerated; + } + // At least one of the variables must be generated; otherwise we just keep the expanded form. + // We do this because we want compound assignments to be represented in ILAst only when strictly necessary; + // other compound assignments will be introduced by ReplaceMethodCallsWithOperator + // (which uses a reversible transformation, see ReplaceMethodCallsWithOperator.RestoreOriginalAssignOperatorAnnotation) + if (!hasGeneratedVar) + return false; + + ILExpression op = expr.Arguments.Last(); + // in case of compound assignments with a lifted operator the result is inside NullableOf and the operand is inside ValueOf + bool liftedOperator = false; + if (op.Code == ILCode.NullableOf) { + op = op.Arguments[0]; + liftedOperator = true; + } + if (!CanBeRepresentedAsCompoundAssignment(op)) + return false; + + ILExpression ldelem = op.Arguments[0]; + if (liftedOperator) { + if (ldelem.Code != ILCode.ValueOf) + return false; + ldelem = ldelem.Arguments[0]; + } + if (ldelem.Code != expectedLdelemCode) + return false; + Debug.Assert(ldelem.Arguments.Count == expr.Arguments.Count - 1); + for (int i = 0; i < ldelem.Arguments.Count; i++) { + if (!ldelem.Arguments[i].MatchLdloc((ILVariable)expr.Arguments[i].Operand)) + return false; + } + expr.Code = ILCode.CompoundAssignment; + expr.Operand = null; + expr.Arguments.RemoveRange(0, ldelem.Arguments.Count); + // result is "CompoundAssignment(<OP>(ldelem.any(...), <RIGHT>))" + return true; + } + + static bool CanBeRepresentedAsCompoundAssignment(ILExpression expr) + { + switch (expr.Code) { + case ILCode.Add: + case ILCode.Add_Ovf: + case ILCode.Add_Ovf_Un: + case ILCode.Sub: + case ILCode.Sub_Ovf: + case ILCode.Sub_Ovf_Un: + case ILCode.Mul: + case ILCode.Mul_Ovf: + case ILCode.Mul_Ovf_Un: + case ILCode.Div: + case ILCode.Div_Un: + case ILCode.Rem: + case ILCode.Rem_Un: + case ILCode.And: + case ILCode.Or: + case ILCode.Xor: + case ILCode.Shl: + case ILCode.Shr: + case ILCode.Shr_Un: + return true; + case ILCode.Call: + var m = expr.Operand as MethodReference; + if (m == null || m.HasThis || expr.Arguments.Count != 2) return false; + switch (m.Name) { + case "op_Addition": + case "op_Subtraction": + case "op_Multiply": + case "op_Division": + case "op_Modulus": + case "op_BitwiseAnd": + case "op_BitwiseOr": + case "op_ExclusiveOr": + case "op_LeftShift": + case "op_RightShift": + return true; + default: + return false; + } + default: + return false; + } + } + #endregion + + #region IntroducePostIncrement + + bool IntroducePostIncrement(List<ILNode> body, ILExpression expr, int pos) + { + bool modified = IntroducePostIncrementForVariables(body, expr, pos); + Debug.Assert(body[pos] == expr); // IntroducePostIncrementForVariables shouldn't change the expression reference + ILExpression newExpr = IntroducePostIncrementForInstanceFields(expr); + if (newExpr != null) { + modified = true; + body[pos] = newExpr; + new ILInlining(method).InlineIfPossible(body, ref pos); + } + return modified; + } + + bool IntroducePostIncrementForVariables(List<ILNode> body, ILExpression expr, int pos) + { + // Works for variables and static fields/properties + + // expr = ldloc(i) + // stloc(i, add(expr, ldc.i4(1))) + // -> + // expr = postincrement(1, ldloca(i)) + ILVariable exprVar; + ILExpression exprInit; + if (!(expr.Match(ILCode.Stloc, out exprVar, out exprInit) && exprVar.IsGenerated)) + return false; + + //The next expression + ILExpression nextExpr = body.ElementAtOrDefault(pos + 1) as ILExpression; + if (nextExpr == null) + return false; + + ILCode loadInstruction = exprInit.Code; + ILCode storeInstruction = nextExpr.Code; + bool recombineVariable = false; + + // We only recognise local variables, static fields, and static getters with no arguments + switch (loadInstruction) { + case ILCode.Ldloc: + //Must be a matching store type + if (storeInstruction != ILCode.Stloc) + return false; + ILVariable loadVar = (ILVariable)exprInit.Operand; + ILVariable storeVar = (ILVariable)nextExpr.Operand; + if (loadVar != storeVar) { + if (loadVar.OriginalVariable != null && loadVar.OriginalVariable == storeVar.OriginalVariable) + recombineVariable = true; + else + return false; + } + break; + case ILCode.Ldsfld: + if (storeInstruction != ILCode.Stsfld) + return false; + if (exprInit.Operand != nextExpr.Operand) + return false; + break; + case ILCode.CallGetter: + // non-static getters would have the 'this' argument + if (exprInit.Arguments.Count != 0) + return false; + if (storeInstruction != ILCode.CallSetter) + return false; + if (!IsGetterSetterPair(exprInit.Operand, nextExpr.Operand)) + return false; + break; + default: + return false; + } + + ILExpression addExpr = nextExpr.Arguments[0]; + + int incrementAmount; + ILCode incrementCode = GetIncrementCode(addExpr, out incrementAmount); + if (!(incrementAmount != 0 && addExpr.Arguments[0].MatchLdloc(exprVar))) + return false; + + if (recombineVariable) { + // Split local variable, unsplit these two instances + // replace nextExpr.Operand with exprInit.Operand + ReplaceVariables(method, oldVar => oldVar == nextExpr.Operand ? (ILVariable)exprInit.Operand : oldVar); + } + + switch (loadInstruction) { + case ILCode.Ldloc: + exprInit.Code = ILCode.Ldloca; + break; + case ILCode.Ldsfld: + exprInit.Code = ILCode.Ldsflda; + break; + case ILCode.CallGetter: + exprInit = new ILExpression(ILCode.AddressOf, null, exprInit); + break; + } + expr.Arguments[0] = new ILExpression(incrementCode, incrementAmount, exprInit); + body.RemoveAt(pos + 1); // TODO ILRanges + return true; + } + + static bool IsGetterSetterPair(object getterOperand, object setterOperand) + { + MethodReference getter = getterOperand as MethodReference; + MethodReference setter = setterOperand as MethodReference; + if (getter == null || setter == null) + return false; + if (!TypeAnalysis.IsSameType(getter.DeclaringType, setter.DeclaringType)) + return false; + MethodDefinition getterDef = getter.Resolve(); + MethodDefinition setterDef = setter.Resolve(); + if (getterDef == null || setterDef == null) + return false; + foreach (PropertyDefinition prop in getterDef.DeclaringType.Properties) { + if (prop.GetMethod == getterDef) + return prop.SetMethod == setterDef; + } + return false; + } + + ILExpression IntroducePostIncrementForInstanceFields(ILExpression expr) + { + // stfld(field, ldloc(instance), add(stloc(helperVar, ldfld(field, ldloc(instance))), ldc.i4(1))) + // -> stloc(helperVar, postincrement(1, ldflda(field, ldloc(instance)))) + + // Also works for array elements and pointers: + + // stelem.any(T, ldloc(instance), ldloc(pos), add(stloc(helperVar, ldelem.any(T, ldloc(instance), ldloc(pos))), ldc.i4(1))) + // -> stloc(helperVar, postincrement(1, ldelema(ldloc(instance), ldloc(pos)))) + + // stobj(T, ldloc(ptr), add(stloc(helperVar, ldobj(T, ldloc(ptr)), ldc.i4(1)))) + // -> stloc(helperVar, postIncrement(1, ldloc(ptr))) + + // callsetter(set_P, ldloc(instance), add(stloc(helperVar, callgetter(get_P, ldloc(instance))), ldc.i4(1))) + // -> stloc(helperVar, postIncrement(1, propertyaddress. callgetter(get_P, ldloc(instance)))) + + if (!(expr.Code == ILCode.Stfld || expr.Code.IsStoreToArray() || expr.Code == ILCode.Stobj || expr.Code == ILCode.CallSetter || expr.Code == ILCode.CallvirtSetter)) + return null; + + // Test that all arguments except the last are ldloc (1 arg for fields and pointers, 2 args for arrays) + for (int i = 0; i < expr.Arguments.Count - 1; i++) { + if (expr.Arguments[i].Code != ILCode.Ldloc) + return null; + } + + ILExpression addExpr = expr.Arguments[expr.Arguments.Count - 1]; + int incrementAmount; + ILCode incrementCode = GetIncrementCode(addExpr, out incrementAmount); + ILVariable helperVar; + ILExpression initialValue; + if (!(incrementAmount != 0 && addExpr.Arguments[0].Match(ILCode.Stloc, out helperVar, out initialValue))) + return null; + + if (expr.Code == ILCode.Stfld) { + if (initialValue.Code != ILCode.Ldfld) + return null; + // There might be two different FieldReference instances, so we compare the field's signatures: + FieldReference getField = (FieldReference)initialValue.Operand; + FieldReference setField = (FieldReference)expr.Operand; + if (!(TypeAnalysis.IsSameType(getField.DeclaringType, setField.DeclaringType) + && getField.Name == setField.Name && TypeAnalysis.IsSameType(getField.FieldType, setField.FieldType))) + { + return null; + } + } else if (expr.Code == ILCode.Stobj) { + if (!(initialValue.Code == ILCode.Ldobj && initialValue.Operand == expr.Operand)) + return null; + } else if (expr.Code == ILCode.CallSetter) { + if (!(initialValue.Code == ILCode.CallGetter && IsGetterSetterPair(initialValue.Operand, expr.Operand))) + return null; + } else if (expr.Code == ILCode.CallvirtSetter) { + if (!(initialValue.Code == ILCode.CallvirtGetter && IsGetterSetterPair(initialValue.Operand, expr.Operand))) + return null; + } else { + if (!initialValue.Code.IsLoadFromArray()) + return null; + } + Debug.Assert(expr.Arguments.Count - 1 == initialValue.Arguments.Count); + for (int i = 0; i < initialValue.Arguments.Count; i++) { + if (!initialValue.Arguments[i].MatchLdloc((ILVariable)expr.Arguments[i].Operand)) + return null; + } + + ILExpression stloc = addExpr.Arguments[0]; + if (expr.Code == ILCode.Stobj) { + stloc.Arguments[0] = new ILExpression(ILCode.PostIncrement, incrementAmount, initialValue.Arguments[0]); + } else if (expr.Code == ILCode.CallSetter || expr.Code == ILCode.CallvirtSetter) { + initialValue = new ILExpression(ILCode.AddressOf, null, initialValue); + stloc.Arguments[0] = new ILExpression(ILCode.PostIncrement, incrementAmount, initialValue); + } else { + stloc.Arguments[0] = new ILExpression(ILCode.PostIncrement, incrementAmount, initialValue); + initialValue.Code = (expr.Code == ILCode.Stfld ? ILCode.Ldflda : ILCode.Ldelema); + } + // TODO: ILRanges? + + return stloc; + } + + ILCode GetIncrementCode(ILExpression addExpr, out int incrementAmount) + { + ILCode incrementCode; + bool decrement = false; + switch (addExpr.Code) { + case ILCode.Add: + incrementCode = ILCode.PostIncrement; + break; + case ILCode.Add_Ovf: + incrementCode = ILCode.PostIncrement_Ovf; + break; + case ILCode.Add_Ovf_Un: + incrementCode = ILCode.PostIncrement_Ovf_Un; + break; + case ILCode.Sub: + incrementCode = ILCode.PostIncrement; + decrement = true; + break; + case ILCode.Sub_Ovf: + incrementCode = ILCode.PostIncrement_Ovf; + decrement = true; + break; + case ILCode.Sub_Ovf_Un: + incrementCode = ILCode.PostIncrement_Ovf_Un; + decrement = true; + break; + default: + incrementAmount = 0; + return ILCode.Nop; + } + if (addExpr.Arguments[1].Match(ILCode.Ldc_I4, out incrementAmount)) { + if (incrementAmount == -1 || incrementAmount == 1) { // TODO pointer increment? + if (decrement) + incrementAmount = -incrementAmount; + return incrementCode; + } + } + incrementAmount = 0; + return ILCode.Nop; + } + #endregion + + #region IntroduceFixedStatements + bool IntroduceFixedStatements(List<ILNode> body, int i) + { + ILExpression initValue; + ILVariable pinnedVar; + int initEndPos; + if (!MatchFixedInitializer(body, i, out pinnedVar, out initValue, out initEndPos)) + return false; + + ILFixedStatement fixedStmt = body.ElementAtOrDefault(initEndPos) as ILFixedStatement; + if (fixedStmt != null) { + ILExpression expr = fixedStmt.BodyBlock.Body.LastOrDefault() as ILExpression; + if (expr != null && expr.Code == ILCode.Stloc && expr.Operand == pinnedVar && IsNullOrZero(expr.Arguments[0])) { + // we found a second initializer for the existing fixed statement + fixedStmt.Initializers.Insert(0, initValue); + body.RemoveRange(i, initEndPos - i); + fixedStmt.BodyBlock.Body.RemoveAt(fixedStmt.BodyBlock.Body.Count - 1); + if (pinnedVar.Type.IsByReference) + pinnedVar.Type = new PointerType(((ByReferenceType)pinnedVar.Type).ElementType); + return true; + } + } + + // find where pinnedVar is reset to 0: + int j; + for (j = initEndPos; j < body.Count; j++) { + ILVariable v2; + ILExpression storedVal; + // stloc(pinned_Var, conv.u(ldc.i4(0))) + if (body[j].Match(ILCode.Stloc, out v2, out storedVal) && v2 == pinnedVar) { + if (IsNullOrZero(storedVal)) { + break; + } + } + } + // Create fixed statement from i to j + fixedStmt = new ILFixedStatement(); + fixedStmt.Initializers.Add(initValue); + fixedStmt.BodyBlock = new ILBlock(body.GetRange(initEndPos, j - initEndPos)); // from initEndPos to j-1 (inclusive) + body.RemoveRange(i + 1, Math.Min(j, body.Count - 1) - i); // from i+1 to j (inclusive) + body[i] = fixedStmt; + if (pinnedVar.Type.IsByReference) + pinnedVar.Type = new PointerType(((ByReferenceType)pinnedVar.Type).ElementType); + + return true; + } + + bool IsNullOrZero(ILExpression expr) + { + if (expr.Code == ILCode.Conv_U || expr.Code == ILCode.Conv_I) + expr = expr.Arguments[0]; + return (expr.Code == ILCode.Ldc_I4 && (int)expr.Operand == 0) || expr.Code == ILCode.Ldnull; + } + + bool MatchFixedInitializer(List<ILNode> body, int i, out ILVariable pinnedVar, out ILExpression initValue, out int nextPos) + { + if (body[i].Match(ILCode.Stloc, out pinnedVar, out initValue) && pinnedVar.IsPinned && !IsNullOrZero(initValue)) { + initValue = (ILExpression)body[i]; + nextPos = i + 1; + HandleStringFixing(pinnedVar, body, ref nextPos, ref initValue); + return true; + } + ILCondition ifStmt = body[i] as ILCondition; + ILExpression arrayLoadingExpr; + if (ifStmt != null && MatchFixedArrayInitializerCondition(ifStmt.Condition, out arrayLoadingExpr)) { + ILVariable arrayVariable = (ILVariable)arrayLoadingExpr.Operand; + ILExpression trueValue; + if (ifStmt.TrueBlock != null && ifStmt.TrueBlock.Body.Count == 1 + && ifStmt.TrueBlock.Body[0].Match(ILCode.Stloc, out pinnedVar, out trueValue) + && pinnedVar.IsPinned && IsNullOrZero(trueValue)) + { + if (ifStmt.FalseBlock != null && ifStmt.FalseBlock.Body.Count == 1 && ifStmt.FalseBlock.Body[0] is ILFixedStatement) { + ILFixedStatement fixedStmt = (ILFixedStatement)ifStmt.FalseBlock.Body[0]; + ILVariable stlocVar; + ILExpression falseValue; + if (fixedStmt.Initializers.Count == 1 && fixedStmt.BodyBlock.Body.Count == 0 + && fixedStmt.Initializers[0].Match(ILCode.Stloc, out stlocVar, out falseValue) && stlocVar == pinnedVar) + { + ILVariable loadedVariable; + if (falseValue.Code == ILCode.Ldelema + && falseValue.Arguments[0].Match(ILCode.Ldloc, out loadedVariable) && loadedVariable == arrayVariable + && IsNullOrZero(falseValue.Arguments[1])) + { + // OK, we detected the pattern for fixing an array. + // Now check whether the loading expression was a store ot a temp. var + // that can be eliminated. + if (arrayLoadingExpr.Code == ILCode.Stloc) { + ILInlining inlining = new ILInlining(method); + if (inlining.numLdloc.GetOrDefault(arrayVariable) == 2 && + inlining.numStloc.GetOrDefault(arrayVariable) == 1 && inlining.numLdloca.GetOrDefault(arrayVariable) == 0) + { + arrayLoadingExpr = arrayLoadingExpr.Arguments[0]; + } + } + initValue = new ILExpression(ILCode.Stloc, pinnedVar, arrayLoadingExpr); + nextPos = i + 1; + return true; + } + } + } + } + } + initValue = null; + nextPos = -1; + return false; + } + + bool MatchFixedArrayInitializerCondition(ILExpression condition, out ILExpression initValue) + { + ILExpression logicAnd; + ILVariable arrayVar; + if (condition.Match(ILCode.LogicNot, out logicAnd) && logicAnd.Code == ILCode.LogicAnd) { + initValue = UnpackDoubleNegation(logicAnd.Arguments[0]); + ILExpression arrayVarInitializer; + if (initValue.Match(ILCode.Ldloc, out arrayVar) + || initValue.Match(ILCode.Stloc, out arrayVar, out arrayVarInitializer)) + { + ILExpression arrayLength = logicAnd.Arguments[1]; + if (arrayLength.Code == ILCode.Conv_I4) + arrayLength = arrayLength.Arguments[0]; + return arrayLength.Code == ILCode.Ldlen && arrayLength.Arguments[0].MatchLdloc(arrayVar); + } + } + initValue = null; + return false; + } + + ILExpression UnpackDoubleNegation(ILExpression expr) + { + ILExpression negated; + if (expr.Match(ILCode.LogicNot, out negated) && negated.Match(ILCode.LogicNot, out negated)) + return negated; + else + return expr; + } + + bool HandleStringFixing(ILVariable pinnedVar, List<ILNode> body, ref int pos, ref ILExpression fixedStmtInitializer) + { + // fixed (stloc(pinnedVar, ldloc(text))) { + // var1 = var2 = conv.i(ldloc(pinnedVar)) + // if (logicnot(logicnot(var1))) { + // var2 = add(var1, call(RuntimeHelpers::get_OffsetToStringData)) + // } + // stloc(ptrVar, var2) + // ... + + if (pos >= body.Count) + return false; + + ILVariable var1, var2; + ILExpression varAssignment, ptrInitialization; + if (!(body[pos].Match(ILCode.Stloc, out var1, out varAssignment) && varAssignment.Match(ILCode.Stloc, out var2, out ptrInitialization))) + return false; + if (!(var1.IsGenerated && var2.IsGenerated)) + return false; + if (ptrInitialization.Code == ILCode.Conv_I || ptrInitialization.Code == ILCode.Conv_U) + ptrInitialization = ptrInitialization.Arguments[0]; + if (!ptrInitialization.MatchLdloc(pinnedVar)) + return false; + + ILCondition ifStmt = body[pos + 1] as ILCondition; + if (!(ifStmt != null && ifStmt.TrueBlock != null && ifStmt.TrueBlock.Body.Count == 1 && (ifStmt.FalseBlock == null || ifStmt.FalseBlock.Body.Count == 0))) + return false; + if (!UnpackDoubleNegation(ifStmt.Condition).MatchLdloc(var1)) + return false; + ILVariable assignedVar; + ILExpression assignedExpr; + if (!(ifStmt.TrueBlock.Body[0].Match(ILCode.Stloc, out assignedVar, out assignedExpr) && assignedVar == var2 && assignedExpr.Code == ILCode.Add)) + return false; + MethodReference calledMethod; + if (!(assignedExpr.Arguments[0].MatchLdloc(var1))) + return false; + if (!(assignedExpr.Arguments[1].Match(ILCode.Call, out calledMethod) || assignedExpr.Arguments[1].Match(ILCode.CallGetter, out calledMethod))) + return false; + if (!(calledMethod.Name == "get_OffsetToStringData" && calledMethod.DeclaringType.FullName == "System.Runtime.CompilerServices.RuntimeHelpers")) + return false; + + ILVariable pointerVar; + if (body[pos + 2].Match(ILCode.Stloc, out pointerVar, out assignedExpr) && assignedExpr.MatchLdloc(var2)) { + pos += 3; + fixedStmtInitializer.Operand = pointerVar; + return true; + } + return false; + } + #endregion + + #region SimplifyLogicNot + static bool SimplifyLogicNot(List<ILNode> body, ILExpression expr, int pos) + { + bool modified = false; + expr = SimplifyLogicNot(expr, ref modified); + Debug.Assert(expr == null); + return modified; + } + + static ILExpression SimplifyLogicNot(ILExpression expr, ref bool modified) + { + ILExpression a; + // "ceq(a, ldc.i4.0)" becomes "logicnot(a)" if the inferred type for expression "a" is boolean + if (expr.Code == ILCode.Ceq && TypeAnalysis.IsBoolean(expr.Arguments[0].InferredType) && (a = expr.Arguments[1]).Code == ILCode.Ldc_I4 && (int)a.Operand == 0) { + expr.Code = ILCode.LogicNot; + expr.ILRanges.AddRange(a.ILRanges); + expr.Arguments.RemoveAt(1); + modified = true; + } + + ILExpression res = null; + while (expr.Code == ILCode.LogicNot) { + a = expr.Arguments[0]; + // remove double negation + if (a.Code == ILCode.LogicNot) { + res = a.Arguments[0]; + res.ILRanges.AddRange(expr.ILRanges); + res.ILRanges.AddRange(a.ILRanges); + expr = res; + } else { + if (SimplifyLogicNotArgument(expr)) res = expr = a; + break; + } + } + + for (int i = 0; i < expr.Arguments.Count; i++) { + a = SimplifyLogicNot(expr.Arguments[i], ref modified); + if (a != null) { + expr.Arguments[i] = a; + modified = true; + } + } + + return res; + } + + /// <summary> + /// If the argument is a binary comparison operation then the negation is pushed through it + /// </summary> + static bool SimplifyLogicNotArgument(ILExpression expr) + { + var a = expr.Arguments[0]; + ILCode c; + switch (a.Code) { + case ILCode.Ceq: c = ILCode.Cne; break; + case ILCode.Cne: c = ILCode.Ceq; break; + case ILCode.Cgt: c = ILCode.Cle; break; + case ILCode.Cgt_Un: c = ILCode.Cle_Un; break; + case ILCode.Cge: c = ILCode.Clt; break; + case ILCode.Cge_Un: c = ILCode.Clt_Un; break; + case ILCode.Clt: c = ILCode.Cge; break; + case ILCode.Clt_Un: c = ILCode.Cge_Un; break; + case ILCode.Cle: c = ILCode.Cgt; break; + case ILCode.Cle_Un: c = ILCode.Cgt_Un; break; + default: return false; + } + a.Code = c; + a.ILRanges.AddRange(expr.ILRanges); + return true; + } + #endregion + + #region SimplifyShiftOperators + static bool SimplifyShiftOperators(List<ILNode> body, ILExpression expr, int pos) + { + // C# compiles "a << b" to "a << (b & 31)", so we will remove the "& 31" if possible. + bool modified = false; + SimplifyShiftOperators(expr, ref modified); + return modified; + } + + static void SimplifyShiftOperators(ILExpression expr, ref bool modified) + { + for (int i = 0; i < expr.Arguments.Count; i++) + SimplifyShiftOperators(expr.Arguments[i], ref modified); + if (expr.Code != ILCode.Shl && expr.Code != ILCode.Shr && expr.Code != ILCode.Shr_Un) + return; + var a = expr.Arguments[1]; + if (a.Code != ILCode.And || a.Arguments[1].Code != ILCode.Ldc_I4 || expr.InferredType == null) + return; + int mask; + switch (expr.InferredType.MetadataType) { + case MetadataType.Int32: + case MetadataType.UInt32: mask = 31; break; + case MetadataType.Int64: + case MetadataType.UInt64: mask = 63; break; + default: return; + } + if ((int)a.Arguments[1].Operand != mask) return; + var res = a.Arguments[0]; + res.ILRanges.AddRange(a.ILRanges); + res.ILRanges.AddRange(a.Arguments[1].ILRanges); + expr.Arguments[1] = res; + modified = true; + } + #endregion + + #region InlineExpressionTreeParameterDeclarations + bool InlineExpressionTreeParameterDeclarations(List<ILNode> body, ILExpression expr, int pos) + { + // When there is a Expression.Lambda() call, and the parameters are declared in the + // IL statement immediately prior to the one containing the Lambda() call, + // using this code for the3 declaration: + // stloc(v, call(Expression::Parameter, call(Type::GetTypeFromHandle, ldtoken(...)), ldstr(...))) + // and the variables v are assigned only once (in that statements), and read only in a Expression::Lambda + // call that immediately follows the assignment statements, then we will inline those assignments + // into the Lambda call using ILCode.ExpressionTreeParameterDeclarations. + + // This is sufficient to allow inlining over the expression tree construction. The remaining translation + // of expression trees into C# will be performed by a C# AST transformer. + + for (int i = expr.Arguments.Count - 1; i >= 0; i--) { + if (InlineExpressionTreeParameterDeclarations(body, expr.Arguments[i], pos)) + return true; + } + + MethodReference mr; + ILExpression lambdaBodyExpr, parameterArray; + if (!(expr.Match(ILCode.Call, out mr, out lambdaBodyExpr, out parameterArray) && mr.Name == "Lambda")) + return false; + if (!(parameterArray.Code == ILCode.InitArray && mr.DeclaringType.FullName == "System.Linq.Expressions.Expression")) + return false; + int firstParameterPos = pos - parameterArray.Arguments.Count; + if (firstParameterPos < 0) + return false; + + ILExpression[] parameterInitExpressions = new ILExpression[parameterArray.Arguments.Count + 1]; + for (int i = 0; i < parameterArray.Arguments.Count; i++) { + parameterInitExpressions[i] = body[firstParameterPos + i] as ILExpression; + if (!MatchParameterVariableAssignment(parameterInitExpressions[i])) + return false; + ILVariable v = (ILVariable)parameterInitExpressions[i].Operand; + if (!parameterArray.Arguments[i].MatchLdloc(v)) + return false; + // TODO: validate that the variable is only used here and within 'body' + } + + parameterInitExpressions[parameterInitExpressions.Length - 1] = lambdaBodyExpr; + Debug.Assert(expr.Arguments[0] == lambdaBodyExpr); + expr.Arguments[0] = new ILExpression(ILCode.ExpressionTreeParameterDeclarations, null, parameterInitExpressions); + + body.RemoveRange(firstParameterPos, parameterArray.Arguments.Count); + + return true; + } + + bool MatchParameterVariableAssignment(ILExpression expr) + { + // stloc(v, call(Expression::Parameter, call(Type::GetTypeFromHandle, ldtoken(...)), ldstr(...))) + ILVariable v; + ILExpression init; + if (!expr.Match(ILCode.Stloc, out v, out init)) + return false; + if (v.IsGenerated || v.IsParameter || v.IsPinned) + return false; + if (v.Type == null || v.Type.FullName != "System.Linq.Expressions.ParameterExpression") + return false; + MethodReference parameterMethod; + ILExpression typeArg, nameArg; + if (!init.Match(ILCode.Call, out parameterMethod, out typeArg, out nameArg)) + return false; + if (!(parameterMethod.Name == "Parameter" && parameterMethod.DeclaringType.FullName == "System.Linq.Expressions.Expression")) + return false; + MethodReference getTypeFromHandle; + ILExpression typeToken; + if (!typeArg.Match(ILCode.Call, out getTypeFromHandle, out typeToken)) + return false; + if (!(getTypeFromHandle.Name == "GetTypeFromHandle" && getTypeFromHandle.DeclaringType.FullName == "System.Type")) + return false; + return typeToken.Code == ILCode.Ldtoken && nameArg.Code == ILCode.Ldstr; + } + #endregion + } +} |